<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>JUPETER의 Notebook</title>
    <link>https://jupeternotebook.tistory.com/</link>
    <description>JUPETER의 취준, 개발, 일상</description>
    <language>ko</language>
    <pubDate>Thu, 11 Jun 2026 17:44:45 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>jupeternotebook</managingEditor>
    <image>
      <title>JUPETER의 Notebook</title>
      <url>https://tistory1.daumcdn.net/tistory/7280648/attach/654b75e39b864863bd9f464cad7a4ddb</url>
      <link>https://jupeternotebook.tistory.com</link>
    </image>
    <item>
      <title>Git &amp;amp; GitHub</title>
      <link>https://jupeternotebook.tistory.com/19</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Git과 GitHub의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Git&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터 파일의 변경사항을 추적하고 여러 명의 사용자들 간에 해당 파일들의 작업을 조율하기 위한 &lt;b&gt;분산 버전 관리 시스템&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Git 저장소 호스팅을 지원하는 가장 인기 있는 &lt;b&gt;오픈 소스 웹 서비스&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 줄로 정리하면, &lt;b&gt;Git은 도구&lt;/b&gt;이고 &lt;b&gt;GitHub는 그 도구를 활용한 웹 플랫폼&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 중요한가?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;github.webp&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zgQwd/dJMcahEpwFc/R8sqL7XFD6uiawMoJ7Jcc0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zgQwd/dJMcahEpwFc/R8sqL7XFD6uiawMoJ7Jcc0/img.webp&quot; data-alt=&quot;Git 리포지토리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zgQwd/dJMcahEpwFc/R8sqL7XFD6uiawMoJ7Jcc0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzgQwd%2FdJMcahEpwFc%2FR8sqL7XFD6uiawMoJ7Jcc0%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;400&quot; data-filename=&quot;github.webp&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Git 리포지토리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Git의 역할&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드의 버전 관리를 용이하게 함&lt;/li&gt;
&lt;li&gt;여러 사람이 동시에 하나의 프로젝트를 협업할 수 있도록 도움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Git이 필요한 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전 작업 기록 확인 가능&lt;/li&gt;
&lt;li&gt;특정 시점으로 되돌아가기 가능&lt;/li&gt;
&lt;li&gt;협업 시 코드 충돌 관리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub의 역할&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 호스팅, 버전 관리 및 협업을 위한 &lt;b&gt;통합 플랫폼&lt;/b&gt; 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub가 필요한 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전 세계 어디서나 프로젝트 공유 가능&lt;/li&gt;
&lt;li&gt;여러 개발자가 함께 협업 가능&lt;/li&gt;
&lt;li&gt;오픈소스 프로젝트 참여 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기존 물리적인 백업 및 공유 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git이 등장하기 전에는 다음과 같은 방식으로 코드를 백업하고 공유했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 방식&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터에서 직접 파일 이동&lt;/li&gt;
&lt;li&gt;USB, 외장하드 사용&lt;/li&gt;
&lt;li&gt;메일 및 클라우드 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 손실 시 복구 가능성이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드가 변경될 때마다 저장해야 해서 관리가 어려움&lt;/li&gt;
&lt;li&gt;컴퓨터가 고장나거나 외장하드를 분실하면 백업 파일이 모두 사라질 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 버전 관리 시스템 (VCS)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 프로그램이 몇 번 수정되어 있는지를 관리하는 프로그램&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 관리 시스템은 크게 두 가지 방식으로 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;중앙 집중식 버전 관리 시스템 (CVCS)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 버전 관리 시스템 (DVCS)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 중앙 집중식 버전 관리 시스템 (CVCS)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서만 프로그램의 버전과 변경사항을 관리하는 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구글 슬라이드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언제 어떤 사람이 문서를 변경했는지 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 사람들과 동시에 자료 보기 가능&lt;/li&gt;
&lt;li&gt;로컬 컴퓨터 저장공간 절약 가능&lt;/li&gt;
&lt;li&gt;작업 순서를 알 수 있음 &amp;rarr; 이전 기록으로 돌아가기 쉬움&lt;/li&gt;
&lt;li&gt;파일 유실이 덜 생김&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 연결이 안 되면 작업 불가능&lt;/li&gt;
&lt;li&gt;서버가 날아가면 복구 어려움&lt;/li&gt;
&lt;li&gt;동시에 같은 내용 작업하기 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 분산 버전 관리 시스템 (DVCS)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 전체 히스토리와 버전 관리 정보를 각 사용자의 로컬 시스템에 복제하여, 네트워크에 의존하지 않고도 버전 관리를 수행할 수 있도록 하는 시스템&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Git이 바로 이 방식에 해당합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작업 순서를 알 수 있음 &amp;rarr; 이전 기록으로 돌아가기 쉬움&lt;/li&gt;
&lt;li&gt;인터넷 연결이 잠시 안 되어도 작업 가능&lt;/li&gt;
&lt;li&gt;서버가 날아가도 복구 가능성 높음&lt;/li&gt;
&lt;li&gt;동시에 같은 내용 작업 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관리가 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. Git 동작 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git의 작업 흐름은 크게 6단계로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계 : Git 저장소 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 디렉토리를 Git 저장소로 초기화&lt;/li&gt;
&lt;li&gt;Working Directory(.git 폴더) 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계 : 파일 스테이징&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git add a.txt&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;흐름 : Untracked Files &amp;rarr; Stage&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3단계 : 파일 수정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 수정 시 Stage 상태에서 Working Directory 내부의 &lt;b&gt;Tracked Files&lt;/b&gt; 중 &lt;b&gt;Unstaged Files&lt;/b&gt; 상태로 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4단계 : 다시 스테이징&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git add&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;흐름 : Unstaged Files &amp;rarr; Stage&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5단계 : 커밋&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git commit&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;흐름 : Stage &amp;rarr; History&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6단계 : 서버 업로드&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경사항을 &lt;b&gt;원격 서버에 업로드&lt;/b&gt; (git push)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름을 한눈에 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Working Directory &amp;rarr; Stage &amp;rarr; History &amp;rarr; Remote Repository
        (add)         (commit)        (push)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 자주 사용하는 Git 명령어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 역할&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;git init&lt;/th&gt;
&lt;th&gt;Working Directory 생성 (저장소 초기화)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;git remote add origin &quot;저장소 주소&quot;&lt;/td&gt;
&lt;td&gt;로컬 저장소에 원격 저장소 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git status&lt;/td&gt;
&lt;td&gt;현재 작업 중인 저장소 상태 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git add 파일명&lt;/td&gt;
&lt;td&gt;파일을 Stage 상태로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git commit -m &quot;메모&quot;&lt;/td&gt;
&lt;td&gt;파일의 변경 내용 확정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git push &amp;lt;원격저장소명&amp;gt; &amp;lt;브랜치명&amp;gt;&lt;/td&gt;
&lt;td&gt;로컬 저장소 변경 내용을 원격 저장소에 업로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git clone &quot;원격 저장소 주소&quot;&lt;/td&gt;
&lt;td&gt;원격 저장소 레포지토리 복제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;git pull&lt;/td&gt;
&lt;td&gt;원격 저장소 최신 내용을 로컬 저장소로 가져옴&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 명령어의 상세한 사용 예시도 정리해두면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저장소 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git init&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원격 저장소 연결&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git remote add origin &quot;저장소 주소&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상태 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스테이징&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;git add 파일명&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커밋&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;git commit -m &quot;메모&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원격 저장소 업로드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;git push &amp;lt;원격저장소명&amp;gt; &amp;lt;브랜치명&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저장소 복제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;git clone &quot;원격 저장소 주소&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최신 내용 가져오기&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git pull&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git과 GitHub는 협업 개발의 &lt;b&gt;필수 도구&lt;/b&gt;입니다. 자주 쓰는 명령어는 다음 5~6개 입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;git init &amp;rarr; 저장소 초기화&lt;/li&gt;
&lt;li&gt;git add &amp;rarr; 스테이징&lt;/li&gt;
&lt;li&gt;git commit &amp;rarr; 변경 확정&lt;/li&gt;
&lt;li&gt;git push &amp;rarr; 원격 업로드&lt;/li&gt;
&lt;li&gt;git pull &amp;rarr; 원격 최신 내용 가져오기&lt;/li&gt;
&lt;li&gt;git clone &amp;rarr; 원격 저장소 복제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료 및 이미지 출처&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/git/what-is-a-git-repository/&quot;&gt;What is a Git Repository? - GeeksforGeeks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.abdulrahmanuk.com/day-8-task-basics-of-git-github/&quot;&gt;Day 8 Task: Basics of Git &amp;amp; GitHub - Abdulrahman&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/GitHub</category>
      <category>git</category>
      <category>github</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/19</guid>
      <comments>https://jupeternotebook.tistory.com/19#entry19comment</comments>
      <pubDate>Sun, 17 May 2026 03:43:27 +0900</pubDate>
    </item>
    <item>
      <title>Java의 메모리 관리</title>
      <link>https://jupeternotebook.tistory.com/18</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 메모리 관리란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램에 필요한 저장 공간을 할당하고 해제하는 것&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목적&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 실행에 필요한 데이터를 저장하기 위해 사용&lt;/li&gt;
&lt;li&gt;효율적인 메모리 사용과 안정성 확보&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. C언어 메모리 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 메모리를 직접 다룰 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대표 함수&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;malloc() : 메모리 할당&lt;/li&gt;
&lt;li&gt;free() : 할당된 메모리 해제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간 과정 없이 직접 다루므로 속도가 빠름&lt;/li&gt;
&lt;li&gt;메모리 사용 효율이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람이 직접 관리하므로 실수 발생 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대표적인 문제&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 누수(Memory Leak) : 사용이 끝난 메모리를 반납하지 않는 문제&lt;/li&gt;
&lt;li&gt;해제된 메모리 접근 : 이미 free() 한 메모리를 다시 접근하는 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Java 메모리 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM 실행 시 운영체제로부터 메모리를 할당받음&lt;/li&gt;
&lt;li&gt;JVM이 메모리를 관리함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Java에서는 개발자가 직접 메모리를 할당하거나 해제할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 JVM은 메모리를 크게 Stack과 Heap 두 영역으로 나누어 관리합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 스택(Stack)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 흐름을 담는 메모리 공간&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개인 작업대&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지역변수(Local Variable) 저장&lt;/li&gt;
&lt;li&gt;메서드 호출 정보 저장&lt;/li&gt;
&lt;li&gt;메서드 실행 종료 시 자동 반납&lt;/li&gt;
&lt;li&gt;메모리 관리가 비교적 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 힙(Heap)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공용으로 사용하는 메모리 공간&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유 주방&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체(Object)와 인스턴스 데이터를 저장&lt;/li&gt;
&lt;li&gt;메서드 종료 후에도 객체가 바로 사라지지 않음&lt;/li&gt;
&lt;li&gt;여러 곳에서 함께 참조 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 스택과 힙을 나누는가?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 관리 효율성과 실행 성능 향상을 위해&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Stack의 특성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빠른 생성 및 제거 가능&lt;/li&gt;
&lt;li&gt;실행 흐름 관리에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Heap의 특성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;크기가 큰 데이터 및 객체 저장에 적합&lt;/li&gt;
&lt;li&gt;여러 객체가 공유 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 정리&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack은 메서드 실행 중심, Heap은 객체 저장 중심&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. GC(Garbage Collector)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙 메모리 영역에서 더 이상 사용되지 않는 객체를 제거하는 기능&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참조되지 않는 객체 제거&lt;/li&gt;
&lt;li&gt;사용 가능한 메모리 확보&lt;/li&gt;
&lt;li&gt;메모리 누수 방지 도움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작 기준&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 제거되는 경우는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GC Root를 기준으로 참조 관계를 탐색했을 때&lt;/li&gt;
&lt;li&gt;더 이상 참조되지 않는 객체인 경우 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GC Root 예시&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스택의 지역 변수&lt;/li&gt;
&lt;li&gt;static 변수&lt;/li&gt;
&lt;li&gt;JNI 참조 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;GC Root를 기준으로 참조 관계 탐색&lt;/li&gt;
&lt;li&gt;사용 중인 객체와 사용되지 않는 객체 구분&lt;/li&gt;
&lt;li&gt;참조되지 않는 힙 메모리 제거&lt;/li&gt;
&lt;li&gt;메모리 정리 수행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Stop-The-World&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GC 과정에서 일부 Stop-The-World가 발생할 수 있음&lt;/li&gt;
&lt;li&gt;이 과정에서 프로그램 실행이 잠시 멈춤&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 JVM GC인 G1 GC, ZGC, Shenandoah 등의 저지연 GC는 이 정지 시간을 줄이기 위해 지속적으로 개선되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메모리 압축(Compaction)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GC는 메모리 압축을 수행하기도 함&lt;/li&gt;
&lt;li&gt;흩어진 메모리를 정리하여 메모리 단편화(Fragmentation)를 줄임&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 전체 흐름 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Stack&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메서드 실행을 관리하는 공간&lt;/li&gt;
&lt;li&gt;지역 변수 저장&lt;/li&gt;
&lt;li&gt;자동 생성 및 자동 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Heap&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체를 저장하는 공간&lt;/li&gt;
&lt;li&gt;여러 곳에서 참조 가능&lt;/li&gt;
&lt;li&gt;GC가 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 메모리 관리는 결국 &quot;개발자 대신 JVM이 알아서 해준다&quot;는 한 문장으로 요약할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 내부에는 다음과 같은 구조가 숨어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stack : 빠르게 생성되고 제거되는 실행 흐름 공간&lt;/li&gt;
&lt;li&gt;Heap : 객체가 살아가는 공유 공간&lt;/li&gt;
&lt;li&gt;GC : Heap을 청소하는 자동 관리자&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/Java</category>
      <category>GC</category>
      <category>heap</category>
      <category>java</category>
      <category>stack</category>
      <category>메모리</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/18</guid>
      <comments>https://jupeternotebook.tistory.com/18#entry18comment</comments>
      <pubDate>Sun, 17 May 2026 02:02:21 +0900</pubDate>
    </item>
    <item>
      <title>컴퓨터 언어 정리</title>
      <link>https://jupeternotebook.tistory.com/17</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 컴퓨터 언어란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진법을 이용하여 논리 연산, 저장을 수행하는 전자 기계와 소통하기 위한 수단&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터와 사람이 &lt;b&gt;의사소통하기 위한 방법&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;명령을 전달하고 결과를 받기 위해 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해, 사람의 말과 컴퓨터의 말이 다르기 때문에 그 둘을 이어주는 &lt;b&gt;번역 도구&lt;/b&gt;가 필요하고, 그 역할을 하는 것이 바로 컴퓨터 언어입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 이진법 (Binary)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0과 1이라는 두 개의 숫자를 사용해 수를 나타내는 진법&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용하는 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터는 &lt;b&gt;전기적인 신호&lt;/b&gt;를 기반으로 동작함&lt;/li&gt;
&lt;li&gt;전기의 상태를 단순하게 두 가지로 구분 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;열림 &amp;rarr; &lt;b&gt;1&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;닫힘 &amp;rarr; &lt;b&gt;0&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전자 회로 설계가 단순해짐&lt;/li&gt;
&lt;li&gt;신호 처리 속도가 빨라짐&lt;/li&gt;
&lt;li&gt;안정성이 높음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람이 읽고 이해하기 어려움&lt;/li&gt;
&lt;li&gt;유지보수가 힘듦&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 기계어 (Machine Language)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터가 &lt;b&gt;직접 이해하는 언어&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이진수(0과 1)로 이루어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용하는 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU가 바로 해석하여 실행 가능하기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한계점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가독성이 매우 낮음&lt;/li&gt;
&lt;li&gt;코드 작성이 어려움&lt;/li&gt;
&lt;li&gt;수정 및 유지보수가 힘듦&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 고급 언어 (High-Level Language)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기계어의 불편한 점들을 해결하기 위해 읽고 쓰기 쉬운 &lt;b&gt;고급 언어&lt;/b&gt;가 등장했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람이 이해하기 쉬움&lt;/li&gt;
&lt;li&gt;생산성이 높음&lt;/li&gt;
&lt;li&gt;유지보수가 편리함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대표적인 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;li&gt;Swift&lt;/li&gt;
&lt;li&gt;Kotlin&lt;/li&gt;
&lt;li&gt;Ruby&lt;/li&gt;
&lt;li&gt;R&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 한 줄 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 특징&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;이진법&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;0과 1로 데이터를 표현&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;기계어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;컴퓨터가 직접 이해하는 언어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고급 언어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사람이 읽기 쉽도록 만든 언어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;목적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사람과 컴퓨터의 원활한 의사소통&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 우리가 사용하는 &lt;b&gt;Java, Python, C++ 같은 고급 언어&lt;/b&gt;는 모두 컴퓨터가 직접 이해할 수 있는 &lt;b&gt;기계어(이진수)&lt;/b&gt; 로 번역되어 실행됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이진법&lt;/b&gt;은 컴퓨터가 신호를 표현하는 방식&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기계어&lt;/b&gt;는 컴퓨터가 직접 이해하는 언어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고급 언어&lt;/b&gt;는 사람이 이해하기 쉽게 만든 언어&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/Java</category>
      <category>java</category>
      <category>프로그래밍언어</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/17</guid>
      <comments>https://jupeternotebook.tistory.com/17#entry17comment</comments>
      <pubDate>Sun, 17 May 2026 01:55:15 +0900</pubDate>
    </item>
    <item>
      <title>Java 기초 정리</title>
      <link>https://jupeternotebook.tistory.com/16</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자바(Java)&lt;/b&gt; : 다양한 플랫폼에서 실행될 수 있도록 설계된, 메모리 관리와 강력한 라이브러리 생태계를 갖춘 객체지향 프로그래밍 언어이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 자바의 탄생 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;당시 상황&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1980년대에는 주로 &lt;b&gt;C&lt;/b&gt;와 &lt;b&gt;C++&lt;/b&gt; 을 사용함&lt;/li&gt;
&lt;li&gt;특히 &lt;b&gt;C언어&lt;/b&gt;는 운영체제 및 시스템 개발의 표준처럼 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가전제품과 임베디드 시스템이 발전하면서 다양한 하드웨어 환경이 등장했지만, 다음과 같은 문제들이 존재했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C언어는 &lt;b&gt;절차지향 언어&lt;/b&gt;라 프로젝트 규모가 커질수록 유지보수 및 관리가 어려움&lt;/li&gt;
&lt;li&gt;프로젝트 규모가 커질수록 유지보수가 어려움&lt;/li&gt;
&lt;li&gt;제조사마다 사용하는 &lt;b&gt;CPU 구조&lt;/b&gt;와 &lt;b&gt;기계어 체계&lt;/b&gt;가 다름&lt;/li&gt;
&lt;li&gt;하드웨어마다 별도로 프로그램을 준비해야 하는 번거로움 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 시도&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;썬 마이크로시스템즈(Sun Microsystems)&lt;/b&gt; 는 CPU와 운영체제에 상관없이 실행 가능한 언어를 만들고자 Java를 개발했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&quot;한 번 작성하면 어디서든 실행 가능(Write Once, Run Anywhere)&quot;&lt;/b&gt; 을 목표로 개발&lt;/li&gt;
&lt;li&gt;초반에는 큰 주목을 받지 못했음&lt;/li&gt;
&lt;li&gt;이후 &lt;b&gt;WWW(World Wide Web)&lt;/b&gt; 의 발전과 함께 다시 부흥함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 자바의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 플랫폼 독립성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 및 운영체제에 상관없이 실행 가능&lt;/li&gt;
&lt;li&gt;중간 단계인 &lt;b&gt;바이트코드(Bytecode)&lt;/b&gt; 를 사용함&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JVM&lt;/b&gt;이 바이트코드를 각 환경의 기계어로 변환하여 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Java 코드 &amp;rarr; 바이트코드(.class) &amp;rarr; JVM &amp;rarr; 기계어&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추가로 알아두면 좋은 점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 단순히 바이트코드를 기계어로 변환만 하는 것이 아니라,&lt;br /&gt;&lt;b&gt;인터프리터 방식&lt;/b&gt;과 &lt;b&gt;JIT(Just-In-Time Compiler)&lt;/b&gt; 를 함께 사용합니다.&lt;br /&gt;자주 실행되는 코드는 JIT 컴파일을 통해 기계어로 변환하여 성능을 향상시킵니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 보안 안정성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JVM 내부&lt;/b&gt;에서 프로그램이 실행됨&lt;/li&gt;
&lt;li&gt;운영체제에 직접 접근하지 않고 JVM이라는 울타리 안에서 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점으로는 ①안정성이 높고 ②악성 코드 실행 위험이 감소하며 ③메모리 접근 오류도 줄어든다는 점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 메모리 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C / C++ 의 경우 개발자가 직접 메모리를 관리해야 하기 때문에 속도는 빠르지만 관리가 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Java는,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JVM이 메모리를 자동 관리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Garbage Collector(GC)&lt;/b&gt; 를 통해 사용하지 않는 메모리 정리&lt;/li&gt;
&lt;li&gt;관련 영역: &lt;b&gt;Heap&lt;/b&gt;, &lt;b&gt;Stack&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 객체지향(Object-Oriented)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;클래스&lt;/b&gt;와 &lt;b&gt;객체&lt;/b&gt; 중심으로 개발&lt;/li&gt;
&lt;li&gt;현실 세계를 코드로 표현하기 쉬움&lt;/li&gt;
&lt;li&gt;장점: 높은 확장성, 높은 코드 재사용성, 유지보수 용이, 높은 유연성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. JVM (Java Virtual Machine)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 &lt;b&gt;바이트코드를 실행하는 가상 머신&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 다음과 같은 역할을 수행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;바이트코드를 기계어로 변환 및 실행&lt;/li&gt;
&lt;li&gt;운영체제로부터 메모리를 할당받음&lt;/li&gt;
&lt;li&gt;JVM 내부에서 메모리 관리 수행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플랫폼 독립성&lt;/b&gt;을 제공하는 핵심 요소&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. JRE (Java Runtime Environment)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 프로그램 &lt;b&gt;실행&lt;/b&gt;을 위한 최소 실행 환경&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성 요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM&lt;/li&gt;
&lt;li&gt;자바 클래스 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 프로그램 실행 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개발(컴파일)은 불가능&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이미 컴파일된 바이트코드를 실행하는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. JDK (Java Development Kit)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 &lt;b&gt;개발&lt;/b&gt;을 위한 도구 모음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 코드 작성 및 컴파일 가능&lt;/li&gt;
&lt;li&gt;바이트코드 생성 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성 요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JRE&lt;/li&gt;
&lt;li&gt;&lt;b&gt;javac&lt;/b&gt; (컴파일러)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디버거(Debugger)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;개발 도구들&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 관계&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1nX9o/dJMcacb4f6t/zdIE543llNkfWR2nIXfKM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1nX9o/dJMcacb4f6t/zdIE543llNkfWR2nIXfKM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1nX9o/dJMcacb4f6t/zdIE543llNkfWR2nIXfKM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1nX9o%2FdJMcacb4f6t%2FzdIE543llNkfWR2nIXfKM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;451&quot; data-filename=&quot;image (1).png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;JDK &amp;sup; JRE &amp;sup; JVM&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;JDK 안에 JRE가 있고, JRE 안에 JVM이 포함&lt;/b&gt;되어 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 전체 구조 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[개발 단계]&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Java 코드 작성
        &amp;darr;
JDK(javac)로 컴파일
        &amp;darr;
바이트코드(.class) 생성&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[실행 단계]&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;JRE 실행 환경 제공
        &amp;darr;
JVM이 바이트코드를 기계어로 변환
        &amp;darr;
운영체제에서 실행&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 &lt;b&gt;&quot;플랫폼 독립적인 언어&quot;&lt;/b&gt; 라는 강력한 특징을 가진 프로그래밍 언어이다. 이를 가능하게 해주는 데에는&amp;nbsp;&lt;b&gt;JVM, JRE, JDK&lt;/b&gt;가 큰 역할을 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JVM&lt;/b&gt; &amp;rarr; 실행 엔진&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JRE&lt;/b&gt; &amp;rarr; 실행 환경&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JDK&lt;/b&gt; &amp;rarr; 개발 도구 모음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지의 관계만 명확히 잡아두면, 앞으로 자바를 공부할 때 훨씬 수월하게 이해할 수 있을 것 같습니다.&lt;/p&gt;</description>
      <category>CS/Java</category>
      <category>java</category>
      <category>jdk</category>
      <category>jre</category>
      <category>jvm</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/16</guid>
      <comments>https://jupeternotebook.tistory.com/16#entry16comment</comments>
      <pubDate>Sun, 17 May 2026 01:33:34 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - H Index (Java)</title>
      <link>https://jupeternotebook.tistory.com/15</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42747&quot;&gt;H-Index&lt;/a&gt; 풀이 과정&lt;br /&gt;1트 오답, 2트 정답(수정), 3트 정답(힌트). 조건문의 의미를 이해하는 데 시간이 걸렸다.&lt;br /&gt;총 3번의 제출&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H-Index란, &lt;code&gt;n&lt;/code&gt;편의 논문 중 &lt;code&gt;h&lt;/code&gt;번 이상 인용된 논문이 &lt;code&gt;h&lt;/code&gt;편 이상이고, 나머지 논문이 &lt;code&gt;h&lt;/code&gt;번 이하 인용되었을 때 가장 큰 &lt;code&gt;h&lt;/code&gt;를 의미한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (오답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 최대 인용 횟수까지 &lt;code&gt;cnt&lt;/code&gt;를 증가시키며 완전탐색&lt;br /&gt;&amp;rarr; 각 &lt;code&gt;cnt&lt;/code&gt;에서 &lt;code&gt;cnt&lt;/code&gt;번 이상 인용된 논문 수(&lt;code&gt;more&lt;/code&gt;)와 이하인 논문 수(&lt;code&gt;less&lt;/code&gt;)를 셈&lt;br /&gt;&amp;rarr; &lt;code&gt;more &amp;gt;= cnt &amp;amp;&amp;amp; less &amp;lt;= cnt&lt;/code&gt;이면 &lt;code&gt;answer&lt;/code&gt;를 &lt;code&gt;more&lt;/code&gt;로 갱신&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int solution(int[] citations) {
        Arrays.sort(citations);
        int max = citations[citations.length-1];

        int cnt = 0;
        int answer = 0;
        while(cnt&amp;lt;=max) {
            int more = 0;
            int less = 0;
            for(int i=0; i&amp;lt;citations.length; i++) {
                if(citations[i] &amp;gt;= cnt) more++;
                if(citations[i] &amp;lt;= cnt) less++;
            }

            if(more&amp;gt;=cnt &amp;amp;&amp;amp; less&amp;lt;=cnt) answer = Math.max(answer, more);
            cnt++;
        }
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H-Index 정의를 코드로 옮기려 했는데, &lt;code&gt;answer = more&lt;/code&gt;로 갱신하는 부분이 잘못됐다.&lt;br /&gt;&lt;code&gt;more&lt;/code&gt;는 &quot;h번 이상 인용된 논문의 수&quot;이지, h값 자체가 아니다.&lt;br /&gt;예를 들어 &lt;code&gt;citations = [3, 0, 6, 1, 5]&lt;/code&gt;일 때 &lt;code&gt;cnt = 3&lt;/code&gt;이면 &lt;code&gt;more = 3&lt;/code&gt;이지만, 이 둘이 같은 건 우연이지 항상 성립하지 않는다.&lt;br /&gt;결국 h값이 뭔지 제대로 정의하지 못한 채 코드를 짰다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 수정 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트 코드에서 &lt;code&gt;answer = Math.max(answer, more)&lt;/code&gt; 한 줄만 &lt;code&gt;answer = cnt&lt;/code&gt;로 바꾸면 정답이 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int solution(int[] citations) {
        Arrays.sort(citations);
        int max = citations[citations.length-1];

        int cnt = 0;
        int answer = 0;
        while(cnt&amp;lt;=max) {
            int more = 0;
            int less = 0;
            for(int i=0; i&amp;lt;citations.length; i++) {
                if(citations[i] &amp;gt;= cnt) more++;
                if(citations[i] &amp;lt;= cnt) less++;
            }

            if(more&amp;gt;=cnt &amp;amp;&amp;amp; less&amp;lt;=cnt) answer = cnt;
            cnt++;
        }
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;answer = cnt&lt;/code&gt;가 맞는 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트에서 &lt;code&gt;answer = more&lt;/code&gt;로 쓴 게 왜 틀렸는지를 다시 보면 답이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건 &lt;code&gt;more &amp;gt;= cnt &amp;amp;&amp;amp; less &amp;lt;= cnt&lt;/code&gt;가 의미하는 건 &quot;&lt;code&gt;cnt&lt;/code&gt;번 이상 인용된 논문이 &lt;code&gt;cnt&lt;/code&gt;편 이상 존재한다&quot;는 것이다.&lt;br /&gt;이 조건을 만족하는 &lt;code&gt;cnt&lt;/code&gt;가 바로 h의 정의를 충족하는 h값이다.&lt;br /&gt;우리가 찾는 건 h값, 즉 &lt;code&gt;cnt&lt;/code&gt;이지 논문의 수인 &lt;code&gt;more&lt;/code&gt;가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;while&lt;/code&gt;이 &lt;code&gt;cnt&lt;/code&gt;를 0부터 &lt;code&gt;max&lt;/code&gt;까지 순회하므로, 조건을 만족할 때마다 &lt;code&gt;answer = cnt&lt;/code&gt;로 덮어쓰면 자연스럽게 가장 큰 h값이 &lt;code&gt;answer&lt;/code&gt;에 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;citations = [3, 0, 6, 1, 5]&lt;/code&gt; 기준으로 단계별로 보면:&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cnt&lt;/th&gt;
&lt;th&gt;more (cnt 이상 인용)&lt;/th&gt;
&lt;th&gt;less (cnt 이하 인용)&lt;/th&gt;
&lt;th&gt;조건 성립?&lt;/th&gt;
&lt;th&gt;answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;cnt = 3&lt;/code&gt;에서 마지막으로 조건이 성립하고, &lt;code&gt;answer = 3&lt;/code&gt;이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트에서 &lt;code&gt;more&lt;/code&gt;를 담은 건, &quot;&lt;code&gt;more&lt;/code&gt;편 이상 인용됐으니 &lt;code&gt;more&lt;/code&gt;가 h값 아닌가?&quot;라는 착각 때문이었다.&lt;br /&gt;하지만 h값은 &quot;몇 번 이상 인용&quot;에 해당하는 기준값이고, 그게 바로 &lt;code&gt;cnt&lt;/code&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2트 (힌트를 받아 정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 내림차순 정렬&lt;br /&gt;&amp;rarr; 인덱스 &lt;code&gt;i&lt;/code&gt;를 순회하면서 &lt;code&gt;list.get(i) &amp;gt;= i+1&lt;/code&gt;이면 &lt;code&gt;answer = i+1&lt;/code&gt;로 갱신&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int solution(int[] citations) {
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        for(int i : citations) list.add(i);
        Collections.sort(list, Collections.reverseOrder());

        int answer = 0;
        for(int i=0; i&amp;lt;list.size(); i++) {
            if(list.get(i) &amp;gt;= i+1) {
                answer = i+1;
            }
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;list.get(i) &amp;gt;= i+1&lt;/code&gt; 이 조건이 왜 맞는가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q1. 내림차순 정렬을 왜 하는가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H-Index를 구하려면 결국 두 가지를 알아야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지금 기준이 되는 인용 수가 얼마인가&lt;/li&gt;
&lt;li&gt;그 인용 수 이상인 논문이 몇 편인가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 없이 이 둘을 알려면 1트처럼 매번 전체를 다 세야 한다.&lt;br /&gt;그런데 &lt;b&gt;내림차순으로 정렬하면 &quot;몇 편인지&quot;를 직접 셀 필요가 없어진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;[3, 0, 6, 1, 5]&lt;/code&gt;를 내림차순 정렬하면 &lt;code&gt;[6, 5, 3, 1, 0]&lt;/code&gt;.&lt;br /&gt;이제 왼쪽부터 논문을 하나씩 볼 때마다, 지금 보고 있는 논문까지는 전부 현재 값 이상 인용됐다는 게 자동으로 보장된다.&lt;br /&gt;순서가 크기 순이니 당연한 얘기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q2. &lt;code&gt;i+1&lt;/code&gt;이 왜 논문 수인가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 0부터 시작한다. 그래서 인덱스 &lt;code&gt;i&lt;/code&gt;에 있다는 건 앞에 &lt;code&gt;i&lt;/code&gt;개가 더 있다는 뜻이고, 지금까지 본 논문 수는 &lt;code&gt;i+1&lt;/code&gt;편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Q1에서 말한 대로, 내림차순이기 때문에 이 &lt;code&gt;i+1&lt;/code&gt;편은 전부 &lt;code&gt;list.get(i)&lt;/code&gt; 이상 인용됐다는 게 보장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 표로 보면 직관적으로 이해가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;[6, 5, 3, 1, 0]&lt;/code&gt; 기준:&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;i&lt;/th&gt;
&lt;th&gt;list.get(i)&lt;/th&gt;
&lt;th&gt;&quot;이 값 이상 인용된 논문 수&quot;는?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;최소 1편 (인덱스 0만)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;최소 2편 (인덱스 0~1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;최소 3편 (인덱스 0~2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;최소 4편 (인덱스 0~3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;최소 5편 (인덱스 0~4)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 값 이상 인용된 논문 수&quot;가 항상 &lt;code&gt;i+1&lt;/code&gt;이다. 별도로 세지 않아도 인덱스를 보면 바로 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q3. 그러면 조건 &lt;code&gt;list.get(i) &amp;gt;= i+1&lt;/code&gt;은 무슨 뜻인가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;H-Index 정의: &lt;b&gt;h편 이상의 논문이 h번 이상 인용&lt;/b&gt;돼야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 정리한 걸 대입하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;i+1&lt;/code&gt; = 지금까지 본 논문 수&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list.get(i)&lt;/code&gt; = 그 논문들의 최솟값 (= 이 값 이상 인용됐다는 기준)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;list.get(i) &amp;gt;= i+1&lt;/code&gt;이면 &amp;rarr; &lt;b&gt;&lt;code&gt;i+1&lt;/code&gt;편의 논문이 &lt;code&gt;i+1&lt;/code&gt;번 이상 인용됐다&lt;/b&gt; &amp;rarr; H-Index 정의를 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계별로 보면:&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;i&lt;/th&gt;
&lt;th&gt;list.get(i)&lt;/th&gt;
&lt;th&gt;i+1&lt;/th&gt;
&lt;th&gt;조건 성립?&lt;/th&gt;
&lt;th&gt;해석&lt;/th&gt;
&lt;th&gt;answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;1편이 6번 이상 인용됨&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;2편이 5번 이상 인용됨&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;3편이 3번 이상 인용됨&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;4편이 1번 이상 인용? &amp;rarr; 조건 미달&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;5편이 0번 이상 인용? &amp;rarr; 조건 미달&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;i = 2&lt;/code&gt;에서 &quot;3편이 3번 이상 인용됐다&quot;가 성립하는 게 마지막이다. &amp;rarr; &lt;code&gt;answer = 3&lt;/code&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내림차순이라 &lt;code&gt;i&lt;/code&gt;가 커질수록 &lt;code&gt;list.get(i)&lt;/code&gt;는 작아지고 &lt;code&gt;i+1&lt;/code&gt;은 커지기 때문에, 한 번 조건이 깨지면 그 뒤로는 다시 성립하지 않는다. 그래서 조건을 만족하는 마지막 &lt;code&gt;i+1&lt;/code&gt;이 최댓값이고, 그게 &lt;code&gt;answer&lt;/code&gt;에 남는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 / 1트 수정 / 2트 비교&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;1트 (오답)&lt;/th&gt;
&lt;th&gt;1트 수정 (정답)&lt;/th&gt;
&lt;th&gt;2트 (정답)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;접근 방식&lt;/td&gt;
&lt;td&gt;완전탐색&lt;/td&gt;
&lt;td&gt;완전탐색&lt;/td&gt;
&lt;td&gt;정렬 후 한 번 순회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시간복잡도&lt;/td&gt;
&lt;td&gt;O(n &amp;times; max)&lt;/td&gt;
&lt;td&gt;O(n &amp;times; max)&lt;/td&gt;
&lt;td&gt;O(n log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;h값 결정&lt;/td&gt;
&lt;td&gt;&lt;code&gt;more&lt;/code&gt;로 잘못 설정&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cnt&lt;/code&gt;로 정확히 표현&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i+1&lt;/code&gt;로 정확히 표현&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;핵심 아이디어&lt;/td&gt;
&lt;td&gt;h의 정의를 코드로 나열&lt;/td&gt;
&lt;td&gt;h의 정의를 코드로 나열&lt;/td&gt;
&lt;td&gt;정렬 후 인덱스가 논문 수를 대변&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트와 1트 수정의 차이는 딱 한 줄, &lt;code&gt;more&lt;/code&gt; &amp;rarr; &lt;code&gt;cnt&lt;/code&gt;뿐이다.&lt;br /&gt;로직의 방향은 처음부터 맞았는데, h값이 &lt;code&gt;cnt&lt;/code&gt;라는 걸 놓쳤다.&lt;br /&gt;2트는 완전탐색 없이 정렬만으로 같은 결과를 얻는다. 인덱스가 논문 수를 대신하기 때문에 별도의 카운팅이 필요 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문 &lt;code&gt;list.get(i) &amp;gt;= i+1&lt;/code&gt; 한 줄이 이해가 안 됐던 이유는, 내림차순 정렬 후 인덱스가 무엇을 의미하는지 파악을 못 했기 때문이다.&lt;br /&gt;정렬 후 인덱스가 &quot;지금까지 몇 편&quot;인지를 나타낸다는 걸 이해하고 나면 조건이 자연스럽게 읽힌다.&lt;br /&gt;앞으로 정렬 문제에서 인덱스가 카운터 역할을 할 수 있다는 걸 기억해두면 좋을 것 같다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>정렬</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/15</guid>
      <comments>https://jupeternotebook.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 7 May 2026 09:58:36 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - K번째 수 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/14</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42748&quot;&gt;K번째수&lt;/a&gt; 풀이 과정&lt;br /&gt;1트 정답. 과거 코드와 비교했을 때 &lt;code&gt;Arrays.copyOfRange()&lt;/code&gt; 덕분에 훨씬 간결해졌다.&lt;br /&gt;총 1번의 제출&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;i&lt;/code&gt;번째부터 &lt;code&gt;j&lt;/code&gt;번째까지 자르고, 정렬한 뒤 &lt;code&gt;k&lt;/code&gt;번째 값을 반환하면 된다.&lt;br /&gt;로직 자체는 단순해서 바로 구현했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;code&gt;commands&lt;/code&gt;를 순회하면서 &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;k&lt;/code&gt;를 꺼낸다.&lt;br /&gt;&amp;rarr; &lt;code&gt;Arrays.copyOfRange()&lt;/code&gt;로 &lt;code&gt;array&lt;/code&gt;의 &lt;code&gt;i-1&lt;/code&gt;부터 &lt;code&gt;j&lt;/code&gt;까지 잘라 새 배열을 만든다.&lt;br /&gt;&amp;rarr; &lt;code&gt;Arrays.sort()&lt;/code&gt;로 정렬한 뒤 &lt;code&gt;k-1&lt;/code&gt; 인덱스 값을 &lt;code&gt;answer&lt;/code&gt;에 담는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public int[] solution(int[] array, int[][] commands) {
        int[] answer = new int[commands.length];

        int idx = 0;
        for(int[] command : commands) {
            int i = command[0];
            int j = command[1];
            int k = command[2];
            int[] copy = Arrays.copyOfRange(array, i-1, j);
            Arrays.sort(copy);
            answer[idx++] = copy[k-1];
        }
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;Arrays.copyOfRange()&lt;/code&gt; 란&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Arrays.copyOfRange(원본 배열, 시작 인덱스, 끝 인덱스)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 배열의 특정 범위를 잘라 &lt;b&gt;새로운 배열&lt;/b&gt;로 반환하는 메서드다.&lt;br /&gt;시작 인덱스는 포함, 끝 인덱스는 미포함이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int[] arr = {1, 2, 3, 4, 5};
int[] copy = Arrays.copyOfRange(arr, 1, 4);
// copy = {2, 3, 4}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원본 배열은 그대로 유지되고, 잘라낸 범위만 담긴 새 배열이 생성된다.&lt;br /&gt;이 문제처럼 배열의 일부를 정렬해야 할 때 원본을 건드리지 않고 쓸 수 있어서 유용하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;copyOfRange()&lt;/code&gt; 한 줄로 범위 추출과 새 배열 생성을 동시에 처리했다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int[]&lt;/code&gt; 기본 타입 배열이라 &lt;code&gt;Arrays.sort()&lt;/code&gt;가 바로 적용된다.&lt;/li&gt;
&lt;li&gt;과거 코드와 비교하면 코드가 눈에 띄게 짧아졌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;과거 코드와 비교&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
class Solution {
    public int[] solution(int[] array, int[][] commands) {
        int[] answer = new int[commands.length];
        for(int i=0; i&amp;lt;commands.length; i++) {
            ArrayList&amp;lt;Integer&amp;gt; tmp = new ArrayList&amp;lt;&amp;gt;();
            for(int j=commands[i][0]-1; j&amp;lt;commands[i][1]; j++) {
                tmp.add(array[j]);
            }
            Collections.sort(tmp);
            answer[i] = tmp.get(commands[i][2]-1);
        }
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;현재 코드&lt;/th&gt;
&lt;th&gt;과거 코드&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;범위 추출&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Arrays.copyOfRange()&lt;/code&gt; 한 줄&lt;/td&gt;
&lt;td&gt;&lt;code&gt;for&lt;/code&gt;문으로 직접 순회하며 &lt;code&gt;add()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자료구조&lt;/td&gt;
&lt;td&gt;&lt;code&gt;int[]&lt;/code&gt; 기본 배열&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ArrayList&amp;lt;Integer&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정렬&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Arrays.sort()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Collections.sort()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인덱스 접근&lt;/td&gt;
&lt;td&gt;&lt;code&gt;copy[k-1]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tmp.get(commands[i][2]-1)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;불필요한 import&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Scanner&lt;/code&gt; import가 있는데 사용 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 코드는 &lt;code&gt;copyOfRange()&lt;/code&gt;를 몰랐으니 &lt;code&gt;for&lt;/code&gt;문으로 직접 &lt;code&gt;ArrayList&lt;/code&gt;에 옮겨 담았다.&lt;br /&gt;로직은 동일하지만 &lt;code&gt;ArrayList&lt;/code&gt;를 쓰면 &lt;code&gt;int &amp;rarr; Integer&lt;/code&gt; 박싱 비용이 발생하고, &lt;code&gt;Collections.sort()&lt;/code&gt;까지 써야 해서 현재 코드보다 무겁다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 코드가 더 나은가 하면 현재 코드다.&lt;br /&gt;&lt;code&gt;copyOfRange()&lt;/code&gt;로 기본 타입 배열을 그대로 다루니까 박싱 오버헤드가 없고, 코드도 짧고 읽기 편하다.&lt;br /&gt;과거 코드가 틀린 건 아니지만, &lt;code&gt;Arrays.copyOfRange()&lt;/code&gt;를 알고 나면 굳이 &lt;code&gt;ArrayList&lt;/code&gt;를 쓸 이유가 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직 자체는 과거에도 맞게 짰다. 달라진 건 &lt;code&gt;copyOfRange()&lt;/code&gt;라는 도구를 하나 알게 된 것뿐이다.&lt;br /&gt;아는 메서드가 늘어날수록 코드가 짧아지고, 불필요한 자료구조 변환도 줄어든다.&lt;br /&gt;앞으로 배열 일부를 다뤄야 할 때 &lt;code&gt;copyOfRange()&lt;/code&gt;를 바로 떠올릴 수 있을 것 같다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS/코딩테스트</category>
      <category>정렬</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/14</guid>
      <comments>https://jupeternotebook.tistory.com/14#entry14comment</comments>
      <pubDate>Fri, 1 May 2026 17:48:23 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 가장 큰 수 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/13</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42746&quot;&gt;가장 큰 수&lt;/a&gt; 풀이 과정&lt;br&gt;스스로 풀지 못했다. 접근 방향은 두 번 바꿨는데 둘 다 코드로 구현하지 못했다.&lt;br&gt;과거 코드(남의 코드)를 참고해서 정답을 냈고, 핵심 로직을 이번에 제대로 이해했다.&lt;br&gt;총 1번의 제출&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;p&gt;처음엔 만들 수 있는 모든 숫자를 &lt;code&gt;ArrayList&lt;/code&gt;에 넣고 내림차순 정렬하면 가장 큰 수가 맨 앞에 오지 않을까라고 단순하게 접근했다.&lt;br&gt;근데 모든 경우의 수는 배열 길이에 따라 너무 커지고, 반복문으로 모든 조합을 만드는 방식 자체가 머릿속에서 정리가 안 됐다.&lt;/p&gt;
&lt;p&gt;두 번째로 생각한 방법은 정렬 기준 자체를 정의하는 거였다.&lt;br&gt;길이가 같으면 내림차순, 길이가 다르면 두 자리 수의 일의 자리와 한 자리 수를 비교해서 정렬하는 방식이었는데,&lt;br&gt;이것도 코드로 구현하기엔 너무 복잡했다.&lt;/p&gt;
&lt;p&gt;결국 과거에 참고했던 남의 코드를 다시 꺼냈다. 근데 여전히 핵심 로직이 이해가 안 됐다.&lt;br&gt;이번 기회에 제대로 뜯어봤다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;1트 (정답, 남의 코드 참고)&lt;/h3&gt;
&lt;p&gt;→ &lt;code&gt;int[]&lt;/code&gt;를 &lt;code&gt;String[]&lt;/code&gt;으로 변환한다.&lt;br&gt;→ 두 문자열 &lt;code&gt;s1&lt;/code&gt;, &lt;code&gt;s2&lt;/code&gt;를 이어 붙인 결과를 비교해서 정렬 순서를 정한다.&lt;br&gt;→ &lt;code&gt;(s2+s1).compareTo(s1+s2)&lt;/code&gt; — 이게 핵심이다.&lt;br&gt;→ 모든 값이 &lt;code&gt;&amp;quot;0&amp;quot;&lt;/code&gt;이면 &lt;code&gt;&amp;quot;0&amp;quot;&lt;/code&gt; 반환, 아니면 정렬된 순서대로 이어 붙인다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.*;

class Solution {
    public String solution(int[] numbers) {
        String[] array = new String[numbers.length];

        for(int i=0; i&amp;lt;numbers.length; i++) {
            array[i] = String.valueOf(numbers[i]);
        }

        Arrays.sort(array, new Comparator&amp;lt;String&amp;gt;() {
            @Override
            public int compare(String s1, String s2) {
                return (s2+s1).compareTo(s1+s2);
            }
        });

        if(array[0].equals(&amp;quot;0&amp;quot;)) return &amp;quot;0&amp;quot;;

        String answer = &amp;quot;&amp;quot;;
        for(String s : array) answer += s;

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;핵심 로직 이해 — &lt;code&gt;(s2+s1).compareTo(s1+s2)&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;이게 왜 동작하는지가 이 문제의 전부다.&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;code&gt;s1 = &amp;quot;3&amp;quot;&lt;/code&gt;, &lt;code&gt;s2 = &amp;quot;30&amp;quot;&lt;/code&gt;이라고 해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s1 + s2&lt;/code&gt; = &lt;code&gt;&amp;quot;330&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s2 + s1&lt;/code&gt; = &lt;code&gt;&amp;quot;303&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;둘 중 어떤 순서로 붙여야 더 큰 수가 될까?&lt;br&gt;&lt;code&gt;&amp;quot;330&amp;quot;&lt;/code&gt; &amp;gt; &lt;code&gt;&amp;quot;303&amp;quot;&lt;/code&gt; 이니까 &lt;code&gt;s1&lt;/code&gt;이 앞에 와야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;compareTo&lt;/code&gt;는 &lt;code&gt;(s2+s1)&lt;/code&gt;이 &lt;code&gt;(s1+s2)&lt;/code&gt;보다 작으면 음수를 반환한다.&lt;br&gt;&lt;code&gt;Arrays.sort&lt;/code&gt;는 음수가 나오면 &lt;code&gt;s1&lt;/code&gt;을 앞에 유지한다.&lt;br&gt;즉, &lt;strong&gt;두 문자열을 이어 붙였을 때 더 큰 쪽이 앞에 오도록&lt;/strong&gt; 정렬되는 것이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;s1&lt;/th&gt;
&lt;th&gt;s2&lt;/th&gt;
&lt;th&gt;s1+s2&lt;/th&gt;
&lt;th&gt;s2+s1&lt;/th&gt;
&lt;th&gt;앞에 와야 할 것&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&amp;quot;3&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;30&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;330&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;303&amp;quot;&lt;/td&gt;
&lt;td&gt;s1 (&amp;quot;3&amp;quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;quot;9&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;5&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;95&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;59&amp;quot;&lt;/td&gt;
&lt;td&gt;s2 (&amp;quot;9&amp;quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;quot;34&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;3&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;343&amp;quot;&lt;/td&gt;
&lt;td&gt;&amp;quot;334&amp;quot;&lt;/td&gt;
&lt;td&gt;s1 (&amp;quot;34&amp;quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;내가 두 번째로 시도했던 &amp;quot;길이가 다르면 일의 자리를 비교하자&amp;quot;는 방향은 사실 이 로직이 해결하는 문제와 같은 문제를 풀려고 한 거였다. 근데 직접 비교 조건을 나열하려다 보니 케이스가 너무 많아졌던 것이고, 이 코드는 그냥 &lt;strong&gt;붙여보고 더 큰 쪽을 앞에 놓는다&lt;/strong&gt; 는 단순한 아이디어로 모든 케이스를 한 번에 해결한 거다.&lt;/p&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;정렬 기준을 직접 정의하는 &lt;code&gt;Comparator&lt;/code&gt;를 활용한 풀이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;array[0].equals(&amp;quot;0&amp;quot;)&lt;/code&gt; 체크는 입력이 &lt;code&gt;[0, 0, 0]&lt;/code&gt;처럼 전부 0인 경우 &lt;code&gt;&amp;quot;000&amp;quot;&lt;/code&gt; 대신 &lt;code&gt;&amp;quot;0&amp;quot;&lt;/code&gt;을 반환하기 위한 처리다.&lt;/li&gt;
&lt;li&gt;코드 자체는 짧지만, &lt;code&gt;(s2+s1).compareTo(s1+s2)&lt;/code&gt; 라는 발상을 스스로 떠올리기가 쉽지 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;이 문제는 솔직히 스스로 못 풀었다.&lt;br&gt;첫 번째 접근(모든 경우의 수)은 방향 자체가 틀렸고, 두 번째 접근(직접 비교 조건 나열)은 방향은 맞았지만 구현을 못 했다.&lt;br&gt;핵심 아이디어인 &lt;strong&gt;&amp;quot;두 수를 이어 붙여서 더 큰 쪽이 앞에 오도록 정렬한다&amp;quot;&lt;/strong&gt; 는 발상은 이번에 제대로 이해했다.&lt;br&gt;다음에 비슷한 문제가 나오면 바로 &lt;code&gt;Comparator&lt;/code&gt;에서 두 원소를 조합해서 비교하는 방식을 먼저 떠올릴 수 있을 것 같다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>정렬</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/13</guid>
      <comments>https://jupeternotebook.tistory.com/13#entry13comment</comments>
      <pubDate>Fri, 1 May 2026 17:38:51 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 디스크 컨트롤러 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/12</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42627&quot;&gt;디스크 컨트롤러&lt;/a&gt; 풀이 과정 우선순위 큐를 써야 한다는 건 알았는데, 요청 시각 기준 정렬과 현재 시간 관리를 놓쳤다. NullPointer에 너무 많이 걸려서 어느 지점에서 터지는지는 GPT 도움을 받았고, 나머지는 스스로 풀었다. 총 2번의 제출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트 (오답) &amp;rarr; 요청 시각 무시, 현재 시간 관리 없이 큐에 전부 넣고 순서대로 처리 2트 (정답) &amp;rarr; 요청 시각 기준 정렬 후, 현재 시간에 들어온 작업만 큐에 투입&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 시간이 짧은 것부터 처리하는 SJF(Shortest Job First) 스케줄링 문제라는 걸 파악하고, 소요 시간 기준 최소 힙으로 접근했다. 문제는 &lt;b&gt;&quot;현재 시각에 요청된 작업만 처리 가능하다&quot;&lt;/b&gt; 는 조건을 처음엔 제대로 반영하지 못했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (오답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 전체 작업을 처음부터 큐에 다 넣어버렸다. &amp;rarr; time을 첫 번째 작업의 요청 시각으로 초기화하고, 큐에서 꺼내는 순서대로 처리했다. &amp;rarr; 요청 시각과 무관하게 소요 시간이 짧은 것부터 처리되는 구조가 됐다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int solution(int[][] jobs) {
        PriorityQueue&amp;lt;int[]&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;((int[] a, int[] b) -&amp;gt; {
            if(a[1] == b[1]) return a[0] - b[0];
            else if(a[2] == b[2]) return a[1] - b[1];
            else return a[2] - b[2];
        });
        
        int num = 0;
        for(int[] job : jobs) {
            pq.offer(new int[]{num++, job[0], job[1]});
        }
        
        int time = pq.peek()[1];
        int answer = 0;
        while(!pq.isEmpty()) {
            int[] cur = pq.poll();
            time += cur[2];
            answer += time - cur[1];
        }
        
        return answer/num;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아직 요청되지도 않은 작업이 큐에 들어가 있으니, 현재 시각보다 늦게 들어오는 작업이 먼저 처리되는 경우가 생긴다.&lt;/li&gt;
&lt;li&gt;예를 들어 요청 시각이 100인 작업이 소요 시간이 짧다고 time = 0일 때 먼저 처리되면 대기 시간 계산 자체가 어긋난다.&lt;/li&gt;
&lt;li&gt;NullPointer도 발생했는데, 큐 비교 로직에서 a[2]가 없는 경우를 처리 못 했기 때문이었다. 이 부분은 GPT에게 어느 지점에서 터지는지 확인했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2트 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 먼저 요청 시각 기준으로 jobs를 정렬했다. &amp;rarr; 현재 시각 time 이하로 요청된 작업만 큐에 넣고, 그 중 소요 시간이 가장 짧은 것을 처리한다. &amp;rarr; 큐가 비어있으면 아직 요청된 작업이 없다는 뜻이므로 time++로 시간을 흘려보낸다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int solution(int[][] jobs) {
        Arrays.sort(jobs, (a,b)-&amp;gt;{
            if(a[0] == b[0]) return a[1] - b[1];
            else return a[0] - b[0];
        });
        
        PriorityQueue&amp;lt;int[]&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;((int[] a, int[] b) -&amp;gt; {
            if(a[1] == b[1]) return a[0] - b[0];
            else if(a[2] == b[2]) return a[1] - b[1];
            else return a[2] - b[2];
        });
        
        int num = 0;
        int time = 0;
        int answer = 0;
        while(num &amp;lt; jobs.length || !pq.isEmpty()) {
            // 현재 시각 이하로 요청된 작업을 큐에 투입
            for(int i=num; i&amp;lt;jobs.length; i++) {
                if(jobs[i][0] &amp;lt;= time) {
                    pq.offer(new int[] {i, jobs[i][0], jobs[i][1]});
                    num++;
                }
            }
            
            if(!pq.isEmpty()) {
                int[] cur = pq.poll();
                time += cur[2];
                answer += time - cur[1];
            }
            else time++; // 아직 요청된 작업 없음 &amp;rarr; 시간만 흘림
        }
        
        return answer/num;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 시각 기준으로 정렬해두니 time 이하인 작업만 순서대로 큐에 넣을 수 있었다.&lt;/li&gt;
&lt;li&gt;else time++이 핵심이다. 큐가 비어있는 구간, 즉 다음 작업이 아직 요청되지 않은 구간을 건너뛰는 역할을 한다.&lt;/li&gt;
&lt;li&gt;루프 조건을 num &amp;lt; jobs.length || !pq.isEmpty()로 잡은 덕분에, 모든 작업을 투입한 뒤 큐에 남은 것까지 처리하고 끝난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트에서 요청 시각 조건을 완전히 무시하고 전부 큐에 넣어버린 게 근본적인 실수였다. 스케줄링 문제에서 &lt;b&gt;&quot;현재 시각에 요청된 작업만 처리 가능하다&quot;&lt;/b&gt; 는 조건은 단순해 보여도 구현에서 가장 중요한 부분이다. NullPointer 위치는 GPT 도움을 받았지만, 구조 자체는 직접 고쳤다. 그래도 AI 없이 완전히 혼자 풀지 못했다는 게 아쉽다. 다음에 비슷한 구조의 문제가 나오면 &lt;b&gt;정렬 &amp;rarr; 현재 시각 기준 투입 &amp;rarr; SJF 처리&lt;/b&gt; 흐름을 바로 떠올릴 수 있을 것 같다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>우선순위큐</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>힙</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/12</guid>
      <comments>https://jupeternotebook.tistory.com/12#entry12comment</comments>
      <pubDate>Tue, 28 Apr 2026 01:40:34 +0900</pubDate>
    </item>
    <item>
      <title>[자료구조] Stack, Queue, Deque, PriorityQueue &amp;mdash; Java</title>
      <link>https://jupeternotebook.tistory.com/10</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트에서 &lt;b&gt;DFS/BFS/시뮬레이션 문제&lt;/b&gt;가 나오면 가장 먼저 떠올려야 할 자료구조들을 정리합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 스택(Stack)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LIFO(Last In First Out)&lt;/b&gt; 구조로, 마지막에 삽입된 데이터가 먼저 나오는 자료구조입니다.&lt;br /&gt;아래 그림처럼 데이터를 위에서 넣고(push), 위에서 꺼냅니다(pop).&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnyoz/dJMcagSPcYP/1X9oMnbNHmCc4YVvrm6WY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnyoz/dJMcagSPcYP/1X9oMnbNHmCc4YVvrm6WY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnyoz/dJMcagSPcYP/1X9oMnbNHmCc4YVvrm6WY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftnyoz%2FdJMcagSPcYP%2F1X9oMnbNHmCc4YVvrm6WY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;521&quot; height=&quot;346&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;888&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;연산&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;시간복잡도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;top에 데이터 삽입&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pop&lt;/td&gt;
&lt;td&gt;top에서 데이터 삭제 및 반환&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;peek&lt;/td&gt;
&lt;td&gt;top 데이터 조회 (삭제 안 함)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩테스트 활용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DFS (깊이 우선 탐색)&lt;/li&gt;
&lt;li&gt;괄호 검사&lt;/li&gt;
&lt;li&gt;후위 연산 (역폴란드 표기법)&lt;/li&gt;
&lt;li&gt;재귀 알고리즘 대체&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;Stack 클래스 vs ArrayDeque&lt;/b&gt;&lt;br /&gt;공식문서에서는 &lt;b&gt;Stack 클래스 대신 Deque 인터페이스 사용을 권장&lt;/b&gt;합니다.&lt;br /&gt;Stack은 내부적으로 &lt;code&gt;Vector&lt;/code&gt;를 상속받아 불필요한 동기화 오버헤드가 발생합니다.&lt;br /&gt;코딩테스트에서는 &lt;code&gt;ArrayDeque&lt;/code&gt;을 스택으로 사용하는 것이 더 빠릅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;// ❌ 권장하지 않음
Stack&amp;lt;Integer&amp;gt; stack = new Stack&amp;lt;&amp;gt;();

// ✅ 권장
Deque&amp;lt;Integer&amp;gt; stack = new ArrayDeque&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.ArrayDeque;
import java.util.Deque;

Deque&amp;lt;Integer&amp;gt; stack = new ArrayDeque&amp;lt;&amp;gt;();

stack.push(1);       // 삽입 (앞에 추가)
stack.push(2);
stack.push(3);

stack.peek();        // 최상단 조회 &amp;rarr; 3 (삭제 안 함)
stack.pop();         // 최상단 제거 및 반환 &amp;rarr; 3
stack.isEmpty();     // 비어있는지 확인
stack.size();        // 원소 개수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 전체 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.ArrayDeque;
import java.util.Deque;

public class StackExample {
    public static void main(String[] args) {
        Deque&amp;lt;Integer&amp;gt; stack = new ArrayDeque&amp;lt;&amp;gt;();

        stack.push(1);
        stack.push(2);
        stack.push(3);

        System.out.println(stack.peek()); // 3
        System.out.println(stack.pop());  // 3
        System.out.println(stack.pop());  // 2
        System.out.println(stack.size()); // 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 큐(Queue)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FIFO(First In First Out)&lt;/b&gt; 구조로, 먼저 삽입된 데이터가 먼저 나오는 자료구조입니다.&lt;br /&gt;아래 그림처럼 rear에서 넣고(offer), front에서 꺼냅니다(poll).&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OGjiT/dJMb997d8cu/f8dKjLrY7aiWSA60j7KHl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OGjiT/dJMb997d8cu/f8dKjLrY7aiWSA60j7KHl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OGjiT/dJMb997d8cu/f8dKjLrY7aiWSA60j7KHl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOGjiT%2FdJMb997d8cu%2Ff8dKjLrY7aiWSA60j7KHl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;323&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1048&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;연산&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;시간복잡도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;offer&lt;/td&gt;
&lt;td&gt;rear에 데이터 삽입&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;poll&lt;/td&gt;
&lt;td&gt;front에서 데이터 삭제 및 반환&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;peek&lt;/td&gt;
&lt;td&gt;front 데이터 조회 (삭제 안 함)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩테스트 활용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;BFS (너비 우선 탐색)&lt;/li&gt;
&lt;li&gt;시뮬레이션 (순서대로 처리)&lt;/li&gt;
&lt;li&gt;슬라이딩 윈도우&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서 Queue는 인터페이스입니다. 코딩테스트에서는 &lt;code&gt;ArrayDeque&lt;/code&gt;이 가장 빠릅니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;// LinkedList &amp;mdash; 가능하지만 느림
Queue&amp;lt;Integer&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();

// ✅ ArrayDeque &amp;mdash; 코딩테스트 권장
Queue&amp;lt;Integer&amp;gt; queue = new ArrayDeque&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;import java.util.ArrayDeque;
import java.util.Queue;

Queue&amp;lt;Integer&amp;gt; queue = new ArrayDeque&amp;lt;&amp;gt;();

queue.offer(1);      // 삽입 (rear에 추가)
queue.offer(2);
queue.offer(3);

queue.peek();        // front 조회 &amp;rarr; 1 (삭제 안 함)
queue.poll();        // front 제거 및 반환 &amp;rarr; 1
queue.isEmpty();     // 비어있는지 확인
queue.size();        // 원소 개수&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;add()&lt;/code&gt; / &lt;code&gt;remove()&lt;/code&gt; 도 있지만, 실패 시 예외를 던집니다.&lt;br /&gt;코딩테스트에서는 null 또는 false를 반환하는 &lt;code&gt;offer()&lt;/code&gt; / &lt;code&gt;poll()&lt;/code&gt;을 사용하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 전체 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.ArrayDeque;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        Queue&amp;lt;Integer&amp;gt; queue = new ArrayDeque&amp;lt;&amp;gt;();

        queue.offer(1);
        queue.offer(2);
        queue.offer(3);

        System.out.println(queue.peek()); // 1
        System.out.println(queue.poll()); // 1
        System.out.println(queue.poll()); // 2
        System.out.println(queue.size()); // 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 덱(Deque)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Double Ended Queue&lt;/b&gt;의 줄임말로, &lt;b&gt;양쪽 끝에서 삽입/삭제가 모두 가능&lt;/b&gt;한 자료구조입니다.&lt;br /&gt;Stack과 Queue의 기능을 모두 포함합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림처럼 앞/뒤 양쪽에서 삽입과 삭제가 모두 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;삽입 메서드&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNBGwF/dJMcac3VoMP/H36m9pbiEUIGnjt9GnBT00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNBGwF/dJMcac3VoMP/H36m9pbiEUIGnjt9GnBT00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNBGwF/dJMcac3VoMP/H36m9pbiEUIGnjt9GnBT00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNBGwF%2FdJMcac3VoMP%2FH36m9pbiEUIGnjt9GnBT00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;168&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;삭제 메서드&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfxKCt/dJMcagkXOQ3/fy2ZYGkYKpCxnGn7IoFZ50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfxKCt/dJMcagkXOQ3/fy2ZYGkYKpCxnGn7IoFZ50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfxKCt/dJMcagkXOQ3/fy2ZYGkYKpCxnGn7IoFZ50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfxKCt%2FdJMcagkXOQ3%2Ffy2ZYGkYKpCxnGn7IoFZ50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;168&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩테스트 활용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;슬라이딩 윈도우 최솟값/최댓값&lt;/li&gt;
&lt;li&gt;양방향 BFS&lt;/li&gt;
&lt;li&gt;팰린드롬 검사&lt;/li&gt;
&lt;li&gt;스택 + 큐를 동시에 필요로 하는 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.ArrayDeque;
import java.util.Deque;

Deque&amp;lt;Integer&amp;gt; deque = new ArrayDeque&amp;lt;&amp;gt;();

// 앞쪽 삽입/조회/삭제
deque.offerFirst(1);   // 앞에 삽입 (= addFirst)
deque.peekFirst();     // 앞 조회
deque.pollFirst();     // 앞 제거 및 반환 (= removeFirst)

// 뒤쪽 삽입/조회/삭제
deque.offerLast(2);    // 뒤에 삽입 (= addLast, offer, add)
deque.peekLast();      // 뒤 조회
deque.pollLast();      // 뒤 제거 및 반환 (= removeLast)

deque.isEmpty();       // 비어있는지 확인
deque.size();          // 원소 개수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 전체 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.ArrayDeque;
import java.util.Deque;

public class DequeExample {
    public static void main(String[] args) {
        Deque&amp;lt;Integer&amp;gt; deque = new ArrayDeque&amp;lt;&amp;gt;();

        deque.offerLast(1);   // [1]
        deque.offerLast(2);   // [1, 2]
        deque.offerFirst(0);  // [0, 1, 2]

        System.out.println(deque.peekFirst()); // 0
        System.out.println(deque.peekLast());  // 2
        System.out.println(deque.pollFirst()); // 0 &amp;rarr; [1, 2]
        System.out.println(deque.pollLast());  // 2 &amp;rarr; [1]
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 우선순위 큐(PriorityQueue)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 순서와 관계없이 &lt;b&gt;우선순위가 높은 원소가 먼저 나오는&lt;/b&gt; 자료구조입니다.&lt;br /&gt;내부적으로 &lt;b&gt;힙(Heap)&lt;/b&gt; 구조로 구현되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값: &lt;b&gt;최솟값(오름차순)&lt;/b&gt;이 먼저 나오는 &lt;b&gt;최소 힙(Min Heap)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Comparator&lt;/code&gt;를 활용해 최대 힙(Max Heap)으로 변경 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;연산&lt;/th&gt;
&lt;th&gt;시간복잡도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;offer (삽입)&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;poll (삭제)&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;peek (조회)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코딩테스트 활용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다익스트라(Dijkstra) 알고리즘&lt;/li&gt;
&lt;li&gt;그리디 &amp;mdash; 항상 최솟값/최댓값 처리&lt;/li&gt;
&lt;li&gt;K번째 원소 찾기&lt;/li&gt;
&lt;li&gt;작업 스케줄링&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;import java.util.PriorityQueue;
import java.util.Collections;

// 최소 힙 (기본)
PriorityQueue&amp;lt;Integer&amp;gt; minHeap = new PriorityQueue&amp;lt;&amp;gt;();

// 최대 힙
PriorityQueue&amp;lt;Integer&amp;gt; maxHeap = new PriorityQueue&amp;lt;&amp;gt;(Collections.reverseOrder());

minHeap.offer(3);    // 삽입
minHeap.offer(1);
minHeap.offer(2);

minHeap.peek();      // 최솟값 조회 &amp;rarr; 1 (삭제 안 함)
minHeap.poll();      // 최솟값 제거 및 반환 &amp;rarr; 1
minHeap.isEmpty();   // 비어있는지 확인
minHeap.size();      // 원소 개수&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 전체 예제 &amp;mdash; 최소 힙 / 최대 힙&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.PriorityQueue;
import java.util.Collections;

public class PriorityQueueExample {
    public static void main(String[] args) {
        // 최소 힙
        PriorityQueue&amp;lt;Integer&amp;gt; minHeap = new PriorityQueue&amp;lt;&amp;gt;();
        minHeap.offer(3);
        minHeap.offer(1);
        minHeap.offer(2);
        System.out.println(minHeap.poll()); // 1
        System.out.println(minHeap.poll()); // 2
        System.out.println(minHeap.poll()); // 3

        // 최대 힙
        PriorityQueue&amp;lt;Integer&amp;gt; maxHeap = new PriorityQueue&amp;lt;&amp;gt;(Collections.reverseOrder());
        maxHeap.offer(3);
        maxHeap.offer(1);
        maxHeap.offer(2);
        System.out.println(maxHeap.poll()); // 3
        System.out.println(maxHeap.poll()); // 2
        System.out.println(maxHeap.poll()); // 1
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 커스텀 정렬 (객체 우선순위 지정)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// [값, 인덱스] 배열을 값 기준 오름차순 정렬
PriorityQueue&amp;lt;int[]&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;((a, b) -&amp;gt; a[0] - b[0]);
pq.offer(new int[]{5, 0});
pq.offer(new int[]{1, 1});
pq.offer(new int[]{3, 2});

System.out.println(pq.poll()[0]); // 1
System.out.println(pq.poll()[0]); // 3
System.out.println(pq.poll()[0]); // 5&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 자료구조 비교 요약&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;Stack&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;Queue&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;Deque&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;PriorityQueue&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;구조&lt;/td&gt;
&lt;td&gt;LIFO&lt;/td&gt;
&lt;td&gt;FIFO&lt;/td&gt;
&lt;td&gt;양방향&lt;/td&gt;
&lt;td&gt;우선순위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;권장 구현체&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ArrayDeque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ArrayDeque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ArrayDeque&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PriorityQueue&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삽입/삭제&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 사용처&lt;/td&gt;
&lt;td&gt;DFS, 괄호검사&lt;/td&gt;
&lt;td&gt;BFS, 순서처리&lt;/td&gt;
&lt;td&gt;슬라이딩 윈도우&lt;/td&gt;
&lt;td&gt;다익스트라, 그리디&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;ArrayDeque는 Stack보다 빠르고, LinkedList보다 큐로서 빠릅니다.&lt;/b&gt; (공식문서)&lt;br /&gt;코딩테스트에서 스택/큐 모두 &lt;code&gt;ArrayDeque&lt;/code&gt;를 기본으로 사용하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 코딩테스트 자주 쓰는 패턴 모음&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ① 괄호 검사 (Stack)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public boolean isValid(String s) {
    Deque&amp;lt;Character&amp;gt; stack = new ArrayDeque&amp;lt;&amp;gt;();

    for (char c : s.toCharArray()) {
        if (c == '(' || c == '{' || c == '[') {
            stack.push(c);
        } else {
            if (stack.isEmpty()) return false;
            char top = stack.pop();
            if (c == ')' &amp;amp;&amp;amp; top != '(') return false;
            if (c == '}' &amp;amp;&amp;amp; top != '{') return false;
            if (c == ']' &amp;amp;&amp;amp; top != '[') return false;
        }
    }
    return stack.isEmpty();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ② BFS 최단 거리 (Queue)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int[] dx = {0, 0, 1, -1};
int[] dy = {1, -1, 0, 0};

Queue&amp;lt;int[]&amp;gt; queue = new ArrayDeque&amp;lt;&amp;gt;();
queue.offer(new int[]{startX, startY});
visited[startX][startY] = true;

while (!queue.isEmpty()) {
    int[] cur = queue.poll();
    int x = cur[0], y = cur[1];

    for (int d = 0; d &amp;lt; 4; d++) {
        int nx = x + dx[d];
        int ny = y + dy[d];
        if (nx &amp;gt;= 0 &amp;amp;&amp;amp; ny &amp;gt;= 0 &amp;amp;&amp;amp; nx &amp;lt; N &amp;amp;&amp;amp; ny &amp;lt; M &amp;amp;&amp;amp; !visited[nx][ny]) {
            visited[nx][ny] = true;
            queue.offer(new int[]{nx, ny});
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ③ 슬라이딩 윈도우 최솟값 (Deque)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
int[] result = new int[nums.length - k + 1];

Deque&amp;lt;Integer&amp;gt; deque = new ArrayDeque&amp;lt;&amp;gt;(); // 인덱스 저장

for (int i = 0; i &amp;lt; nums.length; i++) {
    while (!deque.isEmpty() &amp;amp;&amp;amp; deque.peekFirst() &amp;lt; i - k + 1) {
        deque.pollFirst();
    }
    while (!deque.isEmpty() &amp;amp;&amp;amp; nums[deque.peekLast()] &amp;gt;= nums[i]) {
        deque.pollLast();
    }
    deque.offerLast(i);

    if (i &amp;gt;= k - 1) {
        result[i - k + 1] = nums[deque.peekFirst()];
    }
}
// result = [-1, -3, -3, -3, 3, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ④ K번째 최솟값 (PriorityQueue)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int[] nums = {3, 1, 4, 1, 5, 9, 2, 6};
int k = 3;

PriorityQueue&amp;lt;Integer&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();
for (int n : nums) pq.offer(n);

int result = 0;
for (int i = 0; i &amp;lt; k; i++) {
    result = pq.poll();
}
System.out.println(result); // 3번째 최솟값: 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; ️ 패턴 ⑤ 다익스트라 (PriorityQueue)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;PriorityQueue&amp;lt;int[]&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;((a, b) -&amp;gt; a[0] - b[0]);
int[] dist = new int[N + 1];
Arrays.fill(dist, Integer.MAX_VALUE);

dist[start] = 0;
pq.offer(new int[]{0, start});

while (!pq.isEmpty()) {
    int[] cur = pq.poll();
    int cost = cur[0], node = cur[1];

    if (cost &amp;gt; dist[node]) continue;

    for (int[] next : graph[node]) {
        int nextNode = next[0], nextCost = cost + next[1];
        if (nextCost &amp;lt; dist[nextNode]) {
            dist[nextNode] = nextCost;
            pq.offer(new int[]{nextCost, nextNode});
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  정리 요약&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;자료구조&lt;/th&gt;
&lt;th&gt;핵심 특징&lt;/th&gt;
&lt;th&gt;코딩테스트 활용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Stack (ArrayDeque)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;LIFO&lt;/td&gt;
&lt;td&gt;DFS, 괄호 검사, 역순 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Queue (ArrayDeque)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;FIFO&lt;/td&gt;
&lt;td&gt;BFS, 순서대로 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Deque (ArrayDeque)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;양방향&lt;/td&gt;
&lt;td&gt;슬라이딩 윈도우, 팰린드롬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PriorityQueue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;우선순위(Heap)&lt;/td&gt;
&lt;td&gt;다익스트라, 그리디, K번째 값&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚡ &lt;b&gt;코테 핵심 공식&lt;/b&gt;&lt;br /&gt;&quot;DFS &amp;rarr; Stack(ArrayDeque)&quot;&lt;br /&gt;&quot;BFS &amp;rarr; Queue(ArrayDeque)&quot;&lt;br /&gt;&quot;최솟값/최댓값 반복 &amp;rarr; PriorityQueue&quot;&lt;br /&gt;&quot;양쪽 모두 필요 &amp;rarr; Deque(ArrayDeque)&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  참고 자료&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Stack.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; Stack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Queue.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; Queue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Deque.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; Deque&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ArrayDeque.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; ArrayDeque&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/PriorityQueue.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; PriorityQueue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@nnnyeong/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%8A%A4%ED%83%9D-Stack-%ED%81%90-Queue-%EB%8D%B1-Deque&quot;&gt;velog.io/@nnnyeong &amp;mdash; 스택 Stack, 큐 Queue, 덱 Deque&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/JCF-%F0%9F%A7%B1-Stack-%EA%B5%AC%EC%A1%B0-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EB%A6%AC&quot;&gt;inpa.tistory.com &amp;mdash; Stack 구조 사용법 정리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@wlwlgoyo/JAVA%EC%8A%A4%ED%83%9DStack-%ED%81%90Queue%EB%8D%B1Deque&quot;&gt;velog.io/@wlwlgoyo &amp;mdash; 스택/큐/덱&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://soonmin.tistory.com/49&quot;&gt;soonmin.tistory.com/49&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/자료구조</category>
      <category>덱</category>
      <category>스택</category>
      <category>우선순위큐</category>
      <category>자료구조</category>
      <category>큐</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/10</guid>
      <comments>https://jupeternotebook.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 24 Apr 2026 17:31:25 +0900</pubDate>
    </item>
    <item>
      <title>[자료구조] Hash(해시) &amp;mdash; Java</title>
      <link>https://jupeternotebook.tistory.com/9</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트에서 &lt;b&gt;탐색/카운팅 문제&lt;/b&gt;가 나오면 가장 먼저 떠올려야 할 자료구조, &lt;b&gt;해시(Hash)&lt;/b&gt;를 정리합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 해시란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시는 &lt;b&gt;해시 함수&lt;/b&gt;를 사용해 Key를 특정 인덱스로 변환하여, 해당 인덱스에 데이터를 저장하는 자료구조입니다. Key를 통해 값에 &lt;b&gt;O(1)로 직접 접근&lt;/b&gt;할 수 있어 탐색 횟수가 많은 문제에 적합합니다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Key  &amp;rarr;  [해시 함수]  &amp;rarr;  Hash Value(Index)  &amp;rarr;  Bucket(실제 저장 공간)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;해시 함수&lt;/b&gt;: Key를 정수 인덱스로 변환하는 함수. Java에서는 &lt;code&gt;hashCode()&lt;/code&gt;가 담당&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버킷(Bucket)&lt;/b&gt;: 실제 데이터가 저장되는 공간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해시 테이블&lt;/b&gt;: 버킷들의 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;⚡ 시간복잡도&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;연산&lt;/th&gt;
&lt;th&gt;해시&lt;/th&gt;
&lt;th&gt;배열&lt;/th&gt;
&lt;th&gt;이진탐색트리&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;탐색&lt;/td&gt;
&lt;td&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삽입&lt;/td&gt;
&lt;td&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삭제&lt;/td&gt;
&lt;td&gt;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌이 심하면 최악 O(n)까지 떨어질 수 있지만, 평균적으로 O(1)입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 해시 충돌(Collision)과 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 Key가 동일한 해시 값을 가지는 현상을 &lt;b&gt;충돌(Collision)&lt;/b&gt;이라 합니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;&quot;Lee&quot;  &amp;rarr; hashCode() &amp;rarr; 5
&quot;Chun&quot; &amp;rarr; hashCode() &amp;rarr; 5  //   충돌!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법 ①: 체이닝(Chaining)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌 발생 시 해당 버킷에 &lt;b&gt;연결 리스트(Linked List)&lt;/b&gt;로 데이터를 이어 붙입니다.&lt;br /&gt;Java의 &lt;code&gt;HashMap&lt;/code&gt;이 이 방식을 사용하며, 연결 데이터가 일정 개수를 초과하면 자동으로 &lt;b&gt;이진 탐색 트리&lt;/b&gt;로 전환합니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;index 5: [Lee] &amp;rarr; [Chun] &amp;rarr; null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법 ②: 개방 주소법(Open Addressing)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌 발생 시 다른 빈 버킷을 찾아서 저장합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;선형 탐사&lt;/b&gt;: 1칸씩 이동하며 빈 자리 탐색&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제곱 탐사&lt;/b&gt;: 1, 4, 9... 제곱수만큼 이동하며 탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. HashMap &amp;mdash; 키-값 쌍 저장소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Map&lt;/code&gt; 인터페이스의 해시 테이블 기반 구현체입니다. &lt;b&gt;Key-Value 쌍&lt;/b&gt;으로 데이터를 저장하며, 삽입 순서를 보장하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;import java.util.HashMap;

HashMap&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 삽입 / 수정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.put(&quot;apple&quot;, 3);         // 키-값 추가
map.put(&quot;apple&quot;, 5);         // 이미 있는 키면 값 덮어쓰기
map.putIfAbsent(&quot;apple&quot;, 9); // 키가 없을 때만 삽입 (있으면 무시)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 조회&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.get(&quot;apple&quot;);                      // 값 반환, 없으면 null
map.getOrDefault(&quot;banana&quot;, 0);         // 없으면 기본값(0) 반환
map.containsKey(&quot;apple&quot;);              // 키 존재 여부 &amp;rarr; true/false
map.containsValue(3);                  // 값 존재 여부 &amp;rarr; true/false&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;code&gt;getOrDefault()&lt;/code&gt;는 카운팅 문제에서 NPE 방지에 필수입니다!&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.put(ch, map.getOrDefault(ch, 0) + 1);&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 삭제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.remove(&quot;apple&quot;);          // 키로 삭제
map.clear();                  // 전체 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 상태 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;map.size();       // 데이터 개수
map.isEmpty();    // 비어있는지 여부&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 전체 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.HashMap;

public class HashMapExample {
    public static void main(String[] args) {
        HashMap&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();

        map.put(&quot;apple&quot;, 3);
        map.put(&quot;banana&quot;, 1);
        map.put(&quot;cherry&quot;, 5);

        System.out.println(map.get(&quot;apple&quot;));               // 3
        System.out.println(map.getOrDefault(&quot;grape&quot;, 0));   // 0
        System.out.println(map.containsKey(&quot;banana&quot;));      // true
        System.out.println(map.size());                      // 3

        map.remove(&quot;banana&quot;);
        System.out.println(map.size());                      // 2
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. HashSet &amp;mdash; 중복 없는 집합&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Set&lt;/code&gt; 인터페이스의 해시 테이블 기반 구현체입니다. &lt;b&gt;중복을 허용하지 않으며&lt;/b&gt;, 내부적으로 &lt;code&gt;HashMap&amp;lt;E, Object&amp;gt;&lt;/code&gt;로 구현되어 있습니다. 삽입 순서를 보장하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;import java.util.HashSet;

HashSet&amp;lt;String&amp;gt; set = new HashSet&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  주요 메서드&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;set.add(&quot;apple&quot;);           // 추가 (중복이면 무시, false 반환)
set.remove(&quot;apple&quot;);        // 삭제
set.contains(&quot;apple&quot;);      // 존재 여부 &amp;rarr; true/false
set.size();                 // 원소 개수
set.isEmpty();              // 비어있는지 여부
set.clear();                // 전체 삭제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 전체 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.HashSet;

public class HashSetExample {
    public static void main(String[] args) {
        HashSet&amp;lt;String&amp;gt; set = new HashSet&amp;lt;&amp;gt;();

        set.add(&quot;apple&quot;);
        set.add(&quot;banana&quot;);
        set.add(&quot;apple&quot;);   // 중복 &amp;rarr; 무시됨

        System.out.println(set.size());            // 2
        System.out.println(set.contains(&quot;apple&quot;)); // true

        set.remove(&quot;apple&quot;);
        System.out.println(set.contains(&quot;apple&quot;)); // false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. HashMap vs HashSet 비교&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;HashMap&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;HashSet&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;저장 방식&lt;/td&gt;
&lt;td&gt;Key-Value 쌍&lt;/td&gt;
&lt;td&gt;Value만 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중복 허용&lt;/td&gt;
&lt;td&gt;Key 중복 불가, Value 중복 가능&lt;/td&gt;
&lt;td&gt;중복 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 메서드&lt;/td&gt;
&lt;td&gt;put, get, remove, containsKey&lt;/td&gt;
&lt;td&gt;add, remove, contains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 사용처&lt;/td&gt;
&lt;td&gt;빈도 계산, 매핑, 그룹핑&lt;/td&gt;
&lt;td&gt;중복 제거, 방문 체크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 순회(Iteration)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HashMap 순회&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;// ① Key만 순회
for (String key : map.keySet()) {
    System.out.println(key);
}

// ② Value만 순회
for (int value : map.values()) {
    System.out.println(value);
}

// ③ Key + Value 함께 순회 (가장 많이 쓰임)
for (Map.Entry&amp;lt;String, Integer&amp;gt; entry : map.entrySet()) {
    System.out.println(entry.getKey() + &quot; : &quot; + entry.getValue());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HashSet 순회&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;for (String s : set) {
    System.out.println(s);
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ HashMap과 HashSet 모두 &lt;b&gt;삽입 순서를 보장하지 않습니다.&lt;/b&gt;&lt;br /&gt;순서가 필요하다면 &lt;code&gt;LinkedHashMap&lt;/code&gt; / &lt;code&gt;LinkedHashSet&lt;/code&gt;을 사용하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 정렬(Sorting)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap은 기본적으로 정렬을 지원하지 않으므로 아래 방법을 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① Key 기준 정렬&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 오름차순
List&amp;lt;String&amp;gt; keys = new ArrayList&amp;lt;&amp;gt;(map.keySet());
Collections.sort(keys);

// 내림차순
keys.sort(Collections.reverseOrder());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② Value 기준 정렬&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Value 오름차순
List&amp;lt;Map.Entry&amp;lt;String, Integer&amp;gt;&amp;gt; entries = new ArrayList&amp;lt;&amp;gt;(map.entrySet());
entries.sort(Map.Entry.comparingByValue());

// Value 내림차순
entries.sort(Map.Entry.comparingByValue(Comparator.reverseOrder()));

for (Map.Entry&amp;lt;String, Integer&amp;gt; e : entries) {
    System.out.println(e.getKey() + &quot; : &quot; + e.getValue());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ TreeMap &amp;mdash; Key 정렬 자동 유지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 시 Key 기준 오름차순 정렬이 자동으로 유지됩니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.TreeMap;

TreeMap&amp;lt;String, Integer&amp;gt; treeMap = new TreeMap&amp;lt;&amp;gt;();
treeMap.put(&quot;banana&quot;, 1);
treeMap.put(&quot;apple&quot;, 3);
treeMap.put(&quot;cherry&quot;, 5);

for (String key : treeMap.keySet()) {
    System.out.println(key); // apple &amp;rarr; banana &amp;rarr; cherry
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 코딩테스트 자주 쓰는 패턴 모음&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ① 문자/단어 빈도 카운팅&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;String str = &quot;hello world&quot;;
HashMap&amp;lt;Character, Integer&amp;gt; freq = new HashMap&amp;lt;&amp;gt;();

for (char c : str.toCharArray()) {
    freq.put(c, freq.getOrDefault(c, 0) + 1);
}
// {h=1, e=1, l=3, o=2, ' '=1, w=1, r=1, d=1}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ 패턴 ② 방문 체크 / 중복 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;HashSet&amp;lt;Integer&amp;gt; visited = new HashSet&amp;lt;&amp;gt;();

visited.add(1);
visited.add(2);

if (!visited.contains(3)) {
    System.out.println(&quot;처음 방문!&quot;);
    visited.add(3);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ③ 그룹핑 (같은 키에 여러 값 묶기)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;// 알파벳 순서로 그룹핑 (anagram 문제 등)
HashMap&amp;lt;String, List&amp;lt;String&amp;gt;&amp;gt; groups = new HashMap&amp;lt;&amp;gt;();
String[] words = {&quot;eat&quot;, &quot;tea&quot;, &quot;tan&quot;, &quot;ate&quot;, &quot;nat&quot;, &quot;bat&quot;};

for (String word : words) {
    char[] chars = word.toCharArray();
    Arrays.sort(chars);
    String key = new String(chars);

    groups.computeIfAbsent(key, k -&amp;gt; new ArrayList&amp;lt;&amp;gt;()).add(word);
}
// {aet=[eat, tea, ate], ant=[tan, nat], abt=[bat]}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ④ Value 기준 최빈값 찾기&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;HashMap&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
// ... 데이터 삽입 후

String maxKey = &quot;&quot;;
int maxVal = 0;

for (Map.Entry&amp;lt;String, Integer&amp;gt; e : map.entrySet()) {
    if (e.getValue() &amp;gt; maxVal) {
        maxVal = e.getValue();
        maxKey = e.getKey();
    }
}
System.out.println(&quot;최빈값: &quot; + maxKey + &quot; (&quot; + maxVal + &quot;번)&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  패턴 ⑤ Two Sum (인덱스 역추적)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;int[] nums = {2, 7, 11, 15};
int target = 9;
HashMap&amp;lt;Integer, Integer&amp;gt; seen = new HashMap&amp;lt;&amp;gt;(); // &amp;lt;값, 인덱스&amp;gt;

for (int i = 0; i &amp;lt; nums.length; i++) {
    int complement = target - nums[i];
    if (seen.containsKey(complement)) {
        System.out.println(&quot;[&quot; + seen.get(complement) + &quot;, &quot; + i + &quot;]&quot;); // [0, 1]
        break;
    }
    seen.put(nums[i], i);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  정리 요약&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;사용 상황&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HashMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key-Value, O(1) 탐색&lt;/td&gt;
&lt;td&gt;빈도 계산, 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HashSet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;중복 없는 집합&lt;/td&gt;
&lt;td&gt;방문 체크, 중복 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LinkedHashMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;삽입 순서 유지&lt;/td&gt;
&lt;td&gt;순서가 중요한 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TreeMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key 자동 정렬&lt;/td&gt;
&lt;td&gt;정렬된 맵 필요 시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚡ &lt;b&gt;코테 핵심 공식&lt;/b&gt;&lt;br /&gt;&quot;탐색 횟수가 많다 &amp;rarr; 해시 고려&quot;&lt;br /&gt;&quot;중복 제거 필요하다 &amp;rarr; HashSet&quot;&lt;br /&gt;&quot;키로 값 카운팅 &amp;rarr; HashMap + getOrDefault&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  참고 자료&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; HashMap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; HashSet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html&quot;&gt;Java SE 21 공식문서 &amp;mdash; Map Interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://coding-factory.tistory.com/556&quot;&gt;coding-factory.tistory.com/556&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gyoogle.dev/blog/computer-science/data-structure/Hash.html&quot;&gt;gyoogle.dev &amp;mdash; Hash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@chorok/Java-Hash%ED%95%B4%EC%8B%9C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC&quot;&gt;velog.io/@chorok &amp;mdash; Java Hash 개념 정리&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/자료구조</category>
      <category>자료구조</category>
      <category>해시</category>
      <category>해시맵</category>
      <category>해시셋</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/9</guid>
      <comments>https://jupeternotebook.tistory.com/9#entry9comment</comments>
      <pubDate>Fri, 24 Apr 2026 17:13:41 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 주식 가격 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/8</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42584&quot;&gt;주식가격&lt;/a&gt; 풀이 과정 덱으로 시작했다가 결국 덱을 버리고 이중 for문으로 해결했다. 그리고 나서 스택으로 더 깔끔하게 풀 수 있다는 걸 알았다. 총 4번의 제출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1~3트 (오답) &amp;rarr; 덱 안에서 인덱스 계산하려다 계속 꼬임 4트 (정답) &amp;rarr; 덱 버리고 이중 for문으로 전환&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 스택/큐 카테고리 문제라는 걸 보고 덱을 써야겠다고 먼저 정해버렸다. 전체 가격을 덱에 넣은 뒤, 앞에서 하나씩 꺼내면서 나머지 원소와 비교하는 방식으로 접근했는데, 문제는 덱 안에서의 순서 인덱스(j)와 원래 배열의 절대 인덱스(i)가 기준이 달라서 j - i로 거리를 계산할 수가 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트에서 하락 순간을 time에 반영하지 못했고, 2트에서 time == 0만 틀어막다 실패했고, 3트에서 else { time = j - i; break; } 를 넣었지만 인덱스 기준이 달라서 또 오답.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3트에서 인덱스가 계속 꼬이는 게 덱 구조 때문이라는 걸 깨닫고, 덱을 완전히 버렸다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4트 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; prices 배열을 인덱스로 직접 순회하는 이중 for문으로 바꿨다. &amp;rarr; 현재 인덱스 i에서 시작해 j를 한 칸씩 앞으로 밀면서 비교한다. &amp;rarr; prices[i] &amp;lt;= prices[j]이면 가격 유지, time++. &amp;rarr; 하락하는 순간 time = j - i로 실제 거리를 계산하고 break. &amp;rarr; 끝까지 하락이 없으면 time이 자연스럽게 남은 칸 수만큼 쌓인다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int[] solution(int[] prices) {
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

        for(int i=0; i&amp;lt;prices.length; i++) {
            int time = 0;

            // 현재 인덱스 i, 다음 인덱스 j
            for(int j=i+1; j&amp;lt;prices.length; j++) {
                // 뒤에 올 값이 현재보다 클 경우 -&amp;gt; 최소한 현재값 이상 유지 -&amp;gt; 엔비디아
                if(prices[i] &amp;lt;= prices[j]) time++;

                // 작을 경우 -&amp;gt; 현재값보다 하락 -&amp;gt; 오일쇼크
                else {
                    time = j - i; // 유지한 시간
                    break;
                }
            }
            list.add(time);
        }

        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) {
            answer[i] = list.get(i);
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;j - i가 이제 같은 배열 기준의 인덱스 차이이므로 정확히 유지 시간이 계산된다.&lt;/li&gt;
&lt;li&gt;덱이 오히려 인덱스 추적을 방해하고 있었다. 처음부터 배열로 풀었으면 됐을 문제였다.&lt;/li&gt;
&lt;li&gt;시간 복잡도는 O(n&amp;sup2;)이라 prices 길이가 최대 10만인 걸 감안하면 찜찜하긴 하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 &lt;a href=&quot;https://girawhale.tistory.com/7&quot;&gt;참고 블로그&lt;/a&gt;에서 이중 for문 풀이를 보니까 같은 방식인데 훨씬 간결하게 짰다. 이게 더 간결하네..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; List 없이 ans[] 배열에 바로 결과를 저장한다. &amp;rarr; if (prices[i] &amp;gt; prices[j]) break; 한 줄로 하락 처리 끝. &amp;rarr; ans[i]++를 루프 안에서 먼저 올리고, 하락이면 그냥 break하면 되니까 별도 계산이 필요 없다.&lt;/p&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;class Solution {
    public int[] solution(int[] prices) {
        int[] ans = new int[prices.length];

        for (int i = 0; i &amp;lt; prices.length; i++) {
            for (int j = i + 1; j &amp;lt; prices.length; j++) {
                ans[i]++;
                if (prices[i] &amp;gt; prices[j])
                    break;
            }
        }

        return ans;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 풀이는 time 변수 따로 만들고, List 따로 만들고, 마지막에 배열로 변환까지 했는데 이건 그냥 ans[i]++ 하나로 끝냈다. 로직은 똑같은데 군더더기가 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;더 나은 풀이 &amp;mdash; 스택 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답을 내고 나서 시간복잡도 O(n&amp;sup2;) 이 마음에 걸려서 더 나은 방법이 없을까 찾아봤다. 이중반복문 코드를 참고한 &lt;a href=&quot;https://girawhale.tistory.com/7&quot;&gt;참고 블로그&lt;/a&gt;를 또 보니 이 문제가 사실 스택으로 O(n)에 풀 수 있는 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 아이디어는 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 스택에 가격이 아니라 &lt;b&gt;인덱스&lt;/b&gt;를 저장한다. &amp;rarr; 새로운 가격이 들어올 때, 스택 top의 인덱스가 가리키는 가격보다 &lt;b&gt;낮으면&lt;/b&gt; 하락한 것이므로 pop하면서 i - stack.peek()으로 유지 시간을 바로 계산한다. &amp;rarr; 반복문이 끝난 뒤에도 스택에 남아있는 인덱스들은 끝까지 가격이 떨어지지 않은 것이므로 prices.length - index - 1을 넣어준다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int[] solution(int[] prices) {
        int[] ans = new int[prices.length];
        Stack&amp;lt;Integer&amp;gt; stack = new Stack&amp;lt;&amp;gt;();

        for(int i=0; i&amp;lt;prices.length; i++) {
            // 현재 가격이 스택 top 인덱스의 가격보다 낮으면 &amp;rarr; 하락 발생
            while(!stack.isEmpty() &amp;amp;&amp;amp; prices[i] &amp;lt; prices[stack.peek()]) {
                ans[stack.peek()] = i - stack.peek(); // 유지한 시간 계산
                stack.pop();
            }
            stack.push(i);
        }

        // 스택에 남은 인덱스 = 끝까지 가격이 떨어지지 않은 주식
        while(!stack.isEmpty()) {
            ans[stack.peek()] = prices.length - stack.peek() - 1;
            stack.pop();
        }

        return ans;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시간 복잡도가 O(n)으로 줄어든다. 각 인덱스는 스택에 한 번 들어가고 한 번 나오기 때문이다.&lt;/li&gt;
&lt;li&gt;이중 for문 풀이에서 내가 j - i로 계산하던 거리를 스택이 알아서 들고 있다가 하락 시점에 계산해주는 구조다.&lt;/li&gt;
&lt;li&gt;카테고리가 스택/큐인 이유가 있었다. 그냥 순서대로 세는 문제가 아니라 &lt;b&gt;하락 시점을 기다리는 구조&lt;/b&gt;가 스택과 딱 맞는 문제였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-24 오후 3.41.07.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHXSZz/dJMb990ujlT/rhMgYRInNS4Zz4kcFoKbe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHXSZz/dJMb990ujlT/rhMgYRInNS4Zz4kcFoKbe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHXSZz/dJMb990ujlT/rhMgYRInNS4Zz4kcFoKbe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHXSZz%2FdJMb990ujlT%2FrhMgYRInNS4Zz4kcFoKbe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;559&quot; height=&quot;275&quot; data-filename=&quot;스크린샷 2026-04-24 오후 3.41.07.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조를 먼저 정해두고 시작하는 습관이 오히려 발목을 잡았다. 덱으로 풀겠다고 먼저 정해버리니까 인덱스 관리가 계속 꼬였고, 덱을 버리고 나서야 풀렸다. 그리고 이중 for문으로 정답을 냈을 때 끝낼 게 아니라, &lt;b&gt;왜 스택/큐 카테고리인지&lt;/b&gt; 를 한 번 더 생각해봤어야 했다. 앞으로 카테고리 힌트를 그냥 흘려보내지 말자.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>스택</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/8</guid>
      <comments>https://jupeternotebook.tistory.com/8#entry8comment</comments>
      <pubDate>Fri, 24 Apr 2026 15:41:45 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 다리를 지나는 트럭 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/7</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42583&quot;&gt;다리를 지나는 트럭&lt;/a&gt; 풀이 과정&lt;br /&gt;과거의 나 자신과 현재의 나, 둘 다 정답을 냈다.&lt;br /&gt;신기하게도 접근법이 완전히 달랐다.&lt;br /&gt;총 2번의 제출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트 (정답) &amp;rarr; 매초 트럭 위치를 직접 갱신하는 시간 기반 시뮬레이션&lt;br /&gt;2트 (정답, 과거의 나) &amp;rarr; 빈 슬롯을 0으로 채우는 포화 상태 기반 시뮬레이션&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 매초 다리 위 트럭의 위치를 직접 갱신하는 방식으로 접근했다.&lt;br /&gt;&amp;rarr; 큐에 &lt;code&gt;[무게, 현재 위치]&lt;/code&gt;를 함께 저장하면, 위치가 &lt;code&gt;bridge_length&lt;/code&gt;를 넘었을 때 자연스럽게 제거할 수 있겠다고 생각했다.&lt;br /&gt;&amp;rarr; 큐가 완전히 비면 모든 트럭이 다리를 건넌 것이니 그걸 종료 조건으로 삼았다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public int solution(int bridge_length, int weight, int[] truck_weights) {
        Deque&amp;lt;int[]&amp;gt; bridge = new LinkedList&amp;lt;&amp;gt;(); // 다리

        int curWeight = 0;
        int idx = 0;
        int time = 0;

        while(true) {
            time++;
            // 트럭별 다리 위치 갱신
            if(!bridge.isEmpty()) {
                int size = bridge.size();
                for(int i=0; i&amp;lt;size; i++) {
                    int[] cur = bridge.poll();

                    // 다리 다 건넜을 경우
                    if(cur[1] + 1 &amp;gt; bridge_length) {
                        curWeight -= cur[0];
                        continue;
                    }

                    bridge.offer(new int[] {cur[0], ++cur[1]});
                }
            }

            // 다리 무게 검사 후 진행
            if(idx &amp;lt; truck_weights.length &amp;amp;&amp;amp; curWeight + truck_weights[idx] &amp;lt;= weight) {
                bridge.offer(new int[] {truck_weights[idx], 1});
                curWeight += truck_weights[idx++];
            }

            if(bridge.isEmpty()) break;
        }

        return time;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정답은 맞지만, 매초 큐 전체를 순회해서 위치를 갱신하는 게 조금 무겁다.&lt;/li&gt;
&lt;li&gt;그래도 트럭이 어디 있는지 눈에 보이는 구조라 디버깅하기 편했다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[무게, 위치]&lt;/code&gt; 쌍을 직접 관리하다 보니 코드가 다소 길어졌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2트 (정답, 과거의 나)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 다리를 고정된 크기의 큐로 모델링했다. 트럭이 없는 빈 자리엔 &lt;code&gt;0&lt;/code&gt;을 밀어 넣었다.&lt;br /&gt;&amp;rarr; 큐의 &lt;code&gt;size&lt;/code&gt;가 &lt;code&gt;bridge_length&lt;/code&gt;에 꽉 차면, 맨 앞 트럭을 무조건 꺼내는 방식이다.&lt;br /&gt;&amp;rarr; 반복문은 마지막 트럭이 큐에 들어가는 순간 탈출한다.&lt;br /&gt;&amp;rarr; 그 시점에 마지막 트럭은 다리 첫 번째 칸에 막 올라선 상태이므로, &lt;code&gt;answer += bridge_length&lt;/code&gt;로 나머지 시간을 보정해줬다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public int solution(int bridge_length, int weight, int[] truck_weights) {
        Deque&amp;lt;Integer&amp;gt; dq = new LinkedList&amp;lt;&amp;gt;();
        dq.offer(truck_weights[0]);
        int cur_weight = truck_weights[0];
        int answer = 1;
        int idx = 1;

        // 현재 인덱스가 트럭배열 길이보다 작을 경우 반복
        while(idx &amp;lt; truck_weights.length) {
            int size = dq.size();

            // 다리 포화상태 x
            if(size &amp;lt; bridge_length) {
                if(cur_weight + truck_weights[idx] &amp;lt;= weight) {
                    dq.offer(truck_weights[idx]);
                    cur_weight += truck_weights[idx++];
                }
                else dq.offer(0);

                answer++;
            }

            // 다리 포화상태 o, 트럭 하나 무조건 나와야함
            else cur_weight -= dq.poll();
        }

        // 마지막 트럭이 다리 첫번째 위치에 있는 상태로 반복문 탈출
        // 다리 위 차가 다 건너려면, 마지막 차량이 빠져나와야 함
        // 즉, 소요시간 = 반복문 탈출 시간 + 마지막 차량 다리 건너는 시간(다리 길이)
        answer += bridge_length;

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드가 훨씬 짧고 깔끔하다. 과거의 내가 더 잘 짠 것 같아서 조금 억울하다.&lt;/li&gt;
&lt;li&gt;포화 상태 분기(&lt;code&gt;else&lt;/code&gt;)에서 &lt;code&gt;answer++&lt;/code&gt;가 없다는 점에 주의해야 한다.&lt;/li&gt;
&lt;li&gt;저 &lt;code&gt;else&lt;/code&gt;는 시간이 흐르는 게 아니라 앞 트럭을 빼는 전처리 단계이기 때문이다.&lt;/li&gt;
&lt;li&gt;반복문 탈출 후 &lt;code&gt;+bridge_length&lt;/code&gt; 보정을 빼먹으면 바로 오답이 된다. 과거의 나는 이걸 어떻게 알았을까.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-24 오후 3.05.24.png&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTxFTr/dJMcag6lNrP/KUEeykDmzkw3jpEJtM7xp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTxFTr/dJMcag6lNrP/KUEeykDmzkw3jpEJtM7xp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTxFTr/dJMcag6lNrP/KUEeykDmzkw3jpEJtM7xp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTxFTr%2FdJMcag6lNrP%2FKUEeykDmzkw3jpEJtM7xp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2026-04-24 오후 3.05.24.png&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 문제를 두 번 풀었는데 접근법이 완전히 달랐다. 1트는 위치를 직접 추적하는 방식이라 코드가 길지만 흐름이 직관적이고, 2트(과거의 나)는 빈 슬롯을 &lt;code&gt;0&lt;/code&gt;으로 채워 넣는 방식으로 훨씬 간결하다.&lt;br /&gt;다만 2트 방식은 &lt;b&gt;반복문 탈출 시점&lt;/b&gt;과 &lt;b&gt;&lt;code&gt;+bridge_length&lt;/code&gt; 보정&lt;/b&gt; 두 가지를 정확히 이해하고 짜야 한다. 이 두 가지 중 하나라도 놓치면 경계 조건에서 1~2 차이로 오답이 난다.&lt;br /&gt;앞으로 시뮬레이션 문제에서 &lt;b&gt;&quot;굳이 위치를 추적할 필요가 있나, 슬롯 개념으로 추상화할 수 있지 않나&quot;&lt;/b&gt; 를 먼저 생각해보는 습관을 들여야겠다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS/코딩테스트</category>
      <category>덱</category>
      <category>코딩테스트</category>
      <category>큐</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/7</guid>
      <comments>https://jupeternotebook.tistory.com/7#entry7comment</comments>
      <pubDate>Fri, 24 Apr 2026 15:19:09 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 프로세스 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/6</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42587&quot;&gt;프로세스&lt;/a&gt; 풀이 과정&lt;br /&gt;3시간을 헤매다 결국 과거에 통과했던 나의 코드를 참고해서 겨우 통과했다.&lt;br /&gt;점수 흐름: 60 &amp;rarr; 45 &amp;rarr; 40 &amp;rarr; 60 &amp;rarr; 100&lt;br /&gt;총 5번의 제출&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (오답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 처음 봤을 때 떠올린 건 우선순위 큐였다. 우선순위가 높은 것부터 처리한다는 문장만 보고, Comparable을 구현한 Process 클래스를 만들어 우선순위 내림차순으로 정렬해서 꺼내면 되겠다고 판단했다. PriorityQueue에서 꺼내는 순서가 곧 실행 순서라고 본 것이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;

class Process implements Comparable&amp;lt;Process&amp;gt; {
    int priority;
    int idx;

    public Process(int priority, int idx) {
        this.priority = priority;
        this.idx = idx;
    }

    public int compareTo(Process p) {
        if(this.priority == p.priority) return this.idx - p.idx;
        else return p.priority - this.priority;
    }
}
class Solution {
    public int solution(int[] priorities, int location) {
        PriorityQueue&amp;lt;Process&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();

        for(int i=0; i&amp;lt;priorities.length; i++) {
            pq.offer(new Process(priorities[i], i));
        }

        int answer = 1;
        while(!pq.isEmpty()) {
            Process cur = pq.poll();

            if(cur.idx == location) break;
            else answer++;
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;60점. 절반 정도는 맞았다. 하지만 이 접근은 문제의 핵심을 놓친 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 단순히 높은 우선순위부터 전역적으로 꺼내는 구조가 아니다. 동작 방식을 다시 읽으면, 큐의 &lt;b&gt;맨 앞&lt;/b&gt; 프로세스를 꺼낸 뒤 현재 큐에 자신보다 우선순위가 높은 것이 있으면 &lt;b&gt;다시 뒤로 보낸다&lt;/b&gt;. 없을 때만 실행된다. 즉 원래 순서(큐의 앞뒤 관계)를 유지하면서, 맨 앞 원소가 기준이 되어 조건부로 실행 여부를 판단하는 &lt;b&gt;시뮬레이션&lt;/b&gt;이다. PriorityQueue는 전체를 우선순위 기준으로 정렬해서 꺼내는 자료구조라, 이 흐름을 재현할 수 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중간 3번의 북벌&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위 큐로는 안 된다는 걸 느끼고 일반 덱으로 방향을 틀었다. 매 라운드마다 덱 전체를 순회해 최대 우선순위를 파악하고, 해당 원소를 꺼내는 구조를 짰다. 4트까지 이 방향으로 이것저것 고쳐봤는데, 계속 엣지 케이스에서 틀렸다. 60점 벽을 넘지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 돌아보니 이유가 명확했다. 덱에서 전체 최대값을 찾아 바로 꺼내면, &lt;b&gt;큐 순서가 사라진다.&lt;/b&gt; 예를 들어 [1, 1, 9, 1, 1]에서 location = 0이라면, 맨 앞의 1은 9보다 우선순위가 낮아 뒤로 밀려야 하고, 9가 실행된 뒤 남은 1들이 원래 순서대로 실행되어야 한다. 그런데 내 코드는 그냥 9를 전체에서 꺼내버리니, 그 이후의 순서 보장이 깨졌다. &lt;b&gt;&quot;전체 최대를 찾는다&quot;와 &quot;맨 앞 원소가 기준이다&quot;를 계속 혼동하고 있었던 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5트 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3시간을 넘기고 나서 싱싱했던 반년 전 나의 정답 코드를 참고했다. 보는 순간 바로 이해가 됐다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;

class Process {
    int index;
    int priority;

    public Process(int index, int priority) {
        this.index = index;
        this.priority = priority;
    }
}

class Solution {
    public int solution(int[] priorities, int location) {
        Queue&amp;lt;Process&amp;gt; q = new LinkedList&amp;lt;&amp;gt;();

        for(int i=0; i&amp;lt;priorities.length; i++) {
            q.offer(new Process(i, priorities[i]));
        }

        int answer = 1;
        while(true) {
            int max = 0;
            for(int i=0; i&amp;lt;q.size(); i++) {
                Process tmp = q.poll();
                max = Math.max(max, tmp.priority);
                q.offer(tmp);
            }

            while(true) {
                Process tmp = q.peek();
                if(tmp.priority == max) break;
                else q.offer(q.poll());
            }
            Process cur = q.poll();
            if(cur.index == location) break;
            else answer++;
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조는 두 단계다. 먼저 현재 큐 전체를 순회해 최대 우선순위 max를 구한다. 그 다음 큐의 맨 앞부터 확인하며, max와 우선순위가 같은 원소가 나올 때까지 뒤로 보낸다. 맨 앞에 온 순간 꺼내서 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 계속 놓쳤던 건 이 두 번째 단계였다. 최대값을 찾은 뒤 바로 꺼내는 게 아니라, &lt;b&gt;큐 순서를 보존하면서 해당 원소가 맨 앞에 올 때까지 기다렸다가 꺼낸다.&lt;/b&gt; 이렇게 해야 &quot;대기 중인 프로세스 중 우선순위가 높은 것이 있으면 먼저 처리&quot;한다는 조건이 순서까지 포함해서 정확히 재현된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-24 오전 2.16.46.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NBQ3C/dJMcagSOnnK/uECQoKDQGAyzIVRs3vGxC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NBQ3C/dJMcagSOnnK/uECQoKDQGAyzIVRs3vGxC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NBQ3C/dJMcagSOnnK/uECQoKDQGAyzIVRs3vGxC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNBQ3C%2FdJMcagSOnnK%2FuECQoKDQGAyzIVRs3vGxC0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;608&quot; data-filename=&quot;스크린샷 2026-04-24 오전 2.16.46.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 사투가 보이는가... 3시간 동안 헤맨 이유를 한 마디로 정리하면, &lt;b&gt;&quot;맨 앞 원소가 기준&quot;이라는 걸 끝까지 이해하지 못했다.&lt;/b&gt; 문제 설명을 읽고 우선순위가 높은 것부터 처리한다는 부분만 보고 우선순위 큐로 먼저 달려들었고, 방향을 바꾼 뒤에도 &quot;전체에서 최대를 찾아 꺼낸다&quot;는 사고에서 벗어나지 못했다. 큐 자료구조의 핵심은 &lt;b&gt;순서&lt;/b&gt;인데, 그 순서를 시뮬레이션의 도구로 써야 한다는 감각이 부족했다. 과거의 코드를 참고했다는 게 조금 부끄럽지만, 덕분에 내가 어디서 막혔는지는 명확하게 정리됐다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>덱</category>
      <category>코딩테스트</category>
      <category>큐</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/6</guid>
      <comments>https://jupeternotebook.tistory.com/6#entry6comment</comments>
      <pubDate>Fri, 24 Apr 2026 02:24:42 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 올바른 괄호 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/5</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot;&gt;올바른 괄호&lt;/a&gt; 풀이 과정&lt;br /&gt;어렵지 않게 한 번에 통과했다.&lt;br /&gt;총 1번의 제출&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 문자열을 순회하며 &lt;code&gt;(&lt;/code&gt;는 스택에 push, &lt;code&gt;)&lt;/code&gt;는 스택에서 pop한다.&lt;br /&gt;&amp;rarr; &lt;code&gt;)&lt;/code&gt;를 만났을 때 스택이 비어 있으면 짝이 맞지 않으므로 즉시 &lt;code&gt;false&lt;/code&gt;를 반환한다.&lt;br /&gt;&amp;rarr; 순회가 끝난 뒤 스택이 비어 있으면 모든 괄호가 올바르게 닫힌 것이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    boolean solution(String s) {
        Stack&amp;lt;Character&amp;gt; stack = new Stack&amp;lt;&amp;gt;();

        for(char c : s.toCharArray()) {
            if(c=='(') stack.push(c);
            else {
                if(stack.isEmpty()) return false;
                stack.pop();
            }
        }

        return stack.isEmpty() ? true : false;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 스택인가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;괄호 문제를 보는 순간 스택이 떠오른 건 자연스러운 흐름이었다. &lt;code&gt;(&lt;/code&gt;는 &quot;나중에 닫혀야 할 괄호&quot;이고, &lt;code&gt;)&lt;/code&gt;는 &quot;가장 최근에 열린 괄호를 닫는다&quot;는 구조가 &lt;b&gt;후입선출(LIFO)&lt;/b&gt; 과 정확히 맞아떨어지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;)&lt;/code&gt;를 만났을 때의 처리가 핵심이다. 이 시점에 스택이 비어 있다는 건 짝이 없는 닫는 괄호라는 의미이므로 바로 &lt;code&gt;false&lt;/code&gt;를 반환한다. 비어 있지 않다면 가장 최근에 push된 &lt;code&gt;(&lt;/code&gt;와 짝이 맞는 것이므로 pop한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순회가 끝난 뒤에는 스택이 비어 있어야 한다. 스택에 &lt;code&gt;(&lt;/code&gt;가 남아 있다는 건 닫히지 않은 괄호가 있다는 의미이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 &lt;code&gt;false&lt;/code&gt;가 되는 케이스는 딱 두 가지다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;)&lt;/code&gt;를 만났는데 스택이 비어 있는 경우 &amp;rarr; 짝 없는 닫는 괄호&lt;/li&gt;
&lt;li&gt;순회가 끝났는데 스택에 원소가 남아 있는 경우 &amp;rarr; 닫히지 않은 여는 괄호&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 케이스만 막으면 나머지는 자연스럽게 &lt;code&gt;true&lt;/code&gt;가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-24 오전 2.00.07.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbbcRy/dJMcacv7HV8/wIxONQKbhPR7LbM9X1zLQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbbcRy/dJMcacv7HV8/wIxONQKbhPR7LbM9X1zLQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbbcRy/dJMcacv7HV8/wIxONQKbhPR7LbM9X1zLQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbbcRy%2FdJMcacv7HV8%2FwIxONQKbhPR7LbM9X1zLQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1264&quot; height=&quot;196&quot; data-filename=&quot;스크린샷 2026-04-24 오전 2.00.07.png&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;괄호 문제는 스택의 LIFO 특성과 구조가 딱 맞아 떨어지는 대표적인 유형이라, 이 문제를 풀면서 &quot;스택을 써야겠다&quot;는 판단은 거의 즉각적으로 나왔다. 앞으로 괄호, 중첩 구조, 뒤에서 검증해야 하는 패턴이 보이면 스택을 가장 먼저 떠올리는 습관을 유지해야겠다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>스택</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/5</guid>
      <comments>https://jupeternotebook.tistory.com/5#entry5comment</comments>
      <pubDate>Fri, 24 Apr 2026 02:01:24 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 기능개발 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/4</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot;&gt;기능개발&lt;/a&gt; 풀이 과정&lt;br /&gt;큐 두 개를 굴리다가 인덱스 동기화 실수로 1차에 틀렸다.&lt;br /&gt;2차에서 큐 하나로 정리하며 통과&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (오답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;code&gt;progresses&lt;/code&gt;와 &lt;code&gt;speeds&lt;/code&gt;를 각각 별도의 &lt;code&gt;Deque&lt;/code&gt;에 담는다.&lt;br /&gt;&amp;rarr; 매 라운드마다 전체 순회하며 속도를 더한 뒤, 앞에서부터 100 이상인 항목을 꺼내 카운트한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int[] solution(int[] progresses, int[] speeds) {
        Deque&amp;lt;Integer&amp;gt; pg = new LinkedList&amp;lt;&amp;gt;();
        Deque&amp;lt;Integer&amp;gt; sp = new LinkedList&amp;lt;&amp;gt;();

        for(int i=0; i&amp;lt;speeds.length; i++) {
            pg.offer(progresses[i]);
            sp.offer(speeds[i]);
        }

        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        while(!pg.isEmpty()) {
            int size = pg.size();
            int cnt = 0;

            for(int i=0; i&amp;lt;size; i++) {
                int curProg = pg.poll();
                int curSpd = sp.poll();

                pg.offer(curProg+curSpd);
                sp.offer(curSpd);
            }

            for(int i=0; i&amp;lt;size; i++) {
                int cur = pg.peek();

                if(cur &amp;gt;= 100) {
                    pg.poll();
                    cnt++;
                }
                else break;
            }
            if(cnt &amp;gt; 0) list.add(cnt);
        }

        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) {
            answer[i] = list.get(i);
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;발상 자체는 맞다. 큐를 라운드 단위로 순회하며 진도를 갱신하고, 앞에서부터 완료된 기능을 꺼내 묶는다는 흐름은 올바르다.&lt;/li&gt;
&lt;li&gt;문제는 &lt;b&gt;속도 큐(&lt;code&gt;sp&lt;/code&gt;)를 함께 굴리는 과정에서 인덱스 동기화가 어긋난다&lt;/b&gt;는 점이다. 진도와 속도를 각각 분리된 덱에 넣고 &lt;code&gt;poll &amp;rarr; offer&lt;/code&gt;를 반복하면, 라운드가 거듭될수록 &lt;code&gt;pg&lt;/code&gt;와 &lt;code&gt;sp&lt;/code&gt;의 원소 순서가 뒤섞인다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;진도 업데이트 루프에서 &lt;code&gt;pg.poll()&lt;/code&gt;과 &lt;code&gt;sp.poll()&lt;/code&gt;을 같이 꺼내 더한 뒤 다시 offer한다.&lt;/li&gt;
&lt;li&gt;이때 &lt;b&gt;완료 판정 루프에서 &lt;code&gt;pg.poll()&lt;/code&gt;로 꺼낸 원소에 대응하는 속도가 &lt;code&gt;sp&lt;/code&gt;에서도 동시에 빠져야 하는데&lt;/b&gt;, 그냥 놔두니 두 덱 사이에 원소 수 불일치가 생긴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2트 (정답)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 속도 덱을 없앴다.&lt;br /&gt;&amp;rarr; 앞 작업이 100을 넘어야만 뒤 작업도 덱에서 빠져나올 수 있으므로, &lt;b&gt;덱에서 빠져나간 수만큼 &lt;code&gt;speeds&lt;/code&gt; 앞부분을 건너뛰면&lt;/b&gt; 현재 덱에 남아 있는 작업의 속도를 정확히 참조할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
class Solution {
    public int[] solution(int[] progresses, int[] speeds) {
        // 우선 순서대로 큐에 작업진도 삽입
        Deque&amp;lt;Integer&amp;gt; dq = new LinkedList&amp;lt;&amp;gt;();
        for(int pro : progresses) {
            dq.offer(pro);
        }

        // 작업진도 최신화
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        while(!dq.isEmpty()) {
            int size = dq.size();

            for(int i=0; i&amp;lt;size; i++) {
                dq.offer(dq.poll() + speeds[speeds.length - size + i]); // 기존작업의 개수 - 현재 큐 사이즈 + i = 해당 작업의 속도
            }

            // 앞에서부터 검사하며, 100이 넘으면 카운트, 안 넘는 즉시 반복 탈출
            int cnt = 0;
            for(int i=0; i&amp;lt;size; i++) {
                if(dq.peek() &amp;gt;= 100) {
                    dq.poll();
                    cnt++;
                }
                else break;
            }

            // 카운트가 0보다 클 경우 리스트 추가
            if(cnt&amp;gt;0) list.add(cnt);
        }

        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) {
            answer[i] = list.get(i);
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 핵심: &lt;code&gt;speeds[speeds.length - size + i]&lt;/code&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제의 배포 조건은 &lt;b&gt;앞 작업이 100을 넘어야만 뒤 작업도 함께 배포된다&lt;/b&gt;는 것이다. 즉 덱에서 빠져나오는 순서는 반드시 앞에서부터이고, 중간이나 뒤에서 먼저 빠져나오는 일은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특성을 반영해서, 현재 덱에 &lt;code&gt;size&lt;/code&gt;개의 원소가 남아 있다는 건 &lt;code&gt;speeds&lt;/code&gt; 배열의 앞에서부터 &lt;code&gt;speeds.length - size&lt;/code&gt;개가 이미 완료돼 빠져나갔다는 의미가 된다. 따라서 지금 덱의 &lt;code&gt;i&lt;/code&gt;번째 원소에 해당하는 속도는 &lt;code&gt;speeds[speeds.length - size + i]&lt;/code&gt;로 정확히 역산할 수 있다. 속도 덱을 따로 굴릴 필요 없이, 빠져나간 수만큼 원배열의 시작점을 밀면 그만이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 순서의 특성(앞이 완료돼야 뒤도 빠져나옴) 파악 &amp;rarr; 덱 이탈 순서가 보장됨을 확인&lt;/li&gt;
&lt;li&gt;빠져나간 수 = &lt;code&gt;speeds.length - size&lt;/code&gt; 로 시작 인덱스 역산&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-24 오전 1.33.04.png&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BOVOj/dJMcaipyAXV/LpQA5TLfim8VeAYLfGnkW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BOVOj/dJMcaipyAXV/LpQA5TLfim8VeAYLfGnkW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BOVOj/dJMcaipyAXV/LpQA5TLfim8VeAYLfGnkW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBOVOj%2FdJMcaipyAXV%2FLpQA5TLfim8VeAYLfGnkW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1276&quot; height=&quot;404&quot; data-filename=&quot;스크린샷 2026-04-24 오전 1.33.04.png&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1트에서 두 덱을 함께 굴리다 꼬인 건, 완료 판정 시점에 속도 덱을 동기화하지 않아서였다. 하지만 진짜 핵심은 그 앞에 있었다. &lt;b&gt;이 문제는 앞 작업이 100을 넘어야만 뒤 작업도 빠져나올 수 있다는 조건 덕분에, 덱의 이탈 순서가 항상 보장된다.&lt;/b&gt; 그걸 파악하고 나면 속도 덱 자체가 필요 없어진다는 게 자연스럽게 따라온다. 문제의 조건을 자료구조 설계에 연결해서 생각하는 연습이 더 필요하다고 느꼈다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS/코딩테스트</category>
      <category>덱</category>
      <category>코딩테스트</category>
      <category>큐</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/4</guid>
      <comments>https://jupeternotebook.tistory.com/4#entry4comment</comments>
      <pubDate>Fri, 24 Apr 2026 01:36:04 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 같은 숫자는 싫어 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/3</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12906&quot;&gt;같은 숫자는 싫어&lt;/a&gt; 풀이 과정&lt;br /&gt;스택으로 먼저 풀고, 덱으로 리팩토링하다가 한 번 더 틀렸다.&lt;br /&gt;마지막엔 스택/덱 없이 인덱스 비교만으로 가장 간결하게 정리했다.&lt;br /&gt;총 4번의 제출&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1트 (정답, 스택 활용)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 스택의 &lt;code&gt;peek()&lt;/code&gt;으로 직전 값을 확인하고, 현재 값과 같으면 건너뛴다.&lt;br /&gt;&amp;rarr; 스택과 별도로 &lt;code&gt;List&lt;/code&gt;에도 같이 담아서 최종 배열을 구성했다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
public class Solution {
    public int[] solution(int []arr) {
        Stack&amp;lt;Integer&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        stack.push(arr[0]);
        list.add(arr[0]);

        for(int i=1; i&amp;lt;arr.length; i++) {
            if(stack.peek()==arr[i]) continue;
            else {
                stack.push(arr[i]);
                list.add(arr[i]);
            }
        }
        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) {
            answer[i] = list.get(i);
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정답은 맞지만, 스택과 리스트를 &lt;b&gt;동시에 관리&lt;/b&gt;하는 구조가 불필요하게 중복이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Stack&lt;/code&gt;만으로도 되는데 굳이 &lt;code&gt;List&lt;/code&gt;를 따로 유지한 셈이다.&lt;/li&gt;
&lt;li&gt;리팩토링 여지가 있어서 덱으로 다시 시도했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2트 (오답, 덱 활용)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;code&gt;Stack&lt;/code&gt; + &lt;code&gt;List&lt;/code&gt; 조합 대신, 덱 하나로 처리하도록 변경했다.&lt;br /&gt;&amp;rarr; &lt;code&gt;peekLast()&lt;/code&gt;로 직전 값을 확인하고, 다르면 &lt;code&gt;offer()&lt;/code&gt;로 뒤에 추가하는 방식.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
public class Solution {
    public int[] solution(int []arr) {
        Deque&amp;lt;Integer&amp;gt; dq = new LinkedList&amp;lt;&amp;gt;();

        for(int i : arr) {
            if(dq.isEmpty()) {
                dq.offer(i);
                continue;
            }
            if(dq.peekLast() == i) continue;
            else dq.offer(i);
        }

        int[] answer = new int[dq.size()];
        for(int i=0; i&amp;lt;dq.size(); i++) {
            answer[i] = dq.poll();
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;dq.size()&lt;/code&gt;를 반복문 조건식에 직접 쓰면, &lt;code&gt;dq.poll()&lt;/code&gt;로 요소를 꺼낼 때마다 &lt;b&gt;&lt;code&gt;size()&lt;/code&gt;가 줄어들어&lt;/b&gt; 반복이 절반만 돌고 끝나버린다.&lt;/li&gt;
&lt;li&gt;결과적으로 배열 뒷부분이 전부 0으로 채워지는 오답이 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3트 (정답, 덱 활용)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 반복문 진입 전에 &lt;code&gt;dq.size()&lt;/code&gt;를 &lt;code&gt;size&lt;/code&gt; 변수에 &lt;b&gt;미리 저장&lt;/b&gt;해두고 고정값으로 사용했다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
public class Solution {
    public int[] solution(int []arr) {
        Deque&amp;lt;Integer&amp;gt; dq = new LinkedList&amp;lt;&amp;gt;();

        for(int i : arr) {
            if(dq.isEmpty()) {
                dq.offer(i);
                continue;
            }

            if(dq.peekLast() == i) continue;
            else dq.offer(i);
        }

        int[] answer = new int[dq.size()];
        int size = dq.size();
        for(int i=0; i&amp;lt;size; i++) {
            answer[i] = dq.pollFirst();
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;를 미리 고정해두니 &lt;code&gt;poll()&lt;/code&gt;로 꺼내도 반복 횟수가 줄어들지 않아 정상 동작한다.&lt;/li&gt;
&lt;li&gt;덱 하나로 깔끔하게 처리 완&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4트 (리팩토링, 스택/덱 없이)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 스택도, 덱도 필요 없다는 걸 깨달았다.&lt;br /&gt;&amp;rarr; &lt;code&gt;arr[i] != arr[i-1]&lt;/code&gt; 인덱스 비교만으로 직전 값을 확인할 수 있으니, 그냥 &lt;code&gt;List&lt;/code&gt;에 바로 담으면 된다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
public class Solution {
    public int[] solution(int[] arr) {
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        list.add(arr[0]);
        for(int i=1; i&amp;lt;arr.length; i++) {
            if(arr[i] != arr[i-1]) list.add(arr[i]);
        }

        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) answer[i] = list.get(i);
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스택/덱의 &lt;code&gt;peek()&lt;/code&gt; 없이도 &lt;b&gt;배열 인덱스로 직전 값 비교&lt;/b&gt;가 가능하다는 걸 활용했다.&lt;/li&gt;
&lt;li&gt;자료구조 오버헤드가 사라지고 코드가 훨씬 간결해졌다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isEmpty()&lt;/code&gt; 체크도 필요 없고, &lt;code&gt;size&lt;/code&gt; 저장 실수도 생길 여지가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/7angjung/post/820dbcc2-524b-4340-923c-3676837c30bd/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링하다가 오히려 한 번 더 틀렸다. &lt;code&gt;dq.size()&lt;/code&gt;를 반복 조건에 그대로 쓰면 루프 도중에 값이 바뀐다는 게 맹점이었다. 앞으로 &lt;b&gt;컬렉션을 순회하면서 동시에 꺼내는 경우엔 size를 미리 저장&lt;/b&gt;하는 습관을 들여야겠다.&lt;br /&gt;그리고 이번 문제처럼 &lt;b&gt;&quot;직전 값과 비교&quot;가 목적이라면 굳이 자료구조를 쓸 필요 없이 인덱스로 해결&lt;/b&gt;할 수 있다는 것도 기억해두자.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>스택</category>
      <category>코딩테스트</category>
      <category>큐</category>
      <category>프로그래머스</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/3</guid>
      <comments>https://jupeternotebook.tistory.com/3#entry3comment</comments>
      <pubDate>Thu, 23 Apr 2026 02:26:26 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 베스트 앨범 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/2</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42579&quot;&gt;베스트 앨범&lt;/a&gt; 풀이 과정&lt;br&gt;해시 카테고리 문제인데, 정렬 기준이 여러 개라 꽤 헤맸다.&lt;br&gt;총 3번의 제출 끝에 통과&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h3&gt;1트 (오답)&lt;/h3&gt;
&lt;p&gt;→ 장르별 총 스트리밍 횟수를 &lt;code&gt;genMap&lt;/code&gt;에 저장하고, &lt;code&gt;TreeMap&lt;/code&gt;의 &lt;strong&gt;Key 기준 역순 정렬&lt;/strong&gt;로 장르 순서를 정하려 했다.&lt;br&gt;→ 각 곡의 인덱스는 &lt;code&gt;playIdx&lt;/code&gt;에 &lt;code&gt;plays[i] → i&lt;/code&gt; 형태로 저장했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;
// 음악 클래스
class Music implements Comparable&amp;lt;Music&amp;gt; {
    String genre;
    int play;

    public Music(String genre, int play) {
        this.genre = genre;
        this.play = play;
    }

    @Override
    public int compareTo(Music m) {
        return m.play - this.play;
    }
}
class Solution {
    public int[] solution(String[] genres, int[] plays) {
        Map&amp;lt;String, Integer&amp;gt; genMap = new HashMap&amp;lt;&amp;gt;(); // 장르별 스트리밍 횟수 저장
        Map&amp;lt;Integer, Integer&amp;gt; playIdx = new HashMap&amp;lt;&amp;gt;(); // 스트리밍 횟수 인덱스 저장
        List&amp;lt;Music&amp;gt; playlist = new ArrayList&amp;lt;&amp;gt;(); // 곡별 스트리밍 횟수 저장

        for(int i=0; i&amp;lt;plays.length; i++) {
            genMap.put(genres[i], genMap.getOrDefault(genres[i],0) + plays[i]);
            playIdx.put(plays[i], i);
            playlist.add(new Music(genres[i], plays[i]));
        }

        // 새로 알게 된 개념 -&amp;gt; Key 기준으로 정렬
        Map&amp;lt;String, Integer&amp;gt; genSort = new TreeMap&amp;lt;&amp;gt;(Collections.reverseOrder());
        genSort.putAll(genMap);

        // 리스트에 장르별 베스트 2개 담기
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

        for(Map.Entry&amp;lt;String, Integer&amp;gt; gen : genSort.entrySet()) {
            List&amp;lt;Music&amp;gt; tmp = new ArrayList&amp;lt;&amp;gt;();

            for(Music m : playlist) {
                if(gen.getKey().equals(m.genre)) tmp.add(m);
            }

            Collections.sort(tmp);

            if(tmp.size() == 1) {
                list.add(playIdx.get(tmp.get(0).play));
            }
            else {
                list.add(playIdx.get(tmp.get(0).play));
                list.add(playIdx.get(tmp.get(1).play));
            }
        }

        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) answer[i] = list.get(i);

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TreeMap&lt;/code&gt;의 역순 정렬은 &lt;strong&gt;Key(장르 이름) 알파벳 기준&lt;/strong&gt;으로 정렬하는 것이라, 총 스트리밍 횟수 기준 정렬과 전혀 관계없다. 장르 순서가 완전히 잘못됐다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;playIdx&lt;/code&gt;에 &lt;code&gt;plays[i] → i&lt;/code&gt;를 저장하는 방식은 &lt;strong&gt;같은 스트리밍 횟수를 가진 곡이 있으면 덮어씌워진다&lt;/strong&gt;. 인덱스를 제대로 추적할 수 없는 구조다.&lt;/li&gt;
&lt;li&gt;히스토리 기준으로 &lt;strong&gt;20점&lt;/strong&gt; 구간에 해당하는 풀이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2트 (오답)&lt;/h3&gt;
&lt;p&gt;→ &lt;code&gt;playIdx&lt;/code&gt;의 덮어쓰기 문제를 인식하고, &lt;code&gt;Music&lt;/code&gt; 객체에 &lt;strong&gt;인덱스(&lt;code&gt;idx&lt;/code&gt;)를 직접 포함&lt;/strong&gt;시켜 &lt;code&gt;playIdx&lt;/code&gt; Map을 완전히 제거했다.&lt;br&gt;→ 덕분에 같은 스트리밍 횟수의 곡이 있어도 인덱스를 잃지 않게 됐다.&lt;br&gt;→ 그러나 장르 정렬 방식은 그대로 &lt;code&gt;TreeMap(Collections.reverseOrder())&lt;/code&gt;를 유지했다. 이 부분이 여전히 문제임을 아직 파악하지 못한 상태였다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;
// 음악 클래스
class Music implements Comparable&amp;lt;Music&amp;gt; {
    String genre;
    int play;
    int idx; // idx 필드 추가

    public Music(String genre, int play, int idx) {
        this.genre = genre;
        this.play = play;
        this.idx = idx;
    }

    @Override
    public int compareTo(Music m) {
        return m.play - this.play;
    }
}
class Solution {
    public int[] solution(String[] genres, int[] plays) {
        Map&amp;lt;String, Integer&amp;gt; genMap = new HashMap&amp;lt;&amp;gt;(); // 장르별 스트리밍 횟수 저장
        List&amp;lt;Music&amp;gt; playlist = new ArrayList&amp;lt;&amp;gt;(); // 곡별 스트리밍 횟수 저장

        for(int i=0; i&amp;lt;plays.length; i++) {
            genMap.put(genres[i], genMap.getOrDefault(genres[i],0) + plays[i]);
            playlist.add(new Music(genres[i], plays[i], i)); // playIdx 제거, idx를 객체에 직접 포함
        }

        // Key 기준 역순 정렬 (문제 있음 - 아직 미수정)
        Map&amp;lt;String, Integer&amp;gt; genSort = new TreeMap&amp;lt;&amp;gt;(Collections.reverseOrder());
        genSort.putAll(genMap);

        // 리스트에 장르별 베스트 2개 담기
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

        for(Map.Entry&amp;lt;String, Integer&amp;gt; gen : genSort.entrySet()) {
            List&amp;lt;Music&amp;gt; tmp = new ArrayList&amp;lt;&amp;gt;();

            for(Music m : playlist) {
                if(gen.getKey().equals(m.genre)) tmp.add(m);
            }

            Collections.sort(tmp);

            if(tmp.size() == 1) {
                list.add(tmp.get(0).idx); // playIdx 대신 객체의 idx 직접 참조
            }
            else {
                list.add(tmp.get(0).idx);
                list.add(tmp.get(1).idx);
            }
        }

        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) answer[i] = list.get(i);

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;playIdx&lt;/code&gt; 덮어쓰기 문제는 해결됐다. &lt;code&gt;Music&lt;/code&gt; 객체에 &lt;code&gt;idx&lt;/code&gt;를 포함시킨 덕분에 인덱스 추적은 이제 정확하다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;TreeMap(Collections.reverseOrder())&lt;/code&gt;는 &lt;strong&gt;Key인 장르 이름의 알파벳 역순&lt;/strong&gt;으로 정렬한다. &lt;code&gt;genMap&lt;/code&gt;의 Value(총 스트리밍 횟수)와는 아무 관계가 없어서 장르 순서가 여전히 틀렸다.&lt;/li&gt;
&lt;li&gt;히스토리 기준 &lt;strong&gt;13.3점&lt;/strong&gt; 구간.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;3트 (정답)&lt;/h3&gt;
&lt;p&gt;→ &lt;code&gt;TreeMap&lt;/code&gt; Key 정렬 문제를 마침내 파악했다. 장르 정렬을 &lt;strong&gt;Value(총 스트리밍 횟수) 기준&lt;/strong&gt;으로 바꿨다.&lt;br&gt;→ &lt;code&gt;keySet&lt;/code&gt;을 꺼내 &lt;code&gt;Comparator&lt;/code&gt;로 커스텀 정렬하는 방식으로 수정했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;
// 음악 클래스
class Music implements Comparable&amp;lt;Music&amp;gt; {
    String genre;
    int play;
    int idx;

    public Music(String genre, int play, int idx) {
        this.genre = genre;
        this.play = play;
        this.idx = idx;
    }

    @Override
    public int compareTo(Music m) {
        return m.play - this.play;
    }
}
class Solution {
    public int[] solution(String[] genres, int[] plays) {
        Map&amp;lt;String, Integer&amp;gt; genMap = new HashMap&amp;lt;&amp;gt;(); // 장르별 스트리밍 횟수 저장
        List&amp;lt;Music&amp;gt; playlist = new ArrayList&amp;lt;&amp;gt;(); // 곡별 스트리밍 횟수 저장

        for(int i=0; i&amp;lt;plays.length; i++) {
            genMap.put(genres[i], genMap.getOrDefault(genres[i],0) + plays[i]);
            playlist.add(new Music(genres[i], plays[i], i));
        }

        // Value 기준으로 정렬
        List&amp;lt;String&amp;gt; keySet = new ArrayList&amp;lt;&amp;gt;(genMap.keySet());
        keySet.sort(new Comparator&amp;lt;String&amp;gt;() {
            @Override
            public int compare(String o1, String o2) {
                return genMap.get(o2) - genMap.get(o1);
            }
        });

        // 리스트에 장르별 베스트 2개 담기
        List&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();

        for(String gen : keySet) {
            List&amp;lt;Music&amp;gt; tmp = new ArrayList&amp;lt;&amp;gt;();

            for(Music m : playlist) {
                if(gen.equals(m.genre)) tmp.add(m);
            }

            Collections.sort(tmp);

            if(tmp.size() == 1) {
                list.add(tmp.get(0).idx);
            }
            else {
                list.add(tmp.get(0).idx);
                list.add(tmp.get(1).idx);
            }
        }

        int[] answer = new int[list.size()];
        for(int i=0; i&amp;lt;list.size(); i++) answer[i] = list.get(i);

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;genMap.keySet()&lt;/code&gt;을 꺼내 &lt;code&gt;Comparator&lt;/code&gt;로 Value 기준 내림차순 정렬 → 장르 순서가 정확히 잡혔다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Music&lt;/code&gt; 객체에 &lt;code&gt;idx&lt;/code&gt;를 포함시켜 같은 재생 횟수여도 인덱스를 잃지 않는다.&lt;/li&gt;
&lt;li&gt;정확성 테스트 통과&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/7angjung/post/0247c45e-939b-4bc0-98f7-2a1eee1a4691/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;3번의 시도를 돌아보면, &lt;strong&gt;1트에서 2트로 넘어갈 때&lt;/strong&gt;는 &lt;code&gt;playIdx&lt;/code&gt; 덮어쓰기 문제를 잡았고, &lt;strong&gt;2트에서 3트로 넘어갈 때&lt;/strong&gt;는 &lt;code&gt;TreeMap(reverseOrder())&lt;/code&gt;이 Key 알파벳 정렬임을 뒤늦게 깨달았다. 두 문제를 한 번에 잡지 못하고 한 번씩 나눠서 틀린 셈이다.&lt;br&gt;핵심은 &lt;strong&gt;&amp;quot;정렬 기준이 Value일 때는 keySet을 꺼내 Comparator로 직접 정렬&amp;quot;&lt;/strong&gt; 하는 것, 그리고 &lt;strong&gt;&amp;quot;객체에 인덱스를 처음부터 포함시키는 것&amp;quot;&lt;/strong&gt; 이다. 다음엔 더 빠르게 보이길 바라며.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>해시</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/2</guid>
      <comments>https://jupeternotebook.tistory.com/2#entry2comment</comments>
      <pubDate>Thu, 23 Apr 2026 02:23:15 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 - 전화번호 목록 (JAVA)</title>
      <link>https://jupeternotebook.tistory.com/1</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;프로그래머스 &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot;&gt;전화번호 목록&lt;/a&gt; 풀이 과정&lt;br&gt;약 1년 전에도 한 번 풀었던 문제다. 그때도 꽤 헤맸는데... 다시 풀어보니 또 헤맸다.&lt;br&gt;총 9번의 제출... 여전히 부족하다&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/7angjung/post/6ca2f5dd-46e5-4584-901c-e4fa302d37b3/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;1트 (오답)&lt;/h3&gt;
&lt;p&gt;→ 배열 요소 중 &lt;strong&gt;가장 짧은 길이&lt;/strong&gt;를 기준으로, 슬라이딩 윈도우처럼 모든 부분 문자열을 Map에 저장한다.&lt;br&gt;→ 동일한 부분 문자열이 2개 이상 등장하면, 접두어 관계가 존재한다고 간주한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        // 배열 요소 최소 길이 구하기
        int min_len = Integer.MAX_VALUE;
        for(String num : phone_book) min_len = Math.min(min_len, num.length());

        // 최소 길이로 나올 수 있는 모든 경우 맵에 저장
        Map&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        for(String num : phone_book) {
            for(int i=0; i&amp;lt;=num.length()-min_len; i++) {
                String str = num.substring(i, i+min_len);
                map.put(str, map.getOrDefault(str,0)+1);
            }
        }

        // 1보다 큰 경우 접두어가 있는걸로 간주
        for(Map.Entry&amp;lt;String, Integer&amp;gt; entry : map.entrySet()) {
            if(entry.getValue() &amp;gt; 1) return false;
        }

        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;접두어 관계는 반드시 &lt;strong&gt;앞에서부터&lt;/strong&gt; 시작해야 하는데, 중간 인덱스(&lt;code&gt;i&lt;/code&gt;)부터 시작하는 부분 문자열까지 전부 비교하고 있어 잘못된 접근이다.&lt;/li&gt;
&lt;li&gt;예를 들어 &lt;code&gt;&amp;quot;123&amp;quot;&lt;/code&gt;과 &lt;code&gt;&amp;quot;312&amp;quot;&lt;/code&gt;가 있다면, 슬라이딩으로 &lt;code&gt;&amp;quot;23&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;31&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;12&amp;quot;&lt;/code&gt; 등이 겹쳐 접두어가 없는데도 false를 리턴하는 오탐이 발생한다.&lt;/li&gt;
&lt;li&gt;히스토리 기준으로 &lt;strong&gt;0점, 66.7점&lt;/strong&gt; 구간에 해당하는 풀이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2트 (오답)&lt;/h3&gt;
&lt;p&gt;→ 슬라이딩이 문제였으니, &lt;strong&gt;시작 인덱스를 0으로 고정&lt;/strong&gt;한다.&lt;br&gt;→ 각 번호의 앞 &lt;code&gt;min_len&lt;/code&gt; 글자만 잘라서 Map에 저장하면, 최소 길이 번호가 다른 번호의 접두어인 경우를 잡을 수 있지 않을까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        // 배열 요소 최소 길이 구하기
        int min_len = Integer.MAX_VALUE;
        for(String num : phone_book) min_len = Math.min(min_len, num.length());

        // 최소 길이로 나올 수 있는 모든 경우 맵에 저장
        Map&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        for(String num : phone_book) {
            String str = num.substring(0, min_len);
            map.put(str, map.getOrDefault(str,0)+1);
        }

        // 1보다 큰 경우 접두어가 있는걸로 간주
        for(Map.Entry&amp;lt;String, Integer&amp;gt; entry : map.entrySet()) {
            if(entry.getValue() &amp;gt; 1) return false;
        }

        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;최소 길이 번호가 접두어인 케이스는 어느 정도 잡히지만, 여전히 &lt;strong&gt;길이가 다른 번호들 사이의 접두어 관계&lt;/strong&gt;를 전부 처리하지 못한다.&lt;/li&gt;
&lt;li&gt;예를 들어 &lt;code&gt;&amp;quot;12&amp;quot;&lt;/code&gt;와 &lt;code&gt;&amp;quot;123&amp;quot;&lt;/code&gt;은 잡히지만, 접두어 관계 없이 앞 두 글자가 우연히 같은 번호들끼리 오탐이 생긴다.&lt;/li&gt;
&lt;li&gt;히스토리 기준으로 &lt;strong&gt;54.2점, 79.2점, 91.7점&lt;/strong&gt; 구간을 오가던 풀이의 원인이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;3트 (정답)&lt;/h3&gt;
&lt;p&gt;→ 발상을 바꾸자. 모든 번호를 Map에 넣어두고, 각 번호의 &lt;strong&gt;모든 접두어(prefix)&lt;/strong&gt; 를 직접 잘라서 Map에 존재하는지 확인하자.&lt;br&gt;→ 어떤 번호의 접두어가 Map에 존재한다면, 그게 곧 다른 번호와의 접두어 관계를 의미한다.&lt;br&gt;(솔직히 이 방향은 AI의 도움을 받아서 풀었던 나의 과거 풀이를 참고했다...)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        Map&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        for(String num : phone_book) {
            map.put(num, map.getOrDefault(num,0)+1);
        }

        for(String num : phone_book) {
            for(int j=0; j&amp;lt;num.length(); j++) {
                if(map.containsKey(num.substring(0,j))) {
                    return false;
                }
            }
        }

        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;정확성과 효율성 테스트 드뎌 통과&lt;/li&gt;
&lt;li&gt;히스토리상 &lt;strong&gt;2025-06-12, 2026-04-21&lt;/strong&gt; 두 번 모두 이 방향으로 결국 통과했다.&lt;/li&gt;
&lt;li&gt;다만 &lt;code&gt;Map&amp;lt;String, Integer&amp;gt;&lt;/code&gt;을 쓰고 있는데, 이 풀이에서 Value(Integer)는 전혀 활용되지 않는다.&lt;/li&gt;
&lt;li&gt;Key의 존재 여부만 확인하면 되므로, &lt;code&gt;Map&lt;/code&gt;보다 &lt;code&gt;Set&lt;/code&gt;이 더 적합한 자료구조다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4트 (리팩토링)&lt;/h3&gt;
&lt;p&gt;→ &lt;code&gt;Map&lt;/code&gt; 대신 &lt;code&gt;Set&lt;/code&gt;을 사용해 불필요한 Value 관리를 제거하자.&lt;br&gt;→ 코드가 더 간결해지고, 의도도 명확해진다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.*;

class Solution {
    public boolean solution(String[] phone_book) {
        Set&amp;lt;String&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
        for(String num : phone_book) {
            set.add(num);
        }

        for(String num : phone_book) {
            for(int j=0; j&amp;lt;num.length(); j++) {
                if(set.contains(num.substring(0,j))) {
                    return false;
                }
            }
        }

        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;위 풀이의 결과&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Map&lt;/code&gt; → &lt;code&gt;Set&lt;/code&gt;으로 교체하면서 코드가 훨씬 간결해졌다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contains()&lt;/code&gt; 하나로 접두어 존재 여부를 O(1)에 확인할 수 있어 가독성과 효율 모두 개선됐다.&lt;/li&gt;
&lt;li&gt;최종 제출 겨우 통과~&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;1년 전에 한 번 풀었던 문제인데도 다시 같은 구간에서 헤맸다. 그때의 나도, 지금의 나도 슬라이딩 윈도우 방향으로 먼저 손이 갔다는 게 신기하면서도 조금 부끄럽다. 핵심은 &lt;strong&gt;&amp;quot;모든 접두어를 직접 잘라서 Set에서 탐색한다&amp;quot;&lt;/strong&gt; 는 단순한 발상이었는데, 앞으로는 이 패턴을 더 빠르게 떠올릴 수 있도록 많이 풀어봐야겠다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;</description>
      <category>CS/코딩테스트</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>해시</category>
      <author>jupeternotebook</author>
      <guid isPermaLink="true">https://jupeternotebook.tistory.com/1</guid>
      <comments>https://jupeternotebook.tistory.com/1#entry1comment</comments>
      <pubDate>Thu, 23 Apr 2026 02:20:41 +0900</pubDate>
    </item>
  </channel>
</rss>