<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>UIHYEON</title>
    <link>https://kuh97.tistory.com/</link>
    <description>개발하는 하루</description>
    <language>ko</language>
    <pubDate>Sat, 4 Jul 2026 11:55:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>의현</managingEditor>
    <image>
      <title>UIHYEON</title>
      <url>https://tistory1.daumcdn.net/tistory/7500827/attach/601d04d40e5248a382553728c6f4d95a</url>
      <link>https://kuh97.tistory.com</link>
    </image>
    <item>
      <title>65줄짜리 텍스트 파일이 GitHub 124k Stars를 받은 이유 (CLAUDE.md 4원칙)</title>
      <link>https://kuh97.tistory.com/entry/65%EC%A4%84%EC%A7%9C%EB%A6%AC-%ED%85%8D%EC%8A%A4%ED%8A%B8-%ED%8C%8C%EC%9D%BC%EC%9D%B4-GitHub-124k-Stars%EB%A5%BC-%EB%B0%9B%EC%9D%80-%EC%9D%B4%EC%9C%A0-CLAUDEmd-4%EC%9B%90%EC%B9%99</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. AI의 역설 (성능은 올라가지만, 신뢰는 떨어진다.)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 LLM의 성능은 새로운 모델이 나올 때마다 더 좋아지지만, 실제 AI를 활용하다보면 다음과 같은 상황을 한 번쯤은 직면해봤을 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;할루시네이션 (Hallucination)&lt;/b&gt; : 아주 그럴듯하게 틀린 정보를 제공하는 문제&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성급한 일반화&lt;/b&gt; : 모호한 질문에 대해 &quot;알아서&quot; 가정을 세우고 코드를 작성하는 문제&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복잡도 증가&lt;/b&gt; : 간단히 해결할 수 있는 문제를 수백 줄의 복잡한 코드로 구현하는 문제&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제는 AI가 못해서 생기는 문제가 아니라, 오히려 모델 성능이 올라갈수록 더 많은 지식을 통해 더 나은 답변을 생성하려고 하기 때문에 발생하는 문제라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능은 올라가는데 신뢰는 오히려 떨어지는 상황이 발생하고 이러한 상황을 &lt;b&gt;&quot;지능의 역설&quot;&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;결국 AI의 지능을&amp;nbsp;&lt;b&gt;제어하고 검증하는 능력&lt;/b&gt;이 점점 더 중요해지는 시대가 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 등장한 개념이&amp;nbsp;&lt;b&gt;하네스 (Harness)&lt;/b&gt; 다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 하네스 (Harness)가 뭐지?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하네스 (Harness)는 원래 마구(馬具, 말을 제어하기 위해 채우는 장치)를 뜻한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발에서는 소프트웨어를&amp;nbsp;&lt;b&gt;통제된 환경에서 실행하고 검증하기 위한 툴&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;다음과 같은 테스트들이 있다면, 어떤 코드 변경이 들어와도 &quot;맞다/틀리다&quot;를 즉시 판단할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1778490603695&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe(&quot;결제 모듈&quot;, () =&amp;gt; {
  it(&quot;잔액 부족 시 에러를 던진다&quot;, () =&amp;gt; { ... });
  it(&quot;중복 결제를 방지한다&quot;, () =&amp;gt; { ... });
  it(&quot;타임아웃 시 롤백한다&quot;, () =&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AI에게 &quot;이렇게 해줘&quot;라고 말하는 대신, &quot;이 기준을 만족시켜줘&quot;라고 말하는 것&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❌ &lt;b&gt;명령형&lt;/b&gt; &lt;br /&gt;&quot;이 함수에 에러 처리 추가해줘&quot; &lt;br /&gt;&lt;br /&gt;✅ &lt;b&gt;하네스 방식&lt;/b&gt; &lt;br /&gt;&quot;null 입력, 빈 배열, 네트워크 타임아웃 케이스를 테스트로 먼저 작성하고, &lt;br /&gt;그 테스트가 전부 통과하도록 함수를 수정해줘&quot;&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;차이는 단순해 보일 수 있지만 결과는 완전히 다르다. 두 번째 방식은 API가 성공/실패 신호를 스스로 판단하며 루프를 돌 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Andrej Karpathy의 언급&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Andrej Karpathy는 Open AI 공동창업자이자 전 Tesla AI 디렉터로, AI/ML 분야에서 가장 유명한 실무자 중 한명이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드레 카파시가 트위터에 아래와 같은 글을 올렸다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;모델들은 사용자를 대신하여 잘못된 가정을 세우고 검증 없이 그대로 진행합니다. 혼란을 관리하지도 않고, 설명을 요청하지도 않고, 불일치를 드러내지도 않고, 장단점을 제시하지도 않으며, 필요할 때 반박하지도 않습니다.&quot;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;그들은 코드를 지나치게 복잡하게 만들고, 추상화를 과도하게 늘리고, 사용되지 않는 코드를 정리하지 않고... 100줄이면 충분할 코드를 1000줄 넘게 부풀려 구현하는 걸 정말 좋아합니다.&quot;&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;그들은 여전히 ​​때때로 자신들이 충분히 이해하지 못하는 부작용을 이유로 주석이나 코드를 수정하거나 삭제하는데, 이는 설령 그것이 작업과 직접적인 관련이 없더라도 마찬가지입니다.&quot;&lt;/blockquote&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;이를 바탕으로 개발자 Forrest Chang 이&amp;nbsp;&lt;b&gt;CLAUDE.md&amp;nbsp;&lt;/b&gt;파일로 정리했고, &lt;a href=&quot;https://github.com/forrestchang/andrej-karpathy-skills&quot;&gt;andrej-karpathy-skills&lt;/a&gt; 레포로 GitHub에 공개되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 한 줄도 없지만 124k 이상의 Star를 받으면서 화제가 되었다. 그 만큼 많은 개발자들이 이러한 상황을 겪고 있었다는 것을 의미한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. CLAUDE.md 4가지 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/forrestchang/andrej-karpathy-skills&quot;&gt;andrej-karpathy-skills&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 레포에 있는 CLAUDE.md는 다음과 같은 4가지 원칙을 따르고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbxNuQ/dJMb997pMqE/HFD5aDcs1ULxeQxSKaKwk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbxNuQ/dJMb997pMqE/HFD5aDcs1ULxeQxSKaKwk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbxNuQ/dJMb997pMqE/HFD5aDcs1ULxeQxSKaKwk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbxNuQ%2FdJMb997pMqE%2FHFD5aDcs1ULxeQxSKaKwk0%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;500&quot; height=&quot;159&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Think Before Coding : 잘못된 가정, 숨겨진 혼란, 간과된 절충안&lt;/li&gt;
&lt;li&gt;Simplicity First : 지나친 복잡함, 과장된 추상화&lt;/li&gt;
&lt;li&gt;Surgical Changes : 직교한 부수 변경, 건드리면 안되는 코드를 건드리는 행위&lt;/li&gt;
&lt;li&gt;Goal-Driven Execution : 검증 가능한 성공 기준의 부재&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 Think Before Coding - 코딩하기 전에 생각해라.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u5zdp/dJMcaffxiqQ/NWjpeQ5IUWLv1Tl0unSIj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u5zdp/dJMcaffxiqQ/NWjpeQ5IUWLv1Tl0unSIj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u5zdp/dJMcaffxiqQ/NWjpeQ5IUWLv1Tl0unSIj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu5zdp%2FdJMcaffxiqQ%2FNWjpeQ5IUWLv1Tl0unSIj0%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;600&quot; height=&quot;167&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;가정하지 마세요. 혼란을 숨기지 마세요. 트레이드오프를 드러내세요.&quot;&lt;/b&gt;&lt;/span&gt;&lt;u&gt;&lt;b&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;/u&gt;이러한 문제는 AI에게 모호한 질문을 하면 자기 마음대로 해석한 뒤 개발을 시작해버리는 문제를 해결하기 위한 방안이다.&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;&quot;검색 API가 좀 느린 것 같아. 최적화해줘.&quot; 라고 요청을 한다고 가정을 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGSD53/dJMcaaSOcdm/RgmCkCf1kgIRLrTABUJhP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGSD53/dJMcaaSOcdm/RgmCkCf1kgIRLrTABUJhP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGSD53/dJMcaaSOcdm/RgmCkCf1kgIRLrTABUJhP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGSD53%2FdJMcaaSOcdm%2FRgmCkCf1kgIRLrTABUJhP0%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;600&quot; height=&quot;265&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙이 적용되지 않았다면 AI가 스스로 판단하고 개발을 진행하게되고, 만약 원하는 요구사항과 다르다면 이러한 코드를 전부 고쳐야 되는 상황이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 원칙을 적용하게 된다면, 모호한 질문에 AI가 역으로 질문을 하게 되고 요구사항을 명확화하면서 개발을 이어나갈 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 Simplicity First - 단순함이 먼저다.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVmB8Z/dJMcaaSObpj/AOUnPK03IL3lnbY9mEk1vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVmB8Z/dJMcaaSObpj/AOUnPK03IL3lnbY9mEk1vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVmB8Z/dJMcaaSObpj/AOUnPK03IL3lnbY9mEk1vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVmB8Z%2FdJMcaaSObpj%2FAOUnPK03IL3lnbY9mEk1vK%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;600&quot; height=&quot;314&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;요청된 것만. 추측성 기능 없음. 단일 사용 코드에 추상화 없음.&quot;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&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;&quot;두 수를 더하는 함수&quot;를 만들어줘라고 요청을 한다고 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YmJcd/dJMcafT7auN/WVs3XGL9BuCwrnuHvkUK4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YmJcd/dJMcafT7auN/WVs3XGL9BuCwrnuHvkUK4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YmJcd/dJMcafT7auN/WVs3XGL9BuCwrnuHvkUK4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYmJcd%2FdJMcafT7auN%2FWVs3XGL9BuCwrnuHvkUK4k%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;700&quot; height=&quot;348&quot; data-origin-width=&quot;1670&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&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;하지만 우측과 같이 단순하게 구현할 수 있다. 딱봐도 우측의 경우가 원하는 요구사항을 간단하게 구현을 한 것으로 볼 수 있다. (그렇다고 1번이 안좋은 건 아니다. 상황에 따라 달라질 수 있다.)&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;2번째 원칙 글의 마지막 부분에 &quot;시니어 엔지니어가 보면 과도하다고 할 코드인가? 그렇다면 단순화해라.&quot; 라고 명시가 되어있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 Surgical Changes - 수술하듯 고쳐라.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0oguD/dJMcaarJMCY/JTlWyyVA7kZ4kwsHmwVYok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0oguD/dJMcaarJMCY/JTlWyyVA7kZ4kwsHmwVYok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0oguD/dJMcaarJMCY/JTlWyyVA7kZ4kwsHmwVYok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0oguD%2FdJMcaarJMCY%2FJTlWyyVA7kZ4kwsHmwVYok%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;600&quot; height=&quot;430&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;꼭 필요한 곳만 건드려라. 본인이 만든 잔해만 치워라.&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;calculate_total 버그만 고쳐줘&quot;라고 했을 때, AI가 옆 함수를 리팩토링하고 타입 힌트 추가하고 주석 스타일을 바꾸게 되면 리뷰가 지옥이 된다.&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;&quot;getUserById() 에서 null 반환하는 버그 하나만 고쳐줘&quot; 라고 요청했다고 가정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TmTeA/dJMcabc3FE9/7Ck8b1bQmPjKhfEMdSqGOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TmTeA/dJMcabc3FE9/7Ck8b1bQmPjKhfEMdSqGOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TmTeA/dJMcabc3FE9/7Ck8b1bQmPjKhfEMdSqGOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTmTeA%2FdJMcabc3FE9%2F7Ck8b1bQmPjKhfEMdSqGOk%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;700&quot; height=&quot;226&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙을 지키지 않을 경우 null 체크를 수정하면서 스스로 판단해 다른 부분도 최적화해버리는 경우가 존재한다. 이러한 부분은 오히려 불필요한 개선을하게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.4 Goal-Driven Execution - 목표 기반 실행&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfA1w0/dJMcabREkbu/yTI2R5QwubDNRX5EvXJHu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfA1w0/dJMcabREkbu/yTI2R5QwubDNRX5EvXJHu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfA1w0/dJMcabREkbu/yTI2R5QwubDNRX5EvXJHu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfA1w0%2FdJMcabREkbu%2FyTI2R5QwubDNRX5EvXJHu0%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;600&quot; height=&quot;327&quot; data-origin-width=&quot;1718&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;성공 기준을 정의하고, 검증될 때까지 루프를 돌려라.&quot;&lt;/span&gt;&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;span style=&quot;color: #333333;&quot;&gt;&quot;정렬 함수 버그 고쳐줘&quot;라고 요청을 했다고 가정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;좌측은 선언형이고, 우측은 명령형이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rqF7u/dJMcagS06Es/DCRHLpkXlbjIkYaGAcS0M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rqF7u/dJMcagS06Es/DCRHLpkXlbjIkYaGAcS0M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rqF7u/dJMcagS06Es/DCRHLpkXlbjIkYaGAcS0M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrqF7u%2FdJMcagS06Es%2FDCRHLpkXlbjIkYaGAcS0M1%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;700&quot; height=&quot;261&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙을 지킬 경우 실패 테스트 케이스를 작성하고, 수정을 한 뒤, 실패 테스트 케이스가 통과되는지 스스로 루프를 돌면서 확인을 하게된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 설치 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1 Skill 로 명시적으로 호출하고 싶은 경우 - 플러그인 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1778494074255&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/plugin marketplace add forrestchang/andrej-karpathy-skills
/plugin install andrej-karpathy-skills@karpathy-skills&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dj8EIA/dJMcag6xDTY/uD6h0c5WqL61AnV5Frmi50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dj8EIA/dJMcag6xDTY/uD6h0c5WqL61AnV5Frmi50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dj8EIA/dJMcag6xDTY/uD6h0c5WqL61AnV5Frmi50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdj8EIA%2FdJMcag6xDTY%2FuD6h0c5WqL61AnV5Frmi50%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;600&quot; height=&quot;215&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인을 설치하고, /karpathy-guidelines 를 실행하면 적용이 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2 CLAUDE.md 파일을 직접 다운받고 싶다면 - curl 한 줄&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전역 전용 (모든 프로젝트)&lt;/p&gt;
&lt;pre id=&quot;code_1778494212436&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -o CLAUDE.md https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md&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;/p&gt;
&lt;pre id=&quot;code_1778494252218&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;echo &quot;&quot; &amp;gt;&amp;gt; CLAUDE.md
curl https://raw.githubusercontent.com/forrestchang/andrej-karpathy-skills/main/CLAUDE.md &amp;gt;&amp;gt; CLAUDE.md&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 성능이 좋아질수록 개발자의 역할이 '구현'에서 '설계와 검증'으로 빠르게 변화하고 있는 것 같다고 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드레 카파시가 강조한 원칙들은 단순히 AI 실수를 줄이는 방법이 아니라, 앞으로 개발자들이 갖춰야할 역량인 'Harness Engineering'의 기초라고 생각한다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=gol5jv4wcfs&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=gol5jv4wcfs&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/forrestchang/andrej-karpathy-skills&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/forrestchang/andrej-karpathy-skills&lt;/a&gt;&lt;/p&gt;</description>
      <category>AI</category>
      <category>Ai</category>
      <category>Andrej Karpathy</category>
      <category>claude.md</category>
      <category>Goal-Driven Execution</category>
      <category>Harness</category>
      <category>Simplicity First</category>
      <category>Surgical Changes</category>
      <category>Think Before Coding</category>
      <category>안드레 카파시</category>
      <category>할루시네이션 (Hallucination)</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/257</guid>
      <comments>https://kuh97.tistory.com/entry/65%EC%A4%84%EC%A7%9C%EB%A6%AC-%ED%85%8D%EC%8A%A4%ED%8A%B8-%ED%8C%8C%EC%9D%BC%EC%9D%B4-GitHub-124k-Stars%EB%A5%BC-%EB%B0%9B%EC%9D%80-%EC%9D%B4%EC%9C%A0-CLAUDEmd-4%EC%9B%90%EC%B9%99#entry257comment</comments>
      <pubDate>Mon, 11 May 2026 19:18:29 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] FE/BE 배포 아키텍처 (CI/CD) 설계부터 자동화까지</title>
      <link>https://kuh97.tistory.com/entry/AWS-FEBE-%EB%B0%B0%ED%8F%AC-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-CICD-%EC%84%A4%EA%B3%84%EB%B6%80%ED%84%B0-%EC%9E%90%EB%8F%99%ED%99%94%EA%B9%8C%EC%A7%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS를 이용해 프론트엔드와 백엔드를 어떻게 배포를 하고 관리를 할 수 있는지에 대해 공부를 한 내용을 바탕으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 기능의 세세한 내용보다는 &lt;b&gt;전체적으로 데이터가 어떻게 흐르고 배포가 되는지&lt;/b&gt;에 대해 다뤄볼 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 한 눈에 보는 전체 설계도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 GitHub에 Push를 하면 FE와 BE가 각각 최적의 경로로 자동 배포되는 것이다.&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;Frontend&lt;/b&gt; : S3 + CloudFront(CDN) (서버 없는 확장 구조)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Backend&lt;/b&gt; : EC2 + ALB(Application Load Balancer) + CodeDeploy (고가용성 무중단 구조)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CI/CD&lt;/b&gt; : GitHub Actions&lt;/li&gt;
&lt;li&gt;&lt;b&gt;권한 관리&lt;/b&gt; : IAM Role (각 AWS 서비스가 신뢰할 수 있는 역할 부여)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시크릿 관리&lt;/b&gt; : AWS Secrets Manager / Parameter Store + GitHub Secrets&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biKAe7/dJMcabYsWrg/9oGX9RSwqkNdKc1b9PS9t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biKAe7/dJMcabYsWrg/9oGX9RSwqkNdKc1b9PS9t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biKAe7/dJMcabYsWrg/9oGX9RSwqkNdKc1b9PS9t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiKAe7%2FdJMcabYsWrg%2F9oGX9RSwqkNdKc1b9PS9t0%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;700&quot; height=&quot;384&quot; data-origin-width=&quot;1693&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 사전 준비 : IAM Role 설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 구축을 시작하기 전에&amp;nbsp;&lt;b&gt;IAM Role&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;필요한 IAM Role&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 76px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;Role 이름&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;부여 대상&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;필요한 이유&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;EC2-CodeDeploy-Role&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;EC2 인스턴스&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;S3에서 배포 파일 다운로드, CodeDeploy Agent 통신&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;CodeDeploy-Role&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;CodeDeploy 서비스&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;EC2에 배포 명령 전달, Auto Scaling 연동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;GitHub Actions용 IAM User&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;GitHub Actions&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;S3 업로드, CodeDeploy 배포 트리거&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  EC2 인스턴스에&amp;nbsp;&lt;b&gt;AmazonS3ReadOnlyAccess&lt;/b&gt; 와 &lt;b&gt;AmazonEC2RoleforAWSCodeDeploy&lt;/b&gt; 정책을 가진 Role을 붙여야 CodeDeploy Agent가 S3에서 파일을 가져올 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Frontend : 서버가 필요 없는 'Serverless' 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 (React, Next.js 등) 는 빌드를 하게되면 결국 HTML, CSS, JS 파일로 구성되어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드의 경우는 정적인 파일이기 때문에 따로 EC2 서버를 띄워둘 필요가 없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 배포 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;빌드&lt;/b&gt; : GitHub Actions 에서 npm run build를 수행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;S3 업로드&lt;/b&gt; : 빌드된 파일들을 AWS의 파일 저장소인 &lt;b&gt;S3&lt;/b&gt;에 업로드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정적 웹 호스팅&lt;/b&gt; : S3의 &lt;b&gt;'웹 서비스 호스팅'&lt;/b&gt; &lt;b&gt;기능&lt;/b&gt;을 활성화하면, S3 자체가 Nginx 같은 웹 서버 역할을 하며 파일을 사용자에게 전달한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;보안 설정 (OAC, Origin Access Control)&amp;nbsp;&lt;/b&gt;: S3 버킷은 public 차단 상태로 두고, CloudFront가 S3에 접근할 수 있도록 OAC을 설정한다. S3를 직접 public으로 하면 CloudFront를 우회한 직접 접근이 가능해져 보안에 취약해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전 세계 배포 (CloudFront)&lt;/b&gt; : S3 앞에&amp;nbsp;&lt;b&gt;CloudFront(CND)&lt;/b&gt; 를 붙여 전 세계 어디서든 초고속으로 접속하게 만들고 HTTPS 보안 연결까지 적용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시 무효화 (Cache Invalidation)&amp;nbsp;&lt;/b&gt;: S3에 새 파일을 올렸다고 끝이 아니라, CloudFront는 엣지 서버에 이전 파일을 캐싱하고 있기 때문에, 배포 후 반드시&amp;nbsp;&lt;b&gt;aws cloudfront create-invalidation&lt;/b&gt;으로 캐시를 초기화해야 사용자가 최신 버전을 받을 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  프론트엔드는 S3 저장소에 빌드된 파일을 올려두면 끝나기 때문에, 따로 EC2 서버가 필요없다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;⚠️ SPA 라우팅 주의사항&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;React Router 같은 클라이언트 라우팅을 사용할 경우, CloudFront에서&amp;nbsp;&lt;b&gt;커스텀 에러 페이지&lt;/b&gt;를 설정해야 한다.&lt;br /&gt;/about 같은 경로를 직접 새로고침하면 S3에서&amp;nbsp;&lt;b&gt;404&lt;/b&gt;가 반환되는데, 이를 &lt;b&gt;index.html로 리다이렉&lt;/b&gt;트 해줘야 SPA가 정상적으로 동작한다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Backend : 여러 대의 서버를 안전하게 '무중단' 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 (Spring Boot 등) 는 코드가 서버 메모리 위에서 24시간 실행되어야 한다. 그래서 여러 대의 인스턴스를 관리하는 전략이 중요하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 EC2 사전 준비 : CodeDeploy Agent 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy가 EC2에 배포 명령을 내릴 때 Agent를 통해 통신하기 때문에 EC2 인스턴스에는 CodeDeploy Agent가 설치되어 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 UserData나 프로비저닝 단계에서 미리 설치해두는 것이 좋다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습을 위한 참고용 코드 (정확한 코드가 아닐 수 있습니다.)&lt;/p&gt;
&lt;pre id=&quot;code_1778410994806&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Amazon Linux 2 기준
sudo yum update -y
sudo yum install -y ruby wget
cd /home/ec2-user
wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
sudo systemctl start codedeploy-agent
sudo systemctl enable codedeploy-agent&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 ALB (Application Load Balancer) 와 Target Group&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서 동일한 서버에 트래픽을 균일하게 분산시키기위해 ALB와 Target Group이 필요하다.&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;Target Group&lt;/b&gt; : 같은 환경의 EC2 인스턴스들을 하나의 그룹으로 지정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ALB (Application Load Balancer)&lt;/b&gt; : 사용자의 요청을 받아 Target Group 내의 건강한 서버들에게 트래픽을 골고루 분산시켜줌&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Health Check&lt;/b&gt; : ALB는 주기적으로 EC2 인스턴스의 &lt;b&gt;/health&lt;/b&gt; 같은 엔드포인트를 확인하며 서버가 정상 동작하는지 확인하고, health check를 통과한 인스턴스에만 트래픽을 보냄&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 appspec.yml&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy는 &quot;배포해&quot;라는 명령만 받을 뿐,&amp;nbsp;&lt;b&gt;실제로 뭘 해야 하는지 전혀 알지 못한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;appspec.yml&amp;nbsp;&lt;/b&gt;파일이 &quot;지시서&quot; 역할을 해주고, S3에 올린 파일 내부에 이 파일이 없으면 CodeDeploy는 배포 자체를 실패 처리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6rOoA/dJMcabjSPQC/C6DwokZTzwaJWk9x0Lfm4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6rOoA/dJMcabjSPQC/C6DwokZTzwaJWk9x0Lfm4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6rOoA/dJMcabjSPQC/C6DwokZTzwaJWk9x0Lfm4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6rOoA%2FdJMcabjSPQC%2FC6DwokZTzwaJWk9x0Lfm4K%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;300&quot; height=&quot;414&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;appsepc.yml은 크게 3개의 블록으로 구성되어있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;files&lt;/b&gt; : 파일 복사&lt;/li&gt;
&lt;li&gt;&lt;b&gt;permissions&lt;/b&gt; : 권한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;hooks&lt;/b&gt; : 스크립트 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.4 배포 전략 : Rolling Deployment Vs Blue/Green Deployment&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CodeDeploy는 Rolling Deployment 와 Blue/Green Deployment 두 가지 주요 배포 전략을 지원한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.4.1 Rolling Deployment (순차 배포)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 한 대씩(또는 그룹씩) 업데이트 하는 방식. 비용이 효율적이지만 배포 중 구 버전과 신버전이 동시에 서비스될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2696&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMcirH/dJMcac36qMg/cIpusKRGKMDV5TLNJVi4WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMcirH/dJMcac36qMg/cIpusKRGKMDV5TLNJVi4WK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMcirH/dJMcac36qMg/cIpusKRGKMDV5TLNJVi4WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMcirH%2FdJMcac36qMg%2FcIpusKRGKMDV5TLNJVi4WK%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;700&quot; height=&quot;190&quot; data-origin-width=&quot;2696&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트 과정&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;ALB가 서버A 트래픽 차단&lt;/li&gt;
&lt;li&gt;서버A 업데이트&lt;/li&gt;
&lt;li&gt;헬스 체크 통과&lt;/li&gt;
&lt;li&gt;ALB가 서버A 트래픽 복구&lt;/li&gt;
&lt;li&gt;다음 서버 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.4.2 Blue/Green Deployment&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 서버 그룹(Blue)과 동일한 신규 서버 그룹(Green)을 새로 띄우고, 완전히 준비되면 ALB가 한 번에 트래픽을 전환하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 실패 시 ALB만 다시 기존 서버 그룹(Blue)으로 돌리면 되므로 &lt;b&gt;롤백이 매우 빠르다.&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;추가 서버 비용이 발생하지만, 실제 운영 환경에서는 Blue/Green 배포 방식이 더 안전하다는 장점이 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 시크릿 &amp;amp; 환경변수 관리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 비밀번호, API 키 같은 민감한 정보를 코드에 하드코딩하거나 S3에 올리면 절대 안된다.&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;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 76px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;방법&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;용도&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;GitHub Secrets&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;GitHub Actions 워크플로우에서 사용하는 AWS 키, 환경 변수&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;CI/CD 파이프라인 내&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;AWS Parameter Store&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;서버 실행 시 EC2가 직접 읽어오는 설정값&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;DB URL, 설정 파일 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;AWS Secrets Manager&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;자동 로테이션이 필요한 DB 패스워드, API 시크릿&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;고보안 자격증명&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. GitHub Actions을 통한 CI/CD 파이프라인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GitHub Actions의 각 FE/BE의 배포 워크플로우를 설정하여 GitHub에 Push가 되었을 때 배포가 자동화 될 수 있도록 설정할 수 있다.&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;(아래의 yml코드는 예시코드로 대략적인 구성을 확인하는 용도로 보시면 됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FE 배포 워크플로우&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778412919696&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# .github/workflows/fe-deploy.yml
name: FE Deploy

on:
  push:
    branches: [ main ]
    paths: [ 'frontend/**' ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install &amp;amp; Build
        working-directory: ./frontend
        run: |
          npm ci
          npm run build
        env:
          REACT_APP_API_URL: ${{ secrets.API_URL }}

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      - name: Upload to S3
        run: |
          aws s3 sync ./frontend/build s3://${{ secrets.S3_BUCKET_FE }} \
            --delete \
            --cache-control &quot;public, max-age=31536000&quot; \
            --exclude &quot;index.html&quot;
          # index.html은 캐싱 제외 (항상 최신 버전 제공)
          aws s3 cp ./frontend/build/index.html s3://${{ secrets.S3_BUCKET_FE }}/index.html \
            --cache-control &quot;no-cache&quot;

      - name: Invalidate CloudFront Cache
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
            --paths &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;BE 배포 워크플로우&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778412941829&quot; class=&quot;ruby&quot; data-ke-language=&quot;ruby&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# .github/workflows/be-deploy.yml
name: BE Deploy

on:
  push:
    branches: [ main ]
    paths: [ 'backend/**' ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Build with Gradle
        working-directory: ./backend
        run: ./gradlew build -x test

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      - name: Package for CodeDeploy
        # appspec.yml + scripts/ + jar를 하나의 zip으로 묶기
        run: |
          mkdir -p deploy
          cp backend/build/libs/*.jar deploy/app.jar
          cp appspec.yml deploy/
          cp -r scripts/ deploy/
          cd deploy &amp;amp;&amp;amp; zip -r ../deploy-${{ github.sha }}.zip .

      - name: Upload zip to S3
        run: |
          aws s3 cp deploy-${{ github.sha }}.zip \
            s3://${{ secrets.S3_BUCKET_BE }}/deploy/deploy-${{ github.sha }}.zip

      - name: Trigger CodeDeploy
        run: |
          aws deploy create-deployment \
            --application-name ${{ secrets.CODEDEPLOY_APP_NAME }} \
            --deployment-group-name ${{ secrets.CODEDEPLOY_GROUP_NAME }} \
            --s3-location bucket=${{ secrets.S3_BUCKET_BE }},key=deploy/deploy-${{ github.sha }}.zip,bundleType=zip \
            --deployment-config-name CodeDeployDefault.OneAtATime \
            --description &quot;Deploy ${{ github.sha }}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  참고&lt;br /&gt;&lt;b&gt;github.sha&lt;/b&gt; 를 파일명에 사용하면 각 커밋마다 고유한 배포 파일이 생성되어 버전 관리와 롤백이 쉬워진다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 이 방식의 장점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;관리 편의성&amp;nbsp;&lt;/b&gt;: 개발자가 SSH로 서버에 직접 접속하여 업데이트를 하지 않아도 되고, GitHub만 신경 쓰면 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;안정성&amp;nbsp;&lt;/b&gt;: 배포 중에 에러가 나도 로드 밸런서가 살아있는 서버로만 연결해주니 사용자는 에러를 겪지 않을 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CodeDeploy의 &lt;b&gt;ValidateService&lt;/b&gt;&amp;nbsp;훅에서 실패하면 자동으로 이전 버전으로 롤백해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&amp;nbsp;&lt;/b&gt;: 서버가 2대든 10대든 태그만 달아주면 CodeDeploy가 알아서 모두 업데이트를 해준다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Auto Scaling Group과 연동하면 트래픽에 따라 서버 수도 자동 조절할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안&amp;nbsp;&lt;/b&gt;: S3는 public 차단, 민감 정보는 Secrets Manager / Parameter Store 에 분리하여 코드 저장소에 노출되는 저보를 최소화한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Amazon Cloud/Deploy</category>
      <category>appspec.yml</category>
      <category>aws</category>
      <category>aws alb</category>
      <category>aws ci/cd</category>
      <category>AWS CodeDeploy</category>
      <category>Blue/Green Deployment</category>
      <category>IAM role</category>
      <category>Rolling Deployment</category>
      <category>s3</category>
      <category>target group</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/256</guid>
      <comments>https://kuh97.tistory.com/entry/AWS-FEBE-%EB%B0%B0%ED%8F%AC-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-CICD-%EC%84%A4%EA%B3%84%EB%B6%80%ED%84%B0-%EC%9E%90%EB%8F%99%ED%99%94%EA%B9%8C%EC%A7%80#entry256comment</comments>
      <pubDate>Sun, 10 May 2026 20:48:13 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] EBS(Elastic Block Store) 알아보기</title>
      <link>https://kuh97.tistory.com/entry/AWS-EBSElastic-Block-Store-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 들어가기 전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS를 살펴보기전에 먼저 Block Store와 Object Store에 대해서 간단하게 비교해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S3와 같은 Object Store는 프로그램 등을 설치할 수 없고, 파일만 저장되는 저장소이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Block Store는 &lt;b&gt;프로그램 등을 설치할 수 있는 저장소&lt;/b&gt;를 의미한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. EBS (Elastic Block Store)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 본체를 보면 CPU와 램카드, 그래픽카드, SSD, HDD가 다같이 장착되어 컴퓨터가 동작하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스가 연산에 관한(CPU, 메모리 등) 처리를 한다고 한다면, &lt;b&gt;데이터를 저장하는 역할(SSD, HDD)&lt;/b&gt;은 바로 EBS가 한다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, EBS는 클라우드에서 사용하는&amp;nbsp;&lt;u&gt;&lt;b&gt;가상 하드디스크(HDD)&lt;/b&gt;&lt;/u&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;EBS는 AWS 클라우드의 Amazon EC2 인스터스에 사용할&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;영구 블록 스토리지 볼륨&lt;/b&gt;&lt;/span&gt;을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 단 몇분 내에 사용량을 많게 또는 적게 확장할 수 있으며, 프로비저닝(빌리는 행위)한 부분에 대해서만 저렴한 비용을 지불할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 EBS &amp;harr; EC2 연결 특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS의 가장 큰 특징은 EC2&amp;nbsp;&lt;u&gt;&lt;b&gt;인스턴스가 종료되어도 별개로 작동&lt;/b&gt;&lt;/u&gt;하여 유지가 가능하다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 컴퓨터 본체가 꺼지면 하드도 당연히 꺼져 이용을 못하겠지만 EBS는&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;네트워크로 별개로 연결된&amp;nbsp;&lt;/b&gt;&lt;/span&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;만일 잠시 인스턴스 처리 기능이 필요하지 않고 저장 장치 기능만 필요할때는, 인스턴스를 정지시켜도 EBS는 독립적으로 살아있기 때문에 스토리지 기능만 이용하는데 인스턴스의 추가 요금을 내지 않아도 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1825&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WRCX7/dJMcadhEZop/KVlbnUBxVUyx60SiWvKNd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WRCX7/dJMcadhEZop/KVlbnUBxVUyx60SiWvKNd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WRCX7/dJMcadhEZop/KVlbnUBxVUyx60SiWvKNd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWRCX7%2FdJMcadhEZop%2FKVlbnUBxVUyx60SiWvKNd1%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;550&quot; height=&quot;260&quot; data-origin-width=&quot;1825&quot; data-origin-height=&quot;862&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div data-composer-surface=&quot;true&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;이러한 구성은 다음과 같은 장점이 있다.&lt;/div&gt;
&lt;div&gt;만약 컴퓨터의 CPU를 다른 것으로 업그레이드 한다고 하면 컴퓨터를 끄고 본체를 뜯어서 새로운 CPU로 교체해줘야 하지만,&lt;/div&gt;
&lt;div&gt;&lt;b&gt;네트워크로 연결된 인스턴스와 EBS&lt;/b&gt;는 단순히 인스턴스만 다른걸로 EBS와 재연결 시켜주면 된다.&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kUT7o/dJMcaayucxg/bKEYCGTnPF9IZhHEITJqi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kUT7o/dJMcaayucxg/bKEYCGTnPF9IZhHEITJqi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kUT7o/dJMcaayucxg/bKEYCGTnPF9IZhHEITJqi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkUT7o%2FdJMcaayucxg%2FbKEYCGTnPF9IZhHEITJqi1%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;500&quot; height=&quot;339&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;그리고 인스턴스 입장에서 &lt;b&gt;여러가지 EBS를 붙일 수 있다는 특징&lt;/b&gt;이 있다.&lt;/div&gt;
&lt;div&gt;하나의 컴퓨터에 HDD, SSD를 여러개 부착할 수 있는 것처럼 인스턴스도 같은 원리이다.&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rJ2df/dJMcag6wkDC/tqfd0vxSFoG81alyAkvu6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rJ2df/dJMcag6wkDC/tqfd0vxSFoG81alyAkvu6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rJ2df/dJMcag6wkDC/tqfd0vxSFoG81alyAkvu6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrJ2df%2FdJMcag6wkDC%2Ftqfd0vxSFoG81alyAkvu6K%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;450&quot; height=&quot;476&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;반대로 &lt;b&gt;하나의 EBS를 여러 EC2에 장착(EBS Multi Attach)&lt;/b&gt;도 가능하다.&lt;/div&gt;
&lt;div&gt;여러 컴퓨터가 하나의 하드를 공용 저장소로 사용하는 원리이다. 이것은 당연히 물리적인 컴퓨터입장에서는 불가능하지만, 네트워크(클라우드)에서만 가능한 특징이다.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db3WHN/dJMcaiJZqjB/2KAa1tnbSQYAINofoBJIt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db3WHN/dJMcaiJZqjB/2KAa1tnbSQYAINofoBJIt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db3WHN/dJMcaiJZqjB/2KAa1tnbSQYAINofoBJIt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb3WHN%2FdJMcaiJZqjB%2F2KAa1tnbSQYAINofoBJIt0%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;500&quot; height=&quot;364&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS는 &lt;u&gt;&lt;b&gt;EC2와 같은 가용영역(AZ, Availability Zone)에 존재&lt;/b&gt;&lt;/u&gt;한다. AZ가 같아야 연결 및 통신이 빠르기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 다른 AZ로 생성해서 EC2에 붙이려고 한다면 에러가 나게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KeknM/dJMcafNlJzX/cdwINgkrAQ38QflOslGWGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KeknM/dJMcafNlJzX/cdwINgkrAQ38QflOslGWGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KeknM/dJMcafNlJzX/cdwINgkrAQ38QflOslGWGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKeknM%2FdJMcafNlJzX%2FcdwINgkrAQ38QflOslGWGk%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;450&quot; height=&quot;420&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 EBS 볼륨 (Volumn) 이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS로 생성한 &lt;b&gt;디스크 하나하나 저장 단위&lt;/b&gt;를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS 볼륨을 인스턴스에 연결한다는 말은 EC2에 물리적 하드 드라이브처럼 사용하겠다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, 윈도우에서 흔히 볼 수 있는 C드라이브, D드라이브는 각각 디스크이며 볼륨이라고 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO9ttj/dJMcaipJNJz/vywD5kH724wrDCYD5k9ND0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO9ttj/dJMcaipJNJz/vywD5kH724wrDCYD5k9ND0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO9ttj/dJMcaipJNJz/vywD5kH724wrDCYD5k9ND0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO9ttj%2FdJMcaipJNJz%2FvywD5kH724wrDCYD5k9ND0%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;600&quot; height=&quot;254&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1.2.1 EBS 볼륨 유형 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS 타입이란 시중에서도 같은 하드 저장 디스크라도 SSD와 HDD, NVMe로 나뉘고 용량에 따라 성능과 가격이 차이나는 것처럼, EBS도 각각 타입으로 나눠놓은 것이다.&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;EBS는 &lt;b&gt;총 5가지 타입을 제공&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;범용 (General Purpose of GP3) : SSD&lt;/li&gt;
&lt;li&gt;프로지너이 된 IOPS (Provisioned IOPS or io2) : SSD
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IOPS (Input/Output Operations Per Second)는 HDD/SDD같은 저장 장치가 1초당 얼마나 많은 읽기/쓰기 작업을 처리할 수 있는지 나타내는 성능 단위&lt;/li&gt;
&lt;li&gt;빠르게 데이터를 전송할 때 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;쓰루풋 최적화 (Throughput Optimized HDD or st1)
&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;/li&gt;
&lt;li&gt;콜드 HDD (SC1)&lt;/li&gt;
&lt;li&gt;마그네틱 (Standard)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqk3hV/dJMcaakVkBn/OaJbKFaAc2dk3RKan4VYj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqk3hV/dJMcaakVkBn/OaJbKFaAc2dk3RKan4VYj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqk3hV/dJMcaakVkBn/OaJbKFaAc2dk3RKan4VYj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqk3hV%2FdJMcaakVkBn%2FOaJbKFaAc2dk3RKan4VYj0%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;700&quot; height=&quot;134&quot; data-origin-width=&quot;1952&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&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;각 타입의 성능은&amp;nbsp;&lt;b&gt;용량&lt;/b&gt;과&amp;nbsp;&lt;b&gt;MAX IOPS 수치&lt;/b&gt;를 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tEpTm/dJMcaarIrcI/RQE1Cq6ugvky7ognhnifkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tEpTm/dJMcaarIrcI/RQE1Cq6ugvky7ognhnifkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tEpTm/dJMcaarIrcI/RQE1Cq6ugvky7ognhnifkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtEpTm%2FdJMcaarIrcI%2FRQE1Cq6ugvky7ognhnifkK%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;700&quot; height=&quot;428&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOPS 수치가 높을수록 데이터 통신이 빠르다고 보면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.3 EBS와 Instance Store&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스 저장 타입은&amp;nbsp;&lt;b&gt;대표적을 두가지&lt;/b&gt;가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 보았던 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;EBS 기반&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;인스턴스 저장 기반&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1583&quot; data-origin-height=&quot;993&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ce3G16/dJMcafzM6Me/HmNXCVkwAWo85n3ERVdQj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ce3G16/dJMcafzM6Me/HmNXCVkwAWo85n3ERVdQj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ce3G16/dJMcafzM6Me/HmNXCVkwAWo85n3ERVdQj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fce3G16%2FdJMcafzM6Me%2FHmNXCVkwAWo85n3ERVdQj0%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;550&quot; height=&quot;345&quot; data-origin-width=&quot;1583&quot; data-origin-height=&quot;993&quot;/&gt;&lt;/span&gt;&lt;/figure&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;[ EBS 기반 ]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;EC2가 EBS와 네트워크로 연결 -&amp;gt; 속도는 느림&lt;/li&gt;
&lt;li&gt;하지만 인스턴스가 삭제되더라도 EBS는 남아있음 (&lt;b&gt;영구적 스토리지&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;하나의 인스턴스에 연결한 EBS 볼륨을 따로 분리해서 다른 인스턴스에 연결 가능 (마치 USB를 넣었다 뺏다 하듯이)&lt;/li&gt;
&lt;/ol&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;[ 인스턴스 저장 기반 (Instance Store) ]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;EC2안에 store가 들어있음 (네트워크 연결 X) -&amp;gt; 속도가 빠름&lt;/li&gt;
&lt;li&gt;안에 내장되어있는 형태로, 인스턴스가 삭제되면 store도 같이 삭제됨&lt;/li&gt;
&lt;li&gt;EBS처럼 store를 분리해서 다른 인스턴스에 연결 불가능&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;e.g. &lt;b&gt;cache 데이터&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지와 같이 EC2를 생성할 때, EC2 타입을 고르는 항목에서 Instance Store 항목에 &lt;b&gt;EBS 전용&lt;/b&gt;과 &lt;b&gt;1 x 100 (SSD)&lt;/b&gt; 라고 써져있는 유형들을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS 전용은 말 그대로 스토리지를 EBS로만 사용할 수 있는 EC2 인스턴스 타입이라는 뜻이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(SSD) 라고 명시되어있는 것이 Instance Store를 따로 가지고 있다는 뜻이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCjSbA/dJMb99TQTkC/DpJyoMorOjzoh59jxfn7dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCjSbA/dJMb99TQTkC/DpJyoMorOjzoh59jxfn7dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCjSbA/dJMb99TQTkC/DpJyoMorOjzoh59jxfn7dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCjSbA%2FdJMb99TQTkC%2FDpJyoMorOjzoh59jxfn7dK%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;700&quot; height=&quot;226&quot; data-origin-width=&quot;1956&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwxspp/dJMcaaZwAwD/yPkWeKq4LoKWoJBbSlC6sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwxspp/dJMcaaZwAwD/yPkWeKq4LoKWoJBbSlC6sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwxspp/dJMcaaZwAwD/yPkWeKq4LoKWoJBbSlC6sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwxspp%2FdJMcaaZwAwD%2FyPkWeKq4LoKWoJBbSlC6sk%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;700&quot; height=&quot;136&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&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;&lt;a href=&quot;https://www.youtube.com/watch?v=Hz27LEk8Z48&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=21&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=Hz27LEk8Z48&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=21&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Hz27LEk8Z48&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=21&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Iw5q8/dJMb81fWZ3u/YsORxPBJoVWgGX7NgrFK0k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/erOXx1/dJMb85WXI6D/wHaU8NemRtC930hRIEfpq0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;(리뉴얼)쉽게 설명하는 AWS 기초강의 14.EBS와 AMI&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Hz27LEk8Z48&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&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;a href=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/what-is-ebs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ebs/latest/userguide/what-is-ebs.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778315573882&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;What is Amazon Elastic Block Store? - Amazon EBS&quot; data-og-description=&quot;What is Amazon Elastic Block Store? Amazon Elastic Block Store (Amazon EBS) provides scalable, high-performance block storage resources that can be used with Amazon Elastic Compute Cloud (Amazon EC2) instances. With Amazon Elastic Block Store, you can crea&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/what-is-ebs.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/what-is-ebs.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/what-is-ebs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ebs/latest/userguide/what-is-ebs.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;What is Amazon Elastic Block Store? - Amazon EBS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What is Amazon Elastic Block Store? Amazon Elastic Block Store (Amazon EBS) provides scalable, high-performance block storage resources that can be used with Amazon Elastic Compute Cloud (Amazon EC2) instances. With Amazon Elastic Block Store, you can crea&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Amazon Cloud/EC2</category>
      <category>aws</category>
      <category>EBS (Elastic Block Store)</category>
      <category>EBS Volumn</category>
      <category>EBS vs Instance Store</category>
      <category>EBS 볼륨 유형 타입</category>
      <category>ec2</category>
      <category>Instance Store</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/255</guid>
      <comments>https://kuh97.tistory.com/entry/AWS-EBSElastic-Block-Store-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0#entry255comment</comments>
      <pubDate>Sat, 9 May 2026 17:40:37 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] AMI / Snapshot 개념 알아보기</title>
      <link>https://kuh97.tistory.com/entry/AWS-AMI-Snapshot-%EA%B0%9C%EB%85%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1. EC2 데이터 백업&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;백업 및 복원은 서버를 사용하는 사람들에게는 필수적으로 알아야할 내용이며, 백업하는 습관은 의무적이라고 할만큼 중요한 습관이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;EC2는 기본적으로 사용을 하면 할 수록 요금이 많이 발생하게 된다. 따라서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;절약하기 위해서는 주기적으로 사용하지 않는 EC2를 중지하거나 종료&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 해당 EC2에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;중요한 데이터&lt;/b&gt;나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;다시 사용할 수 있는 데이터&lt;/b&gt;들이 담겨져 있는 경우가 많은데,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 백업 및 복원에 대한 유지관리를 편하게 할 수 있는 방법이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;AMI 이미지&lt;/b&gt;&lt;/span&gt;를 통해 EC2의 정보를 저장하고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;EBS Snapshot&lt;/b&gt;&lt;/span&gt;을 통해 데이터를 저장하는 방법이 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 EBS 스냅샷, AMI에 대해 알아보고, 이를 백업하여 관리하는 방법을 알아볼 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxX8DP/dJMcacC4ARd/Kyo3ELRas5B0ttKnEsyUE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxX8DP/dJMcacC4ARd/Kyo3ELRas5B0ttKnEsyUE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxX8DP/dJMcacC4ARd/Kyo3ELRas5B0ttKnEsyUE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxX8DP%2FdJMcacC4ARd%2FKyo3ELRas5B0ttKnEsyUE1%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;700&quot; height=&quot;435&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;2. 스냅샷(Snapshot)&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스냅샷(Snapshot)은&amp;nbsp;&lt;b&gt;EBS를 저장하는 효율적인 방법&lt;/b&gt;으로서,&amp;nbsp;&lt;b&gt;특정 시간에 EBS 볼륨 상태의 저장본&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;✏️ EBS 볼륨(Volumn)이란?&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;br /&gt;간단히 말해서 EBS로 생성한 디스크 하나하나 저장단위를 말한다.&lt;br /&gt;디스크는 윈도우에서 C드라이브, D드라이브와 같은 것을 의미한다.&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;EBS의 데이터 저장 상태에 대해 사진(백업본)을 찍어둔 개념이라고 이해하면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 필요시에 스냅샷을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;특정시간의 저장 데이터에 대한 EBS 복구&lt;/b&gt;&lt;/u&gt;가 가능한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스냅샷은 다음과 같은 상황에서 자주 이용이 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;OS와 별개로&amp;nbsp;&lt;b&gt;데이터만 백업&lt;/b&gt;하고 싶은 경우&lt;/li&gt;
&lt;li&gt;생성된&amp;nbsp;&lt;b&gt;스냅샷을 기반으로 여러 종류의 AMI를 생성&lt;/b&gt;하고 싶은 경우&lt;/li&gt;
&lt;li&gt;Amazon DLM을 활용하여 백업을 자동화하고, 스토리지 비용 최적화를 원하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 볼륨에 대한 스냅샷을 여러번 생성하면&amp;nbsp;&lt;b&gt;변경된 부분에 대한 데이터만 추가적으로 백업&amp;nbsp;&lt;/b&gt;+ 오래된 스냅샷은 자동으로 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 스냅샷 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스냅샷의 가장 큰 특징은&amp;nbsp;&lt;b&gt;&lt;u&gt;증분식 저장(변화된 부분만 저장)&lt;/u&gt;&amp;nbsp;&lt;/b&gt;한다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아래 사진과 같이&amp;nbsp;&lt;b&gt;파란색 블럭, 노란색 블럭, 초록색 블럭&lt;/b&gt; 이라는 각기 다른 데이터가 있다고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 이 데이터 블럭들의 각&amp;nbsp;&lt;b&gt;특정시간대 마다 저장 상태에 대해 백업&lt;/b&gt;을 한다고 가정하면, 각 시간대 마다&amp;nbsp;&lt;b&gt;저장 용량(파란박스)&amp;nbsp;&lt;/b&gt;을 따로따로 추가로 할당해줘야 할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yEkuN/dJMcahqQmGp/XrAZJDlkUZckzb82e4CBAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yEkuN/dJMcahqQmGp/XrAZJDlkUZckzb82e4CBAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yEkuN/dJMcahqQmGp/XrAZJDlkUZckzb82e4CBAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyEkuN%2FdJMcahqQmGp%2FXrAZJDlkUZckzb82e4CBAK%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;700&quot; height=&quot;272&quot; data-origin-width=&quot;1974&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&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;따라서 저장용량 공간 전체를 각 시간대 마다 할당하는게 아닌,&amp;nbsp;&lt;b&gt;추가된 데이터(변화된 부분)의 크기(용량)만 스냅샷&lt;/b&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;만약 HDD가 16tb라면 스냅샷 할 때마다 16tb를 매번 만드는 것이 아닌, 변화가 된 부분만 찍어두는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 처음에는 16tb가 필요하지만 이후에는 100mb가 바뀌었으면 해당 부분만 찍어 스토리지를 효율적으로 저장한다.&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-origin-width=&quot;1962&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/weYz9/dJMcac35UpQ/C8wvwNXlAEbpad3L1DMx91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/weYz9/dJMcac35UpQ/C8wvwNXlAEbpad3L1DMx91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/weYz9/dJMcac35UpQ/C8wvwNXlAEbpad3L1DMx91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FweYz9%2FdJMcac35UpQ%2FC8wvwNXlAEbpad3L1DMx91%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;700&quot; height=&quot;266&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  스냅샷 파일들은 보통 S3(파일저장소)에 보관된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Data Lifecycle Manager / AWS Backup 등으로 자동화 해서 생성 사능 (직접 Snapshot 관리를 안해도 됨)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. AMI (Amazon Machine Image)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tn21U/dJMcagk8fO5/48KDy3HrmmcwHpFWulhWnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tn21U/dJMcagk8fO5/48KDy3HrmmcwHpFWulhWnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tn21U/dJMcagk8fO5/48KDy3HrmmcwHpFWulhWnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftn21U%2FdJMcagk8fO5%2F48KDy3HrmmcwHpFWulhWnk%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;350&quot; height=&quot;352&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AM는 EC2&amp;nbsp;&lt;b&gt;&lt;u&gt;인스턴스를 실행하기 위해 필요한 정보를 모은 템플릿&lt;/u&gt;&amp;nbsp;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2(가상 컴퓨터)를 실행하기 위해서 CPU 프로세서 타입이라던가 저장공간 용량은 몇인지, 32비트인지 64비트인지, 운영체제는 무엇인지, 소프트웨어는 어떤게 설치되어있는지 ... 등 정보가 필요한데 이러한&amp;nbsp;&lt;b&gt;세팅 정보(템플릿)를 저장한 단위&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;즉, AMI란 서버에 필요한 운영체제와 다양한 소프트웨어로 구성된 플랫폼이며, AWS 이를&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;이미지&lt;/b&gt;&lt;/span&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;템플릿에는 컴퓨터 OS 환경설정 정보 뿐만 아니라 인스턴스의 EBS에 대한 정보도 모두 포함되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS에 대한 정보라는 것은 &lt;b&gt;EC2 인스턴스가 어떤 EBS 스냅샷과 연결되어있는지&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;AMI 백업 방식은 다음과 같은 상황에서 자주 이용된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;긴급하게 백업된 인스턴스를 복구해야하는 경우&lt;/li&gt;
&lt;li&gt;설정이 완료된 인스턴스 이미지를 만들고 싶은 경우&lt;/li&gt;
&lt;li&gt;Auto Scaling Group 에서 새 인스턴스를 자동으로 생성하기 위해&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 AMI를 사용하여 현재 상태의 EC2 세팅(템플릿)을 복제해서 다른 계정이나 다른 리전에게 전달도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 프로그램 형태로 AMI 배포가 가능하기 때문에 다른사람이 받아 내 EC2 상태 그대로를 사용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 AMI 구성&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1개 이상의 EBS 스냅샷&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AMI에는 인스턴스가 어떤 EBS 스냅샷과 연결되어있는지에 대한 정보가 포함&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;인스턴스 저장 기반 인스턴스란는 EC2 서버에 물리적으로 붙어있는 &lt;b&gt;임시 디스크(내장 SSD/HDD)&lt;/b&gt;를 사용하는 방식을 의미함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사용 권한 (어떤 AWS 어카운트가 사용할 수 있는지)&lt;/li&gt;
&lt;li&gt;EBS 블록 디바이스 매핑 (EC2 인스턴스를 위한 볼륨 정보 = EBS가 무슨 용량으로 몇 개 붙는지)&lt;/li&gt;
&lt;li&gt;필요에 따라 Private으로 가지고 있거나 Public 공개 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 AMI 생성 과정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;EBS의 스냅샷을 찍음&lt;/li&gt;
&lt;li&gt;스냅샷에는 OS, 파일, 시작권한 ... 등이 존재&lt;/li&gt;
&lt;li&gt;스냅샷을 S3에 저장&lt;/li&gt;
&lt;li&gt;스냅샷을 기반으로 AMI를 만듦&lt;/li&gt;
&lt;li&gt;AMI을 가지고 EC2를 실행하거나, 다른 사람에게 공유하거나 복사함&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oTC8q/dJMcafGyI6Y/ip84ZIUxklEU58k05aEym0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oTC8q/dJMcafGyI6Y/ip84ZIUxklEU58k05aEym0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oTC8q/dJMcafGyI6Y/ip84ZIUxklEU58k05aEym0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoTC8q%2FdJMcafGyI6Y%2Fip84ZIUxklEU58k05aEym0%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;700&quot; height=&quot;333&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. AMI와 Snapshot의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 &lt;u&gt;&lt;b&gt;EBS를 백업&lt;/b&gt;&lt;/u&gt;한다는 점에서 AMI와 Snapshot은 동일하다.&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;span style=&quot;color: #ee2323;&quot;&gt;AMI&lt;/span&gt;는 EC2 인스턴스에 연결되어 있는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;모든 EBS Volumn을 동시에 백업&lt;/b&gt;&lt;/span&gt;하는 것이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Snapshot&lt;/span&gt; 기능은 사용자가 &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;선택한 EBS Volumn 하나를 백업&lt;/span&gt;&lt;/b&gt;한다는 점에서 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면, AMI는 컴퓨터 하나를 통째로 백업한다는 느낌이고, Snapshot은 하드디스크 내용물만 백업한다는 것으로 이해하면 된다.&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;또한, AMI와 Snapshot을 이용하여 새로운 &lt;b&gt;EC2 인스턴스를 생성&lt;/b&gt;할 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;AMI&lt;/span&gt;인 경우에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;바로 EC2 인스턴스를 생성&lt;/b&gt;&lt;/span&gt;할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;Snapshot&lt;/span&gt;인 경우는 Snapshot을 이용해 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;AMI를 생성하는 단계를 거쳐야 한다&lt;/b&gt;&lt;/span&gt;는 차이가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Snapshot은 EBS 내용을 백업한 데이터라 바로 인스턴스를 만드는게 불가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SSCmD/dJMcacpwBWH/f1JR1FdY4KbLiKIKbpyKT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SSCmD/dJMcacpwBWH/f1JR1FdY4KbLiKIKbpyKT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SSCmD/dJMcacpwBWH/f1JR1FdY4KbLiKIKbpyKT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSSCmD%2FdJMcacpwBWH%2Ff1JR1FdY4KbLiKIKbpyKT1%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;700&quot; height=&quot;297&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;[ AMI ]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;EC2에 연결된 전체 볼륨 백업 (EC2 인스턴스에 연결되어있는 OS가 설치된 루트 장치를 포함)&lt;/li&gt;
&lt;li&gt;인스턴스가 어떤 EBS 스냅샷과 연결되어있는지에 대한 정보도 포함&lt;/li&gt;
&lt;li&gt;긴급하게 백업된 인스턴스를 복구해야하는 경우&lt;/li&gt;
&lt;li&gt;인스턴스 설정 등이 복잡해서 설정이 완료된 인스턴스 이미지를 만들고 싶은 경우&lt;/li&gt;
&lt;li&gt;Auto Scaling Group 에서 새 인스턴스를 자동으로 생성하기 위해 만드는 경우&lt;/li&gt;
&lt;/ol&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;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;[ Snapshot ]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;특점 시점 백업, 특정 EBS 볼륨 백업&lt;/li&gt;
&lt;li&gt;Snapshot을 이용해 AMI 생성 가능, 단 이 경우 &lt;b&gt;지정된 Snapshot이 Root Volumn&lt;/b&gt;으로 지정됨&lt;/li&gt;
&lt;li&gt;OS와 별개로 데이터만 백업하고 싶은 경우&lt;/li&gt;
&lt;li&gt;생성된 스냅샷을 기반으로 여러 종류의 AMI를 생성하고 싶은 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&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;&lt;a href=&quot;https://www.youtube.com/watch?v=Hz27LEk8Z48&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=21&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=Hz27LEk8Z48&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=21&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Hz27LEk8Z48&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=21&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Iw5q8/dJMb81fWZ3u/YsORxPBJoVWgGX7NgrFK0k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/erOXx1/dJMb85WXI6D/wHaU8NemRtC930hRIEfpq0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;(리뉴얼)쉽게 설명하는 AWS 기초강의 14.EBS와 AMI&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Hz27LEk8Z48&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&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;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778308518457&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Amazon Machine Images in Amazon EC2 - Amazon Elastic Compute Cloud&quot; data-og-description=&quot;Amazon Machine Images in Amazon EC2 An Amazon Machine Image (AMI) is an image that provides the software that is required to set up and boot an Amazon EC2 instance. Each AMI also contains a block device mapping that specifies the block devices to attach to&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c8WWdh/dJMb88e41kB/Z5LE6h7j7I2Fok6JmTWiuk/img.png?width=481&amp;amp;height=381&amp;amp;face=0_0_481_381&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c8WWdh/dJMb88e41kB/Z5LE6h7j7I2Fok6JmTWiuk/img.png?width=481&amp;amp;height=381&amp;amp;face=0_0_481_381');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Amazon Machine Images in Amazon EC2 - Amazon Elastic Compute Cloud&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Amazon Machine Images in Amazon EC2 An Amazon Machine Image (AMI) is an image that provides the software that is required to set up and boot an Amazon EC2 instance. Each AMI also contains a block device mapping that specifies the block devices to attach to&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Amazon Cloud/EC2</category>
      <category>AMI (Amazon Machine Image)</category>
      <category>AMI 이미지</category>
      <category>aws</category>
      <category>EBS</category>
      <category>EBS Snapshot</category>
      <category>EC2 데이터 백업</category>
      <category>Elastic Block Store</category>
      <category>Snapshot</category>
      <category>스냅샷</category>
      <category>증분식 저장</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/254</guid>
      <comments>https://kuh97.tistory.com/entry/AWS-AMI-Snapshot-%EA%B0%9C%EB%85%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0#entry254comment</comments>
      <pubDate>Sat, 9 May 2026 15:37:24 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] EC2 개념 살펴보기 (Instance / EBS / AMI)</title>
      <link>https://kuh97.tistory.com/entry/EC2-%EA%B0%9C%EB%85%90-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-Instance-EBS-AMI</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. EC2 (Elastic Compute Cloud) 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2란 Amazon Elastic Compute Cloud의 줄임말로 AWS에서 제공하는 클라우드 컴퓨팅을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;u&gt;&lt;b&gt;독립된 컴퓨터를 임대해주는 서비스&lt;/b&gt;&lt;/u&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;EC2를 사용하는 이유를 간단하게 살펴보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 돌리기위해 컴퓨터를 사고 세팅을하고 컴퓨터를 켜놓는 비용 대비, EC2는 몇 분만에 바로 컴퓨터 1대와 같은 기능을 하는 서버를 쉽게 만들 수 있다.&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;또한 인터넷을 통해 자유롭게 EC2에 접속할 수 있고, &lt;b&gt;이미지(AMI) 기능&lt;/b&gt;도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지(AMI) 기능을 간단하게 살펴보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터를 사용하면 프로그램 설치하고, 파일을 저장하고, 설정을 변경하게 되는데, 이 OS 상태를 그대로 저장하는 기능을 이미지(AMI)라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;b&gt;윈도우 백업 설정&lt;/b&gt;이라고 보면 된다.&lt;span style=&quot;color: #333333; background-color: #ffffff;&quot;&gt;&lt;/span&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;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  EC2를 통해 원하는 만큼 가상 서버를 구축하고 보안 및 네트워크 구성과 스토리지 관리가 가능하며, 요구 사항이나 갑작스런 트래픽 증가 등 변동 사항에 따라 신속하게 규모를 확장하거나 축소할 수 있어 서버 트래픽 예측 필요성이 줄어든다는 장점이 있다.&lt;span style=&quot;color: #333333; text-align: center; background-color: #ffffff;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #141414; text-align: center;&quot;&gt;1.1 EC2 특징 요약&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #141414; text-align: center;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&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;Linux / Windows 중 &lt;b&gt;OS 선택이 쉽다.&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;li&gt;&lt;b&gt;여러 다른 AWS 서비스와의 유기적인 연동&lt;/b&gt;이 가능하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. EC2의 구성 (Instance / EBS / AMI)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 서버를 보면 보통 &lt;b&gt;컴퓨팅(CPU, RAM), 하드디스크, 랜카드&lt;/b&gt; 로 나뉘어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 EC2에 대입해보면 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;컴퓨팅(CPU, RAM) = &lt;span style=&quot;color: #006dd7;&quot;&gt;Instance&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;하드디스크 = &lt;span style=&quot;color: #006dd7;&quot;&gt;EBS&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;랜카드 = &lt;span style=&quot;color: #006dd7;&quot;&gt;ENI&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✏️ &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;랜카드&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;한 네트워크 상에서 컴퓨터끼리 신호를 주고받는 데 쓰이는 하드웨어&amp;nbsp;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 EC2 Instance 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스란 AWS 클라우드에서 사용하는 &lt;u&gt;&lt;b&gt;가상 컴퓨터&lt;/b&gt;&lt;/u&gt;라고 이해하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상의 컴퓨터의 &lt;b&gt;CPU, 메모리, 그래픽카드 등 연산을 위한 하드웨어 부분을 담당&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;Amazon EC2는 &lt;u&gt;&lt;b&gt;각&lt;/b&gt;&lt;b&gt;사용 목적에 따라 최적화된 다양한 인스턴스 유형&lt;/b&gt;&lt;/u&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;/p&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;  즉, EC2 인스턴스를 통해 애플리케이션 종류와 목적에 따라 적합한 리소스 조합을 선택할 수 있는 유연성을 제공하는 서비스이다.&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;span style=&quot;text-align: center;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1.1 EC2 Instance 유형&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPG 게임을 예로들면 쉽게 이해할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPG 게임에서 캐릭터를 생성하면 한정된 스탯 포인트로 힘/민첩/지구력 .. 등을 골라 배분할 수 있다.&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;EC2 인스턴스에 스탯 분배 개념을 대입&lt;/b&gt;해 보면 이해하기가 쉽다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;779&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mL8PL/dJMcadPqjbH/uq8shkCl4arWWFKPCagFO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mL8PL/dJMcadPqjbH/uq8shkCl4arWWFKPCagFO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mL8PL/dJMcadPqjbH/uq8shkCl4arWWFKPCagFO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmL8PL%2FdJMcadPqjbH%2Fuq8shkCl4arWWFKPCagFO0%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;650&quot; height=&quot;439&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;779&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 RPG 게임에서 한정된 스탯으로 힘/민첩/지능/운 중에 어느 것을 찍어 생성할지 결정하는 것처럼,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  EC2 인스턴스란 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;한정된 요금&lt;/b&gt;&lt;/span&gt;으로 EC2 인스턴스의 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;유형(직업)&lt;/span&gt;&lt;/b&gt;을 고르고 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사이즈(스탯 포인트)&lt;/b&gt;&lt;/span&gt;를 골라 인스턴스별로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;사용 목적&lt;/b&gt;&lt;/span&gt;에 따라 최적화를 시키는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 CPU에 힘을 실어 연산능력이 좋은 서버를 만든다던지, 메모리 위주에 힘을 실어 저장소로서 기능 구성한다던지 등 다양하게 구성이 가능하다.&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;RPG 게임에도 직업이 전사/도적/마법사 등이 있듯이, 인스턴스 유형에도 &lt;b&gt;타입&lt;/b&gt;이라는 것이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS는 각 인스턴스&amp;nbsp;&lt;u&gt;&lt;b&gt;사용 목적(서버용, 머신러닝용, 게임용 등)에 따라 타입별로 인스턴스에 이름을 부여해 구분&lt;/b&gt;&lt;/u&gt;하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g. t타입, m타입, inf타입 ...등&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1983&quot; data-origin-height=&quot;793&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ic9xk/dJMcaaeaN2B/00skelGSMUfWEYK6xwFOvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ic9xk/dJMcaaeaN2B/00skelGSMUfWEYK6xwFOvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ic9xk/dJMcaaeaN2B/00skelGSMUfWEYK6xwFOvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIc9xk%2FdJMcaaeaN2B%2F00skelGSMUfWEYK6xwFOvK%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;700&quot; height=&quot;280&quot; data-origin-width=&quot;1983&quot; data-origin-height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;/figure&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;t타입과 m타입은 범용타입이기 때문에 AWS 초보자들이 가장 많이 사용하는 프리티어에서 사용하는 타입으로 자주 접해볼 수 있는 타입이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1.2 EC2 Instance Size&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스 크키는 &lt;b&gt;CPU 갯수, 메모리 크기, 성능 등으로 사이즈가 결정됨&lt;/b&gt;을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  인스턴스 사이즈가 클수록 더 많은 메모리, 더 많은 CPU, 더 많은 네트워크 대역폭을 가질 수 있다는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2754&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ea6fOg/dJMcadBR7OE/bOc9vUDAOAFDkuO9gQEkQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ea6fOg/dJMcadBR7OE/bOc9vUDAOAFDkuO9gQEkQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ea6fOg/dJMcadBR7OE/bOc9vUDAOAFDkuO9gQEkQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fea6fOg%2FdJMcadBR7OE%2FbOc9vUDAOAFDkuO9gQEkQk%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;750&quot; height=&quot;205&quot; data-origin-width=&quot;2754&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 표를 보면 t2 인스턴스 타입에 nano, micro, small, ...xlarge 로 크기가 나뉘어진 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU나 메모리 사이즈가 클 수록 성능도 빨라진다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1.3 EC2 Instance 타입 읽는 법&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/luXo7/dJMcafGyNeO/AOEzGBKxNvXAvjiSXFj2t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/luXo7/dJMcafGyNeO/AOEzGBKxNvXAvjiSXFj2t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/luXo7/dJMcafGyNeO/AOEzGBKxNvXAvjiSXFj2t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FluXo7%2FdJMcafGyNeO%2FAOEzGBKxNvXAvjiSXFj2t0%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;500&quot; height=&quot;259&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 Instance 타입을 모두 외워야 하는건 아니다.&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;위 'c7gn.2xlarge' 단어를 해석하면 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;c : 컴퓨팅 최적화(Compute Optimized) 인스턴스 패밀리를 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;7 : 7세대 인스턴스를 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;g : AWS Graviton 프로세서를 사용하는 ARM 기반 인스턴스를 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;n : 네트워크 성능이 강화된(Network Optimized) 유형을 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;2xlarge : 인스턴스 크기(Size)를 의미하며, `xlarge`보다 더 큰 사양을 가진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 EBS (Elastic Block Store) 이해하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.1에서 살펴본 Instance가 연산 (CPU, 메모리 등) 에 관한 처리를 한다고 하면,&amp;nbsp;&lt;u&gt;&lt;b&gt;데이터를 저장&lt;/b&gt;&lt;/u&gt;하는 역할은 EBS가 담당한다고 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  즉, 클라우드에서 사용하는&amp;nbsp;&lt;b&gt;가상 하드디스크(HDD)&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS는 AWS 클라우드의 Amazon EC2 인스턴스에 사용할&amp;nbsp;&lt;u&gt;&lt;b&gt;영구 블록 스토리지&lt;/b&gt;&lt;/u&gt; 볼륨을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 단 몇분 내에 사용량을 많게 또는 적게 확장할 수 있고, 프로비저닝(빌리는 행위)한 부분에 대해서만 저렴한 비용을 지불할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.1 EBS 볼륨 유형 타입&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBS 타입이란 시중에서도 같은 하드 저장 디스크라도 SSD, HDD, NVMe로 나뉘고 용량에 따라 성능과 가격이 차이나는 것처럼, EBS도 각각 타입으로 나눠놓은 것이다.&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;EBS는 총 5가지 타입을 제공&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;범용 (General Purpose of GP3) : SSD&lt;/li&gt;
&lt;li&gt;프로비저닝 된 IOPS (Provisioned IOPS of io2) : SSD&lt;/li&gt;
&lt;li&gt;쓰루풋 최적화 (Throughput Optimized HDD or st 1)&lt;/li&gt;
&lt;li&gt;콜드 HDD (SC1)&lt;/li&gt;
&lt;li&gt;마그네틱 (standard)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;901&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HaYJj/dJMcacpv9gJ/KbG1Ppoj2YimHWRZQKp9uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HaYJj/dJMcacpv9gJ/KbG1Ppoj2YimHWRZQKp9uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HaYJj/dJMcacpv9gJ/KbG1Ppoj2YimHWRZQKp9uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHaYJj%2FdJMcacpv9gJ%2FKbG1Ppoj2YimHWRZQKp9uk%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;700&quot; height=&quot;362&quot; data-origin-width=&quot;1744&quot; data-origin-height=&quot;901&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 범용타입인 GP3를 선택하면 되지만, 요금을 아끼겠다 하면 마그네틱을 사용하면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.3 AMI (Amazon Machine Image) 이해하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMI는 EC2 &lt;u&gt;&lt;b&gt;인스턴스를 실행하기 위한 정보를 모은 단위&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2(가상 컴퓨터)를 실행하기 위해서 CPU 프로세서 타입이라던가 저장공간 용량은 몇인지, 32비트인지 64비트인지, 운영체제는 무엇인지, 소프트웨어는 어떤게 설치되어있는지 ... 등 정보가 필요한데 이러한&amp;nbsp;&lt;u&gt;&lt;b&gt;세팅 정보(템플릿)를 저장한 단위&lt;/b&gt;&lt;/u&gt;라고 생각하면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✏️ &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;AMI&lt;/b&gt;&lt;/span&gt;&lt;br /&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;예를들어 '리눅스가 깔린 EC2를 사용 할 것이다'라고 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스턴스를 생성하고 OS를 일일이 설치하고 구성해야 하는 것이 아니라, &lt;b&gt;리눅스 환경 세팅이 된 리눅스 템플릿 AMI&lt;/b&gt;를 EC2에 적용시켜주면 간편하게 리눅스 환경인 컴퓨터를 장만하게 되는 원리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스냅샷을 기반으로 AMI 구성이 가능하기 때문에, AMI를 사용하여&amp;nbsp;&lt;b&gt;현재 상태의 EC2 세팅(템플릿)을 복제해서 다른 계정이나 다른 리전에게 전달&lt;/b&gt;도 가능하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✏️ &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Snapshot (스냅샷)&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;b&gt;EBS를 저장하는 효율적인 방법&lt;/b&gt;으로서, &lt;b&gt;특정 시간에 EBS 볼륨 상태의 저장본&lt;/b&gt;을 뜻한다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&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;&lt;a href=&quot;https://www.youtube.com/watch?v=WC_WarZJy5I&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=19&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=WC_WarZJy5I&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=19&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=WC_WarZJy5I&amp;amp;list=PLfth0bK2MgIYuFahPhXTpTomkwVx5Fl-v&amp;amp;index=19&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/vVlVO/dJMb81fWZ0I/c2i7EHRAs1ohlYQmX7MqN1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/b6gDKY/dJMb9dHsvbE/1VNmzD7BdyXIdEhoj4Ik10/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;(리뉴얼)쉽게 설명하는 AWS 기초강의 12.Amazon EC2 기초&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/WC_WarZJy5I&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&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;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1778232405508&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;What is Amazon EC2? - Amazon Elastic Compute Cloud&quot; data-og-description=&quot;What is Amazon EC2? Amazon Elastic Compute Cloud (Amazon EC2) provides on-demand, scalable computing capacity in the Amazon Web Services (AWS) Cloud. Using Amazon EC2 reduces hardware costs so you can develop and deploy applications faster. You can use Ama&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/8zH36/dJMb9cBMijd/hcmCNYtyJScI4fyQUt8DGK/img.png?width=831&amp;amp;height=219&amp;amp;face=0_0_831_219&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/concepts.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/8zH36/dJMb9cBMijd/hcmCNYtyJScI4fyQUt8DGK/img.png?width=831&amp;amp;height=219&amp;amp;face=0_0_831_219');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;What is Amazon EC2? - Amazon Elastic Compute Cloud&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;What is Amazon EC2? Amazon Elastic Compute Cloud (Amazon EC2) provides on-demand, scalable computing capacity in the Amazon Web Services (AWS) Cloud. Using Amazon EC2 reduces hardware costs so you can develop and deploy applications faster. You can use Ama&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Amazon Cloud/EC2</category>
      <category>Amazon Elastic Compute Cloud</category>
      <category>AMI (Amazon Machine Image)</category>
      <category>aws 클라우드</category>
      <category>EBS (Elastic Block Store)</category>
      <category>ec2</category>
      <category>instance</category>
      <category>가상 컴퓨터</category>
      <category>클라우드 컴퓨팅</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/253</guid>
      <comments>https://kuh97.tistory.com/entry/EC2-%EA%B0%9C%EB%85%90-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0-Instance-EBS-AMI#entry253comment</comments>
      <pubDate>Fri, 8 May 2026 19:01:37 +0900</pubDate>
    </item>
    <item>
      <title>React 18, 무엇이 달라졌는가</title>
      <link>https://kuh97.tistory.com/entry/React-18-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 새로운 진입점: `createRoot`&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18의 모든 새 기능은 진입점부터 달라졌다. 기존에 17이하 버전에서 사용하던 `ReactDOM.render` 대신 `ReactDOM.createRoot`를 사용하도록 변경이 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1777364610926&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React 17 이하
import ReactDOM from 'react-dom';
ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById('root'));

// React 18
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(&amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 18 이상의 버전에서도 `ReactDOM.render`을 사용할 수 있지만, 이 API를 그대로 사용하게 되면 &lt;b&gt;React 18의 동시성 기능이 활성화되지 않는다는 문제가 발생&lt;/b&gt;한다. 즉, createRoot로 마이그레이션해야 React 18에서 다루는 기능을 활용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 동시성 모드(Concurrent Mode)란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18에서 동시성 모드란 가장 중요한 개념이고, 가장 오해하기 쉬운 개념이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 기존 React의 렌더링 방식 (동기적)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 17까지의 렌더링은&amp;nbsp;&lt;b&gt;중단할 수 없는 동기 작업&lt;/b&gt;으로 실행이 되었다. React가 렌더링을 시작하면, 그 작업이 끝날 때까지 브라우저는 아무것도 할 수 없었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMAZAj/dJMcaiiO8k9/K5085EWXp6gg4AzhUkqQg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMAZAj/dJMcaiiO8k9/K5085EWXp6gg4AzhUkqQg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMAZAj/dJMcaiiO8k9/K5085EWXp6gg4AzhUkqQg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMAZAj%2FdJMcaiiO8k9%2FK5085EWXp6gg4AzhUkqQg1%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;700&quot; height=&quot;134&quot; data-origin-width=&quot;2002&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 리스트가 1,000개인 컴포넌트를 렌더링하는 동안 사용자가 버튼을 클릭해도 렌더링 끝나기 전에는 반응이 없고, 이러한 현상으로 &quot;UI가 버벅거린다&quot;라는 느낌을 들게한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 동시성(Concurrent) 렌더링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;동시성 모드는 멀티스레딩이 아니다.&lt;/b&gt;&lt;/span&gt; JavaScript는 여전히 싱글 스레드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, React가 렌더링 작업을&amp;nbsp;&lt;b&gt;나눠서,&lt;/b&gt; 중간중간 브라우저에게 제어권을 돌려주는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식으로 기존에는 렌더링이 한 번 시작되면 중단할 수 없었지만, 동시성 렌더링은&amp;nbsp;&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-origin-width=&quot;1992&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSrv3J/dJMcadIyiqz/wXO0oZVGLTdGf8L3mf1w11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSrv3J/dJMcadIyiqz/wXO0oZVGLTdGf8L3mf1w11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSrv3J/dJMcadIyiqz/wXO0oZVGLTdGf8L3mf1w11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSrv3J%2FdJMcadIyiqz%2FwXO0oZVGLTdGf8L3mf1w11%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;700&quot; height=&quot;154&quot; data-origin-width=&quot;1992&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 &quot;동시성 모드&quot;라는 이름이 헷갈리는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React팀이 React 18 출시 시점에 &quot;Concurrent Mode&quot;라는 표현 대신&amp;nbsp;&lt;b&gt;&quot;Concurrent Features&quot;&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;예전에는 &quot;모드를 켜면 전체가 바뀐다&quot;는 뉘앙스였는데&lt;/li&gt;
&lt;li&gt;실제로는&amp;nbsp;&lt;b&gt;필요한 곳에서만 선택적으로 쓰는 기능&lt;/b&gt;이기 때문에 이와 같은 표현을 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`useTransition`, `useDeferredValue`같은 API를 사용한 컴포넌트에서만 동시성 렌더링이 적용이되고, 기존 코드는 그대로 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Automatic Batching&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 Batching이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Batching은 여러 개의 state 업데이트를 하나로 묶어서&amp;nbsp;&lt;b&gt;한 번만 렌더링&lt;/b&gt;하는 최적화이다.&lt;/p&gt;
&lt;pre id=&quot;code_1777365658325&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다음과 같은 코드는 총 몇번의 렌더링이 일어날까?
function handleClick() {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
  setName('React');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 setCount, setFlag, setName 총 3개의 상태가 변하고, 각 상태가 변할 때마다 렌더링이 이루어지는게 아니라 하나의 리렌더링으로 묶어 1번만 렌더링이 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 React 17 과 18의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React의 버전에 따라 &lt;b&gt;Batching의 범위가 다르니 주의&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1777365815318&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React 17

// ✅ batching 적용 &amp;rarr; 렌더링 1번
function handleClick() {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
}

// ❌ batching 미적용 &amp;rarr; 렌더링 2번
setTimeout(() =&amp;gt; {
  setCount(c =&amp;gt; c + 1); // 렌더링 발생
  setFlag(f =&amp;gt; !f);     // 또 렌더링 발생
}, 1000);

// ❌ batching 미적용 &amp;rarr; 렌더링 2번
fetch('/api/data').then(() =&amp;gt; {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 17에서는 이벤트 핸들러에서는 Batching이 적용되지만, &lt;b&gt;fetch 또는 setTimeout과 같은 비동기는 각각 렌더링이 이루어지게 된다.&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;React 18 :&amp;nbsp;&lt;/b&gt;`createRoot`를 사용하면 &lt;b&gt;어디서든 자동으로 Batching이 적용&lt;/b&gt;된다.&lt;/p&gt;
&lt;pre id=&quot;code_1777365860385&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// React 18

// ✅ 모두 batching 적용 &amp;rarr; 렌더링 1번
setTimeout(() =&amp;gt; {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
}, 1000);

// ✅ Promise 내부도 적용 &amp;rarr; 렌더링 1번
fetch('/api/data').then(() =&amp;gt; {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 batching을 원하지 않을 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`createRoot`를 통해 자동으로 batching이 이루어지게 되고, 만약 batching을 사용하고 싶지 않다면 어떻게 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 경우는 거의 발생하지 않지만 각 state 변경 후 즉시 렌더링이 필요하다면 `flushSync`를 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1777366031427&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() =&amp;gt; {
    setCount(c =&amp;gt; c + 1); // 여기서 즉시 렌더링
  });
  flushSync(() =&amp;gt; {
    setFlag(f =&amp;gt; !f); // 여기서 또 즉시 렌더링
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Transition : 렌더링에 우선순위 부여하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색창에 타이핑을 할 때 다음과 같은 상황을 생각해보자. 두 가지 일이 동시에 일어나게 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&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;React 17에서는 둘 다 같은 우선순위로 처리된다. 결과 목록이 많아지게되면 입력 자체가 버벅이는 현상이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 startTransition&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18에서는 `startTransition`을 이용해 &quot;이 업데이트는 조금 늦어도 돼&quot;라고 명시할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1777366221138&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { startTransition } from 'react';

function handleChange(e) {
  // 급함: 입력값 업데이트
  setInputValue(e.target.value);

  // 급하지 않음: 검색 결과 업데이트 (transition으로 표시)
  startTransition(() =&amp;gt; {
    setSearchQuery(e.target.value);
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`startTransition` 안의 업데이트는&amp;nbsp;&lt;b&gt;긴급하지 않은(non-urgent)&lt;/b&gt; 업데이트로 표시가 된다. 더 긴급한 업데이트(사용자 입력)이 들어오면, transition 렌더링은 중단되고 긴급한 것을 먼저 처리하는 방식으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 useTransition 훅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`useTransition`은 `startTransition`의 훅 버전이다. 추가로&amp;nbsp;&lt;b&gt;&quot;현재 transition이 진행 중인지&quot;&lt;/b&gt; 여부를 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1777366433832&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useTransition } from 'react';

function SearchPage() {
  const [isPending, startTransition] = useTransition();
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  function handleChange(e) {
    setQuery(e.target.value); // 급한 : 즉시 반영
    
    startTransition(() =&amp;gt; {
      const filtered = heavyFilter(e.target.value);
      setResults(filtered); // 덜 급함 : transition
    }
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;input value={query} onChange={handleChange} /&amp;gt;
      {isPending ? &amp;lt;Spinner /&amp;gt; : &amp;lt;ResultList results={results} /&amp;gt;}
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`isPending`이 `true`인 동안 로딩 스피너를 보여줄 수 있어, 사용자 경험이 크게 향상된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.4 Transition vs Debounce&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 경험(UX) 측면에서 두 방식은 &quot;입력이 끝난 뒤 결과가 나온다&quot;는 점에서 비슷하게 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 내부 메커니즘과 목적은 완전히 다르다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;Transition&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;Debounce&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;핵심 원리&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;우선순위를 낮춰서 메인 스레드 점유를 피함&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;`setTimeout`으로 실행 자체를 뒤로 미룸&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;대기 시간&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;없음 (컴퓨터 성능에 따라 가변적)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;개발자가 설정한 고정 시간(e.g. 300ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;중단 가능성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;새로운 입력이 들어오면 렌더링을 즉시 중단&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;타이머가 끝나면 무조건 실행됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;사용 목적&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;UI 응답성 유지 (입력창 버벅임 방지)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;API 호출 횟수 제한, 과도한 연산 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Suspense의 진화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.1 React 17까지의 Suspense&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 16.6에서`Suspense`가 도입됐지만, 당시에는&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;React.lazy를 이용한 코드 스플리팅&lt;/b&gt;에서만 공식 지원했다.&lt;/p&gt;
&lt;pre id=&quot;code_1777366868036&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const LazyComponent = React.lazy(() =&amp;gt; import('./SomeComponent'));

&amp;lt;Suspense fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
  &amp;lt;LazyComponent /&amp;gt;
&amp;lt;/Suspense&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 페칭에 Suspense를 쓰는 건 권장되지 않았었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.2 React 18의 Suspense&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18에서 React 17과 마찬가지로&amp;nbsp;&lt;b&gt;Suspense 경계 안의 형제 컴포넌트들이 전부 준비될 때까지 기다린다.&lt;/b&gt; 이 부분은 동일하지만&amp;nbsp;&lt;b&gt;fallback처리 방식이 변화&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;React 17의 문제 : 페이지 이동 시 기존 화면이 즉시 사라짐&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 사용자가 &quot;상세보기&quot; 클릭한 상황을 생각해보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기존 목록 화면이 즉시 사라진다.&lt;/li&gt;
&lt;li&gt;&amp;lt;Spinner /&amp;gt; 로 교체 (fallback)&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;즉, 상세보기 버튼을 클릭할 경우 즉시 fallback으로 덮어버려 부자연스러운 UX라는 생각이 들게 된다.&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;React 18 : Transition과 결합하면 기존 화면을 유지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`startTransition`안에서 일어나는 업데이트가 Suspense를 트리거하면, React는 기존 화면을 fallback으로 교체하지 않고 &lt;b&gt;새 화면이 준비될 때까지 기존 화면을 그대로 유지&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1777368216017&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  const [page, setPage] = useState('list');
  const [isPending, startTransition] = useTransition();

  function navigate(nextPage) {
    startTransition(() =&amp;gt; {
      setPage(nextPage); // &quot;급하지 않음&quot;으로 표시
    });
  }

  return (
    &amp;lt;&amp;gt;
      {isPending &amp;amp;&amp;amp; &amp;lt;TopBarSpinner /&amp;gt;} {/* 기존 화면 위에 살짝 표시 */}
      &amp;lt;Suspense fallback={&amp;lt;FullPageSpinner /&amp;gt;}&amp;gt;
        {page === 'list' ? &amp;lt;ListPage /&amp;gt; : &amp;lt;DetailPage /&amp;gt;}
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 다음과 같이 개선된 UX로 동작을 하게 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기존 목록 화면이 그대로 유지&lt;/li&gt;
&lt;li&gt;상단에 작은 로딩 표시만 나옴 (fallback)&lt;/li&gt;
&lt;li&gt;데이터 로드 완료&lt;/li&gt;
&lt;li&gt;상세 화면으로 자연스럽게 교체 (UX 개선)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 새로 추가된 Hook들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.1 useId&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적 : 서버와 클라이언트 양쪽에서 안정적인 고유 ID 생성&lt;/p&gt;
&lt;pre id=&quot;code_1777368349064&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function FormField() {
  const id = useId();

  return (
    &amp;lt;&amp;gt;
      &amp;lt;label htmlFor={id}&amp;gt;이름&amp;lt;/label&amp;gt;
      &amp;lt;input id={id} type=&quot;text&quot; /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 필요할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR 환경에서 `Math.random()`이나 증가하는 카운터로 ID를 만들면, 서버에서 생성한 ID와 클라이언트에서 hydration할 때 생성한 ID가 달라져&amp;nbsp;&lt;b&gt;hydration mismatch&lt;/b&gt; 에러가 발생한다. `useId` 훅은 컴포넌트 트리 구조를 기반으로 ID를 생성하기 때문에 서버/클라이언트 양쪽에서 동일한 ID가 나오게된다.&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;주의 : key prop이나 CSS 선택자에는 사용함녀 안된다. 클론(`:`)이 포함된 형식이라 CSS 선택자와 충돌할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.2 useDeferredValue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적 : 특정 값의 업데이트를 &quot;뒤로 미루기&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 보았던 `useTransition`과 개념은 같지만, state setter에 접근할 수 없는 상황(e.g. 부모로부터 prop을 받을 때)에서 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1777368552178&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function SearchResults({ query }) {
  // query가 빠르게 바뀌어도, deferredQuery는 여유 있을 때 업데이트됨
  const deferredQuery = useDeferredValue(query);

  return &amp;lt;HeavyList filter={deferredQuery} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 `query`는 즉시 반영되고, `deferredQuery`는 브라우저가 여유 있을 때 업데이트된다. 덕분에 입력창은 즉각 반응하면서, 무거운 리스트는 약간 지연되어 렌더링된다.&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;useTransition vs useDeferredValue&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;useTransition&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;useDeferredValue&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;사용 위치&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;state를 직접 업데이트하는 곳&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;이미 내려운 prop 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;코드 변경&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;setState 감싸기&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;값을 래핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&lt;b&gt;isPending&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;제공&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;6.3 useSyncExternalStore&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적 : React 외부 스토어 (Redux, Zustand 등)를 안전하게 구독&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 렌더링 환경에서는 렌더링 도중 외부 스토어 값이 바뀔 수 있는데, 이때 컴포넌트마다 다른 값을 읽으면 UI가 일관되지 않는 tearing 현상이 발생하게 된다. 이를 `useSyncExternalStore`가 방지한다.&lt;/p&gt;
&lt;pre id=&quot;code_1777368823024&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useSyncExternalStore } from 'react';

function useWindowWidth() {
  return useSyncExternalStore(
    (callback) =&amp;gt; {
      window.addEventListener('resize', callback);
      return () =&amp;gt; window.removeEventListener('resize', callback);
    },
    () =&amp;gt; window.innerWidth,          // 클라이언트 snapshot
    () =&amp;gt; 0                           // 서버 snapshot (SSR용)
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 앱 개발자보다는&amp;nbsp;&lt;b&gt;라이브러리 개발자&lt;/b&gt;가 주로 사용한다고 한다. Redux, Zustand등 주요 상태 관리 라이브러리들은 이미 해당 훅을 내부적으로 적용을 했다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.4 useInsertionEffect&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적 : CSS-in-JS 라이브러리를 위한 특수 훅&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 순서 : `useInsertionEffect` -&amp;gt; `useLayoutEffect` -&amp;gt; `useEffect`&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM mutation이 일어나기 전, 스타이을 주입하기 위한 훅으로 `styled-components`, `emotion`같은 CSS-in-JS 라이브러리가 동시성 렌더링에서 안전하게 스타일을 삽입할 수 있도록 만들어진 것이다.&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;보통은 CSS-in-JS 라이브러리 내부에서 사용하는 API이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. Strict Mode의 새로운 동작&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.1 변경 내용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 18 Strict Mode에서 새로 추가된 동작이 있다.&amp;nbsp;&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;컴포넌트 마운트 (effect 실행)&lt;/li&gt;
&lt;li&gt;언마운트 (effect cleanup 실행)&lt;/li&gt;
&lt;li&gt;다시 마운트 (effect 재실행)&lt;/li&gt;
&lt;/ol&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.2 왜 이렇게 바뀌었을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 팀이 미래에 도입하는 기능 중에&amp;nbsp;&lt;b&gt;Offscreen API&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;이를 위해서 컴포넌트 마운트 -&amp;gt; 언마운트 -&amp;gt; 재마운트 사이클을 정상적으로 견뎌야한다. Strict Mode가 이것을 미리 검사해주는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1777370017212&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ❌ 문제: 두 번 구독하고 한 번만 해제
useEffect(() =&amp;gt; {
  const subscription = externalStore.subscribe(handler);
  // cleanup을 안 반환하면, 두 번째 마운트에서 구독이 중복됨
}, []);

// ✅ 올바른 작성
useEffect(() =&amp;gt; {
  const subscription = externalStore.subscribe(handler);
  return () =&amp;gt; subscription.unsubscribe(); // cleanup 반드시 작성
}, []);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔에 effect가 두 번 실행되는 이유는 Strict Mode가 &quot;cleanup을 제대로 작성했는지&quot;를 검사하는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. Streaming SSR&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8.1 기존 SSR의 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 SSR은 폭포수(waterfall) 구조이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서버 : 모든 데이터 fetch 완료 대기&lt;/li&gt;
&lt;li&gt;서버 : 전체 HTML 렌더링&lt;/li&gt;
&lt;li&gt;서버 : 완성된 HTML 전송&lt;/li&gt;
&lt;li&gt;클라이언트 : JS 번들 다운로드&lt;/li&gt;
&lt;li&gt;클라이언트 : 전체 hydration 완료&lt;/li&gt;
&lt;/ol&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;h3 data-ke-size=&quot;size23&quot;&gt;8.2 React 18의 Streaming SSR&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Suspense` 경계가 스트리밍의 단위가 되고, 빠른 부분이 먼저 사용자에게 표시되고, 느린 부분은 데이터가 준비되면 그때 전송된다.&lt;/p&gt;
&lt;pre id=&quot;code_1777369349399&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function App() {
  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;Header /&amp;gt;             {/* 빠름: 즉시 전송 */}
      &amp;lt;Suspense fallback={&amp;lt;Skeleton /&amp;gt;}&amp;gt;
        &amp;lt;SlowDataComponent /&amp;gt; {/* 느림: 준비되면 스트리밍 */}
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리 정리&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;기능&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;핵심 요약&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;createRoot&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;동시성 기능의 진입점. 이걸 써야 나머지가 활성화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;동시성 렌더링&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;렌더링을 잘게 쪼개 중단/재개/폐기 가능. 우선순위 기반 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Automatic Batching&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;setTimeout, Promise 내부도 자동으로 state 업데이트를 묶어 한 번만 렌더링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Transition&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&quot;이 업데이트는 덜 급해&quot;라고 표시. 사용자 입력이 항상 먼저 처리됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Suspense&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;동시성과 통합. Streaming SSR과 함께 쓸 때 더 유용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;useId&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;SSR 환경에서도 안전한 고유 ID 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;useDeferredValue&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;prop을 받는 쪽에서 값 업데이트를 지연&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;useSyncExternalStore&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;외부 스토어 구독 시 tearing 방지. 라이브러리 내부용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;useInsertionEffect&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;CSS-in-JS 라이브러리용 스타일 주입 훅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Strict Mode&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;이중 마운트로 effect cleanup 올바름 검사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;b&gt;Streaming SSR&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Suspense 기반으로 HTML을 조각내어 스트리밍 전송&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;&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;&lt;a href=&quot;https://ko.react.dev/blog/2022/03/29/react-v18&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ko.react.dev/blog/2022/03/29/react-v18&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1777404049420&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React v18.0 &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;ko.react.dev&quot; data-og-source-url=&quot;https://ko.react.dev/blog/2022/03/29/react-v18&quot; data-og-url=&quot;https://ko.react.dev/blog/2022/03/29/react-v18&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dzlBtj/dJMb88F73bZ/EXlyomlJFdzNGMGX98arA1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/ctI1cZ/dJMb82eP7ez/r4SUM56XDsU133o8kF9WE0/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://ko.react.dev/blog/2022/03/29/react-v18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.react.dev/blog/2022/03/29/react-v18&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dzlBtj/dJMb88F73bZ/EXlyomlJFdzNGMGX98arA1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/ctI1cZ/dJMb82eP7ez/r4SUM56XDsU133o8kF9WE0/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React v18.0 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;a href=&quot;https://www.youtube.com/watch?v=WtiF3DP6oWk&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=WtiF3DP6oWk&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=WtiF3DP6oWk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/dNU3AY/dJMb9aKH9cZ/fge2K6J2RcVIriJNS9Ztpk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/fraOb/dJMb82eP3XP/JKQiPZ7UQK9ZKnuKko9pX1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;PedroTech&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/WtiF3DP6oWk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;</description>
      <category>프론트엔드/React</category>
      <category>Automatic batching</category>
      <category>Concurrent Feature</category>
      <category>createRoot</category>
      <category>react 18</category>
      <category>startTransition</category>
      <category>Streaming SSR</category>
      <category>Transition</category>
      <category>useDeferredValue</category>
      <category>useTransition</category>
      <category>동시성 렌더링</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/252</guid>
      <comments>https://kuh97.tistory.com/entry/React-18-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0#entry252comment</comments>
      <pubDate>Tue, 28 Apr 2026 18:57:12 +0900</pubDate>
    </item>
    <item>
      <title>리액트 불변성(Immutability), 대체 왜 지켜야 할까?</title>
      <link>https://kuh97.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%B6%88%EB%B3%80%EC%84%B1Immutability-%EB%8C%80%EC%B2%B4-%EC%99%9C-%EC%A7%80%EC%BC%9C%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;불변성이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불변성(Immutability)은 말 그대로 &quot;변하지 않는 성질&quot;을 나타낸다. 프로그래밍에서는&amp;nbsp;&lt;b&gt;한 번 할당된 메모리의 값을 직접 바꾸지 않는 것&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;예를 들어 `let a = 1`이라고 선언했을 때, `a = 2`로 재할당하면 숫자 1이 있던 메모리 공간을 바꾸는 게 아니라,&amp;nbsp;&lt;b&gt;2가 저장된 새 메모리 주소를 a에 연결&lt;/b&gt;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1APSI/dJMcaf7rVj0/VMi9vyNTY8C8ztxqHfKeq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1APSI/dJMcaf7rVj0/VMi9vyNTY8C8ztxqHfKeq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1APSI/dJMcaf7rVj0/VMi9vyNTY8C8ztxqHfKeq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1APSI%2FdJMcaf7rVj0%2FVMi9vyNTY8C8ztxqHfKeq0%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;600&quot; height=&quot;328&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;핵심 한 줄 요약&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;불변성 = &lt;u&gt;&lt;b&gt;메모리에 있는 값 자체를 건드리지 않는 것&lt;/b&gt;&lt;/u&gt;. 업데이트가 필요하면 새 값을 만들어서 교체한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원시 타입 vs 참조 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 값은 크게 두 종류로 나뉘는데, 이게 불변성을 이해하는 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 원시 타입(Primitive Type)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;e.g. `let a = 1;`, `b = a`를 하게 되면 어떻게 될까?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a = 1을 할당하면 메모리주소 #100번에 1이 저장되어 매핑이 된다. 이후 b = a를 하게되면 메모리주소 #101번에 새로운 1이 생성되고, #101번이 b에 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, b를 변경해도 a에 영향을 주지 않는다. (값을 복사하는 방식)&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;2. 참조 타입(Reference Type)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;e.g. `let obj1 = { val: 30 };`, `obj2 = obj1`을 하게 되면 어떻게 될까?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`{ val: 30}`을 생성한 객체가 메모리 주소 #100번에 생성되어 obj1에 해당 메모리 주소가 매핑이 된다. 이후 obj2 = obj1을 하게 되면 새로 메모리 주소를 생성하지 않고 #100번이 obj2에 매핑이 되어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, obj2를 변경할 경우 obj1에 영향을 미치게된다. (주소를 복사하는 방식)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이러한 복사 방식을 얕은 복사(shallow copy)라 부른다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BrdD4/dJMcaakMJAh/YGpYrTCxyiXXaVPddq98SK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BrdD4/dJMcaakMJAh/YGpYrTCxyiXXaVPddq98SK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BrdD4/dJMcaakMJAh/YGpYrTCxyiXXaVPddq98SK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBrdD4%2FdJMcaakMJAh%2FYGpYrTCxyiXXaVPddq98SK%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;600&quot; height=&quot;328&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트는 어떻게 변화를 감지할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트가 리렌더링을 결정하는 방식은 생각보다 간단하다. 이전 state와 새 state를 &lt;b&gt;얕은 비교(Shallow Comparison)&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;얕은 비교란&amp;nbsp;&lt;b&gt;메모리 주소(참조값)이 같은지 확인&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;따라서 객체 state를 직접 수정하면 메모리 주소는 변하지 않아서 리액트는 &quot;아, 안 바뀌었네?&quot;라고 판단하여 화면을 업데이트하지 않는 문제가 발생할 수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;올바른 state 업데이트 패턴&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 원시 타입(Primitive Type)&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;gt; 원시 타입 값은 메모리 주소에 새로 할당하여 매핑이 되기 때문에&lt;/p&gt;
&lt;pre id=&quot;code_1777285373050&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [count, setCount] = useState(0);

// 원시 타입은 값 자체를 그냥 넘겨도 OK
setCount(5);
setCount(count + 1);&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;2. 참조 타입(Reference Type)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Spread Operator (...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프레드 연산자를 이용해 객체를 새로 생성할 수 있다. 물론 재귀를 통해서도 복사를 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1777285564455&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 올바르지 않는 방법
const [user, setUser] = useState({
  name: '의현', age: 100
});

// 직접 수정 &amp;rarr; 주소 안 바뀜
user.age = 30;
setUser(user); // 리렌더링 안 됨!

--------------------------------------

// 올바른 방법 (스프레드 연산자 이용)
const [user, setUser] = useState({
  name: '의현', age: 100
});

// 새 객체 생성 &amp;rarr; 새 주소!
setUser({ ...user, age: 30 });
// 리렌더링 성공!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 중첩 객체의 경우는 불변성을 보장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 1depth까지만 복사를 한다고 이해하면 된다.&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;2. immer 라이브러리 (중첩 객체 복사)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프레드 연산자를 이용해서도 중첩 객체의 불변성을 지키도록 새롭게 생성할 수는 있다. 하지만 중첩이 많아질수록 코드가 지저분해지는 단점이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1777285788364&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [profile, setProfile] = useState({
  name: '의현',
  address: { city: '성남', zip: '13494' }
});

// 중첩된 address도 새 객체로 만들어야 한다.
setProfile({
  ...profile,
  address: {
    ...profile.address,
    city: '판교'   // city만 바꾸고 zip은 유지
  }
});&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;immer 라이브러리를 이용하면 중첩된 객체를 쉽게 복사를 할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Immer란 무엇인가?&lt;/span&gt;&lt;br /&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가변(Mutable) 코드를 작성해도 불변(Immutable) 상태로 만들어주는 라이브러리로, 복잡한 복사 로직 없이 단순히 값을 대입하는 것만으로 리액트가 원하는 &quot;새로운 객체&quot;를 반환해준다.&lt;/span&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;immer의 핵심 동작 방식: Proxy와 Copy-on-Write&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Proxy 생성 : immer의 `produce`함수를 실행하면 원본 데이터를 복사하는 대신, 원본을 감싸는 Proxy인 `draft`를 생성한다.&lt;/li&gt;
&lt;li&gt;기록(Recipe) : `draft.address.city = '판교'`라고 값을 변경하면, 이 변경 사항은 원본에 바로 반영되지 않고 Proxy 내부의 Recipe에 기록이 된다.&lt;/li&gt;
&lt;li&gt;Copy-on-Write : 변경이 일어나는 순간, immer는 해당 경로에 있는 객체들만 새롭게 생성(깊은 복사)한다. &lt;b&gt;나머지 변경되지 않는 부분은 원본의 주소를 그대로 참조하게 된다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1777287280911&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [profile, setProfile] = useState({
  name: '의현',
  address: { city: '성남', zip: '13494' },
  hobbies: ['coding', 'tennis']
});

setProfile(
  produce(draft =&amp;gt; {
    draft.address.city = '판교'; 
  })
);&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; `hobbies`는 얕은 복사가 된 상태이고, 이를 수정하게 되면 원본에 영향을 미치는게 아닐까?&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, profile에 city만 변경해서 user에 새로운 객체를 복사했다고 가정을 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1777288030047&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [profile, setProfile] = useState({
  name: '의현',
  address: { city: '성남', zip: '13494' },
  hobbies: ['coding', 'tennis']
});

const user = produce(profile, (draft) =&amp;gt; {
  draft.address.city = '강남';
});&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;immer의 복사를 통해 hobbies는 얕은 복사를 통해 메모리 주소(#500)을 공유하고 있는 상황이다.&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;만약 user의 hobbies를 수정하려고하면 어떤일이 일어날까?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;공유 단계 : `profile`과 새로운 `user`는 모두 &lt;b&gt;동일한 hobbies 주소(#500)&lt;/b&gt;을 가리키고있다.&lt;/li&gt;
&lt;li&gt;수정 시도 : `user.hobbies.push(...)`를 통해 수정을 시도&lt;/li&gt;
&lt;li&gt;immer의 개입 : `user`는 단순 객체가 아니라 &lt;b&gt;Proxy&lt;/b&gt;이기 때문에 수정하려고 하면 immer가&amp;nbsp;원본 주소(#500)를 수정하지 못하게 제지 당하게 된다.&lt;/li&gt;
&lt;li&gt;분리 : &lt;b&gt;immer는 즉시 `hobbies` 배열을 복사한 새로운 주소(#700)를 생성&lt;/b&gt;하고, user가 이 새 주소를 바라보게 갈아끼운 뒤 수정을 진행하게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 변경을 하지 않아 얕은 복사로 메모리 주소를 공유하고 있는 값들도&amp;nbsp;&lt;b&gt;실제 수정이 일어나게 될 경우 새로운 메모리 주소로 생성하여 원본에 영향을 주지 않게 동작&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;왜 immer는 이런 방식을 사용할까? (성능 vs 안전)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 데이터를 처음부터 끝까지 깊은 복사(Deep Copy)하면 안전하지만, 거대한 객체를 다룰 대마다 메모리와 CPU를 엄청나게 소모하게 된다.&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;결국 immer는 구조적 공유(Structural Sharing)를 통해 성능과 안전을 둘 다 잡는 방식을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변경 안 된 것 : 주소만 가져와서 메모리 아끼기 (얕은 복사)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변경 된 것 : 수정 직전에 새 참조 만들기 (Proxy 기반의 참조 분리)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트에서 불변성이 필요한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;1. 효율적인 렌더링 최적화 (성능 향상)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트의 핵심 성능은&amp;nbsp;&lt;b&gt;바뀐 부분만 다시 그리는 것&lt;/b&gt;에 있다. 이때 리액트는 '뭐가 바뀌었는지' 판단하기 위해 &lt;b&gt;얕은 비교(Shallow Comparison)&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;불변성을 지키지 않으면 : 객체 내부의 값만 바뀌고 주소값은 그대로라면, 리액트는 `prevProfile === nextProfile` 결과가 `true`라고 판단하게 되고, &quot;&lt;b&gt;아무것도 바뀌지 않았네?&quot;&lt;/b&gt;라고 판단해 화면을 업데이트하지 않는 버그가 발생하게 된다.&lt;/li&gt;
&lt;li&gt;불변성을 지키면 : 값이 바뀌면&amp;nbsp;&lt;b&gt;무조건 새로운 메모리 주소&lt;/b&gt;를 갖게되고, 리액트는 복잡하게 객체 내부를 다 비교할 필요없이 &quot;&lt;b&gt;주소 값이 다르네? 그럼 업데이트 해야지!&lt;/b&gt;&quot;하고 즉시 감지하여 렌더링을 결정하게 된다.&lt;/li&gt;
&lt;/ul&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;u&gt;&lt;b&gt;2. 예측 가능한 데이터 (사이드 이펙트 방지)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 컴포넌트가 하나의 객체를 공유하고 있을 때, 한 곳에서 데이터를 직접 수정(Mutation)해버리면 다른 컴포넌트의 데이터도 의도치 않게 같이 변해버리게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불변성을 지키면 : 기존 데이터는 그대로 보존되고 항상&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;3. 디버깅과 '시간 여행'기능 (Time Travel Debugging)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 생태계의 도구들 (Redux DevTools 등)은 상태가 바뀔 때마다 기록(snapshot)을 보관한다.&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;불변성을 지키면 : 과거의 상태 객체들이 각각 다른 주소값으로 메모리에 살아있고, &quot;이전 버튼&quot;을 눌렀을 때 과거 상태를 그대로 가져오기만 하면되서&amp;nbsp;&lt;b&gt;실행 취소(Undo/Redo)&lt;/b&gt; 기능이나&amp;nbsp;&lt;b&gt;타임머신 디버깅&lt;/b&gt;을 완벽하게 구현할 수 있게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&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;&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;&amp;nbsp;&lt;/p&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;a href=&quot;https://youtu.be/gER3iJZyd0A?si=w9yrlndk4lDKOzKg&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://youtu.be/gER3iJZyd0A?si=w9yrlndk4lDKOzKg&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=gER3iJZyd0A&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/ceRLPr/dJMb8T92zjR/AUnmglZCnNyc6n33N4G081/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[쌤즈 &amp;ndash; 인강] 리액트에 불변성이 필수인 이유 | 코딩 강의 | 리액트&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/gER3iJZyd0A&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드/React</category>
      <category>immer 동작 방식</category>
      <category>immutable</category>
      <category>mutable</category>
      <category>REACT</category>
      <category>react immer</category>
      <category>객체 복사</category>
      <category>리액트에서 불변성이 필요한 이유</category>
      <category>불변성</category>
      <category>올바른 state 업데이트 패턴</category>
      <category>원시 타입 vs 객체 타입</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/251</guid>
      <comments>https://kuh97.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%B6%88%EB%B3%80%EC%84%B1Immutability-%EB%8C%80%EC%B2%B4-%EC%99%9C-%EC%A7%80%EC%BC%9C%EC%95%BC-%ED%95%A0%EA%B9%8C#entry251comment</comments>
      <pubDate>Mon, 27 Apr 2026 20:30:46 +0900</pubDate>
    </item>
    <item>
      <title>NeetCode Maximum Subarray (JavaScript), 누적합, Greedy</title>
      <link>https://kuh97.tistory.com/entry/NeetCode-Maximum-Subarray-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9-Greedy</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://neetcode.io/problems/maximum-subarray/question&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://neetcode.io/problems/maximum-subarray/question&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Given an array of integers&amp;nbsp;nums, find the subarray with the largest sum and return the sum.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A&amp;nbsp;subarray&amp;nbsp;is a contiguous non-empty sequence of elements within an array.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [2,-3,4,-2,2,1,-1,4]

Output: 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation: The subarray [4,-2,2,1,-1,4] has the largest sum 8.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;Input: nums = [-1]

Output: -1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1 &amp;lt;= nums.length &amp;lt;= 1000-1000 &amp;lt;= nums[i] &amp;lt;= 1000&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;1 &amp;lt;= nums.length &amp;lt;= 1000&lt;/li&gt;
&lt;li&gt;1000 &amp;lt;= nums[i] &amp;lt;= 1000&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누적 합과 Greedy를 이용하여 문제를 해결&lt;/p&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;현재 index까지 최대 값으로 만들 수 있는 누적 합을 계산하도록 로직을 작성해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g. i번째를 계산하기 위해 i - 1번째 까지의 최대 누적 합에 현재 i번째를 더한 값이 i번째 보다 클 경우 i번째의 값을 i - 1 + i 를 합한 값으로 갱신해줘야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갱신이 끝났다면 answer에 최대 누적합을 갱신해주면 됨&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    /**
     * @param {number[]} nums
     * @return {number}
     */
    maxSubArray(nums) {
        let answer = nums[0];
        for (let i = 1; i &amp;lt; nums.length; i++) {
            if (nums[i - 1] + nums[i] &amp;gt; nums[i]) {
                nums[i] = nums[i - 1] + nums[i];
            }

            answer = Math.max(answer, nums[i]);
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Greedy문제 + 누적 합을 활용해서 문제를 접근해야 함.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>Greedy</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Maximum Subarray</category>
      <category>neetcode</category>
      <category>그리디</category>
      <category>누적 합</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/250</guid>
      <comments>https://kuh97.tistory.com/entry/NeetCode-Maximum-Subarray-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9-Greedy#entry250comment</comments>
      <pubDate>Fri, 10 Apr 2026 17:29:38 +0900</pubDate>
    </item>
    <item>
      <title>NeetCode Islands and Treasure (JavaScript), BFS</title>
      <link>https://kuh97.tistory.com/entry/NeetCode-Islands-and-Treasure-JavaScript-BFS</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://neetcode.io/problems/islands-and-treasure/question&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://neetcode.io/problems/islands-and-treasure/question&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;You are given a&amp;nbsp;m&amp;times;nm&amp;times;n&amp;nbsp;2D&amp;nbsp;grid&amp;nbsp;initialized with these three possible values:&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;1&amp;nbsp;- A water cell that&amp;nbsp;can not&amp;nbsp;be traversed.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0&amp;nbsp;- A treasure chest.&lt;/li&gt;
&lt;li&gt;INF&amp;nbsp;- A land cell that&amp;nbsp;can&amp;nbsp;be traversed. We use the integer&amp;nbsp;2^31 - 1 = 2147483647&amp;nbsp;to represent&amp;nbsp;INF.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Fill each land cell with the distance to its nearest treasure chest. If a land cell cannot reach a treasure chest then the value should remain&amp;nbsp;INF.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Assume the grid can only be traversed up, down, left, or right.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Modify the&amp;nbsp;grid&amp;nbsp;in-place.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: [
  [2147483647,-1,0,2147483647],
  [2147483647,2147483647,2147483647,-1],
  [2147483647,-1,2147483647,-1],
  [0,-1,2147483647,2147483647]
]

Output: [
  [3,-1,0,1],
  [2,2,1,-1],
  [1,-1,2,-1],
  [0,-1,3,4]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: [
  [0,-1],
  [2147483647,2147483647]
]

Output: [
  [0,-1],
  [1,2]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;m == grid.lengthn == grid[i].length1 &amp;lt;= m, n &amp;lt;= 100grid[i][j]&amp;nbsp;is one of&amp;nbsp;{-1, 0, 2147483647}&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;m == grid.length&lt;/li&gt;
&lt;li&gt;n == grid[i].length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= m, n &amp;lt;= 100&lt;/li&gt;
&lt;li&gt;grid[i][j]&amp;nbsp;is one of&amp;nbsp;{-1, 0, 2147483647}&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 위치에서 보물까지의 최소거리를 구하는 문제로 BFS를 활용하면 문제를 해결&lt;/p&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;각 좌표에서 보물까지의 최소 거리를 구하는 형태로 로직을 작성할 경우 O(N * N)의 시간 복잡도를 가진다. 결국 n이 커지게되면 시간 초과 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, bfs를 n번 반복하는게 아닌&amp;nbsp;&lt;b&gt;1번만 반복하도록&amp;nbsp;&lt;/b&gt;로직을 구성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번만 반복하도록 하기위해서 각 좌표에서 보물까지의 최소 거리를 구하는게 아닌, 각 보물 좌표에서 이동하면서 각 좌표에 최소 거리를 최신화 해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 보물 위치에서 단 한번의 BFS로 모든 좌표의 보물까지의 최소거리를 구할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    /**
     * @param {number[][]} grid
     */
    islandsAndTreasure(grid) {
        const cols = grid.length;
        const rows = grid[0].length;
        const dirs = [
            [-1, 0],
            [1, 0],
            [0, -1],
            [0, 1]
        ];
        const visited = new Set();
        const queue = [];

        for (let y = 0; y &amp;lt; cols; y++) {
            for (let x = 0; x &amp;lt; rows; x++) {
                if (grid[y][x] === 0) {
                    queue.push([y, x, 0]);
                    visited.add(`${y},${x}`);
                }
            }
        }

        while (queue.length &amp;gt; 0) {
            const [y, x, cnt] = queue.shift();
            grid[y][x] = cnt;

            for (const [dy, dx] of dirs) {
                const my = y + dy;
                const mx = x + dx;
                if (my &amp;gt;= 0 &amp;amp;&amp;amp; my &amp;lt; cols &amp;amp;&amp;amp; mx &amp;gt;= 0 &amp;amp;&amp;amp; mx &amp;lt; rows &amp;amp;&amp;amp; !visited.has(`${my},${mx}`) &amp;amp;&amp;amp; grid[my][mx] !== -1) {
                    visited.add(`${my},${mx}`);
                    queue.push([my, mx, cnt + 1]);
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS 형태의 문제이지만 BFS를 어떻게 활용할 건지에 대한 고민이 필요한 문제같다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>BFS</category>
      <category>Islands and Treasure</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>neetcode</category>
      <category>최소거리 구하기</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/249</guid>
      <comments>https://kuh97.tistory.com/entry/NeetCode-Islands-and-Treasure-JavaScript-BFS#entry249comment</comments>
      <pubDate>Thu, 9 Apr 2026 19:44:34 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Cheapest Flights Within K Stops (JavaScript), Dijkstra + BFS</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Cheapest-Flights-Within-K-Stops-JavaScript-Dijkstra-BFS</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/cheapest-flights-within-k-stops/description/?envType=problem-list-v2&amp;amp;envId=9id5lube&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/cheapest-flights-within-k-stops/description/?envType=problem-list-v2&amp;amp;envId=9id5lube&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;There are&amp;nbsp;n&amp;nbsp;cities connected by some number of flights. You are given an array&amp;nbsp;flights&amp;nbsp;where&amp;nbsp;flights[i] = [fromi, toi, pricei]&amp;nbsp;indicates that there is a flight from city&amp;nbsp;fromi&amp;nbsp;to city&amp;nbsp;toi&amp;nbsp;with cost&amp;nbsp;pricei.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You are also given three integers&amp;nbsp;src,&amp;nbsp;dst, and&amp;nbsp;k, return&amp;nbsp;&lt;b&gt;the cheapest price&lt;/b&gt;&amp;nbsp;from&amp;nbsp;src&amp;nbsp;to&amp;nbsp;dst&amp;nbsp;with at most&amp;nbsp;k&amp;nbsp;stops.&amp;nbsp;If there is no such route, return&amp;nbsp;**-1.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YVtlC/dJMcadO1jV9/hGdLyz7Jhdp7WzjpIsM7Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YVtlC/dJMcadO1jV9/hGdLyz7Jhdp7WzjpIsM7Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YVtlC/dJMcadO1jV9/hGdLyz7Jhdp7WzjpIsM7Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYVtlC%2FdJMcadO1jV9%2FhGdLyz7Jhdp7WzjpIsM7Tk%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;332&quot; height=&quot;392&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1
Output: 700
Explanation:
The graph is shown above.
The optimal path with at most 1 stop from city 0 to 3 is marked in red and has cost 100 + 600 = 700.
Note that the path through cities [0,1,2,3] is cheaper but is invalid because it uses 2 stops.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnqDxy/dJMcacCEwIY/Roiw6yJk5T558goW65OvY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnqDxy/dJMcacCEwIY/Roiw6yJk5T558goW65OvY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnqDxy/dJMcacCEwIY/Roiw6yJk5T558goW65OvY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnqDxy%2FdJMcacCEwIY%2FRoiw6yJk5T558goW65OvY1%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;332&quot; height=&quot;242&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 1
Output: 200
Explanation:
The graph is shown above.
The optimal path with at most 1 stop from city 0 to 2 is marked in red and has cost 100 + 100 = 200.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blOtcG/dJMcadO1jWa/pwofD3A9gWZ4plIgO1KkAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blOtcG/dJMcadO1jWa/pwofD3A9gWZ4plIgO1KkAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blOtcG/dJMcadO1jWa/pwofD3A9gWZ4plIgO1KkAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblOtcG%2FdJMcadO1jWa%2FpwofD3A9gWZ4plIgO1KkAK%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;332&quot; height=&quot;242&quot; data-origin-width=&quot;332&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 0
Output: 500
Explanation:
The graph is shown above.
The optimal path with no stops from city 0 to 2 is marked in red and has cost 500.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;2 &amp;lt;= n &amp;lt;= 100&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= flights.length &amp;lt;= (n * (n - 1) / 2)&lt;/li&gt;
&lt;li&gt;flights[i].length == 3&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= fromi, toi &amp;lt; n&lt;/li&gt;
&lt;li&gt;fromi != toi&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= pricei &amp;lt;= 104&lt;/li&gt;
&lt;li&gt;There will not be any multiple flights between two cities.&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= src, dst, k &amp;lt; n&lt;/li&gt;
&lt;li&gt;src != dst&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 지점에서 특정 지점으로 가는 최소 비용을 구하는 문제로 다익스트라를 떠올릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 k횟수 라는 제한이 있기 때문에 일반적인 다익스트라로는 구현이 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 다익스트라는&amp;nbsp;&lt;b&gt;'비용'&lt;/b&gt;적인 측면에서 최적화하기 때문에 횟수라는 제한이 추가가되면 일반적인 다익스트라 알고리즘으로 해결을 할 수 없다. -&amp;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;u&gt;&lt;b&gt;dijkstra + bfs를 활용하여 문제를 해결&lt;/b&gt;&lt;/u&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;graph : src -&amp;gt; dst로 가는 비용을 저장하는 배열&lt;/li&gt;
&lt;li&gt;distance : src로 부터 각 지점까지의 최소 비용을 저장하는 배열
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 src는 0으로 초기화 (자기 자신이기 때문에)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;k라는 횟수를 파악하기 위해 queue 배열을 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[src, cost, stops] : 시작 지점, 비용, 횟수를 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;각 지점에서 최소 비용을 계산하는 로직은 dijkstra형태로 진행이 되고, 최소 횟수를 통해 검사하는 노드는 bfs 형태로 진행을 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, queue의 배열의 길이가 0보다 크다면 계속 해서 검사&lt;/li&gt;
&lt;li&gt;최소 비용은 일반적인 dijkstra에서 각 노드를 거쳐 최소비용이 되는지 검사하는 로직을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} n
 * @param {number[][]} flights
 * @param {number} src
 * @param {number} dst
 * @param {number} k
 * @return {number}
 */
var findCheapestPrice = function(n, flights, src, dst, k) {
    const graph = Array.from({length: n}, () =&amp;gt; []);
    flights.forEach(([s, d, c]) =&amp;gt; graph[s].push([d, c]));

    const distance = Array(n).fill(Infinity);
    distance[src] = 0;

    // src, cost, stops
    const queue = [[src, 0, 0]];

    while (queue.length &amp;gt; 0) {
        const [from, cost, stops] = queue.shift();

        for (const [nei, c] of graph[from]) {
            const totalCost = cost + c;
            if (totalCost &amp;lt; distance[nei] &amp;amp;&amp;amp; stops &amp;lt;= k) {
                distance[nei] = totalCost;
                queue.push([nei, distance[nei], stops + 1]);
            }
        }
    }

    return distance[dst] === Infinity ? -1 : distance[dst];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 일반적인 '비용'에 중점을 둔 dijkstra형태가 아닌, 횟수가 포함되어 BFS 형태를 응용해서 풀어야 하는 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 동작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 최소 비용 구하기 : dijkstra&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 탐색 : bfs&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>BFS</category>
      <category>Cheapest Flights Within K Stops</category>
      <category>Dijkstra</category>
      <category>dijkstra + bfs</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Queue</category>
      <category>너비우선탐색</category>
      <category>다익스트라</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/248</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Cheapest-Flights-Within-K-Stops-JavaScript-Dijkstra-BFS#entry248comment</comments>
      <pubDate>Sat, 4 Apr 2026 23:54:35 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Perfect Squares (JavaScript), BFS, DP</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Perfect-Squares-JavaScript-BFS-DP</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/perfect-squares/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/perfect-squares/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an integer&amp;nbsp;n, return&amp;nbsp;the least number of perfect square numbers that sum to&amp;nbsp;n.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A&amp;nbsp;&lt;b&gt;perfect square&lt;/b&gt;&amp;nbsp;is an integer that is the square of an integer; in other words, it is the product of some integer with itself. For example,&amp;nbsp;1,&amp;nbsp;4,&amp;nbsp;9, and&amp;nbsp;16&amp;nbsp;are perfect squares while&amp;nbsp;3&amp;nbsp;and&amp;nbsp;11&amp;nbsp;are not.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: n = 12
Output: 3
Explanation: 12 = 4 + 4 + 4.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= n &amp;lt;= 104&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. BFS 풀이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[현재 값, 카운트]를 기준으로 제곱 값을 더해가면서 n과 같은 경우를 찾는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 같은 숫자가 중복해서 발생할 수 있으므로 visited를 설정해 이미 검사한 숫자는 다시 검사하지 않도록 해야 함.&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} n
 * @return {number}
 */
var numSquares = function(n) {
    const queue = [[0, 0]];
    const visited = new Set(); // 방문한 숫자는 검사하지 않도록
    visited.add(0);

    while (queue.length &amp;gt; 0) {
        const [sum, count] = queue.shift();

        for (let i = 1; ; i++) {
            const nextSum = sum + i * i;
            if (nextSum === n) return count + 1;
            if (nextSum &amp;gt; n) break;

            if (!visited.has(nextSum)) {
                visited.add(nextSum);
                queue.push([nextSum, count + 1]);
            }
        }
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. DP 풀이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i 번째 : 숫자 i를 만들기 위한 최소 횟수를 저장&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;u&gt;&lt;b&gt;dp[i - j * j] + 1 (j * j &amp;lt;= i)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i에서 어떤 제곱수(j * j)를 뺏을 때 최솟 값 중에서 1을 더한 값을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g. i가 4이고, j 가 1인 경우를 해석하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[4] = 4이고 dp[4 - 1*1] = dp[3] = 3이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 4번째에서 1번째를 뺀 3숫자를 만드는 경우의 수에서 1을 더한 값이 4를 만들 수 있는 경우의 수이다. (3 + 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 j * j가 i보다 작거나 같을 때까지 반복하며 최소 경우의 수를 구해서 i번째에 할당한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775210476139&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} n
 * @return {number}
 */
var numSquares = function(n) {
    const dp = new Array(n + 1).fill(0);
    for (let i = 0; i &amp;lt;= n; i++) {
        dp[i] = i;
    }

    for (let i = 1; i &amp;lt;= n; i++) {
        for (let j = 1; j * j &amp;lt;= i; j++) {
            dp[i] = Math.min(dp[i], dp[i - j * j] + 1);
        }
    }

    return dp[n];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS와 DP형태로 문제를 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 경로의 수를 구하는 문제로 DP보다 BFS가 더 효율적으로 해결이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 BFS같은 경우 최소 경로를 만나면 즉시 종료를 시키기 때문에 어떠한 경우에서는 DP보다 효율적으로 동작할 수 있다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>BFS</category>
      <category>dp</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Perfect Squares</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/247</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Perfect-Squares-JavaScript-BFS-DP#entry247comment</comments>
      <pubDate>Fri, 3 Apr 2026 19:07:36 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Count of Interesting Subarrays (JavaScript), 누적합, 해시맵</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Count-of-Interesting-Subarrays-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9-%ED%95%B4%EC%8B%9C%EB%A7%B5</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아래 문제를 먼저 보고 오시는걸 추천드립니다!&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kuh97.tistory.com/entry/LeetCode-Subarray-Sum-Equals-K-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kuh97.tistory.com/entry/LeetCode-Subarray-Sum-Equals-K-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9&lt;/a&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/count-of-interesting-subarrays/description/?envType=daily-question&amp;amp;envId=2025-04-25&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/count-of-interesting-subarrays/description/?envType=daily-question&amp;amp;envId=2025-04-25&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You are given a&amp;nbsp;&lt;b&gt;0-indexed&lt;/b&gt;&amp;nbsp;integer array&amp;nbsp;nums, an integer&amp;nbsp;modulo, and an integer&amp;nbsp;k.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Your task is to find the count of subarrays that are&amp;nbsp;&lt;b&gt;interesting&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A&amp;nbsp;&lt;b&gt;subarray&lt;/b&gt;&amp;nbsp;nums[l..r]&amp;nbsp;is&amp;nbsp;&lt;b&gt;interesting&lt;/b&gt;&amp;nbsp;if the following condition holds:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Let&amp;nbsp;cnt&amp;nbsp;be the number of indices&amp;nbsp;i&amp;nbsp;in the range&amp;nbsp;[l, r]&amp;nbsp;such that&amp;nbsp;nums[i] % modulo == k. Then,&amp;nbsp;cnt % modulo == k.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return&amp;nbsp;an integer denoting the count of interesting subarrays.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Note:&lt;/b&gt;&amp;nbsp;A subarray is&amp;nbsp;a contiguous non-empty sequence of elements within an array.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [3,2,4], modulo = 2, k = 1
Output: 3
Explanation: In this example the interesting subarrays are:
The subarray nums[0..0] which is [3].
- There is only one index, i = 0, in the range [0, 0] that satisfies nums[i] % modulo == k.
- Hence, cnt = 1 and cnt % modulo == k.
The subarray nums[0..1] which is [3,2].
- There is only one index, i = 0, in the range [0, 1] that satisfies nums[i] % modulo == k.
- Hence, cnt = 1 and cnt % modulo == k.
The subarray nums[0..2] which is [3,2,4].
- There is only one index, i = 0, in the range [0, 2] that satisfies nums[i] % modulo == k.
- Hence, cnt = 1 and cnt % modulo == k.
It can be shown that there are no other interesting subarrays. So, the answer is 3.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [3,1,9,6], modulo = 3, k = 0
Output: 2
Explanation:In this example the interesting subarrays are:
The subarray nums[0..3] which is [3,1,9,6].
- There are three indices, i = 0, 2, 3, in the range [0, 3] that satisfy nums[i] % modulo == k.
- Hence, cnt = 3 and cnt % modulo == k.
The subarray nums[1..1] which is [1].
- There is no index, i, in the range [1, 1] that satisfies nums[i] % modulo == k.
- Hence, cnt = 0 and cnt % modulo == k.
It can be shown that there are no other interesting subarrays. So, the answer is 2.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= nums.length &amp;lt;= 105&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= nums[i] &amp;lt;= 109&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= modulo &amp;lt;= 109&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= k &amp;lt; modulo&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식 (누적합 + hash map)&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;구간 (i, j)에 대한 누적합은 sum[j] - sum[i] 로 구할 수 있다.&lt;/li&gt;
&lt;li&gt;이전 누적합을 map으로 관리하도록 하는 방법을 찾아야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번에 대한 이전 누적합은 관리하는 방법은 아래를 통해 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(i, j)에 대한 누적합은 sum[j] - sum[i]이고 구하려고하는 것은 &lt;b&gt;(sum[j] - sum[i]) % modulo == k&lt;/b&gt; 인 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 변형하면 &lt;b&gt;sum[i] % modulo = sum[j] % modulo - k&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;sum[i] % modulo 는 이전 값에 대한 누적 합을 의미하고,&lt;/li&gt;
&lt;li&gt;sum[j] % modulo 는 현재 값에 대한 누적 합을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이전 누적 합을 modulo로 나눈 값과 현재 누적합을 modulo로 나눈 값에서 k를 뺀 것과 일치하는 것을 찾으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 즉, 이전 누적 합을 modulo로 나눈 값을 map에 저장해두면 O(1)의 시간 복잡도로 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &lt;b&gt;sum[j] % modulo - k&lt;/b&gt;에 문제가 있는데, 이 값이 음수가 될 수 있다는 것이다. 이를 방지하기위해 + modulo를 더한 값에 modulo를 나눈 값을 사용하도록 하면 음수가 나오는 문제를 방지할 수 있다.&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;sum[i] % modulo = (sum[j] % modulo - k + modulo) % modulo&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @param {number} modulo
 * @param {number} k
 * @return {number}
 */
var countInterestingSubarrays = function(nums, modulo, k) {
    let answer = 0;
    let prefixSum = 0;

    const map = new Map();
    map.set(0, 1);

    for (let num of nums) {
        // 현재 숫자가 조건(nums[i] % modulo === k)에 만족하면 prefixSum 1증가
        if (num % modulo === k) {
            prefixSum++;
        }

		// 현재 누적 합에서 modulo를 나눈 값
        let currentModValue = prefixSum % modulo;
        let target = (currentModValue - k + modulo) % modulo;

        if (map.has(target)) {
            answer += map.get(target);
        }

        map.set(currentModValue, (map.get(currentModValue) || 0) + 1);
    }

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누적 합을 어떠한 방식으로 관리를 할 것인가에 대한 부분과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누적 합을 어떻게 변형을 시킬 수 있는지를 만드는 게 중요한 포인트인 것 같다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>Count of Interesting Subarrays</category>
      <category>hash map</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>map</category>
      <category>구간 합</category>
      <category>누적 합</category>
      <category>해시맵</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/246</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Count-of-Interesting-Subarrays-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9-%ED%95%B4%EC%8B%9C%EB%A7%B5#entry246comment</comments>
      <pubDate>Fri, 3 Apr 2026 17:39:04 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Subarray Sum Equals K (JavaScript), 누적합</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Subarray-Sum-Equals-K-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/subarray-sum-equals-k/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/subarray-sum-equals-k/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an array of integers&amp;nbsp;nums&amp;nbsp;and an integer&amp;nbsp;k, return&amp;nbsp;the total number of subarrays whose sum equals to&amp;nbsp;k.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A subarray is a contiguous&amp;nbsp;&lt;b&gt;non-empty&lt;/b&gt;&amp;nbsp;sequence of elements within an array.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [1,1,1], k = 2
Output: 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [1,2,3], k = 3
Output: 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= nums.length &amp;lt;= 2 * 104&lt;/li&gt;
&lt;li&gt;1000 &amp;lt;= nums[i] &amp;lt;= 1000&lt;/li&gt;
&lt;li&gt;107 &amp;lt;= k &amp;lt;= 107&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제는 누적합 + hash를 이용하여 해결 (이외의 알고리즘은 시간 초과 오류가 발생)&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;문제는 구간의 누적합이 k와 같은 경우의 수를 찾는 문제이다.&lt;/p&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;[i, j]의 구간합이 k와 같다는 것을 식으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prefixSum[j] : 현재(j)까지의 누적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prefixSum[i] : 이전(i)까지의 누적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, [i, j]의 누적합은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prefixSum[j] - prefixSum[i] = k;&lt;/b&gt; 이를 현재를 기준으로 변형하게 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;prefixSum[i] = prefixSum[j] - k;&lt;/b&gt; 를 의미하고, 이는 현재 누적합에서 k를 뺀 값이 이전 누적합과 같다는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결국, 이전에 저장해둔 누적 값이 이미 존재할 경우 O(1)로 빠르게 접근해서 찾을 수 있다는 점을 이용해야 한다.&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;map.set(0, 1)을 해주는 이유는?&amp;nbsp;&lt;b&gt;배열의 맨 첫 번째 요소부터 시자하는 부분 배열&lt;/b&gt;을 표현하기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 아무것도 안 더했을 때 합은 0이고 이는 1개 존재한다. 라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 설정이 없다면 발생하는 문제상황에 대해 간단하게 살펴보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 nums = [3, 2, 1]이고 k = 3인 상황을 가정해보자.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;첫 번째 숫자 3을 만납니다. sum = 3이 된다.&lt;/li&gt;
&lt;li&gt;찾는 target은 sum - k, 즉 3 - 3 = 0이다.&lt;/li&gt;
&lt;li&gt;그런데 이전 기록에 0이 기록되어 있지 않다면?
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;분명 [3] 자체로 정답인데, 프로그램은 &lt;b data-index-in-node=&quot;25&quot; data-path-to-node=&quot;5,2,1,0,0&quot;&gt;&quot;이전에 합이 0이었던 적이 없네?&quot;&lt;/b&gt; 하고 정답이 아니게 되는 오류가 발생하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var subarraySum = function(nums, k) {
    const map = new Map();
    map.set(0, 1);

    let sum = 0;
    let answer = 0;

    for (let num of nums) {
        sum += num;
        const target = sum - k;

        if (map.has(target)) {
            answer += map.get(target);
        }

        map.set(sum, (map.get(sum) || 0) + 1);
    }

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투 포인터나 이중 루프로 접근하면 쉽지만, 배열이 커짐에 따라 시간 복잡도가 급격하게 커져 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 누적 합의 상태를 맵에 기록하여 사용하면 한 번의 반복(O(N))에도 정답을 찾을 수 있다.&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;결국 이 문제의 핵심은 구간의 합을 구하는 방식에서 현재 누적 합 - k가 이전 누적 합과 같다는 식을 찾아내는 것이 중요하고, 이 정보를 찾아야 해시 맵을 통해 빠르게 정답을 찾을 수 있다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>LeetCode 560</category>
      <category>Subarray Sum Equals K</category>
      <category>구간합</category>
      <category>누적합</category>
      <category>해시맵</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/245</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Subarray-Sum-Equals-K-JavaScript-%EB%88%84%EC%A0%81%ED%95%A9#entry245comment</comments>
      <pubDate>Thu, 2 Apr 2026 19:12:18 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Course Schedule || (JavaScript), 위상정렬</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Course-Schedule-JavaScript-%EC%9C%84%EC%83%81%EC%A0%95%EB%A0%AC</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/course-schedule-ii/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/course-schedule-ii/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;There are a total of&amp;nbsp;numCourses&amp;nbsp;courses you have to take, labeled from&amp;nbsp;0&amp;nbsp;to&amp;nbsp;numCourses - 1. You are given an array&amp;nbsp;prerequisites&amp;nbsp;where&amp;nbsp;prerequisites[i] = [ai, bi]&amp;nbsp;indicates that you&amp;nbsp;&lt;b&gt;must&lt;/b&gt;&amp;nbsp;take course&amp;nbsp;bi&amp;nbsp;first if you want to take course&amp;nbsp;ai.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;For example, the pair&amp;nbsp;[0, 1], indicates that to take course&amp;nbsp;0&amp;nbsp;you have to first take course&amp;nbsp;1.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return&amp;nbsp;the ordering of courses you should take to finish all courses. If there are many valid answers, return&amp;nbsp;&lt;b&gt;any&lt;/b&gt;&amp;nbsp;of them. If it is impossible to finish all courses, return&amp;nbsp;&lt;b&gt;an empty array&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: numCourses = 2, prerequisites = [[1,0]]
Output: [0,1]
Explanation: There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1].
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output: [0,2,1,3]
Explanation: There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0.
So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3].
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: numCourses = 1, prerequisites = []
Output: [0]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제는 위상정렬 알고리즘을 안다면 쉽게 해결할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위상정렬이란?&lt;br /&gt;&lt;/span&gt;순서가 정해진 작업을 순서대로 수행할 때, 그 순서를 결정해주는 알고리즘&lt;br /&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;adj 배열&lt;/b&gt; : [a, b]가 있을 때, b를 하기위해서는 a가 선행되어야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;b -&amp;gt; [a] 형태로 값을 관리하는 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;inDegree 배열&lt;/b&gt; : [a, b]가 있을 때, a가 수행되기 위해 이전에 선행되어야 하는 작업의 갯수를 의미
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a : 3 형태로 a가 수행되려면 몇 개의 작업이 수행되어야 하는지를 count로 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;queue 배열&lt;/b&gt; : 현재 작업을 수행할 수 있는 목록의 배열&lt;/li&gt;
&lt;li&gt;&lt;b&gt;order 배열&amp;nbsp;&lt;/b&gt;: 최종적으로 순차대로 수행된 작업의 목록을 저장하는 배열&lt;/li&gt;
&lt;/ol&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;adj, inDegree배열을 채우기&lt;/li&gt;
&lt;li&gt;현재 inDegree에서 바로 수행할 수 있는 값(값이 0이라면 해당 작업은 바로 수행가능)을 queue배열에 추가&lt;/li&gt;
&lt;li&gt;queue 배열의 길이가 남아있다면 아래 로직을 반복적으로 수행
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;queue에서 하나를 뺀다.(shift)&lt;/li&gt;
&lt;li&gt;해당 작업은 완료된 작업으로 order(정답)에 추가&lt;/li&gt;
&lt;li&gt;뺀 작업이후 수행할 수 있는 목록의 값의 count를 1씩 감소시키면서, count가 0이라면 queue에 추가해준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;최종적으로 order에 쌓인 작업의 갯수가 numCourses와 같다면 정상적으로 작업이 수행된 것을 의미
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 코스를 들을 수 없다면 빈 배열([])을 리턴&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {number[]}
 */
var findOrder = function(numCourses, prerequisites) {
    const adj = {};
    const inDegree = Array.from({length: numCourses}, () =&amp;gt; 0);

    for (let i = 0; i &amp;lt; prerequisites.length; i++) {
        const [course, pre] = prerequisites[i];
        if (!adj[pre]) {
            adj[pre] = [course];
        } else {
            adj[pre].push(course);
        }
        inDegree[course]++;
    }

    const queue = [];
    for (let i = 0; i &amp;lt; numCourses; i++) {
        if (inDegree[i] === 0) {
            queue.push(i);
        }
    }

    const order = [];
    while (queue.length &amp;gt; 0) {
        const c = queue.shift();
        order.push(c);

        if (adj[c]) {
            for (let next of adj[c]) {
                inDegree[next]--;
                if (inDegree[next] === 0) {
                    queue.push(next);
                }
            }
        }
    }

    return order.length === numCourses ? order : [];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위상정렬의 핵심은 adj, inDegree배열과 queue를 활용하여 순차대로 작업을 실행할 수 있는지 여부를 판단할 수 있어야 한다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>adj</category>
      <category>Course Schedule ||</category>
      <category>indegree</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>nodejs</category>
      <category>위상정렬</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/244</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Course-Schedule-JavaScript-%EC%9C%84%EC%83%81%EC%A0%95%EB%A0%AC#entry244comment</comments>
      <pubDate>Thu, 2 Apr 2026 17:48:37 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Sort Colors (JavaSciprt), three pointer</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Sort-Colors-JavaSciprt-three-pointer</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/sort-colors/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/sort-colors/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an array&amp;nbsp;nums&amp;nbsp;with&amp;nbsp;n&amp;nbsp;objects colored red, white, or blue, sort them&amp;nbsp;&lt;a href=&quot;https://en.wikipedia.org/wiki/In-place_algorithm&quot;&gt;**in-place&lt;/a&gt;**&amp;nbsp;so that objects of the same color are adjacent, with the colors in the order red, white, and blue.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;We will use the integers&amp;nbsp;0,&amp;nbsp;1, and&amp;nbsp;2&amp;nbsp;to represent the color red, white, and blue, respectively.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You must solve this problem without using the library's sort function.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [2,0,2,1,1,0]
Output: [0,0,1,1,2,2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [2,0,1]
Output: [0,1,2]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;n == nums.length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= n &amp;lt;= 300&lt;/li&gt;
&lt;li&gt;nums[i]&amp;nbsp;is either&amp;nbsp;0,&amp;nbsp;1, or&amp;nbsp;2.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Follow up:&lt;/b&gt;&amp;nbsp;Could you come up with a one-pass algorithm using only&amp;nbsp;constant extra space?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;0, 1, 2를 정렬하기 위해 3개의 포인터 start, mid, end를 설정&lt;/li&gt;
&lt;li&gt;start -&amp;gt; 0, mid -&amp;gt; 1, end -&amp;gt; 2를 나타냄&lt;/li&gt;
&lt;li&gt;정렬하기 위한 로직 (mid &amp;lt;= end인 경우 아래 로직을 따르면 정렬이 됨)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mid가 2인 경우 end와 교체, end는 1 감소&lt;/li&gt;
&lt;li&gt;mid가 0인 경우 start와 교체, start, mid 1 증가&lt;/li&gt;
&lt;li&gt;mid가 1인 경우 mid 1 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var sortColors = function(nums) {
    let [start, mid, end] = [0, 0, nums.length - 1];

    while (mid &amp;lt;= end) {
        if (nums[mid] === 2) {
            [nums[mid], nums[end]] = [nums[end], nums[mid]];
            end--;
        } else if (nums[mid] === 0) {
            [nums[mid], nums[start]] = [nums[start], nums[mid]];
            start++;
            mid++;
        } else {
            mid++;
        }
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 기준점 (start, mid, end 포인터)를 이용해 0, 1, 2를 구분하는 방식으로 해결&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Sort Colors</category>
      <category>three pointer</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/243</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Sort-Colors-JavaSciprt-three-pointer#entry243comment</comments>
      <pubDate>Thu, 26 Mar 2026 19:04:16 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Edit Distance (JavaScript), DP</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Edit-Distance-JavaScript-DP</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/edit-distance/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/edit-distance/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given two strings&amp;nbsp;word1&amp;nbsp;and&amp;nbsp;word2, return&amp;nbsp;the minimum number of operations required to convert&amp;nbsp;word1&amp;nbsp;to&amp;nbsp;word2.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You have the following three operations permitted on a word:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Insert a character&lt;/li&gt;
&lt;li&gt;Delete a character&lt;/li&gt;
&lt;li&gt;Replace a character&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;Input: word1 = &quot;horse&quot;, word2 = &quot;ros&quot;
Output: 3
Explanation:
horse -&amp;gt; rorse (replace 'h' with 'r')
rorse -&amp;gt; rose (remove 'r')
rose -&amp;gt; ros (remove 'e')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;Input: word1 = &quot;intention&quot;, word2 = &quot;execution&quot;
Output: 5
Explanation:
intention -&amp;gt; inention (remove 't')
inention -&amp;gt; enention (replace 'i' with 'e')
enention -&amp;gt; exention (replace 'n' with 'x')
exention -&amp;gt; exection (replace 'n' with 'c')
exection -&amp;gt; execution (insert 'u')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;0 &amp;lt;= word1.length, word2.length &amp;lt;= 500&lt;/li&gt;
&lt;li&gt;word1&amp;nbsp;and&amp;nbsp;word2&amp;nbsp;consist of lowercase English letters.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식 (DP를 이용해 풀이)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;word1, word2의 길이 + 1 만큼의 2차원 배열을 생성 (&lt;b&gt;&quot;&quot;인 경우도 고려해 + 1한 값을 배열로 생성&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;u&gt;i번째 (word1기준) 까지의 부분 문자열로 j번째 (word2기준) 까지 부분 문자열로 만들 수 있는 경우의 수 구하기&lt;/u&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(i, j) = word1의 i 번째까지로 word2의 j 번째까지 만드는 최소의 경우의 수를 의미&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dp[i - 1][j] : Delete, 삭제&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;word1의 i - 1번째까지가 j 번째와 같다면 i번째는 쓸모없으니 제거해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dp[i - 1][j - 1] : Replace, 교체&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;word1의 i - 1번째가 word2의 j - 1번째와 같을 때 다음 문자는 word2에 맞게 교체되어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dp[i][j - 1] : Insert, 삽입&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;word1의 i번째까지가 word2의 j - 1번째까지와 같다면 word2의 j번째 문자를 추가해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 119px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;(i, j)&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;&quot;&quot;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;r&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;o&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;&quot;&quot;&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;h&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;o&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;r&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;s&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;e&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 20%; text-align: center; height: 17px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;3&lt;/span&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;[ 좀 더 자세한 설명, (1, 1)에서 최소 비용 구하는 과정 ]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/woy8Q/dJMcagEWGWL/5aNnWk1cuCELZ63OQxx1Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/woy8Q/dJMcagEWGWL/5aNnWk1cuCELZ63OQxx1Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/woy8Q/dJMcagEWGWL/5aNnWk1cuCELZ63OQxx1Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwoy8Q%2FdJMcagEWGWL%2F5aNnWk1cuCELZ63OQxx1Ak%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;1612&quot; height=&quot;838&quot; data-origin-width=&quot;1612&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;/figure&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;결국 (i, j)를 구하는 방식은 다음과 같이 이해할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 64.3014%; height: 114px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignCenter&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 48px;&quot;&gt;
&lt;td style=&quot;width: 34.2676%; height: 48px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;( i - 1, j - 1 )&lt;br /&gt;교체&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.0347%; height: 48px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;( i - 1, j )&lt;br /&gt;삭제&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 66px;&quot;&gt;
&lt;td style=&quot;width: 34.2676%; height: 66px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;( i, j - 1 )&lt;br /&gt;삽입&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.0347%; height: 66px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;( i, j )&lt;br /&gt;현재 위치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 현재 위치는 교체된 값, 삭제된 값, 삽입된 값에 + 1(현재 j번째의 word2 문자를 더하기)을 한 값 중에서 최소 값을 구하면 된다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g. 삽입의 경우 글로 이해를 해본다면, word1의 i 위치까지의 부분 문자열과 word2의 j 위치까지의 부분 문자열이 같아야 하고, word2의 j가 추가가 되어야 한다면, word1은 i번째가 아닌 i - 1번째에 j를 추가해야 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {string} word1
 * @param {string} word2
 * @return {number}
 */
var minDistance = function(word1, word2) {
    const [rows, cols] = [word1.length, word2.length];
    const dp = Array.from({length: rows + 1}, () =&amp;gt; Array.from({length: cols + 1}, () =&amp;gt; 0));

    for (let i = 0; i &amp;lt;= rows; i++) {
        dp[i][0] = i;
    }
    for (let j = 0; j &amp;lt;= cols; j++) {
        dp[0][j] = j;
    }

    for (let i = 1; i &amp;lt;= rows; i++) {
        for (let j = 1; j &amp;lt;= cols; j++) {
            // 비교하는 두 문자가 같다면 대각선 위 (변경 x)
            if (word1[i - 1] === word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(
                    dp[i - 1][j], // 삭제
                    dp[i - 1][j - 1], // 교체
                    dp[i][j - 1] // 삽입
                ) + 1;
            }
        }
    }

    return dp[rows][cols];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bfs와 같은 형태로 문제를 해결할 순 있지만, 문자의 길이가 길어지면 시간 오류가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, DP와 같은 알고리즘으로 접근을 해서 해결을 해야한다.&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;위 문제도 DP를 이용해 접근을 해야 하지만, 문제를 이해하는 과정이 생각보다 쉽지 않다고 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 2차원 배열의 특정 위치가 삽입, 삭제, 교체가 되는지 이해를 하는게 이 문제를 해결할 수 있는 방법인 것 같다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6jvSfC2-JDo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=6jvSfC2-JDo&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=6jvSfC2-JDo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bwwL5T/dJMb87NV8kB/x63TpEF3EOOVwbJKn71K1K/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cjs67T/dJMb9dHnOLC/Ta3LVlqOcAI4CpDaHkxbx0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/b9t0Dq/dJMb9frFaSi/xZEUOHjDH44irbXKg5ih4k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[리트코드 해설] #72 Edit Distance (C++)&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/6jvSfC2-JDo&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>BFS</category>
      <category>dp</category>
      <category>Dynamic Programming</category>
      <category>Edit Distance</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>다이나믹 프로그래밍</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/242</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Edit-Distance-JavaScript-DP#entry242comment</comments>
      <pubDate>Thu, 26 Mar 2026 18:40:16 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Unique Paths ll (JavaScript), DP</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Unique-Paths-ll-JavaScript-DP</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/unique-paths-ii/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/unique-paths-ii/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You are given an&amp;nbsp;m x n&amp;nbsp;integer array&amp;nbsp;grid. There is a robot initially located at the&amp;nbsp;&lt;b&gt;top-left corner&lt;/b&gt;&amp;nbsp;(i.e.,&amp;nbsp;grid[0][0]). The robot tries to move to the&amp;nbsp;&lt;b&gt;bottom-right corner&lt;/b&gt;&amp;nbsp;(i.e.,&amp;nbsp;grid[m - 1][n - 1]). The robot can only move either down or right at any point in time.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;An obstacle and space are marked as&amp;nbsp;1&amp;nbsp;or&amp;nbsp;0&amp;nbsp;respectively in&amp;nbsp;grid. A path that the robot takes cannot include&amp;nbsp;&lt;b&gt;any&lt;/b&gt;&amp;nbsp;square that is an obstacle.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return&amp;nbsp;the number of possible unique paths that the robot can take to reach the bottom-right corner.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The testcases are generated so that the answer will be less than or equal to&amp;nbsp;2 * 109.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;242&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rpoy7/dJMcajnZQM5/8IQIHtlSCHBgkBs4Xn3W0k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rpoy7/dJMcajnZQM5/8IQIHtlSCHBgkBs4Xn3W0k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rpoy7/dJMcajnZQM5/8IQIHtlSCHBgkBs4Xn3W0k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frpoy7%2FdJMcajnZQM5%2F8IQIHtlSCHBgkBs4Xn3W0k%2Fimg.jpg&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;242&quot; height=&quot;242&quot; data-origin-width=&quot;242&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
Output: 2
Explanation: There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:
1. Right -&amp;gt; Right -&amp;gt; Down -&amp;gt; Down
2. Down -&amp;gt; Down -&amp;gt; Right -&amp;gt; Right
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;162&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cROJB1/dJMcabp3Ziz/fPPEz11WvlyozAiDxb0Zt0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cROJB1/dJMcabp3Ziz/fPPEz11WvlyozAiDxb0Zt0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cROJB1/dJMcabp3Ziz/fPPEz11WvlyozAiDxb0Zt0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcROJB1%2FdJMcabp3Ziz%2FfPPEz11WvlyozAiDxb0Zt0%2Fimg.jpg&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;162&quot; height=&quot;162&quot; data-origin-width=&quot;162&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: obstacleGrid = [[0,1],[0,0]]
Output: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;m == obstacleGrid.length&lt;/li&gt;
&lt;li&gt;n == obstacleGrid[i].length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= m, n &amp;lt;= 100&lt;/li&gt;
&lt;li&gt;obstacleGrid[i][j]&amp;nbsp;is&amp;nbsp;0&amp;nbsp;or&amp;nbsp;1.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. DFS 로 접근 (실패, 시간 초과)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- (0, 0)부터 시작해 배열의 끝에 도달했을 경우 answer을 1씩 증가 시켜주도록 로직을 작성 (dfs)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열의 길이가 길어지면 시간 초과 오류가 발생&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    let answer = 0;

    if (obstacleGrid[0][0] === 1) {
        return 0;
    }

    let move = [[0, 1], [1, 0]];

    const dfs = (x, y) =&amp;gt; {
        if (x === obstacleGrid[0].length - 1 &amp;amp;&amp;amp; y === obstacleGrid.length - 1) {
            answer++;
            return;
        }

        for (let i = 0; i &amp;lt; move.length; i++) {
            const mx = x + move[i][0];
            const my = y + move[i][1];

            if (mx &amp;lt; obstacleGrid[0].length &amp;amp;&amp;amp; my &amp;lt; obstacleGrid.length &amp;amp;&amp;amp; obstacleGrid[my][mx] === 0) {
                dfs(mx, my);
            }
        }
    }

    dfs(0, 0);
    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. DP로 접근 (성공)&lt;/h3&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;b&gt;왼쪽, 위&lt;/b&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;- 단, 1씩 더하게되면 장애물이 1을 나타내므로 로직에 잘못된 판단이 될 수 있어. 경우의 수는 2씩 더하고, 최종적으로 2를 나눈 값이 끝지점에 도달할 수 있는 모든 경우의 수를 의미한다.&lt;/p&gt;
&lt;pre id=&quot;code_1774334377877&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    if (obstacleGrid[0][0] === 1) {
        return 0;
    }
    
    let before = [[-1, 0], [0, -1]]; // 왼쪽, 위

    for (let y = 0; y &amp;lt; obstacleGrid.length; y++) {
        for (let x = 0; x &amp;lt; obstacleGrid[0].length; x++) {
            if (x === 0 &amp;amp;&amp;amp; y === 0) {
                obstacleGrid[0][0] = 2;
                continue;
            }

            if (obstacleGrid[y][x] === 1) {
                continue;
            }

            for (let i = 0; i &amp;lt; before.length; i++) {
                const bx = x + before[i][0];
                const by = y + before[i][1];

                if (bx &amp;gt;= 0 &amp;amp;&amp;amp; by &amp;gt;= 0 &amp;amp;&amp;amp; obstacleGrid[by][bx] !== 1) {
                    obstacleGrid[y][x] += obstacleGrid[by][bx];
                }
            }
        }
    }

    return Math.floor(obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1] / 2);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. DP 개선 (성공)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2번째와 유사하게 특정 위치의 경우의 수는 왼쪽, 위쪽의 경우를 더한 값을 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- for문 안에 반복문 제거하여 더 깔끔하게 로직 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 반복문 설명&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;x, y =&amp;gt;&amp;nbsp; 0 인 경우 처음 위치를 나타내므로 1로 치환&lt;/li&gt;
&lt;li&gt;특정 위치가 1인 경우 장애물이므로 0으로 치환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0으로 치환하는 이유는 다음 값에서 해당 위치를 읽어 더해야 하기 때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;y가 0인 경우 -&amp;gt; x기준 왼쪽에서 오는 경로만 계산하면 됨&lt;/li&gt;
&lt;li&gt;x가 0인 경우 -&amp;gt; y기준 위쪽에서 오는 경로만 계산하면 됨&lt;/li&gt;
&lt;li&gt;(3, 4)가 아닌 경우 -&amp;gt; 왼쪽, 위에서 오는 경로를 합해주면 됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최종적으로 배열의 끝을 리턴해주면 됨.&lt;/p&gt;
&lt;pre id=&quot;code_1774334509094&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
var uniquePathsWithObstacles = function(obstacleGrid) {
    const [rows, cols] = [obstacleGrid[0].length, obstacleGrid.length];
    if (obstacleGrid[0][0] || obstacleGrid[cols - 1][rows - 1]) {
        return 0;
    }

    for (let y = 0; y &amp;lt; cols; y++) {
        for (let x = 0; x &amp;lt; rows; x++) {
            if (x === 0 &amp;amp;&amp;amp; y === 0) {
                obstacleGrid[y][x] = 1;
                continue;
            }

            if (obstacleGrid[y][x] === 1) {
                obstacleGrid[y][x] = 0;
                continue;
            }

            if (y === 0) {
                obstacleGrid[y][x] = obstacleGrid[y][x - 1];
                continue;
            }

            if (x === 0) {
                obstacleGrid[y][x] = obstacleGrid[y - 1][x];
                continue;
            }

            obstacleGrid[y][x] = obstacleGrid[y - 1][x] + obstacleGrid[y][x - 1];
        }
    }

    return obstacleGrid[cols - 1][rows - 1];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 경로를 계산하기 위해 가장 쉬운 방법은 dfs를 이용하는 것이지만, 이는 시간 초과 오류가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 특정 위치의 경로를 어떻게 계산하는지 DP형태의 문제로 접근을 해서 해결해야 한다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>depth first search</category>
      <category>dfs</category>
      <category>dp</category>
      <category>Dynamic Programming</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Unique Paths ll</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/241</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Unique-Paths-ll-JavaScript-DP#entry241comment</comments>
      <pubDate>Tue, 24 Mar 2026 15:50:13 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Longest Substring Without Repeating Characters (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Longest-Substring-Without-Repeating-Characters-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/longest-substring-without-repeating-characters/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/longest-substring-without-repeating-characters/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a string&amp;nbsp;s, find the length of the&amp;nbsp;&lt;b&gt;longest&lt;/b&gt;&amp;nbsp;&lt;b&gt;substring&lt;/b&gt;&amp;nbsp;without duplicate characters.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Input: s = &quot;abcabcbb&quot;
Output: 3
Explanation: The answer is &quot;abc&quot;, with the length of 3. Note that &quot;bca&quot; and &quot;cab&quot; are also correct answers.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;Input: s = &quot;bbbbb&quot;
Output: 1
Explanation: The answer is &quot;b&quot;, with the length of 1.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;Input: s = &quot;pwwkew&quot;
Output: 3
Explanation: The answer is &quot;wke&quot;, with the length of 3.
Notice that the answer must be a substring, &quot;pwke&quot; is a subsequence and not a substring.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;0 &amp;lt;= s.length &amp;lt;= 5 * 104&lt;/li&gt;
&lt;li&gt;s&amp;nbsp;consists of English letters, digits, symbols and spaces.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&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;포인터를 2개 두어 해당 포인터의 길이 차이를 구하도록 함 (left, right)&lt;/li&gt;
&lt;li&gt;두 포인터 안에는 중복되는 문자가 존재하지 않아야 함 -&amp;gt; set 을 통해 관리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 문자가 없어질 때까지 set 배열에서 제거 (while 문)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;현재 위치의 문자를 넣고 길이의 최댓값 계산 (right - left + 1)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let answer = 0;
    const str = new Set();
    let left = 0;

    for (let right = 0; right &amp;lt; s.length; right++) {
        while (str.has(s[right])) {
            str.delete(s[left]);
            left++;
        }

        str.add(s[right]);
        answer = Math.max(answer, right - left + 1);
    }

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;set을 통해 O(1)로 검색을 하도록 했고, for문을 한 번만 탐색해도 최대 길이를 구할 수 있도록 했음&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;for문 안에 while문이 있으면 O(n^2)이 아닌가?&lt;/b&gt; 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for문 안에 while문으로 O(n^2)처럼 보일 수 있지만 실제 동작을 보면 다른걸 알 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;right 포인터 : 문자열을 처음부터 끝까지 한 번만 탐색 (n번)&lt;/li&gt;
&lt;li&gt;left 포인터 : while 문 안에서 동작하지만, 전체 과정을 보면 문자열의 각 인덱스를 &lt;b&gt;최대 한 번씩만&amp;nbsp;&lt;/b&gt;탐색함 (n번)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, 한 번 left가 지나간 자리는 다시 돌아가지 않음 -&amp;gt; O(n^2)이 안되는 이유&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 모든 문자는 최대 두 번씩만 처리되기 때문에 연산 횟수는 2n에 비례해서 O(n)의 시간복잡도를 가지게 된다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Longest Substring Without Repeating Characters</category>
      <category>set</category>
      <category>two pointer</category>
      <category>부분문자열</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/240</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Longest-Substring-Without-Repeating-Characters-JavaScript#entry240comment</comments>
      <pubDate>Thu, 19 Mar 2026 16:14:32 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Median of Two Sorted Arrays (JavaScript), Binary Search, Two Pointer</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Median-of-Two-Sorted-Arrays-JavaScript-Binary-Search</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/median-of-two-sorted-arrays/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/median-of-two-sorted-arrays/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given two sorted arrays&amp;nbsp;nums1&amp;nbsp;and&amp;nbsp;nums2&amp;nbsp;of size&amp;nbsp;m&amp;nbsp;and&amp;nbsp;n&amp;nbsp;respectively, return&amp;nbsp;&lt;b&gt;the median&lt;/b&gt;&amp;nbsp;of the two sorted arrays.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The overall run time complexity should be&amp;nbsp;O(log (m+n)).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;Input: nums1 = [1,3], nums2 = [2]
Output: 2.00000
Explanation: merged array = [1,2,3] and median is 2.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums1 = [1,2], nums2 = [3,4]
Output: 2.50000
Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;nums1.length == m&lt;/li&gt;
&lt;li&gt;nums2.length == n&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= m &amp;lt;= 1000&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= n &amp;lt;= 1000&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= m + n &amp;lt;= 2000&lt;/li&gt;
&lt;li&gt;106 &amp;lt;= nums1[i], nums2[i] &amp;lt;= 106&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙 값을 구하는 문제로 O(log (m + n))의 시간복잡도로 해결을 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 일반적인 sort로 접근을 하면 위 시간복잡도로 문제를 해결할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Two Pointer 이용 (투 포인터, O(m + n))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;조건&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 배열을 합친 길이가 짝수인 경우 중앙 값은 2개를 더한 값으로 구해야 한다.&lt;/li&gt;
&lt;li&gt;홀수인 경우 1개의 중앙 값만 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;prev, curr로 이전 값, 현재 값을 구한다. (짝수 / 홀수를 대비하기 위해)&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;두 개의 포인터 p1, p2&lt;/b&gt;&lt;/u&gt;로 각 nums1, nums2의 배열의 index 값을 나타낸다.&lt;/li&gt;
&lt;li&gt;모든 배열 탐색하는 것이 아닌 &lt;u&gt;&lt;b&gt;두 배열의 길이의 합의 절반 만큼&lt;/b&gt;&lt;/u&gt;만 진행
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;각 위치에서 작은 값을 기준으로 포인터 값을 1씩 증가 시켜 나감&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;최종적으로 중앙 값을 계산
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;전체 길이가 짝수인 경우 (prev + curr) / 2 가 중앙값&lt;/li&gt;
&lt;li&gt;홀수인 경우 curr이 중앙값 (1개 이므로 2로 나눌 필요 없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function(nums1, nums2) {
    const len1 = nums1.length;
    const len2 = nums2.length;
    const totalLen = len1 + len2

    const mid = Math.floor(totalLen / 2);
    let [prev, curr] = [0, 0];
    let [p1, p2] = [0, 0];
    
    for (let i = 0; i &amp;lt;= mid; i++) {
        prev = curr;

        if (p1 &amp;lt; len1 &amp;amp;&amp;amp; (p2 &amp;gt;= len2 || nums1[p1] &amp;lt;= nums2[p2])) {
            curr = nums1[p1];
            p1++;
        } else {
            curr = nums2[p2];
            p2++;
        }
    }

    if (totalLen % 2 === 0) {
        return (prev + curr) / 2;
    } else {
        return curr;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Binary Search (이분 탐색 이용, O(log(min(m, n)))&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;변수 정리&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;part1 : nums1에서 왼쪽으로 보내는 원소 개수&lt;/li&gt;
&lt;li&gt;part2 : nums2에서 왼쪽으로 보내는 원소 개수 (총 길이 절반 - part1)&lt;/li&gt;
&lt;li&gt;maxLeft1 : nums1 왼쪽 부분의 최댓값&lt;/li&gt;
&lt;li&gt;minRight1 : nums1 오른쪽 부분의 최솟값&lt;/li&gt;
&lt;li&gt;maxLeft2 : nums2 왼쪽 부분의 최댓값&lt;/li&gt;
&lt;li&gt;minRight2 : nums2 오른쪽 부분의 최솟값&lt;/li&gt;
&lt;/ol&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;u&gt;&lt;b&gt;핵심 방법&lt;/b&gt;&lt;/u&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;nbsp;&lt;b&gt;&quot;전체 원소의 절반은 왼쪽, 절반은 오른쪽&quot;&lt;/b&gt;이 되는 경계이다. 이를 이용해 문제를 다르게 바라볼 수 있다.&lt;/li&gt;
&lt;li&gt;두 배열 nums1, nums2를 각각 왼쪽/오른쪽으로 나누는&amp;nbsp;&lt;b&gt;분할점(partition)&lt;/b&gt;을 잡고, 아래 두 조건을 만족하면 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;maxLeft1 &amp;lt;= minRight2 (nums1의 왼쪽 최댓값 &amp;lt;= nums2의 오른쪽 최솟값)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;maxLeft2 &amp;lt;= minRight1 (nums2의 왼쪽 최댓값 &amp;lt;= num1의 오른쪽 최솟값)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위 두 조건을 만족하면 두 배열을 합쳐서 정렬했을 때 왼쪽 절반과 오른쪽 절반을 정렬된 상태로 나눌 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;왜 위와 같은 조건을 만족해야 하나? (보충 설명)&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;위 문제의 핵심은 정렬된 상태를 만드는게 아니라&amp;nbsp;&lt;b&gt;분할점을 기준으로 정렬이 된다고 가정을 했을 경우 왼쪽에 오는 값들의 집합 + 오른쪽에 오른 값들의 집합을 구하는 것이 핵심임.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;결국 분할점을 기준으로 왼쪽 집합, 오른쪽 집합을 만들었을 때, &lt;b&gt;왼쪽 집합의 최댓값이 오른쪽 집합의 최솟값보다 작거나 같으면&lt;/b&gt; 실제로 정렬하지 않아도 &quot;이 분할은 올바르다&quot;고 확신할 수 있다. 두 조건(maxLeft1 &amp;le; minRight2, maxLeft2 &amp;le; minRight1)은 바로 그것을 두 배열에 교차로 검증하는 것이다. &lt;br /&gt;&lt;/span&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;u&gt;&lt;b&gt;주의 사항&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이진 탐색을 긴 배열을 기준으로 하게되면 part2가 음수가 될 수 있으므로, 항상 짧은 배열이 nums1이 되도록 해야한다.&lt;/li&gt;
&lt;li&gt;경계값 (-Infinity, Infinity) 처리를 안하게되면 part1 = 0 이거나 part2 = len1일 때 잘못된 index를 참조하게 된다.&lt;/li&gt;
&lt;li&gt;part2 계산 시 (len1 + len2 + 1) / 2에서 +1을 안하면 홀수 길이일 때 잘못된 중앙값을 갖게된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;[&lt;b&gt; [1, 3, 8]&lt;/b&gt; / &lt;b&gt;[7, 9, 10, 11]&lt;/b&gt; 의 중앙값 찾는 과정 시각화 ]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cx6rn4/dJMcahDGPmw/mM92W1VkNklI1xIWorTXr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cx6rn4/dJMcahDGPmw/mM92W1VkNklI1xIWorTXr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cx6rn4/dJMcahDGPmw/mM92W1VkNklI1xIWorTXr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcx6rn4%2FdJMcahDGPmw%2FmM92W1VkNklI1xIWorTXr1%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;637&quot; height=&quot;909&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&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;u&gt;&lt;b&gt;중앙값 구하는 방법&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;홀수 길이인 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 분할이 완성된 순간 위 예제의 경우 { 1, 3, 7, &lt;b&gt;8&lt;/b&gt; } | {9, 10, 11} 로 나뉘어진다.&lt;/li&gt;
&lt;li&gt;즉, 중앙값은 8의 위치한 자리로 &lt;u&gt;&lt;b&gt;왼쪽 집합을 기준 가장 끝 값&lt;/b&gt;&lt;/u&gt;을 나타낸다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Math.max(maxLeft1, maxLeft2)를 구하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;짝수 길이인 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;올바른 분할이 완성된 순간 { ... , &lt;b&gt;a&lt;/b&gt; } | { &lt;b&gt;b&lt;/b&gt;, ... }로 나뉘어지고 중앙 값은 ( a + b ) / 2의 값을 나타낸다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2를 구하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1773828980403&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number}
 */
var findMedianSortedArrays = function(nums1, nums2) {
    if (nums1.length &amp;gt; nums2.length) {
        [nums1, nums2] = [nums2, nums1];
    }

    const len1 = nums1.length;
    const len2 = nums2.length;
    let [left, right] = [0, len1];

    while (left &amp;lt;= right) {
        const part1 = Math.floor((left + right) / 2);
        const part2 = Math.floor((len1 + len2 + 1) / 2) - part1;

        const maxLeft1 = part1 === 0 ? -Infinity : nums1[part1 - 1];
        const minRight1 = part1 === len1 ? Infinity : nums1[part1];
        const maxLeft2 = part2 === 0 ? -Infinity : nums2[part2 - 1];
        const minRight2 = part2 === len2 ? Infinity : nums2[part2];

        if (maxLeft1 &amp;lt;= minRight2 &amp;amp;&amp;amp; maxLeft2 &amp;lt;= minRight1) {
            if ((len1 + len2) % 2 === 0) {
                return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2;
            } else {
                return Math.max(maxLeft1, maxLeft2);
            }
        } else if (maxLeft1 &amp;gt; minRight2) {
            right = part1 - 1;
        } else {
            left = part1 + 1;
        }
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; 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;를 &lt;b&gt;&quot;올바른 분할점을 찾는 문제&quot;&lt;/b&gt;로 재정의한 것이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 재정의하고 이 문제를 Binary Search를 통해 해결해야 하는 문제로 생각보다 어려운 Binary Search 문제라고 생각이 든다.&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;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>binary search</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Median of Two Sorted Arrays</category>
      <category>two pointer</category>
      <category>올바른 분할점을 찾는 문제</category>
      <category>이분 탐색</category>
      <category>중앙값을 찾는 문제</category>
      <category>코딩테스트</category>
      <category>투 포인터</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/239</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Median-of-Two-Sorted-Arrays-JavaScript-Binary-Search#entry239comment</comments>
      <pubDate>Wed, 18 Mar 2026 19:54:43 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Trapping Rain Water (JavaScript), Two pointer</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Trapping-Rain-Water-JavaScript-Two-pointer</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;링크 : &lt;a style=&quot;color: #000000;&quot; href=&quot;https://leetcode.com/problems/trapping-rain-water/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/trapping-rain-water/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제 설명&lt;/span&gt;&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Given&amp;nbsp;n&amp;nbsp;non-negative integers representing an elevation map where the width of each bar is&amp;nbsp;1, compute how much water it can trap after raining.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PY1LD/dJMcahDCdVv/ZkqAwQx7HTZTwujdtOLEyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PY1LD/dJMcahDCdVv/ZkqAwQx7HTZTwujdtOLEyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PY1LD/dJMcahDCdVv/ZkqAwQx7HTZTwujdtOLEyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPY1LD%2FdJMcahDCdVv%2FZkqAwQx7HTZTwujdtOLEyK%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;412&quot; height=&quot;161&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6
Explanation: The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: height = [4,2,0,3,2,5]
Output: 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Constraints:&lt;/b&gt;&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;n == height.length&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1 &amp;lt;= n &amp;lt;= 2 * 104&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;0 &amp;lt;= height[i] &amp;lt;= 105&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제 풀이&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;접근 방법 (투 포인터를 이용하여 해결)&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;맨 왼쪽, 맨 오른쪽의 &lt;u&gt;포인터를 2개 두어 범위를 좁혀 오면서 계산&lt;/u&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;범위를 좁히는 기준 -&amp;gt; 둘 중 더 작은 곳을 이동&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 위치에서 계산을 하기 위해 leftMax, rightMax를 설정 -&amp;gt; &lt;u&gt;어떠한 높이 기준으로 계산을 할 건지 필요&lt;/u&gt;하기 때문&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 Max 값보다 현재 위치가 더 클 경우 Max값 조정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;더 작다면 물을 채울 공간이 있다는 의미이므로 계산 (Max값 - 현재 위치의 높이)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위치 이동&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;1337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TkTkm/dJMcag5OchA/9CFTrFiS9tauKxnWLLnk1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TkTkm/dJMcag5OchA/9CFTrFiS9tauKxnWLLnk1K/img.png&quot; data-alt=&quot;로직 진행에 따른 변화 (빨강 : 이전, 파랑 : 이후)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TkTkm/dJMcag5OchA/9CFTrFiS9tauKxnWLLnk1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTkTkm%2FdJMcag5OchA%2F9CFTrFiS9tauKxnWLLnk1K%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;700&quot; height=&quot;658&quot; data-origin-width=&quot;1422&quot; data-origin-height=&quot;1337&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로직 진행에 따른 변화 (빨강 : 이전, 파랑 : 이후)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    let [left, right] = [0, height.length - 1];
    let [leftMax, rightMax] = [0, 0];
    let answer = 0;

    while (left &amp;lt; right) {
        if (height[left] &amp;lt;= height[right]) {
            if (height[left] &amp;gt; leftMax) {
                leftMax = height[left];
            } else {
                answer += leftMax - height[left];
            }
            left++;
        } else {
            if (height[right] &amp;gt; rightMax) {
                rightMax = height[right];
            } else {
                answer += rightMax - height[right];
            }
            right--;
        }
    }

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 문제는 O(n)의 시간 복잡도로 해결할 수 있고, 이를 위해 투 포인터 알고리즘을 활용하여 풀어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순히 left, right의 두 개의 포인터만 이용해서 해결할 수 없고, 각 위치에서 &lt;u&gt;어떠한 기준으로 계산할지&lt;/u&gt;에 대한 값이 필요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 왼쪽, 오른쪽 각 Max값을 저장해 특정 위치에서 높이차이를 이용해 계산을 해야함.&lt;/span&gt;&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Trapping Rain Water</category>
      <category>two pointer</category>
      <category>코딩테스트</category>
      <category>투포인터</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/238</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Trapping-Rain-Water-JavaScript-Two-pointer#entry238comment</comments>
      <pubDate>Thu, 12 Mar 2026 19:53:31 +0900</pubDate>
    </item>
    <item>
      <title>[SQLD] SQL 개발자 60회차 후기 (합격) : 공부 방법부터 유용한 사이트 공유까지</title>
      <link>https://kuh97.tistory.com/entry/SQLD-SQL-%EA%B0%9C%EB%B0%9C%EC%9E%90-60%ED%9A%8C%EC%B0%A8-%EC%8B%9C%ED%97%98-%ED%9B%84%EA%B8%B0-2026</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시험 정보 (2026년도)&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Ugnt/dJMcaf6RMfU/KwVtfSalHQoBdp13LCZkSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Ugnt/dJMcaf6RMfU/KwVtfSalHQoBdp13LCZkSK/img.png&quot; data-alt=&quot;2026년 SQL 시험 일정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Ugnt/dJMcaf6RMfU/KwVtfSalHQoBdp13LCZkSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Ugnt%2FdJMcaf6RMfU%2FKwVtfSalHQoBdp13LCZkSK%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;700&quot; height=&quot;510&quot; data-origin-width=&quot;1081&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2026년 SQL 시험 일정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.dataq.or.kr/www/accept/schedule.do#none&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.dataq.or.kr/www/accept/schedule.do#none&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773215729784&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;데이터자격검정 - 데이터자격시험, DAP, DAsP, SQLP, SQLD, ADP, ADsP.&quot; data-og-description=&quot;데이터자격시험, DAP, DAsP, SQLP, SQLD, ADP, ADsP.&quot; data-og-host=&quot;www.dataq.or.kr&quot; data-og-source-url=&quot;https://www.dataq.or.kr/www/accept/schedule.do#none&quot; data-og-url=&quot;https://www.dataq.or.kr/www/main.do&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.dataq.or.kr/www/accept/schedule.do#none&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.dataq.or.kr/www/accept/schedule.do#none&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;데이터자격검정 - 데이터자격시험, DAP, DAsP, SQLP, SQLD, ADP, ADsP.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;데이터자격시험, DAP, DAsP, SQLP, SQLD, ADP, ADsP.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.dataq.or.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기본 정보&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQL 개발자 시험은 총 2과목으로 구성&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터 모델링의 이해&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQL 기본 및 활용&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개정된 이후 주관식은 없고 &lt;u&gt;&lt;b&gt;전부 객관식으로 이루어져있는 시험&lt;/b&gt;&lt;/u&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;응시료는 5만원이며, 응시자격 제한은 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1과목 10문제, 2과목 40문제로 총 50문제가 출제되며, 각 문제당 2점씩 총 100점으로 구성되어있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;60점이상 합격, 각 과목 40% 미만이면 과락&lt;/u&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시험 시간은 총 90분이 주어지며, 시험 시작 30분 후 부터 퇴실이 가능.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공부 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실무에서도 SQL 관련해서 다룬 경험이 있어 엄청 빡세게 준비하지는 않았다. (&lt;b&gt;개발자 기준&lt;/b&gt;으로 보면 좋을 것 같다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대략 2주 정도의 시간으로 &lt;b&gt;하루에 2시간 정도씩 공부&lt;/b&gt;를 진행했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1주는 내용 파악 및 개념 정리&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1주는 기출 문제 풀이 및 부족한 개념 정리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 SQLD 관련 나오는 내용을 대략적으로 파악하기 위해 &lt;u&gt;유튜브나 요약된 PDF&lt;/u&gt;를 찾아보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;[유튜브 채널 추천]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유명한 '&lt;b&gt;홍쌤의 데이터랩&lt;/b&gt;' 이라는 채널을 통해 SQLD 1과목, 2과목에 대해 대략적으로 파악했다. (현재는 유료로 바뀌어 모든 내용을 볼 수 없는 것 같음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비개발자 기준이라면 홍쌤의 데이터랩을 보고 공부를 하는 것이 좋을 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.youtube.com/watch?v=FsyBH7NHY-c&amp;amp;list=PLbflMVhwy2jMaJbOd1hvC3UMsmRQX24mo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=FsyBH7NHY-c&amp;amp;list=PLbflMVhwy2jMaJbOd1hvC3UMsmRQX24mo&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=FsyBH7NHY-c&amp;amp;list=PLbflMVhwy2jMaJbOd1hvC3UMsmRQX24mo&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/7EDoj/dJMb8XkdGCT/Rrgtr4x94yygvQShnki5g1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=858_234_1042_436,https://scrap.kakaocdn.net/dn/LsCj4/dJMb9jgvxoF/78HGKWxWKG0a3IRteKvdOK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=858_234_1042_436&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[2026개정] SQLD 1과목 미리보기 #데이터모델링 #엔터티&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/FsyBH7NHY-c&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빠르게 요약을 하고 싶은 경우 '&lt;b&gt;IT의 답을 터득하다, 아답터&lt;/b&gt;'라는 채널에서 요약 강의를 보는 것도 좋을 것 같다. (어느정도 내용을 알고 있을 경우 해당 채널이 더 좋을 것 같다 생각함)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.youtube.com/watch?v=Rv4Q4ZrPS94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=Rv4Q4ZrPS94&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Rv4Q4ZrPS94&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cl6Eso/dJMb83krfrq/offcmxiZ9zyD1z1JKPDrQ0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bhPELU/dJMb8951UVC/QFzArli2p8XVm3xsYeT5k0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;[New!! 2026] (2과목) SQLD 완벽 요약강의 | 요약강의 | SQL 기본 및 활용 | 최단시간 최대효율  | 핵심 &quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Rv4Q4ZrPS94&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;[ 요약 PDF 추천 ]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 요약된 pdf 공유 사이트들이 많지만 아래 사이트에서 공유해준 pdf를 통해 공부를 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://bruders.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bruders.tistory.com/173&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773216690918&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[SQLD] 2024년 개정 SQLD 합격 후기, SQLD 개정판 개념정리 pdf 공유&quot; data-og-description=&quot;[ 목차 ]1. SQLD 시험&amp;bull; 정보&amp;bull; 유의사항&amp;bull; 시험 후기2. 공부 방법&amp;bull; 교재&amp;bull; 추천 강의&amp;bull; 자료 및 웹사이트#. 개념정리 PDF 공유 SQLD 합격을 하게 되어, 준비하는 분들에게 조금이나마 도움이 될까해&quot; data-og-host=&quot;bruders.tistory.com&quot; data-og-source-url=&quot;https://bruders.tistory.com/173&quot; data-og-url=&quot;https://bruders.tistory.com/173&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Oobjb/dJMb9lL9ROt/bTIMExt3RTf8RB0gfmErE1/img.png?width=800&amp;amp;height=209&amp;amp;face=0_0_800_209,https://scrap.kakaocdn.net/dn/cPtVLo/dJMb9gxjIPA/kqg3XqeQJJNTn95HQLZL0K/img.png?width=800&amp;amp;height=209&amp;amp;face=0_0_800_209,https://scrap.kakaocdn.net/dn/hxi1E/dJMb9lk5sSo/sOkLqmaQedLerujERKd7X1/img.png?width=1656&amp;amp;height=978&amp;amp;face=0_0_1656_978&quot;&gt;&lt;a href=&quot;https://bruders.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bruders.tistory.com/173&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Oobjb/dJMb9lL9ROt/bTIMExt3RTf8RB0gfmErE1/img.png?width=800&amp;amp;height=209&amp;amp;face=0_0_800_209,https://scrap.kakaocdn.net/dn/cPtVLo/dJMb9gxjIPA/kqg3XqeQJJNTn95HQLZL0K/img.png?width=800&amp;amp;height=209&amp;amp;face=0_0_800_209,https://scrap.kakaocdn.net/dn/hxi1E/dJMb9lk5sSo/sOkLqmaQedLerujERKd7X1/img.png?width=1656&amp;amp;height=978&amp;amp;face=0_0_1656_978');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[SQLD] 2024년 개정 SQLD 합격 후기, SQLD 개정판 개념정리 pdf 공유&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[ 목차 ]1. SQLD 시험&amp;bull; 정보&amp;bull; 유의사항&amp;bull; 시험 후기2. 공부 방법&amp;bull; 교재&amp;bull; 추천 강의&amp;bull; 자료 및 웹사이트#. 개념정리 PDF 공유 SQLD 합격을 하게 되어, 준비하는 분들에게 조금이나마 도움이 될까해&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bruders.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;[ 기출 문제 ]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQL 시험으로 유명한 '&lt;b&gt;노랭이 책&lt;/b&gt;'이라는 것을 구매해 문제를 풀었다. 가격은 &lt;u&gt;18,000원&lt;/u&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQLP에 대한 내용 (3과목)도 있어서 책을 구매한다면 1, 2 과목 (대략 180문제?) 정도만 풀어 보면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBODyh/dJMcadOHksx/HNPB6GkPuK0KMpYFdip2aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBODyh/dJMcadOHksx/HNPB6GkPuK0KMpYFdip2aK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBODyh/dJMcadOHksx/HNPB6GkPuK0KMpYFdip2aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBODyh%2FdJMcadOHksx%2FHNPB6GkPuK0KMpYFdip2aK%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;200&quot; height=&quot;282&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;책에 대한 해설을 보고 잘 이해가 안된다면 카페를 이용해서 더 자세한 해설을 찾아볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://cafe.naver.com/sqlpd&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cafe.naver.com/sqlpd&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773216852913&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;데이터 전문가 포럼 (빅데이터분석기사... : 네이버 카페&quot; data-og-description=&quot;빅데이터분석기사, ADP, ADsP, SQLP, SQLD, DAP, DAsP, BIS 자격증 취득 등 데이터 전문가 커뮤니티입니다.&quot; data-og-host=&quot;cafe.naver.com&quot; data-og-source-url=&quot;https://cafe.naver.com/sqlpd&quot; data-og-url=&quot;https://cafe.naver.com/sqlpd&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dGTYU9/dJMb9iaPsBb/9CZmct91xSS4FDZZ9mVlD1/img.jpg?width=279&amp;amp;height=279&amp;amp;face=0_0_279_279&quot;&gt;&lt;a href=&quot;https://cafe.naver.com/sqlpd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cafe.naver.com/sqlpd&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dGTYU9/dJMb9iaPsBb/9CZmct91xSS4FDZZ9mVlD1/img.jpg?width=279&amp;amp;height=279&amp;amp;face=0_0_279_279');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;데이터 전문가 포럼 (빅데이터분석기사... : 네이버 카페&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;빅데이터분석기사, ADP, ADsP, SQLP, SQLD, DAP, DAsP, BIS 자격증 취득 등 데이터 전문가 커뮤니티입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cafe.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;노랭이 책 외에도 &lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.sqld.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.sqld.kr/&lt;/a&gt; 에 접속해서 문제를 풀어 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773216905473&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SQLD 문제은행&quot; data-og-description=&quot;SQLD 기출문제들과 예상문제들을 마음껏 풀 수 있는 문제은행 사이트&quot; data-og-host=&quot;www.sqld.kr&quot; data-og-source-url=&quot;https://www.sqld.kr/&quot; data-og-url=&quot;https://www.sqld.kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RFDRo/dJMb8Z3pBtn/MLVHNcnwhGQh1ZHJNxQjzK/img.png?width=350&amp;amp;height=351&amp;amp;face=0_0_350_351,https://scrap.kakaocdn.net/dn/M6yjV/dJMb8WexXjz/9XzzQ6Rpzb2QNxPFBxj1Q0/img.png?width=350&amp;amp;height=351&amp;amp;face=0_0_350_351&quot;&gt;&lt;a href=&quot;https://www.sqld.kr/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.sqld.kr/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RFDRo/dJMb8Z3pBtn/MLVHNcnwhGQh1ZHJNxQjzK/img.png?width=350&amp;amp;height=351&amp;amp;face=0_0_350_351,https://scrap.kakaocdn.net/dn/M6yjV/dJMb8WexXjz/9XzzQ6Rpzb2QNxPFBxj1Q0/img.png?width=350&amp;amp;height=351&amp;amp;face=0_0_350_351');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SQLD 문제은행&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SQLD 기출문제들과 예상문제들을 마음껏 풀 수 있는 문제은행 사이트&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.sqld.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개인적으로는 '&lt;b&gt;이지패스 2026 SQLD SQL 개발자&lt;/b&gt;' 라는 책을 구매하면 앱을 통해 모의고사, 기출들을 풀어볼 수 있는데 앱을 활용해서 푸는게 좋았던 것 같다. (책을 사야 인증번호를 알 수 있음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;책 &lt;u&gt;가격은 26,000원&lt;/u&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시험 후기&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6GVUv/dJMcaaSbS9k/kwZ8JxWDxXa9wSH0lZakvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6GVUv/dJMcaaSbS9k/kwZ8JxWDxXa9wSH0lZakvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6GVUv/dJMcaaSbS9k/kwZ8JxWDxXa9wSH0lZakvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6GVUv%2FdJMcaaSbS9k%2FkwZ8JxWDxXa9wSH0lZakvK%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;500&quot; height=&quot;374&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;2026.03.07 (토)에 진행된 60회차 SQLD 개발자 시험에 합격&lt;/b&gt;&lt;/u&gt;을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;난이도는 이전 기출들과 비슷한 난이도로 출제가 된 것 같다. (이전 기출들을 풀었을 때 70~90점 사이의 점수대가 나왔다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기출은 직전 2개의 회차인 58, 59회차만 풀었고, 틀린 문제는 간단하게 오답노트 정리를 했다.&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;노랭이 문제집 만큼의 난이도로는 나오지는 않는다고 생각을 하긴하는데 유사한 문제들이 꽤 있는 것 같아서 노랭이 문제집을 사서 풀어보는게 시험을 준비하는데 도움이 될 것 같다. (60회차 문제를 풀면서 노랭이 문제집에 있는 한 문제가 거의 똑같이 나온 느낌?이 들었다.)&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;위에서 언급한 요약 pdf, 요약 유튜브 강의, 기출 문제들로 충분히 SQLD 개발자 시험에 합격할 수 있다.&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;이미 SQL에 대해 어느정도 지식이 있는 사람이라면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약 pdf 1회독 &amp;amp; 요약 유튜브 시청 -&amp;gt; 직전 기출 2개분 풀이 정도만 해도 충분히 합격할 수 있을 것이라 생각한다.&lt;/p&gt;</description>
      <category>자격증</category>
      <category>(경기) 성남테크노과학고등학교</category>
      <category>60회차 SQLD 합격 후기</category>
      <category>SQLD 2026 시험 후기</category>
      <category>SQLD 60회차</category>
      <category>sqld 개념정리</category>
      <category>SQLD 공부방법</category>
      <category>SQLD 기출</category>
      <category>sqld 독학</category>
      <category>SQLD 요약 PDF</category>
      <category>SQLD 자격증 시험</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/237</guid>
      <comments>https://kuh97.tistory.com/entry/SQLD-SQL-%EA%B0%9C%EB%B0%9C%EC%9E%90-60%ED%9A%8C%EC%B0%A8-%EC%8B%9C%ED%97%98-%ED%9B%84%EA%B8%B0-2026#entry237comment</comments>
      <pubDate>Wed, 11 Mar 2026 17:23:45 +0900</pubDate>
    </item>
    <item>
      <title>포트폴리오 SSR에서 ISR로 전환하기</title>
      <link>https://kuh97.tistory.com/entry/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-SSR%EC%97%90%EC%84%9C-ISR%EB%A1%9C-%EC%A0%84%ED%99%98%ED%95%98%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개요&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 SSR 형태로 구성되어있던 포트폴리오 웹 사이트를 ISR을 적용한 과정을 정리하고자 한다.&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;ISR을 적용하려고하는 이유는 메인 페이지에서 프로젝트 카드의 &lt;b&gt;자세히 보기&lt;/b&gt; 버튼을 눌렀을 때 상세 페이지로 전환되는 과정이 생각보다 오래 걸렸고, 네트워크 탭을 열어보니 RSC payload 요청이 약 &lt;b&gt;3초&lt;/b&gt; 가까이 소요되고 있었다. &lt;/span&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;span style=&quot;color: #000000;&quot;&gt;기존 SSR 방식은 매번 사용자가 페이지를 요청할 때마다 서버에서 DB에 접근해 데이터를 조회하고, 렌더링해서 응답하기 때문에 오래 걸리는 이슈가 발생했고 이를 개선하고자 한다.&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;포트폴리오 특성상 각 프로젝트의 내용은 자주 변경되지 않기 때문에 데이터를 매 요청마다 새로 조회하는 건 불필요하다 생각했고, 이를 정적 페이지로 미리 생성해두고 캐시에서 빠르게 제공하는 방식으로 개선하는 것이 목표이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SSR vs SSG vs ISR 비교&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SSR (Server-Side Rendering)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 요청 &amp;rarr; DB 조회 &amp;rarr; 렌더링 &amp;rarr; 응답 (매 요청마다 반복)&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매 요청마다 서버에서 데이터를 새로 조회하고 렌더링한다. 항상 최신 데이터를 제공할 수 있지만, 그만큼 응답이 느리다.&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;SSG (Static Site Generation)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빌드 시 &amp;rarr; DB 조회 &amp;rarr; 렌더링 &amp;rarr; HTML 저장 (딱 한 번) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 요청 &amp;rarr; 저장된 HTML 즉시 반환&lt;/span&gt;&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빌드 시점에 모든 페이지를 미리 생성해둔다. 응답은 가장 빠르지만, 배포없이는 데이터를 갱신할 수 없다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;ISR (Incremental Static Regeneration)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빌드 시 &amp;rarr; DB 조회 &amp;rarr; 렌더링 &amp;rarr; HTML 저장 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 요청 &amp;rarr; 캐시 반환 ⚡&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(설정한 시간 경과 후) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 요청 &amp;rarr; 캐시 반환 ⚡ + 백그라운드에서 페이지 재생성 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;재생성 완료 &amp;rarr; 이후 요청부터 새 HTML 반환&lt;/span&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSG의 빠른 응답 속도를 유지하면서, 주기적으로 페이지를 재생성해 데이터를 갱신할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR과 SSG의 장점을 결합한 방식으로 &lt;u&gt;&lt;b&gt;포트폴리오처럼 데이터가 자주 바뀌지는 않지만, 재배포 없이도 변경사항이 반영되어야 하는 경우 ISR이 적합하다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 페이지 레벨 revalidate 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js App Router에서 ISR을 적용하는 가장 간단한 방법은 `page.tsx`에 `revalidate`를 export하는 방법이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1773134859472&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app/page.tsx
export const revalidate = 86400; // 24시간(초 단위)&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;p data-ke-size=&quot;size16&quot;&gt;Next.js는 빌드(`next build`) 시점에 `revalidate` 값이 설정된 페이지를 감지해 정적 HTML로 미리 생성해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 사용자가 해당 페이지에 접근하면 미리 만들어진 HTML을 즉시 반환하고, 설정한 시간이 지난 후 다음 요청이 들어오면 백그라운드에서 페이지를 재생성한다. 재생성이 완료되면 다음 방문자부터 새로 생성된 HTML을 받게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`revalidate` 값은 페이지 단위로 동작하기 때문에, 해당 페이지 내 하위 컴포넌트에서 데이터를 fetch 하더라도 `page.tsx`의 설정이 전체에 적용된다.&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 id=&quot;code_1773135015688&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app/page.tsx - 메인 페이지
export const revalidate = 86400;

// app/[slug]/page.tsx - 프로젝트 상세 직접 접근
export const revalidate = 86400;

// app/@modal/(.).[slug]/page.tsx - 모달로 열릴 때
export const revalidate = 86400;&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Prisma 함수 캐싱 - unstable_cache&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 설정만 할 경우 ISR이 제대로 동작하지 않는 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.j의 `revalidate`는 `fetch` API 기반의 캐싱과 연동되는데,&amp;nbsp;&lt;b&gt;Prisma는 자체 DB 커넥션을 사용하기 때문에 Next.js의 fetch 캐싱이 적용되지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, 페이지 레벨의 `revalidate`는 페이지 전체 HTML을 캐싱하지만, 페이지 내에서 동적으로 데이터를 조회하는 함수는 별도로 캐싱해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;이를 위해 Next.js에서 제공하는 `unstable_cache`를 사용했다.&lt;/b&gt;&lt;/u&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;기존 데이터 조회 코드 (`lib/projects.ts`)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1773135205070&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import prisma from &quot;@/lib/prisma&quot;;

export async function getProjectBySlug(slug: string) {
  try {
    const project = await prisma.project.findUnique({
      where: { slug },
      include: {
        info: true,
        tasks: { orderBy: { id: &quot;asc&quot; } },
      },
    });
    return project;
  } catch (error) {
    console.error(&quot;프로젝트를 찾을 수 없습니다:&quot;, error);
    return null;
  }
}&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 id=&quot;code_1773135223654&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { unstable_cache } from &quot;next/cache&quot;;
import prisma from &quot;@/lib/prisma&quot;;

export const getProjectBySlug = unstable_cache(
  async (slug: string) =&amp;gt; {
    try {
      const project = await prisma.project.findUnique({
        where: { slug },
        include: {
          info: true,
          tasks: { orderBy: { id: &quot;asc&quot; } },
        },
      });
      return project;
    } catch (error) {
      console.error(&quot;프로젝트를 찾을 수 없습니다:&quot;, error);
      return null;
    }
  },
  [&quot;project-by-slug&quot;], // 캐시 키
  { revalidate: 86400 } // 24시간 캐싱
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`unstable_cache`는 세 가지 인자를 받는다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추후 `revalidateTag(&quot;project-by-slug&quot;)`로 특정 캐시만 즉시 무효화할 때 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세 번째 인자 : 캐시 옵션. `revalidate` 값으로 캐시 유효 시간을 &lt;b&gt;초 단위&lt;/b&gt;로 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;변경 빈도가 낮은 포트폴리오 특성 상 유효 시간 기준을 하루(86400)으로 설정하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. DB 데이터 수정 즉시 반영이 필요할 경우 (참고)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터 수정 즉시 반영이 필요하다면 On-demand Revalidateion을 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1773135465088&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from &quot;next/cache&quot;;

export async function POST(req: Request) {
  revalidateTag(&quot;project-by-slug&quot;); // 특정 캐시만 무효화
  revalidatePath(&quot;/&quot;); // 특정 경로 무효화
  return Response.json({ revalidated: true });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 수정 후 이 API를 호출하면 캐시가 즉시 무효화되고, 다음 요청 시 새로운 데이터로 페이지가 재생성된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(티스토리 동영상 패치로인해.. 파일로 업로드 -&amp;gt; 추후 바로 재생가능하다면 변경할 예정)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/5dNz4/dJMcac95dKO/HCGugOnH3wUNdRQgYa0ok0/%E1%84%89%E1%85%AE%E1%84%8C%E1%85%A5%E1%86%BC%20%E1%84%8C%E1%85%A5%E1%86%AB.mov?attach=1&amp;amp;knm=tfile.mov&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;수정 전.mov&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;6.55MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/nwmwu/dJMcagkn46D/DGlFliGTxb1BUKgh5tNXK1/%E1%84%89%E1%85%AE%E1%84%8C%E1%85%A5%E1%86%BC%20%E1%84%92%E1%85%AE.mp4?attach=1&amp;amp;knm=tfile.mp4&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;수정 후.mp4&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;1.78MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&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;데이터 fetch 하는 시간이 200 ~ 300ms 로 단축된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;1304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NWV3a/dJMcahjlf70/gsd6oknWSENeMk9nMVw00k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NWV3a/dJMcahjlf70/gsd6oknWSENeMk9nMVw00k/img.png&quot; data-alt=&quot;fetch 시간 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NWV3a/dJMcahjlf70/gsd6oknWSENeMk9nMVw00k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNWV3a%2FdJMcahjlf70%2Fgsd6oknWSENeMk9nMVw00k%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;650&quot; height=&quot;579&quot; data-origin-width=&quot;1464&quot; data-origin-height=&quot;1304&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;fetch 시간 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>프로젝트/Portfolio 사이트 개발</category>
      <category>ISR</category>
      <category>Next.js revalidate</category>
      <category>Prisma 함수 캐싱</category>
      <category>revalidate</category>
      <category>ssg</category>
      <category>SSR</category>
      <category>SSR에서 ISR로 전환하기</category>
      <category>unstable_cache</category>
      <category>포트폴리오 웹 사이트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/236</guid>
      <comments>https://kuh97.tistory.com/entry/%ED%8F%AC%ED%8A%B8%ED%8F%B4%EB%A6%AC%EC%98%A4-SSR%EC%97%90%EC%84%9C-ISR%EB%A1%9C-%EC%A0%84%ED%99%98%ED%95%98%EA%B8%B0#entry236comment</comments>
      <pubDate>Tue, 10 Mar 2026 18:55:50 +0900</pubDate>
    </item>
    <item>
      <title>Vercel 배포 오류(CVE-2025-55182, CVE-2025-66478) 및 해결 방안</title>
      <link>https://kuh97.tistory.com/entry/Vercel-%EB%B0%B0%ED%8F%AC-%EC%98%A4%EB%A5%98CVE-2025-55182-CVE-2025-66478-%EB%B0%8F-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EC%95%88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2025년 12월 초에 React, Next.js 관련 취약점이 발견되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7YWRDRjz4jU&quot;&gt;https://www.youtube.com/watch?v=7YWRDRjz4jU&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=7YWRDRjz4jU&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/c4faPv/dJMb86OZ2Mr/kKDOfCa5oeIwn8jTYQNhv0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bLgDDY/dJMb8XkdAZO/RqJ86tfpf8edNkQIlKkkO1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/ilpZq/dJMb9efcaRE/cuFgKRFairzHKXzcY43zjK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;리액트/넥스트JS에서 데이터 털리고 서버 뺏기는 최악의 취약점! 바이브코딩은 특히 조심! 해결 &quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/7YWRDRjz4jU&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로인해 오류가 발생하는 버전으로 Vercel에서 배포를 할 경우 다음과 같은 빌드 오류가 발생했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오류 메시지를 간단하게 살펴보면 &lt;u&gt;&lt;b&gt;불안정한 버전 사용으로 Vercel에서 배포를 막고있는 것&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Vercel 오류 메시지&lt;/span&gt;&lt;br /&gt;&lt;u&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Error: Vulnerable version of Next.js detected, please update immediately. Learn More: &lt;/span&gt;&lt;/u&gt;&lt;br /&gt;&lt;u&gt;&lt;span style=&quot;color: #000000;&quot;&gt;https://vercel.link/CVE-2025-66478&lt;/span&gt;&lt;/u&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1773132437937&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React2Shell Security Bulletin      | Vercel Knowledge Base&quot; data-og-description=&quot;CVE-2025-55182 is a critical vulnerability in React, Next.js, and other frameworks that requires immediate action&quot; data-og-host=&quot;vercel.com&quot; data-og-source-url=&quot;https://vercel.link/CVE-2025-66478&quot; data-og-url=&quot;https://vercel.com/kb/bulletin/react2shell&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4lhqx/dJMb8Z3pvCy/6hFsdlQPBxcWEIDjRf9G4k/img.png?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256,https://scrap.kakaocdn.net/dn/SmuBo/dJMb81GVrYc/0ZNAd9PSK4CBFFkkrROJrk/img.png?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256,https://scrap.kakaocdn.net/dn/bWehh2/dJMb8QMacEW/PdLD40I5JHAHtYg7m6hTPK/img.png?width=1920&amp;amp;height=1266&amp;amp;face=0_0_1920_1266&quot;&gt;&lt;a href=&quot;https://vercel.link/CVE-2025-66478&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://vercel.link/CVE-2025-66478&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4lhqx/dJMb8Z3pvCy/6hFsdlQPBxcWEIDjRf9G4k/img.png?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256,https://scrap.kakaocdn.net/dn/SmuBo/dJMb81GVrYc/0ZNAd9PSK4CBFFkkrROJrk/img.png?width=2400&amp;amp;height=1256&amp;amp;face=0_0_2400_1256,https://scrap.kakaocdn.net/dn/bWehh2/dJMb8QMacEW/PdLD40I5JHAHtYg7m6hTPK/img.png?width=1920&amp;amp;height=1266&amp;amp;face=0_0_1920_1266');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React2Shell Security Bulletin | Vercel Knowledge Base&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CVE-2025-55182 is a critical vulnerability in React, Next.js, and other frameworks that requires immediate action&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;vercel.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tHRtt/dJMcaaxJqdd/CZ6slkKKAW9onHAlFPFk4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tHRtt/dJMcaaxJqdd/CZ6slkKKAW9onHAlFPFk4K/img.png&quot; data-alt=&quot;보안 취약점으로 인해 발생할 수 있는 문제 상황&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tHRtt/dJMcaaxJqdd/CZ6slkKKAW9onHAlFPFk4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtHRtt%2FdJMcaaxJqdd%2FCZ6slkKKAW9onHAlFPFk4K%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;750&quot; height=&quot;442&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;보안 취약점으로 인해 발생할 수 있는 문제 상황&lt;/figcaption&gt;
&lt;/figure&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC8ZYn/dJMcag5Mywj/BvU2FUHKxkAwSI4kFw60Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC8ZYn/dJMcag5Mywj/BvU2FUHKxkAwSI4kFw60Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC8ZYn/dJMcag5Mywj/BvU2FUHKxkAwSI4kFw60Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC8ZYn%2FdJMcag5Mywj%2FBvU2FUHKxkAwSI4kFw60Y1%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;750&quot; height=&quot;480&quot; data-origin-width=&quot;1296&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&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;p data-ke-size=&quot;size16&quot;&gt;문제가 발생하는 &lt;b&gt;React, Next.js 버전을 업데이트&lt;/b&gt;를 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령을 통해 최신 보안 패치가 된 버전으로 올려 다시 배포를 해주면 이후 정상적인 배포가 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1773133190259&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// next, react, react-dom 을 최신 버전으로 업데이트
npm install next@latest react@latest react-dom@latest

// 문제가 해결된 버전 이용 (next)
npm fix-react2shell-next&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로젝트/Portfolio 사이트 개발</category>
      <category>CVE-2025-55182</category>
      <category>CVE-2025-66478</category>
      <category>Next.js</category>
      <category>npm fix-react2shell-next</category>
      <category>npm install next@latest react@latest react-dom@latest</category>
      <category>REACT</category>
      <category>Vercel 배포 오류</category>
      <category>버전 업데이트</category>
      <category>서버 컴포넌트 오류</category>
      <category>취약점</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/235</guid>
      <comments>https://kuh97.tistory.com/entry/Vercel-%EB%B0%B0%ED%8F%AC-%EC%98%A4%EB%A5%98CVE-2025-55182-CVE-2025-66478-%EB%B0%8F-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EC%95%88#entry235comment</comments>
      <pubDate>Tue, 10 Mar 2026 18:05:09 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Repeated DNA Sequences (JavaScript), Sliding window</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Repeated-DNA-Sequences-JavaScript-Sliding-window</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/repeated-dna-sequences/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/repeated-dna-sequences/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The&amp;nbsp;&lt;b&gt;DNA sequence&lt;/b&gt;&amp;nbsp;is composed of a series of nucleotides abbreviated as&amp;nbsp;'A',&amp;nbsp;'C',&amp;nbsp;'G', and&amp;nbsp;'T'.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;For example,&amp;nbsp;&quot;ACGAATTCCG&quot;&amp;nbsp;is a&amp;nbsp;&lt;b&gt;DNA sequence&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;When studying&amp;nbsp;&lt;b&gt;DNA&lt;/b&gt;, it is useful to identify repeated sequences within the DNA.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a string&amp;nbsp;s&amp;nbsp;that represents a&amp;nbsp;&lt;b&gt;DNA sequence&lt;/b&gt;, return all the&amp;nbsp;&lt;b&gt;10-letter-long&lt;/b&gt;&amp;nbsp;sequences (substrings) that occur more than once in a DNA molecule. You may return the answer in&amp;nbsp;&lt;b&gt;any order&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;Input: s = &quot;AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT&quot;
Output: [&quot;AAAAACCCCC&quot;,&quot;CCCCCAAAAA&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;Input: s = &quot;AAAAAAAAAAAAA&quot;
Output: [&quot;AAAAAAAAAA&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= s.length &amp;lt;= 105&lt;/li&gt;
&lt;li&gt;s[i]&amp;nbsp;is either&amp;nbsp;'A',&amp;nbsp;'C',&amp;nbsp;'G', or&amp;nbsp;'T'.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10글자의 문자열이 전체 문자열에 2번이상 나오는 문자열을 구하는 문제&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;gt; 해당 문제에서는 간단한게 하기위해 substring 메서드를 이용)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;2개의 set 배열 선언
&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;/li&gt;
&lt;li&gt;현재 index를 기준으로 10개의 문자열을 만듦(substring활용)&lt;/li&gt;
&lt;li&gt;해당 문자열이 한 번 등장 했다면 tmp에 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;tmp에 이미 있다면 2번 이상 등장한 문자열이므로 repeat 배열에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;최종적으로 repeat에 저장된 문자열이 위 문제의 조건을 만족하는 문자열임.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리턴 값은 배열형태이기 때문에 ... 연산을 이용해 set -&amp;gt; array로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {string} s
 * @return {string[]}
 */
var findRepeatedDnaSequences = function(s) {
    const tmp = new Set();
    const repeat = new Set();

    for (let i = 0; i &amp;lt;= s.length - 10; i++) {
        const target = s.substring(i, i + 10);

        if (tmp.has(target)) {
            repeat.add(target);
        } else {
            tmp.add(target);            
        }
    }

    return [...repeat];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번 이상 등장 이라는 조건을 쉽게 파악하기 위해 set 배열을 두개 만들어 관리하는 방식이 이 문제의 핵심&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>repeat</category>
      <category>Repeated DNA Sequences</category>
      <category>sliding window</category>
      <category>슬라이딩 윈도우</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/234</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Repeated-DNA-Sequences-JavaScript-Sliding-window#entry234comment</comments>
      <pubDate>Fri, 6 Mar 2026 18:15:51 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Restore IP Addresses (JavaScript), DFS</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Restore-IP-Addresses-JavaScript-DFS</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/restore-ip-addresses/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/restore-ip-addresses/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A&amp;nbsp;&lt;b&gt;valid IP address&lt;/b&gt;&amp;nbsp;consists of exactly four integers separated by single dots. Each integer is between&amp;nbsp;0&amp;nbsp;and&amp;nbsp;255&amp;nbsp;(&lt;b&gt;inclusive&lt;/b&gt;) and cannot have leading zeros.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;For example,&amp;nbsp;&quot;0.1.2.201&quot;&amp;nbsp;and&amp;nbsp;&quot;192.168.1.1&quot;&amp;nbsp;are&amp;nbsp;&lt;b&gt;valid&lt;/b&gt;&amp;nbsp;IP addresses, but&amp;nbsp;&quot;0.011.255.245&quot;,&amp;nbsp;&quot;192.168.1.312&quot;&amp;nbsp;and&amp;nbsp;&quot;192.168@1.1&quot;&amp;nbsp;are&amp;nbsp;&lt;b&gt;invalid&lt;/b&gt;&amp;nbsp;IP addresses.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a string&amp;nbsp;s&amp;nbsp;containing only digits, return&amp;nbsp;all possible valid IP addresses that can be formed by inserting dots into&amp;nbsp;s. You are&amp;nbsp;&lt;b&gt;not&lt;/b&gt;&amp;nbsp;allowed to reorder or remove any digits in&amp;nbsp;s. You may return the valid IP addresses in&amp;nbsp;&lt;b&gt;any&lt;/b&gt;&amp;nbsp;order.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;Input: s = &quot;25525511135&quot;
Output: [&quot;255.255.11.135&quot;,&quot;255.255.111.35&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;Input: s = &quot;0000&quot;
Output: [&quot;0.0.0.0&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;Input: s = &quot;101023&quot;
Output: [&quot;1.0.10.23&quot;,&quot;1.0.102.3&quot;,&quot;10.1.0.23&quot;,&quot;10.10.2.3&quot;,&quot;101.0.2.3&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= s.length &amp;lt;= 20&lt;/li&gt;
&lt;li&gt;s&amp;nbsp;consists of digits only.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&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;dfs 함수 인자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ip : 현재 저장된 ip 문자열&lt;/li&gt;
&lt;li&gt;index : 현재 검사할 s의 index&lt;/li&gt;
&lt;li&gt;part : .의 갯수 (총 4개의 파트로 구성됨)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dfs 로직
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;part가 4인 경우 4개의 파트로 ip 구성이 되었으므로 index가 LENGTH와 같다면 answer에 추가 (맞는 ip)&lt;/li&gt;
&lt;li&gt;ip를 만들 수 있는 경우의 수 (최대 3개)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 index를 기준으로 최대 3개까지 설정 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2글자 이상인 경우 시작이 0이 아니여야 함&lt;/li&gt;
&lt;li&gt;0 ~ 255 사이의 숫자 값으로 되어 있어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다음 ip를 넘겨줄 때 첫 번째 part(맨 앞 부분의 숫자)가 아니라면 . 을 추가해 ip로 넘겨줌&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {string} s
 * @return {string[]}
 */
var restoreIpAddresses = function(s) {
    const LENGTH = s.length;
    const answer = [];

    const dfs = (ip, index, part) =&amp;gt; {
        if (part === 4) {
            if (index === LENGTH) {
                answer.push(ip);
            }
            return;
        }

        for (let len = 1; len &amp;lt;= 3; len++) {
            const nextIndex = index + len;
            if (nextIndex &amp;gt; LENGTH) break;

            const word = s.slice(index, nextIndex);

            if (word.length &amp;gt; 1 &amp;amp;&amp;amp; word[0] === '0') continue;
            if (parseInt(word) &amp;gt; 255) continue;

            const nextIp = part === 0 ? word : `${ip}.${word}`;
            dfs(nextIp, nextIndex, part + 1);
        }
    }

    dfs('', 0, 0);

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;part를 사용한 부분이 문제를 좀 더 쉽게 해결할 수 있는 방법 중 하나라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 DFS로 접근해 해결&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>depth first search</category>
      <category>dfs</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Restore IP Addresses</category>
      <category>깊이우선탐색</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/233</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Restore-IP-Addresses-JavaScript-DFS#entry233comment</comments>
      <pubDate>Thu, 5 Mar 2026 18:15:34 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Sudoku (JavaScript), Backtracking</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Sudoku-JavaScript-Backtracking</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/sudoku-solver/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/sudoku-solver/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write a program to solve a Sudoku puzzle by filling the empty cells.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A sudoku solution must satisfy&amp;nbsp;&lt;b&gt;all of the following rules&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;Each of the digits&amp;nbsp;1-9&amp;nbsp;must occur exactly once in each row.&lt;/li&gt;
&lt;li&gt;Each of the digits&amp;nbsp;1-9&amp;nbsp;must occur exactly once in each column.&lt;/li&gt;
&lt;li&gt;Each of the digits&amp;nbsp;1-9&amp;nbsp;must occur exactly once in each of the 9&amp;nbsp;3x3&amp;nbsp;sub-boxes of the grid.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The&amp;nbsp;'.'&amp;nbsp;character indicates empty cells.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSvvSV/dJMcai3tPFz/bkDkng5HSaRhtg98xZgUG1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSvvSV/dJMcai3tPFz/bkDkng5HSaRhtg98xZgUG1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSvvSV/dJMcai3tPFz/bkDkng5HSaRhtg98xZgUG1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSvvSV%2FdJMcai3tPFz%2FbkDkng5HSaRhtg98xZgUG1%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;250&quot; height=&quot;250&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;Input: board = [[&quot;5&quot;,&quot;3&quot;,&quot;.&quot;,&quot;.&quot;,&quot;7&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;],[&quot;6&quot;,&quot;.&quot;,&quot;.&quot;,&quot;1&quot;,&quot;9&quot;,&quot;5&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;],[&quot;.&quot;,&quot;9&quot;,&quot;8&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;6&quot;,&quot;.&quot;],[&quot;8&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;6&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;3&quot;],[&quot;4&quot;,&quot;.&quot;,&quot;.&quot;,&quot;8&quot;,&quot;.&quot;,&quot;3&quot;,&quot;.&quot;,&quot;.&quot;,&quot;1&quot;],[&quot;7&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;2&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;6&quot;],[&quot;.&quot;,&quot;6&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;2&quot;,&quot;8&quot;,&quot;.&quot;],[&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;4&quot;,&quot;1&quot;,&quot;9&quot;,&quot;.&quot;,&quot;.&quot;,&quot;5&quot;],[&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;8&quot;,&quot;.&quot;,&quot;.&quot;,&quot;7&quot;,&quot;9&quot;]]
Output: [[&quot;5&quot;,&quot;3&quot;,&quot;4&quot;,&quot;6&quot;,&quot;7&quot;,&quot;8&quot;,&quot;9&quot;,&quot;1&quot;,&quot;2&quot;],[&quot;6&quot;,&quot;7&quot;,&quot;2&quot;,&quot;1&quot;,&quot;9&quot;,&quot;5&quot;,&quot;3&quot;,&quot;4&quot;,&quot;8&quot;],[&quot;1&quot;,&quot;9&quot;,&quot;8&quot;,&quot;3&quot;,&quot;4&quot;,&quot;2&quot;,&quot;5&quot;,&quot;6&quot;,&quot;7&quot;],[&quot;8&quot;,&quot;5&quot;,&quot;9&quot;,&quot;7&quot;,&quot;6&quot;,&quot;1&quot;,&quot;4&quot;,&quot;2&quot;,&quot;3&quot;],[&quot;4&quot;,&quot;2&quot;,&quot;6&quot;,&quot;8&quot;,&quot;5&quot;,&quot;3&quot;,&quot;7&quot;,&quot;9&quot;,&quot;1&quot;],[&quot;7&quot;,&quot;1&quot;,&quot;3&quot;,&quot;9&quot;,&quot;2&quot;,&quot;4&quot;,&quot;8&quot;,&quot;5&quot;,&quot;6&quot;],[&quot;9&quot;,&quot;6&quot;,&quot;1&quot;,&quot;5&quot;,&quot;3&quot;,&quot;7&quot;,&quot;2&quot;,&quot;8&quot;,&quot;4&quot;],[&quot;2&quot;,&quot;8&quot;,&quot;7&quot;,&quot;4&quot;,&quot;1&quot;,&quot;9&quot;,&quot;6&quot;,&quot;3&quot;,&quot;5&quot;],[&quot;3&quot;,&quot;4&quot;,&quot;5&quot;,&quot;2&quot;,&quot;8&quot;,&quot;6&quot;,&quot;1&quot;,&quot;7&quot;,&quot;9&quot;]]
Explanation:&amp;nbsp;The input board is shown above and the only valid solution is shown below:
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVbWXW/dJMcagLq8R0/xAdgEKhV8DuGJvqWhKOPbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVbWXW/dJMcagLq8R0/xAdgEKhV8DuGJvqWhKOPbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVbWXW/dJMcagLq8R0/xAdgEKhV8DuGJvqWhKOPbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVbWXW%2FdJMcagLq8R0%2FxAdgEKhV8DuGJvqWhKOPbk%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;250&quot; height=&quot;250&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;board.length == 9&lt;/li&gt;
&lt;li&gt;board[i].length == 9&lt;/li&gt;
&lt;li&gt;board[i][j]&amp;nbsp;is a digit or&amp;nbsp;'.'.&lt;/li&gt;
&lt;li&gt;It is&amp;nbsp;&lt;b&gt;guaranteed&lt;/b&gt;&amp;nbsp;that the input board has only one solution.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 행, 열, 박스 내부에 1~9 숫자가 있는지 판단하기 위해 2차원 배열 선언 (각 자리 숫자가 쓰였는지 판단하기 위해)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좌표에 따른 박스(squares)의 특정 위치 계산하는 법은 다음과 같음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(x, y) -&amp;gt; (x/9) * 3 + (y/9)&lt;/li&gt;
&lt;li&gt;x를 9로 나눈 몫에 3을 곱한 값 + y를 9로 나눈 몫&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;입력된 board를 토대로 rows, cols, squares 정보를 채워 넣음&lt;/li&gt;
&lt;li&gt;Backtracking을 통해 board를 채워나감&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Backtracking 로직&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;n (0 ~ 81)을 기준으로 재귀를 돎&lt;/li&gt;
&lt;li&gt;n 값을 기준으로 좌표 구하는 법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;n / 9 = x좌표&lt;/li&gt;
&lt;li&gt;n % 9 = y좌표&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 좌표가 '.'일 때 1~9사이의 숫자를 하나 씩 대입하면서 board를 채워나감.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주의 사항 (&lt;b&gt;return 값&lt;/b&gt;)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;board를 채워나가는 solution 함수를 단순히 재귀만 돌렸을 경우 재귀가 끝나고 백트래킹 로직에 의해 모든 곳이 '.'로 초기화 되기 때문에 제대로 구해지지 않음&lt;/li&gt;
&lt;li&gt;따라서, 재귀를 진행하는 과정에서 true를 리턴한다면 롤백 시키지 않도록 조건을 설정해줘야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`if (solution(index + 1)) { return true ; }` 부분&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solveSudoku = function(board) {
    const LENGTH = 9;
    // 각 행에 대한 정보
    const rows = Array.from({ length: LENGTH }, () =&amp;gt; Array(LENGTH).fill(false));
    // 각 열에 대한 정보
    const cols = Array.from({ length: LENGTH }, () =&amp;gt; Array(LENGTH).fill(false));
    // 특정 board 3 x 3 에 대한 정보
    const squares = Array.from({ length: LENGTH }, () =&amp;gt; Array(LENGTH).fill(false));

    // 입력된 정보를 토대로 rows, cols, squares 정보 채우기
    for (let i = 0; i &amp;lt; LENGTH; i++) {
        for (let j = 0; j &amp;lt; LENGTH; j++) {
            if (board[i][j] !== '.') {
                const target = parseInt(board[i][j]);
                rows[i][target] = true;
                cols[j][target] = true;
                squares[Math.floor(j / 3) * 3 + Math.floor(i / 3)][target] = true;
            }       
        }
    }

    const solution = (index) =&amp;gt; {
        if (index === 81) {
            return true;
        }

        const x = Math.floor(index / LENGTH);
        const y = index % LENGTH;

        if (board[y][x] === '.') {
            for (let i = 1; i &amp;lt;= LENGTH; i++) {
                const s_id = Math.floor(x / 3) * 3 + Math.floor(y / 3);
                if (!rows[y][i] &amp;amp;&amp;amp; !cols[x][i] &amp;amp;&amp;amp; !squares[s_id][i]) {
                    rows[y][i] = cols[x][i] = squares[s_id][i] = true;
                    board[y][x] = i.toString();

                    if (solution(index + 1)) {
                        return true;
                    };

                    rows[y][i] = cols[x][i] = squares[s_id][i] = false;
                    board[y][x] = '.';
                }
            }
        } else {
            return solution(index + 1);
        }

        return false;
    }

    solution(0);
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; 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;/li&gt;
&lt;li&gt;특정 좌표를 어떻게 관리할지 -&amp;gt; rows, cols, squares 각각 2차원 배열로 관리&lt;/li&gt;
&lt;li&gt;n (0 ~ 81)의 값을 토대로 x, y 좌표 구하는 법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;x 좌표 : n / 9&lt;/li&gt;
&lt;li&gt;y 좌표 : n % 9&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;x, y 좌표를 통해 특정 squares의 위치(0~8)를 구하는 법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(x / 9) * 3 + (y / 9)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>Backtracking</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>sudoku</category>
      <category>백트래킹</category>
      <category>스도쿠</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/232</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Sudoku-JavaScript-Backtracking#entry232comment</comments>
      <pubDate>Thu, 5 Mar 2026 17:38:30 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Combinations (JavaScript), Backtracking</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Combinations-JavaScript-Backtracking</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/combinations/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/combinations/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given two integers&amp;nbsp;n&amp;nbsp;and&amp;nbsp;k, return&amp;nbsp;all possible combinations of&amp;nbsp;k&amp;nbsp;numbers chosen from the range&amp;nbsp;[1, n].&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You may return the answer in&amp;nbsp;&lt;b&gt;any order&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: n = 4, k = 2
Output: [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
Explanation: There are 4 choose 2 = 6 total combinations.
Note that combinations are unordered, i.e., [1,2] and [2,1] are considered to be the same combination.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: n = 1, k = 1
Output: [[1]]
Explanation: There is 1 choose 1 = 1 total combination.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= n &amp;lt;= 20&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= k &amp;lt;= n&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일반 DFS를 통한 풀이&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k) {
    const answer = [];

    const findNums = (arr) =&amp;gt; {
        if (arr.length === k) {
            answer.push(arr);
            return;
        }

        for (let i = arr[arr.length - 1] + 1; i &amp;lt;= n; i++) {
            findNums([...arr, i]);
        }
    }

    for (let i = 1; i &amp;lt;= n; i++) {
        findNums([i]);
    }

    return answer;
};&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;2. Backtracking을 통한 풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 DFS를 통한 풀이에서 findNums를 하는 과정에서 arr을 계속해서 복제하는 로직으로 매번 새로운 배열을 생성 -&amp;gt; 배열을 복사하는 과정이 O(n)의 시간복잡도를 가지기 때문에 이를 좀 더 효율적으로 만들기 위해 Backtracking을 사용&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Backtracking을 이용하여 매번 배열을 생성하지 않고 하나의 배열을 통해 답을 구하도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 따라서 배열을 생성하는 부분이 없으므로 시간복잡도 측면에서 더 효율적임.&lt;/p&gt;
&lt;pre id=&quot;code_1772693651138&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} n
 * @param {number} k
 * @return {number[][]}
 */
var combine = function(n, k) {
    const answer = [];
    const comb = [];

    const backtracking = (index) =&amp;gt; {
        if (comb.length === k) {
            answer.push([...comb]);
            return;
        }

        for (let i = index; i &amp;lt;= n; i++) {
            comb.push(i);
            backtracking(i + 1);
            comb.pop();
        }
    }

    backtracking(1);

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 dfs를 좀 더 효율적으로 만들 수 있는 방식도 고민하면 좋을 것 같다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>Backtracking</category>
      <category>combinations</category>
      <category>dfs</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>깊이우선탐색</category>
      <category>리트코드</category>
      <category>백트래킹</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/231</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Combinations-JavaScript-Backtracking#entry231comment</comments>
      <pubDate>Thu, 5 Mar 2026 15:56:41 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Gas Station (JavaScript), Greedy</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Gas-Station-JavaScript-Greedy</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;color: #ee2323;&quot;&gt;(시간초과)&lt;/span&gt; 특정 index를 기준으로 순환이 가능한 구조인지 판단하는 로직을 통해 검사 -&amp;gt; 값이 많아지게되면 시간초과 오류가 발생&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} gas
 * @param {number[]} cost
 * @return {number}
 */
var canCompleteCircuit = function(gas, cost) {
    let answer = -1;

    const calCost = (index) =&amp;gt; {
        let tank = gas[index];

        for (let i = index + 1; i &amp;lt; gas.length; i++) {
            if (tank - cost[i - 1] &amp;lt; 0) {
                return -1;
            }
            tank = tank - cost[i - 1] + gas[i];
        }

        for (let i = 0; i &amp;lt;= index; i++) {
            let tmp = 0;
            if (i === 0) {
                tmp = tank - cost[gas.length - 1];
            } else {
                tmp = tank - cost[i - 1];
            }

            if (tmp &amp;lt; 0) {
                return -1;
            }
            tank = tmp + gas[i];
        }

        return index;
    }

    for (let i = 0; i &amp;lt; gas.length; i++) {
        answer = calCost(i);
        if (answer !== -1) {
            return answer;
        }
    }

    return answer;
};&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;2. 로직 개선 (그리디)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;모든 가스의 합이 모든 이동 비용의 합보다 크거나 같다면, 반드시 완주할 수 있는 시작점이 최소 1개는 존재&lt;/b&gt;&lt;/span&gt;한다.&lt;/li&gt;
&lt;li&gt;0부터 검사하다가 중간에 실패하면 그 사이의 값은 전부 불가능한 값이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 지점에서 출발 -&amp;gt; B 지점에서 가스가 모자라서 멈춘다면 (tank &amp;lt; 0)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A와 B 사이의 어떤 지점에서 출발해도 B를 넘길 수 없다.&lt;/li&gt;
&lt;li&gt;따라서, B지점에서 실패한다면 그 다음 후보지인 B + 1로 가서 검사를 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2]&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;시작점(0번 인덱스)에서 출발:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gas[0] - cost[0] = 1 - 3 = -2.&lt;/li&gt;
&lt;li&gt;0보다 작기 때문에 탈락&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,1,2,0&quot;&gt;다음 시작점은 1번으로.&lt;/b&gt; (currentTank는 0으로 초기화)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;1번 인덱스에서 출발:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gas[1] - cost[1] = 2 - 4 = -2.&lt;/li&gt;
&lt;li&gt;0보다 작기 때문에 탈락&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,1,2,0&quot;&gt;다음 시작점은 2번으로.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,2,0&quot;&gt;3번 인덱스(가스 4, 비용 1)에서 출발:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gas[3] - cost[3] = 4 - 1 = 3. (현재 currentTank = 3)&lt;/li&gt;
&lt;li&gt;다음 칸: gas[4] - cost[4] = 5 - 2 = 3. (현재 currentTank = 6)&lt;/li&gt;
&lt;li&gt;배열 끝까지 검사할 경우 일단 &lt;b&gt;후보자는 3번.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,3,0&quot;&gt;마지막 확인:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14,3,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;총 누적 값인 totalTank가 0 이상인지 확인합니다.&lt;/li&gt;
&lt;li&gt;0 이상이면 이전에 찾았던 후보자가 정답&lt;/li&gt;
&lt;li&gt;아니라면 -1&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q. index 3번에서 출발했는데 왜 한 바퀴를 다시 안 돌아도 되는지?&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;전체 가스 &amp;gt;= 전체 비용 조건이 만족된다면 최소 하나의 경로가 존재&lt;/b&gt;한다라는 조건이 만족한다면,&lt;/li&gt;
&lt;li&gt;3번에서 출발해서 끝까지 도착했을 때 남은 가스(currentTank)가, 앞에서 모자랐던 가스들을 충분히 채울 수 있다는게 보장이 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결국, totalTank가 0보다 크다는 것은 순환이 가능하다는 의미이고 이때 구했던 index를 리턴해주면 된다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772623574841&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} gas
 * @param {number[]} cost
 * @return {number}
 */
var canCompleteCircuit = function(gas, cost) {
    let index = 0;
    let totalTank = 0;
    let currentTank = 0;

    for (let i = 0; i &amp;lt; gas.length; i++) {
        const diff = gas[i] - cost[i];
        totalTank += diff;
        currentTank += diff;

        if (currentTank &amp;lt; 0) {
            index = i + 1;
            currentTank = 0;
        }
    }

    return totalTank &amp;gt;= 0 ? index : -1;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리디 문제이지만 최적화를 통해 시간 복잡도를 줄일 수 있는 방안을 찾아야 함.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>GAS STATION</category>
      <category>Greedy</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>그리디</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/230</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Gas-Station-JavaScript-Greedy#entry230comment</comments>
      <pubDate>Wed, 4 Mar 2026 20:40:38 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Find Minimum in Rotated Sorted Array (JavaScript), Binary Search</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Find-Minimum-in-Rotated-Sorted-Array-JavaScript-Binary-Search</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Suppose an array of length&amp;nbsp;n&amp;nbsp;sorted in ascending order is&amp;nbsp;&lt;b&gt;rotated&lt;/b&gt;&amp;nbsp;between&amp;nbsp;1&amp;nbsp;and&amp;nbsp;n&amp;nbsp;times. For example, the array&amp;nbsp;nums = [0,1,2,4,5,6,7]&amp;nbsp;might become:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[4,5,6,7,0,1,2]&amp;nbsp;if it was rotated&amp;nbsp;4&amp;nbsp;times.&lt;/li&gt;
&lt;li&gt;[0,1,2,4,5,6,7]&amp;nbsp;if it was rotated&amp;nbsp;7&amp;nbsp;times.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Notice that&amp;nbsp;&lt;b&gt;rotating&lt;/b&gt;&amp;nbsp;an array&amp;nbsp;[a[0], a[1], a[2], ..., a[n-1]]&amp;nbsp;1 time results in the array&amp;nbsp;[a[n-1], a[0], a[1], a[2], ..., a[n-2]].&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given the sorted rotated array&amp;nbsp;nums&amp;nbsp;of&amp;nbsp;&lt;b&gt;unique&lt;/b&gt;&amp;nbsp;elements, return&amp;nbsp;the minimum element of this array.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You must write an algorithm that runs in&amp;nbsp;O(log n) time.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [3,4,5,1,2]
Output: 1
Explanation: The original array was [1,2,3,4,5] rotated 3 times.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [4,5,6,7,0,1,2]
Output: 0
Explanation: The original array was [0,1,2,4,5,6,7] and it was rotated 4 times.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [11,13,15,17]
Output: 11
Explanation: The original array was [11,13,15,17] and it was rotated 4 times.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;n == nums.length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= n &amp;lt;= 5000&lt;/li&gt;
&lt;li&gt;5000 &amp;lt;= nums[i] &amp;lt;= 5000&lt;/li&gt;
&lt;li&gt;All the integers of&amp;nbsp;nums&amp;nbsp;are&amp;nbsp;&lt;b&gt;unique&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;nums&amp;nbsp;is sorted and rotated between&amp;nbsp;1&amp;nbsp;and&amp;nbsp;n&amp;nbsp;times.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;정렬되지 않은 배열에서 최소 값을 구하기위해 Binary Search를 이용 (O(log n)으로 해결해야 하므로 일반적인 알고리즘은 시간 초과)&lt;/li&gt;
&lt;li&gt;현재 mid값을 기준으로 left, right를 어떻게 움직일 것인가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mid 값보다 right값이 더 작다면 -&amp;gt; 더 작은 숫자가 우측에 있음 -&amp;gt; left 값을 mid + 1로 변경&lt;/li&gt;
&lt;li&gt;mid 값 &amp;lt; right 값 이라면 -&amp;gt; 좌측에 존재 -&amp;gt; right 값을 mid - 1로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @return {number}
 */
var findMin = function(nums) {
    let answer = Infinity;

    let [left, right] = [0, nums.length - 1];

    while (left &amp;lt;= right) {
        const mid = Math.floor((left + right) / 2);
        answer = Math.min(answer, nums[mid]);

        if (nums[mid] &amp;gt; nums[right]) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;left, right 값을 조정하기위해 어떤 값(mid)을 기준으로 할 것인지 판단하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 값을 기준으로 left, right를 어떻게 이동할 건지를 생각해야 하는 문제&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>binary search</category>
      <category>Find minimum in rotated sorted array</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>이분탐색</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/229</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Find-Minimum-in-Rotated-Sorted-Array-JavaScript-Binary-Search#entry229comment</comments>
      <pubDate>Wed, 4 Mar 2026 19:53:03 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Search a 2D Matrix (JavaScript), Binary Search</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Search-a-2D-Matrix-JavaScript-Binary-Search</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/search-a-2d-matrix/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/search-a-2d-matrix/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You are given an&amp;nbsp;m x n&amp;nbsp;integer matrix&amp;nbsp;matrix&amp;nbsp;with the following two properties:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Each row is sorted in non-decreasing order.&lt;/li&gt;
&lt;li&gt;The first integer of each row is greater than the last integer of the previous row.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an integer&amp;nbsp;target, return&amp;nbsp;true&amp;nbsp;if&amp;nbsp;target&amp;nbsp;is in&amp;nbsp;matrix&amp;nbsp;or&amp;nbsp;false&amp;nbsp;otherwise.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You must write a solution in&amp;nbsp;O(log(m * n))&amp;nbsp;time complexity.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3ryi7/dJMcadujY1r/uutsbMk8RDH0oNAYKwV2PK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3ryi7/dJMcadujY1r/uutsbMk8RDH0oNAYKwV2PK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3ryi7/dJMcadujY1r/uutsbMk8RDH0oNAYKwV2PK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3ryi7%2FdJMcadujY1r%2FuutsbMk8RDH0oNAYKwV2PK%2Fimg.jpg&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;322&quot; height=&quot;242&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
Output: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0x30j/dJMcaaYIsAY/K8JX8KUKwKdMkdes7TCIV1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0x30j/dJMcaaYIsAY/K8JX8KUKwKdMkdes7TCIV1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0x30j/dJMcaaYIsAY/K8JX8KUKwKdMkdes7TCIV1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0x30j%2FdJMcaaYIsAY%2FK8JX8KUKwKdMkdes7TCIV1%2Fimg.jpg&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;322&quot; height=&quot;242&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
Output: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;m == matrix.length&lt;/li&gt;
&lt;li&gt;n == matrix[i].length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= m, n &amp;lt;= 100&lt;/li&gt;
&lt;li&gt;104 &amp;lt;= matrix[i][j], target &amp;lt;= 104&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접근 방식&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;target이 어떤 행에 속해있는지 구하기 (index)&lt;/li&gt;
&lt;li&gt;문제에 포함 여부를 판단하는 시간 복잡도가 O(log(m * n))의 시간 복잡도를 가져야 함 -&amp;gt; 일반적인 탐색으로는 불가 -&amp;gt; Binary Search 이용&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} matrix
 * @param {number} target
 * @return {boolean}
 */
var searchMatrix = function(matrix, target) {
    let index = -1;
    for (let i = 0; i &amp;lt; matrix.length; i++) {
        if (matrix[i][0] &amp;lt;= target &amp;amp;&amp;amp; target &amp;lt;= matrix[i][matrix[0].length - 1]) {
            index = i;
            break;
        }
    }

    if (index === -1) {
        return false;
    }

    if (index !== -1 &amp;amp;&amp;amp; matrix[index].length === 1) {
        return true;
    }

    let [left, right] = [0, matrix[0].length - 1];

    while (left &amp;lt;= right) {
        const mid = Math.floor((left + right) / 2);
        if (matrix[index][mid] === target) {
            return true;
        }

        if (target &amp;lt; matrix[index][mid]) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }

    return false;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 O(log(m * n))의 시간 복잡도로 해결하기 위해 이분탐색을 이용하여 특정 숫자를 찾는 문제.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>binary search</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Search a 2D Matrix</category>
      <category>이분탐색</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/228</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Search-a-2D-Matrix-JavaScript-Binary-Search#entry228comment</comments>
      <pubDate>Wed, 4 Mar 2026 19:25:54 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 아이템 줍기 (JavaScript), BFS</title>
      <link>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%8D%EA%B8%B0-JavaScript-BFS</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/87694#&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/87694#&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 다각형 모양 지형에서 캐릭터가 아이템을 줍기 위해 이동하려 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZtbyR/dJMcaiCqUdj/ZT1mEoQg0ojtBeMq7k2qf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZtbyR/dJMcaiCqUdj/ZT1mEoQg0ojtBeMq7k2qf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZtbyR/dJMcaiCqUdj/ZT1mEoQg0ojtBeMq7k2qf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZtbyR%2FdJMcaiCqUdj%2FZT1mEoQg0ojtBeMq7k2qf0%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;850&quot; height=&quot;410&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;지형은 각 변이 x축, y축과 평행한 직사각형이 겹쳐진 형태로 표현하며, 캐릭터는 이 다각형의 둘레(굵은 선)를 따라서 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 직사각형을 겹친 후 다음과 같이 중앙에 빈 공간이 생기는 경우, 다각형의 가장 바깥쪽 테두리가 캐릭터의 이동 경로가 됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ea2ml3/dJMcaflqQvH/HBCNeDbGd9bzuSnwS3si60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ea2ml3/dJMcaflqQvH/HBCNeDbGd9bzuSnwS3si60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ea2ml3/dJMcaflqQvH/HBCNeDbGd9bzuSnwS3si60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fea2ml3%2FdJMcaflqQvH%2FHBCNeDbGd9bzuSnwS3si60%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;408&quot; height=&quot;408&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;단, 서로 다른 두 직사각형의 x축 좌표 또는 y축 좌표가 같은 경우는 없습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co1gwJ/dJMcagxQVNr/SS06ZCedn2qrYKz3vVWTC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co1gwJ/dJMcagxQVNr/SS06ZCedn2qrYKz3vVWTC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co1gwJ/dJMcagxQVNr/SS06ZCedn2qrYKz3vVWTC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco1gwJ%2FdJMcagxQVNr%2FSS06ZCedn2qrYKz3vVWTC1%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;850&quot; height=&quot;410&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위 그림처럼 서로 다른 두 직사각형이 꼭짓점에서 만나거나, 변이 겹치는 경우 등은 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 그림과 같이 지형이 2개 이상으로 분리된 경우도 없습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mWsx7/dJMcaflqQvG/KqUzlkj58eRikWFEulZKBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mWsx7/dJMcaflqQvG/KqUzlkj58eRikWFEulZKBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mWsx7/dJMcaflqQvG/KqUzlkj58eRikWFEulZKBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmWsx7%2FdJMcaflqQvG%2FKqUzlkj58eRikWFEulZKBK%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;407&quot; height=&quot;410&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;한 직사각형이 다른 직사각형 안에 완전히 포함되는 경우 또한 없습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lEvFq/dJMcaflqQvI/zRd9CJwqRFhdk1cqxzT0H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lEvFq/dJMcaflqQvI/zRd9CJwqRFhdk1cqxzT0H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lEvFq/dJMcaflqQvI/zRd9CJwqRFhdk1cqxzT0H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlEvFq%2FdJMcaflqQvI%2FzRd9CJwqRFhdk1cqxzT0H1%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;410&quot; height=&quot;408&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;지형을 나타내는 직사각형이 담긴 2차원 배열 rectangle, 초기 캐릭터의 위치 characterX, characterY, 아이템의 위치 itemX, itemY가 solution 함수의 매개변수로 주어질 때, 캐릭터가 아이템을 줍기 위해 이동해야 하는 가장 짧은 거리를 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제한사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rectangle의 세로(행) 길이는 1 이상 4 이하입니다.&lt;/li&gt;
&lt;li&gt;rectangle의 원소는 각 직사각형의 [좌측 하단 x, 좌측 하단 y, 우측 상단 x, 우측 상단 y] 좌표 형태입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직사각형을 나타내는 모든 좌표값은 1 이상 50 이하인 자연수입니다.&lt;/li&gt;
&lt;li&gt;서로 다른 두 직사각형의 x축 좌표, 혹은 y축 좌표가 같은 경우는 없습니다.&lt;/li&gt;
&lt;li&gt;문제에 주어진 조건에 맞는 직사각형만 입력으로 주어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;charcterX, charcterY는 1 이상 50 이하인 자연수입니다.
&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;/li&gt;
&lt;li&gt;itemX, itemY는 1 이상 50 이하인 자연수입니다.
&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;/li&gt;
&lt;li&gt;캐릭터와 아이템의 처음 위치가 같은 경우는 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 배점의 50%는 직사각형이 1개인 경우입니다.&lt;/li&gt;
&lt;li&gt;전체 배점의 25%는 직사각형이 2개인 경우입니다.&lt;/li&gt;
&lt;li&gt;전체 배점의 25%는 직사각형이 3개 또는 4개인 경우입니다.&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;
rectangle characterX characterY itemX itemY result
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[[1,1,7,4],[3,2,5,5],[4,3,6,9],[2,6,8,8]]&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[[1,1,8,4],[2,2,4,9],[3,6,9,8],[6,3,7,7]]&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[[1,1,5,7]]&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[[2,1,7,5],[6,4,10,10]]&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[[2,2,5,5],[1,3,6,4],[3,1,4,6]]&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #1&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb9uxA/dJMcaiCqUdk/kpACu8K8aPpZUSoNB1mqN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb9uxA/dJMcaiCqUdk/kpACu8K8aPpZUSoNB1mqN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb9uxA/dJMcaiCqUdk/kpACu8K8aPpZUSoNB1mqN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb9uxA%2FdJMcaiCqUdk%2FkpACu8K8aPpZUSoNB1mqN1%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;410&quot; height=&quot;407&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터 위치는 (1, 3)이며, 아이템 위치는 (7, 8)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #2&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQqhS6/dJMcagxQVNq/at0bWFym87zlLTUzQv5hAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQqhS6/dJMcagxQVNq/at0bWFym87zlLTUzQv5hAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQqhS6/dJMcagxQVNq/at0bWFym87zlLTUzQv5hAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQqhS6%2FdJMcagxQVNq%2Fat0bWFym87zlLTUzQv5hAK%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;407&quot; height=&quot;410&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터 위치는 (9, 7)이며, 아이템 위치는 (6, 1)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #3&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIegcE/dJMcaiCqUdi/dFw5ith5VKv3JwuTBiXMiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIegcE/dJMcaiCqUdi/dFw5ith5VKv3JwuTBiXMiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIegcE/dJMcaiCqUdi/dFw5ith5VKv3JwuTBiXMiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIegcE%2FdJMcaiCqUdi%2FdFw5ith5VKv3JwuTBiXMiK%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;410&quot; height=&quot;410&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터 위치는 (1, 1)이며, 아이템 위치는 (4, 7)입니다. 위 그림과 같이 굵은 선을 따라 이동하는 경로가 가장 짧습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #4, #5 설명 생략&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&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;u&gt;접근 방식&lt;/u&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;라인 정보를 그릴 배열 선언 (최대 길이가 50이므로 51인 배열 생성)&lt;/li&gt;
&lt;li&gt;주어진 사각형 정보를 토대로 외각 라인만 그림&lt;/li&gt;
&lt;li&gt;모든 사각형의 라인을 그리고 내부에 포함(겹쳐진)되는 부분을 제거&lt;/li&gt;
&lt;li&gt;캐릭터의 위치에서 아이템의 위치의 최단거리 구하기 (BFS)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 방식대로 구현할 경우 문제가 생김 -&amp;gt; &lt;b&gt;바로 배열의 길이를 51로 선언한 부분에서 로직의 오류가 발생함.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;e.g.) [[2,2,5,5], [1,3,6,4], [3,1,4,6]] 을 좌표로 그릴 경우 다음과 같이 그릴 수 있음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSAKvt/dJMcaadmSMj/emOjRqhnFj8OGDy8kwNik1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSAKvt/dJMcaadmSMj/emOjRqhnFj8OGDy8kwNik1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSAKvt/dJMcaadmSMj/emOjRqhnFj8OGDy8kwNik1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSAKvt%2FdJMcaadmSMj%2FemOjRqhnFj8OGDy8kwNik1%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;400&quot; height=&quot;337&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨간좌표 &lt;b&gt;(1,4) -&amp;gt; (7,3)으로 가는 최단거리를 구하는 과정에서 문제&lt;/b&gt;가 발생&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;1085&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPpWnB/dJMcaflqQWO/KI9fDeYbuRJLUejaQjzqm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPpWnB/dJMcaflqQWO/KI9fDeYbuRJLUejaQjzqm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPpWnB/dJMcaflqQWO/KI9fDeYbuRJLUejaQjzqm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPpWnB%2FdJMcaflqQWO%2FKI9fDeYbuRJLUejaQjzqm1%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;400&quot; height=&quot;357&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;1085&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보라색 위치(3,5)에서 다음 길을 선택할 때 우측(4,5)으로 가는 길도 가능한 걸로 판단하여 최단거리가 제대로 구해지지 않는 문제가 발생함 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;문제를 해결하기 위해 모든 좌표를 * 2&lt;/b&gt;&lt;/span&gt;하여 문제를 해결 (거리를 2배로 늘릴 경우 위에서 언급했던 문제들이 발생하지 않음.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;u&gt;&lt;b&gt;좌표상의 문제는 없지만 가는 경로상의 잘못된 판단을 없애기 위해 길이를 2배로 늘려 검사하도록 함.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(rectangle, characterX, characterY, itemX, itemY) {
    const map = Array.from({length: 102}, () =&amp;gt; Array.from({length: 102}, () =&amp;gt; false));
    const visited = Array.from({length: 102}, () =&amp;gt; Array.from({length: 102}, () =&amp;gt; false));
    
    // 사격형 그리기 (외부 라인)
    for (const [lx, ly, rx, ry] of rectangle) {
        // 가로 선 연결
        for (let row = lx * 2; row &amp;lt;= rx * 2; row++) {
            map[ly * 2][row] = true;
            map[ry * 2][row] = true;
        }
        
        // 세로 선 연결
        for (let col = ly * 2; col &amp;lt;= ry * 2; col++) {
            map[col][lx * 2] = true;
            map[col][rx * 2] = true;
        }
    }
    
    // 겹치는 부분 제거
    for (const [lx, ly, rx, ry] of rectangle) {
        for (let col = ly * 2 + 1; col &amp;lt; ry * 2; col++) {
            for (let row = lx * 2 + 1; row &amp;lt; rx * 2; row++) {
                map[col][row] = false;
            }
        }
    }
    
    // 탐색
    const queue = [[characterX * 2, characterY * 2, 0]];
    visited[characterY * 2][characterX * 2] = true;
    const d = [[-1, 0], [1, 0], [0, -1], [0, 1]];
    
    while (queue.length &amp;gt; 0) {
        const [cx, cy, cnt] = queue.shift();
        
        if (cx === itemX * 2 &amp;amp;&amp;amp; cy === itemY * 2) {
            return cnt / 2;
        }
        
        for (let i = 0; i &amp;lt; d.length; i++) {
            const mx = cx + d[i][0];
            const my = cy + d[i][1];
            
            if (mx &amp;gt; 0 &amp;amp;&amp;amp; mx &amp;lt; 102 &amp;amp;&amp;amp; my &amp;gt; 0 &amp;amp;&amp;amp; my &amp;lt; 102 &amp;amp;&amp;amp;
               map[my][mx] &amp;amp;&amp;amp; !visited[my][mx]) {
                queue.push([mx, my, cnt + 1]);
                visited[my][mx] = true;
            }
        }
    }
    
    return -1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최단거리를 구하는 과정에서 길이를 *2하여 잘못된 경로로 가지 않도록 하는 것이 정답을 맞출 수 있는 핵심인 것 같음.&lt;/p&gt;</description>
      <category>코딩테스트/프로그래머스</category>
      <category>BFS</category>
      <category>JavaScript</category>
      <category>너비우선탐색</category>
      <category>아이템 줍기</category>
      <category>코딩테스트</category>
      <category>탐색</category>
      <category>프로그래머스</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/227</guid>
      <comments>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%95%84%EC%9D%B4%ED%85%9C-%EC%A4%8D%EA%B8%B0-JavaScript-BFS#entry227comment</comments>
      <pubDate>Wed, 4 Mar 2026 18:49:24 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 다리를 지나는 트럭 (JavaScript), (stack/queue)</title>
      <link>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD-JavaScript-stackqueue</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42583&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42583&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트럭 여러 대가 강을 가로지르는 일차선 다리를 정해진 순으로 건너려 합니다. 모든 트럭이 다리를 건너려면 최소 몇 초가 걸리는지 알아내야 합니다. 다리에는 트럭이 최대 bridge_length대 올라갈 수 있으며, 다리는 weight 이하까지의 무게를 견딜 수 있습니다. 단, 다리에 완전히 오르지 않은 트럭의 무게는 무시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 트럭 2대가 올라갈 수 있고 무게를 10kg까지 견디는 다리가 있습니다. 무게가 [7, 4, 5, 6]kg인 트럭이 순서대로 최단 시간 안에 다리를 건너려면 다음과 같이 건너야 합니다.&lt;/p&gt;
경과 시간 다리를 지난 트럭 다리를 건너는 트럭 대기 트럭
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;[7,4,5,6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1~2&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;[7]&lt;/td&gt;
&lt;td&gt;[4,5,6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;[7]&lt;/td&gt;
&lt;td&gt;[4]&lt;/td&gt;
&lt;td&gt;[5,6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;[7]&lt;/td&gt;
&lt;td&gt;[4,5]&lt;/td&gt;
&lt;td&gt;[6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;[7,4]&lt;/td&gt;
&lt;td&gt;[5]&lt;/td&gt;
&lt;td&gt;[6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6~7&lt;/td&gt;
&lt;td&gt;[7,4,5]&lt;/td&gt;
&lt;td&gt;[6]&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;[7,4,5,6]&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;p data-ke-size=&quot;size16&quot;&gt;따라서, 모든 트럭이 다리를 지나려면 최소 8초가 걸립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;solution 함수의 매개변수로 다리에 올라갈 수 있는 트럭 수 bridge_length, 다리가 견딜 수 있는 무게 weight, 트럭 별 무게 truck_weights가 주어집니다. 이때 모든 트럭이 다리를 건너려면 최소 몇 초가 걸리는지 return 하도록 solution 함수를 완성하세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제한 조건&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bridge_length는 1 이상 10,000 이하입니다.&lt;/li&gt;
&lt;li&gt;weight는 1 이상 10,000 이하입니다.&lt;/li&gt;
&lt;li&gt;truck_weights의 길이는 1 이상 10,000 이하입니다.&lt;/li&gt;
&lt;li&gt;모든 트럭의 무게는 1 이상 weight 이하입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예&lt;/h3&gt;
bridge_length weight truck_weights return
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;[7,4,5,6]&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;[10]&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;[10,10,10,10,10,10,10,10,10,10]&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 보면 1초에 트럭이 올라가고, 지나가고 하는 행동이 이루어짐.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;크게 3부분으로 나눔&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;다리 위에 있는 트럭이 1초마다 한 칸 씩 이동&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;queue를 이용해 트럭의 현재 상황을 저장 &lt;b&gt;[트럭 무게, 남은 길이]&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;while문은 모든 트럭이 올라갈 경우 종료가 됨. 즉 queue에 남아있는 트럭에 대한 계산이 필요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; queue의 가장 마지막에 남아있는 트럭이 가장 긴 길이를 가짐(가장 마지막에 추가가 되었으므로)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, queue의 마지막 트럭의 남은 길이만큼 answer에 더해주면 총 걸리는 시간을 구할 수 있음.&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(bridge_length, weight, truck_weights) {
    var answer = 0;
    let queue = [];
    
    let index = 0;
    let sum = 0;
    while (index &amp;lt; truck_weights.length) {
        answer += 1;
        
        // 다리 위에 있는 트럭 1초마다 이동
        for (let i = 0; i &amp;lt; queue.length; i++) {
            queue[i][1] -= 1;
        }
        
        // 지니간 트럭 제거
        while (queue.length &amp;gt; 0 &amp;amp;&amp;amp; queue[0][1] &amp;lt;= 0) {
            sum -= queue[0][0];
            queue.shift();
        }
        
        // 트럭 추가
        if (sum + truck_weights[index] &amp;lt;= weight) {
            queue.push([truck_weights[index], bridge_length]);
            sum += truck_weights[index];
            index++;
        }
    }
    
    answer += queue[queue.length - 1][1];
    
    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack/queue를 이용해 문제를 접근하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트럭이 매초 어떻게 움직이는지 로직을 구하는 문제.&lt;/p&gt;</description>
      <category>코딩테스트/프로그래머스</category>
      <category>JavaScript</category>
      <category>Queue</category>
      <category>Stack</category>
      <category>다리를 지나는 트럭</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/226</guid>
      <comments>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%8B%A4%EB%A6%AC%EB%A5%BC-%EC%A7%80%EB%82%98%EB%8A%94-%ED%8A%B8%EB%9F%AD-JavaScript-stackqueue#entry226comment</comments>
      <pubDate>Tue, 3 Mar 2026 19:45:46 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 최고의 집합 (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%B5%9C%EA%B3%A0%EC%9D%98-%EC%A7%91%ED%95%A9-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12938#&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12938#&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연수 n 개로 이루어진 중복 집합(multi set, 편의상 이후에는 &quot;집합&quot;으로 통칭) 중에 다음 두 조건을 만족하는 집합을 최고의 집합이라고 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 원소의 합이 S가 되는 수의 집합&lt;/li&gt;
&lt;li&gt;위 조건을 만족하면서 각 원소의 곱 이 최대가 되는 집합&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어서 자연수 2개로 이루어진 집합 중 합이 9가 되는 집합은 다음과 같이 4개가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{ 1, 8 }, { 2, 7 }, { 3, 6 }, { 4, 5 }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 각 원소의 곱이 최대인 { 4, 5 }가 최고의 집합입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집합의 원소의 개수 n과 모든 원소들의 합 s가 매개변수로 주어질 때, 최고의 집합을 return 하는 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제한사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최고의 집합은&amp;nbsp;&lt;b&gt;오름차순으로 정렬된 1차원 배열(list, vector)&lt;/b&gt;&amp;nbsp;로 return 해주세요.&lt;/li&gt;
&lt;li&gt;만약 최고의 집합이 존재하지 않는 경우에&amp;nbsp;&lt;b&gt;크기가 1인 1차원 배열(list, vector)&lt;/b&gt;&amp;nbsp;에&amp;nbsp;1&amp;nbsp;을 채워서 return 해주세요.&lt;/li&gt;
&lt;li&gt;자연수의 개수 n은 1 이상 10,000 이하의 자연수입니다.&lt;/li&gt;
&lt;li&gt;모든 원소들의 합 s는 1 이상, 100,000,000 이하의 자연수입니다.&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;
n s result
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;[4, 5]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&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;8&lt;/td&gt;
&lt;td&gt;[4, 4]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예#1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 예시와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예#2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연수 2개를 가지고는 합이 1인 집합을 만들 수 없습니다. 따라서 -1이 들어있는 배열을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예#3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자연수 2개로 이루어진 집합 중 원소의 합이 8인 집합은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{ 1, 7 }, { 2, 6 }, { 3, 5 }, { 4, 4 }&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 각 원소의 곱이 최대인 { 4, 4 }가 최고의 집합입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;s의 합을 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;s = 13, n = 3인 경우 최고의 경우는 [4, 4, 5]&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index 0 번째 부터 가능한 최고의 수를 찾아서 넣으면됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현재 길이 len 을 기준으로 s를 나눴을 때 몫이 해당 index에 올 수 있는 최대 값.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 한 자리는 채워졌으므로 len을 1 감소 시킴(2자리에서 남은 수를 비교하기 위해)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 남은 수 s를 answer에 넣어주면 됨.&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(n, s) {
    if (n &amp;gt; s) {
        return [-1];
    }
    
    let answer = [];
    let len = n;
    for (let i = 1; i &amp;lt; n; i++) {
        answer.push(Math.floor(s / len));
        len -= 1;
        s -= answer[answer.length - 1];
    }
    
    answer.push(s);
    
    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수의 조합중 크게 만들 수 있는 경우를 생각하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 구할 수 있는 로직을 작성하면 됨&lt;/p&gt;</description>
      <category>코딩테스트/프로그래머스</category>
      <category>JavaScript</category>
      <category>구현</category>
      <category>최고의 집합</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/225</guid>
      <comments>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%B5%9C%EA%B3%A0%EC%9D%98-%EC%A7%91%ED%95%A9-JavaScript#entry225comment</comments>
      <pubDate>Tue, 3 Mar 2026 19:02:44 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 2 x n 타일링 (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-2-x-n-%ED%83%80%EC%9D%BC%EB%A7%81-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12900?language=javascript&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12900?language=javascript&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가로 길이가 2이고 세로의 길이가 1인 직사각형모양의 타일이 있습니다. 이 직사각형 타일을 이용하여 세로의 길이가 2이고 가로의 길이가 n인 바닥을 가득 채우려고 합니다. 타일을 채울 때는 다음과 같이 2가지 방법이 있습니다.&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;예를들어서 n이 7인 직사각형은 다음과 같이 채울 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t7TSs/dJMcacvugG3/dIChBExfGmh4lR6HC2nh5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t7TSs/dJMcacvugG3/dIChBExfGmh4lR6HC2nh5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t7TSs/dJMcacvugG3/dIChBExfGmh4lR6HC2nh5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft7TSs%2FdJMcacvugG3%2FdIChBExfGmh4lR6HC2nh5K%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;430&quot; height=&quot;126&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;직사각형의 가로의 길이 n이 매개변수로 주어질 때, 이 직사각형을 채우는 방법의 수를 return 하는 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제한사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가로의 길이 n은 60,000이하의 자연수 입니다.&lt;/li&gt;
&lt;li&gt;경우의 수가 많아 질 수 있으므로, 경우의 수를 1,000,000,007으로 나눈 나머지를 return해주세요.&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;
n result
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입출력 예 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 5가지 방법이 있다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMBMeQ/dJMcacoG4J9/kbodolG43u7lXUjDR81LXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMBMeQ/dJMcacoG4J9/kbodolG43u7lXUjDR81LXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMBMeQ/dJMcacoG4J9/kbodolG43u7lXUjDR81LXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMBMeQ%2FdJMcacoG4J9%2FkbodolG43u7lXUjDR81LXk%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;245&quot; height=&quot;124&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5j1x5/dJMcacoG4Ka/ixijkkHOdawyneVtSGjz1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5j1x5/dJMcacoG4Ka/ixijkkHOdawyneVtSGjz1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5j1x5/dJMcacoG4Ka/ixijkkHOdawyneVtSGjz1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5j1x5%2FdJMcacoG4Ka%2FixijkkHOdawyneVtSGjz1k%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;245&quot; height=&quot;122&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4aCDO/dJMcacvugG2/looDUvRjufsPeMBJ8aij4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4aCDO/dJMcacvugG2/looDUvRjufsPeMBJ8aij4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4aCDO/dJMcacvugG2/looDUvRjufsPeMBJ8aij4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4aCDO%2FdJMcacvugG2%2FlooDUvRjufsPeMBJ8aij4k%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;245&quot; height=&quot;124&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tslk6/dJMcabXCHFG/Ja4yQNimkoeSMpK67RdeO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tslk6/dJMcabXCHFG/Ja4yQNimkoeSMpK67RdeO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tslk6/dJMcabXCHFG/Ja4yQNimkoeSMpK67RdeO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTslk6%2FdJMcabXCHFG%2FJa4yQNimkoeSMpK67RdeO0%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;245&quot; height=&quot;124&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PQcJp/dJMcabXCHFH/CYBRNrNB0Eh0CuscTu7ETK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PQcJp/dJMcabXCHFH/CYBRNrNB0Eh0CuscTu7ETK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PQcJp/dJMcabXCHFH/CYBRNrNB0Eh0CuscTu7ETK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPQcJp%2FdJMcabXCHFH%2FCYBRNrNB0Eh0CuscTu7ETK%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;245&quot; height=&quot;124&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 dfs를 이용해 가로, 세로 타일을 만들어 구하려고 했음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법으로도 해결할 순 있지만 효율성이 낮음. -&amp;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;n = 1부터 하나씩 증가시켜 방법의 수를 구하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2, 3, 5, 8... 형태로 증가함. 즉 이거는 피보나치 수열로 문제를 해결 할 수 있음&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(n) {
    let arr = Array.from({length: n + 1}, () =&amp;gt; 0);
    arr[0] = 1;
    arr[1] = 2;
    
    for (let i = 2; i &amp;lt; n; i++) {
        arr[i] = arr[i - 2] + arr[i - 1];
        arr[i] &amp;gt; 1000000007 ? arr[i] %= 1000000007 : arr[i];
        
    }
    
    return arr[n - 1];
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 해결할 수 있는 방안에 대해 고민을 좀 더 해야할 것 같음.&lt;/p&gt;</description>
      <category>코딩테스트/프로그래머스</category>
      <category>2 x n 타일링</category>
      <category>2xn 타일링</category>
      <category>dp</category>
      <category>FIBO</category>
      <category>JavaScript</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <category>피보나치</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/224</guid>
      <comments>https://kuh97.tistory.com/entry/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-2-x-n-%ED%83%80%EC%9D%BC%EB%A7%81-JavaScript#entry224comment</comments>
      <pubDate>Tue, 3 Mar 2026 18:37:09 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Number of Islands (JavaScript), DFS</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Number-of-Islands-JavaScript-DFS</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/number-of-islands/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/number-of-islands/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an&amp;nbsp;m x n&amp;nbsp;2D binary grid&amp;nbsp;grid&amp;nbsp;which represents a map of&amp;nbsp;'1's (land) and&amp;nbsp;'0's (water), return&amp;nbsp;the number of islands.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;An&amp;nbsp;&lt;b&gt;island&lt;/b&gt;&amp;nbsp;is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;Input: grid = [
  [&quot;1&quot;,&quot;1&quot;,&quot;1&quot;,&quot;1&quot;,&quot;0&quot;],
  [&quot;1&quot;,&quot;1&quot;,&quot;0&quot;,&quot;1&quot;,&quot;0&quot;],
  [&quot;1&quot;,&quot;1&quot;,&quot;0&quot;,&quot;0&quot;,&quot;0&quot;],
  [&quot;0&quot;,&quot;0&quot;,&quot;0&quot;,&quot;0&quot;,&quot;0&quot;]
]
Output: 1

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;Input: grid = [
  [&quot;1&quot;,&quot;1&quot;,&quot;0&quot;,&quot;0&quot;,&quot;0&quot;],
  [&quot;1&quot;,&quot;1&quot;,&quot;0&quot;,&quot;0&quot;,&quot;0&quot;],
  [&quot;0&quot;,&quot;0&quot;,&quot;1&quot;,&quot;0&quot;,&quot;0&quot;],
  [&quot;0&quot;,&quot;0&quot;,&quot;0&quot;,&quot;1&quot;,&quot;1&quot;]
]
Output: 3

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;m == grid.length&lt;/li&gt;
&lt;li&gt;n == grid[i].length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= m, n &amp;lt;= 300&lt;/li&gt;
&lt;li&gt;grid[i][j]&amp;nbsp;is&amp;nbsp;'0'&amp;nbsp;or&amp;nbsp;'1'.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dfs를 이용하여 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2중 for문을 돌며 '1'인 위치에 dfs를 검사 및 answer 1증가 -&amp;gt; 이어진 경로 탐색하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- dfs 함수 : 상, 하, 좌, 우 기준 연결된 모든 지역 검사 및 지나온 구역 '0'으로 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 최종적으로 answer 리턴&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {character[][]} grid
 * @return {number}
 */
var numIslands = function(grid) {
    let answer = 0;
    let direction = [
        [0, 1],
        [1, 0],
        [-1, 0],
        [0, -1]
    ];
    
    function dfs(y, x) {
        grid[y][x] = '0';

        for (const [dx, dy] of direction) {
            const mx = x + dx;
            const my = y + dy;

            if (
                mx &amp;gt;= 0 &amp;amp;&amp;amp; mx &amp;lt; grid[0].length &amp;amp;&amp;amp; 
                my &amp;gt;= 0 &amp;amp;&amp;amp; my &amp;lt; grid.length &amp;amp;&amp;amp;
                grid[my][mx] === '1'
            ) {
                dfs(my, mx);
            }
        }
    }

    for (let i = 0; i &amp;lt; grid.length; i++) {
        for (let j = 0; j &amp;lt; grid[0].length; j++) {
            if (grid[i][j] === '1') {
                dfs(i, j);
                answer++;
            }
        }
    }

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 dfs 문제&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>depth first search</category>
      <category>dfs</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Number of islands</category>
      <category>깊이 우선 탐색</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/223</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Number-of-Islands-JavaScript-DFS#entry223comment</comments>
      <pubDate>Tue, 27 Jan 2026 17:03:15 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Valid Palindrome (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Valid-Palindrome-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/valid-palindrome/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/valid-palindrome/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A phrase is a&amp;nbsp;&lt;b&gt;palindrome&lt;/b&gt;&amp;nbsp;if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward. Alphanumeric characters include letters and numbers.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a string&amp;nbsp;s, return&amp;nbsp;true&amp;nbsp;if it is a&amp;nbsp;&lt;b&gt;palindrome&lt;/b&gt;, or&amp;nbsp;false&amp;nbsp;otherwise.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;Input: s = &quot;A man, a plan, a canal: Panama&quot;
Output: true
Explanation: &quot;amanaplanacanalpanama&quot; is a palindrome.

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;Input: s = &quot;race a car&quot;
Output: false
Explanation: &quot;raceacar&quot; is not a palindrome.

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;Input: s = &quot; &quot;
Output: true
Explanation: s is an empty string &quot;&quot; after removing non-alphanumeric characters.
Since an empty string reads the same forward and backward, it is a palindrome.

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= s.length &amp;lt;= 2 * 105&lt;/li&gt;
&lt;li&gt;s&amp;nbsp;consists only of printable ASCII characters.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 비교를 쉽게 하기 위해 전부 대문자로 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;u&gt;left, right 2개의 포인터를 두어 각각 왼쪽, 오른쪽에서 같은 값을 가지는지 검사&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 중간에 실패할 경우 palindrome 문자가 아님 return false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while 문이 끝나면 palindrome 문자로 return true&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {string} s
 * @return {boolean}
 */
var isPalindrome = function(s) {
    s = s.toUpperCase();
    let [left, right] = [0, s.length - 1];

    while (left &amp;lt; right) {
        while (left &amp;lt; right &amp;amp;&amp;amp; !(s[left] &amp;gt;= '0' &amp;amp;&amp;amp; s[left] &amp;lt;= '9') &amp;amp;&amp;amp; !(s[left] &amp;gt;= 'A' &amp;amp;&amp;amp; s[left] &amp;lt;= 'Z')) {
            left++;
        }

        while (left &amp;lt; right &amp;amp;&amp;amp; !(s[right] &amp;gt;= '0' &amp;amp;&amp;amp; s[right] &amp;lt;= '9') &amp;amp;&amp;amp; !(s[right] &amp;gt;= 'A' &amp;amp;&amp;amp; s[right] &amp;lt;= 'Z')) {
            right--;
        }

        if (s[left] !== s[right]) {
            return false;
        }
        
        left++;
        right--;
    }

    return true;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(n)의 시간복잡도로 해결하기 위해 left, right 의 포인터를 적절하게 이용하면 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;while 문 내부에 0~9, A~Z를 탐색하기 위한 while 문을 쉽게 적용하려면 정규 표현식으로 숫자 또는 문자만 포함되게 설정하면 더 짧게 코드를 작성할 수 있음&lt;/p&gt;
&lt;pre id=&quot;code_1769399504993&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 0~9, A~Z만 포함하는 문자열
s.toUpperCase().replace(/[^0-9A-Z]/g, '');&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Valid palindrome</category>
      <category>문자열</category>
      <category>정규표현식</category>
      <category>팰린드롬</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/222</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Valid-Palindrome-JavaScript#entry222comment</comments>
      <pubDate>Mon, 26 Jan 2026 12:54:17 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Best Time to Buy and Sell Stock (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Best-Time-to-Buy-and-Sell-Stock-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You are given an array&amp;nbsp;prices&amp;nbsp;where&amp;nbsp;prices[i]&amp;nbsp;is the price of a given stock on the&amp;nbsp;ith&amp;nbsp;day.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You want to maximize your profit by choosing a&amp;nbsp;&lt;b&gt;single day&lt;/b&gt;&amp;nbsp;to buy one stock and choosing a&amp;nbsp;&lt;b&gt;different day in the future&lt;/b&gt;&amp;nbsp;to sell that stock.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return&amp;nbsp;the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return&amp;nbsp;0.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: prices = [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell.

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: prices = [7,6,4,3,1]
Output: 0
Explanation: In this case, no transactions are done and the max profit = 0.

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= prices.length &amp;lt;= 105&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= prices[i] &amp;lt;= 104&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. (시간 초과)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 수익을 계산하기 위해 현재 값(i)와 i 이후의 배열의 최대 값을 구해서 계산하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prices에서 i 다음 값들 중 최대 값을 구하기 위해 `slice`를 통해 계산하는 방식은 총 O(n^2)의 시간 복잡도를 가지기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prices의 길이가 길면 시간 초과의 문제가 발생&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    let profit = 0;

    for (let i = 0; i &amp;lt; prices.length; i++) {
        const price = Math.max(...prices.slice(i + 1));
        profit = Math.max(profit, price - prices[i]);
    }

    return profit;
};&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;2. (해결)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(n)의 시간 복잡도로 문제를 해결할 수 있는 로직으로 수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 뒤 부터 계산하면서 최대 이윤을 계산하기 위해 maxPrice를 통해 현재 타겟이될 최대 값을 갱신하도록 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 i번째와 maxPrice의 차이를 통해 최대 이윤 profit을 계산하도록 함&lt;/p&gt;
&lt;pre id=&quot;code_1766906782580&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    let profit = 0;

    let maxPrice = prices[prices.length - 1];

    for (let i = prices.length - 2; i &amp;gt;=0; i--) {
        profit = Math.max(profit, maxPrice - prices[i]);

        maxPrice = Math.max(maxPrice, prices[i]);
    }

    return profit;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 이윤을 계산하기 위해 maxPrice를 최대로 갱신해주는 부분이 이 문제를 해결하는 핵심부분&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>Best Time to Buy and Sell Stock</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>node.js</category>
      <category>profit</category>
      <category>최대 이윤</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/221</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Best-Time-to-Buy-and-Sell-Stock-JavaScript#entry221comment</comments>
      <pubDate>Sun, 28 Dec 2025 16:28:34 +0900</pubDate>
    </item>
    <item>
      <title>npm install vs npm ci 차이점</title>
      <link>https://kuh97.tistory.com/entry/npm-install-vs-npm-ci-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 목록을 설치하기위해 `npm install` 또는 `npm ci` 를 활용하여 의존성 목록을 설치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 `npm install` / `npm ci`가 무엇이고 어떠한 차이점이 있는지 살펴볼 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;npm install&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`npm install`은 패키지를 설치하고 의존성 트리를 구성하는 가장 기본적인 명령어이다.&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;동작 방식&amp;nbsp;&lt;/b&gt;: `package.json` 파일의 버전 범위를 확인하고, `package-lock.json`이 있다면 이를 참고하지만&amp;nbsp;&lt;b&gt;필요에 따라 내용을 수정&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도&amp;nbsp;&lt;/b&gt;: 새로운 패키지를 추가하거나, 기존 패키지를 최신 버전으로 업데이트하고 싶을 때 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&amp;nbsp;&lt;/b&gt;: 설치 과정에서 `package-lock.json`이 업데이트될 수 있고, `node_modules` 폴더를 삭제하지 않고 필요한 부분만 변경한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, `npm install`은&amp;nbsp;&lt;b&gt;유연한 업데이트&lt;/b&gt;라고 할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;npm ci&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`npm ci`(clear-install)은 패키지를 설치하고 의존성 트리를 구성하는 명령어이다.&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; : `package.json`이 아닌 `package-lock.json`에 기록된 정보만을 토대로 설치한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용도&amp;nbsp;&lt;/b&gt;: 배포 서버 빌드, 테스트 환경, 혹은 팀원들과 완전히 동일한 개발 환경을 맞추고 싶을 때 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징&amp;nbsp;&lt;/b&gt;:&amp;nbsp;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기존 `node_modules`를&amp;nbsp;&lt;b&gt;자동으로 삭제&lt;/b&gt;하고 새로 설치&lt;/li&gt;
&lt;li&gt;`package-lock.json`을&amp;nbsp;&lt;b&gt;절대 수정하지 않음&amp;nbsp;&lt;/b&gt;(두 파일 간 버전 불일치 시 에러 발생)&lt;/li&gt;
&lt;li&gt;개별 패키지를 설치할 수 없다. (`npm ci &amp;lt;package-name&amp;gt;` 불가능)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, `npm ci`는&amp;nbsp;&lt;b&gt;엄격한 업데이트&lt;/b&gt;라고 할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 명령어는 크게 3가지의 차이점이 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;`npm install`은 `package.json`에 명시된 버전 범위 내에서 다른 버전을 설치할 가능성이 있지만, `npm ci`는 오직 `package-lock.json`에 정확히 표기된 특정 버전을 따른다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;`npm ci`는 정확한 버전을 따르기 때문에 다른 버전의 의존성을 설치하는 일을 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;또한, 버전을 결정하기 위한 연산을 수행할 필요가 없어 설치 속도에서 유리한 측면이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`npm install`은 `package-lock.json`을 변경할 가능성이 있지만, `npm ci`는 절대 변경하지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 특징으로 `npm ci`는 의존성 목록의 버전을 변경없이 일관되게 유지할 수 있게 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`npm ci`는 매번 `node_modules`를 삭제한 후 새로 설치한다. 이를 통해 이전에 설치된 의존성과 충돌로 인한 문제를 방지한다. 또한, `package-lock.json`에 따라서 매번 동일한 의존성을 설치할 것을 확실하게 보장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 차이로인해 `npm ci`는 CI/CD 환경에서 빌드 과정의 일관성을 보장하기 위한 목적으로 사용되는 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그렇다면 npm ci를 로컬 개발 환경에서 사용하는 것이 좋을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답은 없다. 상황에 따라 `npm install`, `npm ci`를 선택해서 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`npm ci`의 경우는 `node_modules`를 매번 삭제하고 새로 설치하기 때문에 불필요한 시간이 소요될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 로컬에서는 `npm install`을 사용하고, CI/CD 환경에서는 `npm ci`를 사용하는 경우가 많다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.7054%;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;&lt;b&gt;npm install&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%;&quot;&gt;&lt;b&gt;npm ci&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.7054%;&quot;&gt;&lt;b&gt;주요 목적&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;패키지 추가 및 업데이트&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%;&quot;&gt;일관된 환경 구축 (배포/테스트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.7054%;&quot;&gt;&lt;b&gt;lock 파일 수정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;상황에 따라 업데이트함&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%;&quot;&gt;&lt;b&gt;절대 수정 안함&lt;/b&gt; (읽기 전용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.7054%;&quot;&gt;&lt;b&gt;node_modules&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;기존 파일 유지하며 업데이트&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%;&quot;&gt;&lt;b&gt;기존 폴더 삭제 후 재설치&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.7054%;&quot;&gt;&lt;b&gt;속도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;보통 (의존성 계산 과정 필요)&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%;&quot;&gt;&lt;b&gt;빠름&amp;nbsp;&lt;/b&gt;(이미 계산된 lock 파일 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.7054%;&quot;&gt;&lt;b&gt;안정성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.3798%;&quot;&gt;버전 범위에 따라 달라질 수 있음&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%;&quot;&gt;&lt;b&gt;매우 높음&lt;/b&gt; (항상 동일한 버전)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>프론트엔드/JavaScript</category>
      <category>CI/CD</category>
      <category>JavaScript</category>
      <category>node_modules</category>
      <category>npm ci</category>
      <category>npm install</category>
      <category>package-lock.json</category>
      <category>package.json</category>
      <category>의존성 업데이트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/220</guid>
      <comments>https://kuh97.tistory.com/entry/npm-install-vs-npm-ci-%EC%B0%A8%EC%9D%B4%EC%A0%90#entry220comment</comments>
      <pubDate>Mon, 22 Dec 2025 17:56:20 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Decode Ways (JavaScript), (DFS, Memoization)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Decode-Ways-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/decode-ways/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/decode-ways/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You have intercepted a secret message encoded as a string of numbers. The message is&amp;nbsp;&lt;b&gt;decoded&lt;/b&gt;&amp;nbsp;via the following mapping:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;1&quot; -&amp;gt; 'A' &quot;2&quot; -&amp;gt; 'B' ... &quot;25&quot; -&amp;gt; 'Y' &quot;26&quot; -&amp;gt; 'Z'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;However, while decoding the message, you realize that there are many different ways you can decode the message because some codes are contained in other codes (&quot;2&quot;&amp;nbsp;and&amp;nbsp;&quot;5&quot;&amp;nbsp;vs&amp;nbsp;&quot;25&quot;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example,&amp;nbsp;&quot;11106&quot;&amp;nbsp;can be decoded into:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;AAJF&quot;&amp;nbsp;with the grouping&amp;nbsp;(1, 1, 10, 6)&lt;/li&gt;
&lt;li&gt;&quot;KJF&quot;&amp;nbsp;with the grouping&amp;nbsp;(11, 10, 6)&lt;/li&gt;
&lt;li&gt;The grouping&amp;nbsp;(1, 11, 06)&amp;nbsp;is invalid because&amp;nbsp;&quot;06&quot;&amp;nbsp;is not a valid code (only&amp;nbsp;&quot;6&quot;&amp;nbsp;is valid).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Note: there may be strings that are impossible to decode.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a string s containing only digits, return the&amp;nbsp;&lt;b&gt;number of ways&lt;/b&gt;&amp;nbsp;to&amp;nbsp;&lt;b&gt;decode&lt;/b&gt;&amp;nbsp;it. If the entire string cannot be decoded in any valid way, return&amp;nbsp;0.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The test cases are generated so that the answer fits in a&amp;nbsp;&lt;b&gt;32-bit&lt;/b&gt;&amp;nbsp;integer.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;s = &quot;12&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;12&quot; could be decoded as &quot;AB&quot; (1 2) or &quot;L&quot; (12).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;s = &quot;226&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;226&quot; could be decoded as &quot;BZ&quot; (2 26), &quot;VF&quot; (22 6), or &quot;BBF&quot; (2 2 6).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;s = &quot;06&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;06&quot; cannot be mapped to &quot;F&quot; because of the leading zero (&quot;6&quot; is different from &quot;06&quot;). In this case, the string is not a valid encoding, so return 0.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= s.length &amp;lt;= 100&lt;/li&gt;
&lt;li&gt;s&amp;nbsp;contains only digits and may contain leading zero(s).&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 dfs방식으로 문제를 접근할 경우 (1글자, 2글자 의 선택에 대해 재귀적으로 반복하게되면 O(2^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;이러한 문제를 해결하기 위해 DFS에 Memoization을 적용하여 불필요한 연산을 줄일 수 있다. (O(n)의 시간 복잡도를 가짐)&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;특정 index에서 검사하는 부분을 중복해서 검사하지 않도록 memo(Map)을 통해 특정 index의 res값을 저장해두어 사용&lt;/li&gt;
&lt;li&gt;dfs 리턴 조건
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;index가 s길이인 경우 끝에 도달한 것이므로 1리턴 (가능한 경로)&lt;/li&gt;
&lt;li&gt;memo에 특정 index가 이미 존재하는 경우 (해당 index를 통한 검사는 이미 이루어짐, memo에 값을 읽어와 사용)&lt;/li&gt;
&lt;li&gt;s문자열의 index값이 0인 경우 불가능한 값이므로 0리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;dfs 재귀 경로
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;한 글자 검사 (`dfs(index + 1)`)&lt;/li&gt;
&lt;li&gt;두 글자 검사(`dfs(index + 2)`)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단, 두 글자 검사는 index가 유효한 범위내에 존재하고 2글자가 유효한 범위내(10~26사이)인 경우 탐색 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;현재 index를 기준으로 한 글자, 두 글자 탐색을 진행한 뒤 res 값을 memo에 저장 (추후 불필요한 연산을 막기 위해)&lt;/li&gt;
&lt;li&gt;최종적으로 res값 리턴&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {string} s
 * @return {number}
 */
var numDecodings = function(s) {
    if (s[0] === '0') {
        return 0;
    }

    const memo = new Map();

    const dfs = (index) =&amp;gt; {
        // 끝까지 도달 -&amp;gt; 1증가
        if (index === s.length) {
            return 1;
        }

        // 이미 계산된 적이 있다면 저장 값 반환
        if (memo.has(index)) {
            return memo.get(index);
        }

        // 0이면 진행 불가
        if (s[index] === '0') {
            return 0;
        }

        // 한 글자 선택
        let res = dfs(index + 1);

        // 두 글자 선택
        if (index + 1 &amp;lt; s.length) {
            const tmp = parseInt(s.substring(index, index + 2));
            if (tmp &amp;gt;= 10 &amp;amp;&amp;amp; tmp &amp;lt;= 26) {
                res += dfs(index + 2);
            }
        }

        memo.set(index, res);
        return res;
    }

    return dfs(0);
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 적인 dfs로 쉽게 접근했지만 문자열이 길어질수록 O(2^n)의 시간 복잡도의 알고리즘으로 인해 시간 초과 문제가 발생했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 Memoization을 적용하여 불필요한 연산을 줄여 O(n)의 시간 복잡도로 문제를 해결.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀를 도는 과정에서 불필요한 연산을 줄이는게 문제의 핵심&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>Decode ways</category>
      <category>dfs</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>memoization</category>
      <category>시간 복잡도</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/219</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Decode-Ways-JavaScript#entry219comment</comments>
      <pubDate>Mon, 22 Dec 2025 17:15:44 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Triangle (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Triangle-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/triangle/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/triangle/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a&amp;nbsp;triangle&amp;nbsp;array, return&amp;nbsp;the minimum path sum from top to bottom.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For each step, you may move to an adjacent number of the row below. More formally, if you are on index&amp;nbsp;i&amp;nbsp;on the current row, you may move to either index&amp;nbsp;i&amp;nbsp;or index&amp;nbsp;i + 1&amp;nbsp;on the next row.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
Output: 11
Explanation: The triangle looks like:
23 4
 65 7
41 8 3
The minimum path sum from top to bottom is 2 + 3 + 5 + 1 = 11 (underlined above).

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;Input: triangle = [[-10]]
Output: -10

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= triangle.length &amp;lt;= 200&lt;/li&gt;
&lt;li&gt;triangle[0].length == 1&lt;/li&gt;
&lt;li&gt;triangle[i].length == triangle[i - 1].length + 1&lt;/li&gt;
&lt;li&gt;104 &amp;lt;= triangle[i][j] &amp;lt;= 104&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. dfs로 풀이 (시간 초과)&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} triangle
 * @return {number}
 */
var minimumTotal = function(triangle) {
  const dfs = (depth, index, sum, minimum) =&amp;gt; {
    if (minimum &amp;lt; sum) return;
    if (depth === triangle.length - 1) {
      return Math.min(minimum, sum);
    }

    return Math.min(
      dfs(depth + 1, index, sum + triangle[depth + 1][index], minimum), 
      dfs(depth + 1, index + 1, sum + triangle[depth + 1][index + 1], minimum)
    );
  }
    
  return dfs(0, 0, triangle[0][0], Infinity);
};&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;2. dp (top-down) - 성공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최상단에서 다음 row에 대해 각 요소에서 가장 작은 값을 계산하며 마지막 row까지 계산하고, 최종적으로 마지막 row에 가장 작은 값을 리턴하면 됨&lt;/p&gt;
&lt;pre id=&quot;code_1766131919782&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} triangle
 * @return {number}
 */
var minimumTotal = function(triangle) {
  for (let i = 1; i &amp;lt; triangle.length; i++) {
    for (let j = 0; j &amp;lt; triangle[i].length; j++) {
      if (j === 0) {
        triangle[i][j] += triangle[i - 1][j];
      } else if (j === triangle[i].length - 1) {
        triangle[i][j] += triangle[i - 1][j - 1];
      } else {
        triangle[i][j] += Math.min(triangle[i - 1][j - 1], triangle[i - 1][j]);
      }
    }
  }

  return Math.min(...triangle[triangle.length - 1]);
};&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;3. dp (bottom-up) - 성공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;top-down 방식에서 불필요한 if-else 문을 지우고 더 최적화하여 bottom-up형태로 수정&lt;/p&gt;
&lt;pre id=&quot;code_1766131960377&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} triangle
 * @return {number}
 */
var minimumTotal = function(triangle) {
    for (let i = triangle.length - 2; i &amp;gt;=0; i--) {
        for (let j = 0; j &amp;lt; triangle[i].length; j++) {
            triangle[i][j] += Math.min(
                triangle[i + 1][j],
                triangle[i + 1][j + 1]
            )
        }
    }

    return triangle[0][0];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dfs로는 depth가 깊어지면 시간 초과 오류가 발생.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp를 이용해 top-down, bottom-up 형태로 문제를 해결할 수 있고 bottom-up 형태가 불필요한 if-else 문이 없어 더 빠른 시간안에 해결이 가능하다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>bottom-up</category>
      <category>dfs</category>
      <category>dp</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>top-down</category>
      <category>Triangle</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/218</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Triangle-JavaScript#entry218comment</comments>
      <pubDate>Fri, 19 Dec 2025 17:14:31 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Subsets ll (JavaScript), backtracking</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Subsets-ll-JavaScript-backtracking</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/subsets-ii/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/subsets-ii/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an integer array&amp;nbsp;nums&amp;nbsp;that may contain duplicates, return&amp;nbsp;all possible&amp;nbsp;subsets&amp;nbsp;(the power set).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The solution set&amp;nbsp;&lt;b&gt;must not&lt;/b&gt;&amp;nbsp;contain duplicate subsets. Return the solution in&amp;nbsp;&lt;b&gt;any order&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [1,2,2]
Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;Input: nums = [0]
Output: [[],[0]]

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= nums.length &amp;lt;= 10&lt;/li&gt;
&lt;li&gt;10 &amp;lt;= nums[i] &amp;lt;= 10&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. set 자료구조를 이용해 중복 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복되는 subset에 대해서 넘어가기 위해 set에 저장하여 중복 저장이 안되도록 함.&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsetsWithDup = function(nums) {
    const answer = [];
    const set = new Set();
    nums.sort((a, b) =&amp;gt; a - b);

    const dfs = (index, arr) =&amp;gt; {
        answer.push([...arr]);

        for (let i = index; i &amp;lt; nums.length; i++) {
            if (!set.has(JSON.stringify([...arr, nums[i]]))) {
                dfs(i + 1, [...arr, nums[i]]);
                set.add(JSON.stringify([...arr, nums[i]]));
            }
        }
    }

    dfs(0, []);

    return answer;
};&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;2. backtracking 이용 (권장)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHxWqt/dJMcajndeJr/4LRQSgDtRGNF879sPqay4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHxWqt/dJMcajndeJr/4LRQSgDtRGNF879sPqay4K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHxWqt/dJMcajndeJr/4LRQSgDtRGNF879sPqay4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHxWqt%2FdJMcajndeJr%2F4LRQSgDtRGNF879sPqay4K%2Fimg.jpg&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;400&quot; height=&quot;302&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subset을 만드는 과정을 backtracking을 이용해서 표현해보면 위에 그림과 같다.&lt;/p&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;핵심은 탐색을 하는 과정에서 중복된 subset이 존재할 수 있다는 것이다. (파란색 부분)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 0번째 요소 2에서 만들어지는 [2, 3], [2]는 1번째 요소 2로 만들어지는 [2, 3], [2]와 중복되는 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 중복을 방지하기 위해 &lt;b&gt;특정 요소를 선택 안할 경우에 대해 탐색을 진행한 요소와 겹치는 값은 건너뛰도록 index를 조정&lt;/b&gt;해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 0번째에서 2를 검사했으면 1번째에 나오는 2에 대해 탐색을 진행 안해도 됨.&lt;/p&gt;
&lt;pre id=&quot;code_1766048068200&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsetsWithDup = function(nums) {
    const answer = [];
    const subset = [];
    nums.sort((a, b) =&amp;gt; a - b);

    const backtracking = (index) =&amp;gt; {
        if (index === nums.length) {
            answer.push([...subset]);
            return;
        }

        subset.push(nums[index]);
        backtracking(index + 1);
        subset.pop();

        // 중복 숫자 건너 뛰기
        while (index &amp;lt; nums.length &amp;amp;&amp;amp; nums[index] === nums[index + 1]) {
            index++;
        }

        backtracking(index + 1);
    }

    backtracking(0);

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 경우 매번 배열을 문자열로 바꾸는 비용과 set함수를 관리하는 비용을 고려해볼 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(N*(2^N)*N)의 시간 복잡도와 O(N*(2^N))의 공간 복잡도를 가진다.&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;1번을 좀 더 효율적으로 변경하기 위해 backtracking을 이용하여 풀이하게 되면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(N*(2^N))의 시간 복잡도와 O(N)의 공간 복잡도로 문제를 해결 할 수 있다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>2^n</category>
      <category>Backtracking</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>set</category>
      <category>Subsets ll</category>
      <category>백트래킹</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/217</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Subsets-ll-JavaScript-backtracking#entry217comment</comments>
      <pubDate>Thu, 18 Dec 2025 18:04:18 +0900</pubDate>
    </item>
    <item>
      <title>TodoList 만들기 - 성능 개선 (2)</title>
      <link>https://kuh97.tistory.com/entry/TodoList-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-2</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개요&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글에서 불필요한 리렌더링, 불필요한 메모리 낭비 방지를 위해 `useCallback`, `React.memo`를 활용하여 개선을 진행하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 &lt;b&gt;추가/삭제/토글 버튼에 대해서 모든 todo가 리렌더링되는 문제가 계속 발생&lt;/b&gt;했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 `useCallback`의 설정 개선과 `React.memo`가 왜 제대로 동작하지 않았는지를 살펴보고 개선할 예정이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;useCallback 의존성 배열 설정의 문제&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;리렌더링 시 함수 재생성을 방지하기 위해 `useCallback` 훅을 이용하여 설정을 해두었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 추가/삭제/토글 버튼을 클릭할 경우 todo가 바뀌면서 `useCallback`의 &lt;b&gt;의존성 배열로 설정해둔 todo의 영향을 받아 함수가 다시 생성되는 문제가 발생&lt;/b&gt;되었던 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 함수가 재생성되면서 함수의 참조 값이 달라져 `React.memo`로 설정해두었던 `TodoItem`이 리렌더링되는 문제가 발생했던 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그러면 &lt;u&gt;의존성 배열에 todos를 빼면 함수가 다시 생성되지 않고 `TodoItem` 컴포넌트 리렌더링에 영향을 주지 않을까?&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1765965290709&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoList.tsx
...

  const toggleTodo = useCallback((id: number) =&amp;gt; {
    setTodos(
      todos.map((todo) =&amp;gt;
        todo.id === id ? { ...todo, completed: !todo.completed } : todo,
      ),
    );
  }, []); // 의존성 배열에 todos 제거
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;답은&amp;nbsp;&lt;b&gt;그렇다&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;toggleTodo 함수가 재생성되지 않아 결국 같은 참조 값을 사용하므로 `TodoItem` 리렌더링에 영향을 주지 않게된다.&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;하지만 여기에는 문제가 있다. &lt;u&gt;과연 저 toggleTodo를 통해 completed 상태를 변경하는게 제대로 동작할까?&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;답은&amp;nbsp;&lt;b&gt;아니다&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이와 같은 현상이 일어나는 이유는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수의 재생성 방지를 위해 의존성 배열을 비워두어 completed 상태가 제대로 변경되지 않았는데 `useCallback`의 클로저(Closure)문제 때문이다.&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;`useCallback`의 의존성 배열을 비워둘 경우 생기는 문제 상황&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;첫 렌더링 시점의 기억&lt;/b&gt; : `useCallback`은 처음에 만들어진 함수를 메모리에 저장한다. 이때 함수 내부의 `todos` 변수는 초기값(빈 배열이나 첫 상태)를 참조하게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;업데이트의&lt;/b&gt;&amp;nbsp;&lt;b&gt;부재&amp;nbsp;&lt;/b&gt;: 이후 새로운 todo가 추가되어 `todos` 상태가 바뀌어도, 메모리에 저장된 `toggleTodo` 함수는 여전히 맨 처음의 `todos`만 참조하고 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결과&amp;nbsp;&lt;/b&gt;: 결국 함수를 실행하면 예전의 `todos`를 바탕으로 업데이트를 시도하기 때문에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;최신 데이터가 반영되지 않거나, 상태가 꼬이게 되는 문제&lt;/b&gt;&lt;/span&gt;가 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이와 같은 문제를 해결하기 위해서는 의존성 배열에 `[todos]`를 넣어야 한다. 하지만 이렇게 되면 결국 리렌더링 할 때마다 함수가 재생성되어 결국 메모리 낭비가 발생되고 `useCallback`을 사용하는 의미가 없어진다.&lt;/span&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;span style=&quot;color: #000000;&quot;&gt;그러면 &lt;u&gt;`useCallback`을 사용해 함수의 재생성을 방지하면서 `todos`의 최신 상태를 참조할 수 있는 방법은 없을까?&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;`todos`의 최신 상태를 참조하기 위해&amp;nbsp;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;함수형 업데이트&lt;/b&gt;&lt;/span&gt;를 이용할 수 있다.&lt;/span&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수형 업데이트를 사용하면 리액트가 현재 시점의 최신 상태 값을 함수의 인자로 직접 넣어주기 때문에 `useCallback` 함수는 외부 변수인 `todos`를 직접 참조할 필요가 없어진다.&lt;/li&gt;
&lt;/ul&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;최신 상태를 인자로 받기 때문에 의존성 배열에 `todos`를 넣지 않아도 된다. 즉, 함수의 주소값을 영구적으로 고정하면서 데이터는 항상 최신으로 유지할 수 있게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;함수형 업데이트란?&lt;/span&gt;&lt;br /&gt;기존 상태 값을 직접 참조하지 않고, 이전 상태를 인자로 받는 함수로 다음 상태를 계산하는 방식을 말한다.&lt;br /&gt;e.g. `setTodos(prev =&amp;gt; ...)` 형태로 사용&lt;br /&gt;즉, React가 prev를 통해 최신 상태 값을 보장해준다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1765966888601&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoList.tsx
...

  const toggleTodo = useCallback((id: number) =&amp;gt; {
    // 함수형 업데이트(prev)를 통한 todos 업데이트
    setTodos((prev) =&amp;gt;
      prev.map((todo) =&amp;gt;
        todo.id === id ? { ...todo, completed: !todo.completed } : todo,
      ),
    );
  }, []);
  
  ...&lt;/code&gt;&lt;/pre&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 개선을 통해 단순히 `useCallback`, `React.memo`를 사용하는 것만으로는 리렌더링 최적화가 완성되지 않는다는 점을 다시 한 번 확인할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;401&quot; data-start=&quot;302&quot; data-ke-size=&quot;size16&quot;&gt;`useCallback`은 &lt;b&gt;함수의 재생성을 막는 도구&lt;/b&gt;일 뿐이고 의존성 배열과 클로저 특성을 제대로 이해하지 못하면 오히려 상태 불일치나 버그를 만들어낼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;421&quot; data-start=&quot;403&quot; 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-end=&quot;516&quot; data-start=&quot;422&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;443&quot; data-start=&quot;422&quot;&gt;함수의 참조값은 안정적으로 유지하고&lt;/li&gt;
&lt;li data-end=&quot;470&quot; data-start=&quot;444&quot;&gt;상태는 &lt;b&gt;항상 최신 값&lt;/b&gt;을 기준으로 업데이트하며&lt;/li&gt;
&lt;li data-end=&quot;516&quot; data-start=&quot;471&quot;&gt;`React.memo`가 의도한 대로 동작할 수 있는 구조를 만들 수 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;597&quot; data-start=&quot;518&quot; data-ke-size=&quot;size16&quot;&gt;결국 리렌더링 최적화의 핵심은 &lt;b&gt;메모이제이션 자체&lt;/b&gt;가 아니라 &lt;b&gt;상태 흐름을 어떻게 설계&lt;/b&gt;하느냐 라는 점을 다시 한 번 느끼게 되었다.&lt;/p&gt;</description>
      <category>프론트엔드/React</category>
      <category>REACT</category>
      <category>React.memo</category>
      <category>todo app</category>
      <category>TodoList 성능 개선</category>
      <category>useCallback</category>
      <category>메모이제이션</category>
      <category>상태 흐름</category>
      <category>투두리스트 성능 개선</category>
      <category>함수형 업데이트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/216</guid>
      <comments>https://kuh97.tistory.com/entry/TodoList-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-2#entry216comment</comments>
      <pubDate>Wed, 17 Dec 2025 19:29:27 +0900</pubDate>
    </item>
    <item>
      <title>TodoList 만들기 - 성능 개선 (1)</title>
      <link>https://kuh97.tistory.com/entry/TodoList-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-1</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 간단한 Todo App을 만들어보았다. 하지만 이러한 앱은 성능상 많은 문제가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이러한 문제들과 어떻게 해결할 수 있을지에 대해 다뤄볼 예정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 Todo App의 문제점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;1. 불필요한 리렌더링&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 React의 특성상 state의 값이 변경되면 React가 변경을 감지하고 관련 컴포넌트를 리렌더링 하게 된다.&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;현재 state값으로 설정되어있는 값은 `todos`, `newTodo` 두 개가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 todos를 추가하거나 삭제, completed 값을 변경하기 위한 `addTodo`, `deleteTodo`, `toggleTodo` 세 개가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수는 `setTodos`를 통해 todos의 값을 변경하므로 위 함수가 불리게 되면 todos에 영향을 줘 리렌더링이 이루어진다.&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;결국, 새로운 todo를 추가하기 위해 input에 입력하는과정과 새로운 투두를 추가하거나 삭제, completed를 변경하는 함수를 호출하면 모든 todos가 리렌더링 되게 된다. 이러한 상황은 &lt;b&gt;모든 todo가 불필요하게 리렌더링되는 문제가 발생&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;u&gt;2. 함수 재생성 (불필요한 메모리 낭비)&lt;/u&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TodoList 컴포넌트가 리렌더링이 이루어질 때 `addTodo`, `deleteTodo`, `toggleTodo` 함수가 매번 새로 생성되는 걸 확인할 수 있다. 즉, 불필요하게 함수를 재생성하여 메모리를 낭비하는 문제가 발생한다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 렌더링 때마다 함수가 재생성되는 걸 방지하기 위해 `useCallback` 훅을 이용하여 함수가 재생성되는 걸 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 특정 todo만 리렌더링이 이루어지게 하기 위해 `React.memo` 를 이용하여 `TodoItem` 컴포넌트가 리렌더링 되는 걸 방지할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;React.memo 란?&lt;br /&gt;React.memo로 설정한 컴포넌트는 Props의 값이 변경되지 않는다면 리렌더링이 되지 않는다.&lt;br /&gt;&lt;br /&gt;useCallback 이란?&lt;br /&gt;특정 조건(의존성 배열)이 바뀌지 않는 한, 이 함수를 새로 만들지 않고 기존에 만든 함수를 그대로 사용&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TodoList, TodoItem 컴포넌트 개선&lt;/h2&gt;
&lt;pre id=&quot;code_1765963459706&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoList.tsx
...
const addTodo = useCallback(() =&amp;gt; {
    const item: Todo = {
      userId: Date.now(),
      id: Date.now(),
      title: newTodo,
      completed: false,
    };

    setTodos([...todos, item]);
    setNewTodo(&quot;&quot;);
  }, [newTodo, todos]);

  const deleteTodo = useCallback(
    (id: number) =&amp;gt; {
      setTodos(todos.filter((todo) =&amp;gt; todo.id !== id));
    },
    [todos],
  );

  const toggleTodo = useCallback(
    (id: number) =&amp;gt; {
      setTodos(
        todos.map((todo) =&amp;gt;
          todo.id === id ? { ...todo, completed: !todo.completed } : todo,
        ),
      );
    },
    [todos],
  );
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;pre id=&quot;code_1765963481109&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoItem.tsx
import { Todo } from &quot;./types&quot;;
import React from &quot;react&quot;;

interface TodoItemProps {
  todo: Todo;
  deleteTodo: (id: number) =&amp;gt; void;
  toggleTodo: (id: number) =&amp;gt; void;
}

export const TodoItem = React.memo(
  ({ todo, deleteTodo, toggleTodo }: TodoItemProps) =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;input
          type=&quot;checkbox&quot;
          checked={todo.completed}
          onChange={() =&amp;gt; toggleTodo(todo.id)}
        /&amp;gt;
        {todo.title}
        &amp;lt;button onClick={() =&amp;gt; deleteTodo(todo.id)}&amp;gt;삭제&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  },
);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`TodoItem` 컴포넌트를 `React.memo`로 설정하여 Props가 변하지 않으면 리렌더링이 이루어지지않도록 설정을 하면 더 이상 새로운 todo를 추가하기 위해 input을 입력하는 과정에서 todos가 리렌더링이 이루어지지 않는다.&lt;/p&gt;

            &lt;figure class=&quot;unsupported component-kakaotv&quot; contenteditable=&quot;false&quot; style=&quot;background:#000;margin:16px 0;min-height:72px;padding:10px 16px;display:flex;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;width:100%;max-width:100%;&quot;&gt;
                &lt;p contenteditable=&quot;false&quot; style=&quot;margin:0;color:#8a8a8a;font-size:13px;line-height:1.6;user-select:none;pointer-events:none;&quot;&gt;동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.&lt;/p&gt;
            &lt;/figure&gt;
        
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&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;새로운 input 입력에 대해서 TodoItem이 리렌더링이 이루어지지 않게 개선을 하였지만, 추가/삭제/토글 버튼을 클릭할 경우 다시 모든 todo가 리렌더링이 되는 문제가 계속해서 발생하고 있는 걸 볼 수 있다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 추가/삭제/토글 버튼을 클릭하면 모든 todo가 리렌더링이 이루어지는 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`useCallback`의 문제와 이로 인한 `React.memo`가 제대로 동작하지 않는 이유를 다음 글에서 살펴 볼 예정이다.&lt;/p&gt;</description>
      <category>프론트엔드/React</category>
      <category>REACT</category>
      <category>React.memo</category>
      <category>Todo 앱 성능 개선</category>
      <category>TodoList 성능 개선</category>
      <category>useCallback</category>
      <category>리렌더링 방지</category>
      <category>메모리 낭비 방지</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/215</guid>
      <comments>https://kuh97.tistory.com/entry/TodoList-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-1#entry215comment</comments>
      <pubDate>Wed, 17 Dec 2025 18:40:03 +0900</pubDate>
    </item>
    <item>
      <title>TodoList 만들기</title>
      <link>https://kuh97.tistory.com/entry/TodoList-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React를 이용하여 간단한 TodoList를 만들어보고&amp;nbsp;`useCallback`, `React.memo`, `함수형 업데이트` 등을 이용해 하나씩 개선해 볼 예정이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TodoList 만들기 - 초기 세팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 React 앱을 만들기 위해 터미널에 아래와 같은 명령어를 통해 간단한 React App을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1765961840054&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm create vite@latest my-todo-app -- --template react&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;TodoList 정보는 아래 todos 정보를 이용할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jsonplaceholder.typicode.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jsonplaceholder.typicode.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1765961938854&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JSONPlaceholder - Free Fake REST API&quot; data-og-description=&quot;{JSON} Placeholder Free fake and reliable API for testing and prototyping. Powered by JSON Server + LowDB. Serving ~3 billion requests each month.&quot; data-og-host=&quot;jsonplaceholder.typicode.com&quot; data-og-source-url=&quot;https://jsonplaceholder.typicode.com/&quot; data-og-url=&quot;https://jsonplaceholder.typicode.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://jsonplaceholder.typicode.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jsonplaceholder.typicode.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JSONPlaceholder - Free Fake REST API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{JSON} Placeholder Free fake and reliable API for testing and prototyping. Powered by JSON Server + LowDB. Serving ~3 billion requests each month.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jsonplaceholder.typicode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TodoList.tsx, TodoItem.tsx 생성&lt;/h2&gt;
&lt;pre id=&quot;code_1765961969983&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoList.tsx
import { useEffect, useState } from &quot;react&quot;;
import { Todo } from &quot;./types&quot;;
import { TodoItem } from &quot;./TodoItem&quot;;

export const TodoList = () =&amp;gt; {
  const [todos, setTodos] = useState&amp;lt;Todo[]&amp;gt;([]);
  const [newTodo, setNewTodo] = useState(&quot;&quot;);

  useEffect(() =&amp;gt; {
    const fetchData = async () =&amp;gt; {
      try {
        const response = await fetch(
          &quot;https://jsonplaceholder.typicode.com/todos&quot;,
        );

        if (!response.ok) {
          throw new Error(&quot;Response Error&quot;);
        }

        const data: Todo[] = await response.json();
        setTodos(data);
      } catch (error) {
        console.log(error);
      }
    };

    fetchData();
  }, []);

  const addTodo = () =&amp;gt; {
    const item: Todo = {
      userId: Date.now(),
      id: Date.now(),
      title: newTodo,
      completed: false,
    };

    setTodos([...todos, item]);
    setNewTodo(&quot;&quot;);
  };

  const deleteTodo = (id: number) =&amp;gt; {
    setTodos(todos.filter((todo) =&amp;gt; todo.id !== id));
  };

  const toggleTodo = (id: number) =&amp;gt; {
    setTodos(
      todos.map((todo) =&amp;gt;
        todo.id === id ? { ...todo, completed: !todo.completed } : todo,
      ),
    );
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Todo List&amp;lt;/h1&amp;gt;

      &amp;lt;input
        type=&quot;text&quot;
        value={newTodo}
        onChange={(e) =&amp;gt; setNewTodo(e.target.value)}
      /&amp;gt;
      &amp;lt;button onClick={addTodo}&amp;gt;추가&amp;lt;/button&amp;gt;

      &amp;lt;ul&amp;gt;
        {todos.map((todo) =&amp;gt; (
          &amp;lt;li key={todo.id}&amp;gt;
            &amp;lt;TodoItem
              todo={todo}
              deleteTodo={deleteTodo}
              toggleTodo={toggleTodo}
            /&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;pre id=&quot;code_1765962004741&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// TodoItem.tsx
import { Todo } from &quot;./types&quot;;

interface TodoItemProps {
  todo: Todo;
  deleteTodo: (id: number) =&amp;gt; void;
  toggleTodo: (id: number) =&amp;gt; void;
}

export const TodoItem = ({ todo, deleteTodo, toggleTodo }: TodoItemProps) =&amp;gt; {
  console.log(todo.id);
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        type=&quot;checkbox&quot;
        checked={todo.completed}
        onChange={() =&amp;gt; toggleTodo(todo.id)}
      /&amp;gt;
      {todo.title}
      &amp;lt;button onClick={() =&amp;gt; deleteTodo(todo.id)}&amp;gt;삭제&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;pre id=&quot;code_1765962021214&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// types.ts
export interface Todo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`npm run dev` 명령어를 통해 앱을 실행하면 다음과 같은 화면 구성을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;1898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OtAIt/dJMcacuUdJD/MjWANfD7xeKcW4zQszYAb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OtAIt/dJMcacuUdJD/MjWANfD7xeKcW4zQszYAb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OtAIt/dJMcacuUdJD/MjWANfD7xeKcW4zQszYAb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOtAIt%2FdJMcacuUdJD%2FMjWANfD7xeKcW4zQszYAb1%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;600&quot; height=&quot;652&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;1898&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;todos 정보를 불러와 간단하게 화면에 출력하는 todo app을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 만들어진 todo app은 성능상 여러 문제가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서 위 앱의 문제점들과 어떻게 개선할 수 있는지에 대해 다뤄볼 예정이다.&lt;/p&gt;</description>
      <category>프론트엔드/React</category>
      <category>JavaScript</category>
      <category>REACT</category>
      <category>React 성능 개선</category>
      <category>todo app</category>
      <category>typeScript</category>
      <category>렌더링</category>
      <category>투두리스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/214</guid>
      <comments>https://kuh97.tistory.com/entry/TodoList-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry214comment</comments>
      <pubDate>Wed, 17 Dec 2025 18:03:35 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Search in Rotated Sorted Array (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Search-in-Rotated-Sorted-Array-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/search-in-rotated-sorted-array/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/search-in-rotated-sorted-array/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;There is an integer array&amp;nbsp;nums&amp;nbsp;sorted in ascending order (with&amp;nbsp;&lt;b&gt;distinct&lt;/b&gt;&amp;nbsp;values).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prior to being passed to your function,&amp;nbsp;nums&amp;nbsp;is&amp;nbsp;&lt;b&gt;possibly left rotated&lt;/b&gt;&amp;nbsp;at an unknown index&amp;nbsp;k&amp;nbsp;(1 &amp;lt;= k &amp;lt; nums.length) such that the resulting array is&amp;nbsp;[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]&amp;nbsp;(&lt;b&gt;0-indexed&lt;/b&gt;). For example,&amp;nbsp;[0,1,2,4,5,6,7]&amp;nbsp;might be left rotated by&amp;nbsp;3&amp;nbsp;indices and become&amp;nbsp;[4,5,6,7,0,1,2].&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given the array&amp;nbsp;nums&amp;nbsp;&lt;b&gt;after&lt;/b&gt;&amp;nbsp;the possible rotation and an integer&amp;nbsp;target, return&amp;nbsp;the index of&amp;nbsp;target&amp;nbsp;if it is in&amp;nbsp;nums, or&amp;nbsp;-1&amp;nbsp;if it is not in&amp;nbsp;nums.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You must write an algorithm with&amp;nbsp;O(log n)&amp;nbsp;runtime complexity.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: nums = [1], target = 0
Output: -1

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= nums.length &amp;lt;= 5000&lt;/li&gt;
&lt;li&gt;104 &amp;lt;= nums[i] &amp;lt;= 104&lt;/li&gt;
&lt;li&gt;All values of&amp;nbsp;nums&amp;nbsp;are&amp;nbsp;&lt;b&gt;unique&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;nums&amp;nbsp;is an ascending array that is possibly rotated.&lt;/li&gt;
&lt;li&gt;104 &amp;lt;= target &amp;lt;= 104&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mid를 기준으로 왼쪽, 오른쪽 둘 중 하나는 정렬이 되어있다를 이용하여 문제를 접근&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. mid를 기준으로 좌측이 정렬되어 있는 경우 (nums[left] &amp;lt;= nums[mid])&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;target이 좌측안에 존재하는지 판단
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;있다면 right 값을 mid - 1로&lt;/li&gt;
&lt;li&gt;없다면 우측에 있으므로 left 값을 mid + 1로&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. mid를 기준으로 우측이 정렬되어 있는 경우 (nums[mid] &amp;lt;= nums[right])&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;target이 우측안에 존재하는지 판단
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;있다면 left 값을 mid + 1로&lt;/li&gt;
&lt;li&gt;없다면 좌측에 있으므로 right 값을 mid - 1로&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. mid 값이 target과 같다면 mid 리턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. while 문이 끝나면 발견을 하지 못하는 값이므로 -1 리턴&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let [left, right] = [0, nums.length - 1];

    while (left &amp;lt;= right) {
        const mid = Math.floor((left + right) / 2);

        if (nums[mid] === target) {
            return mid;
        }

        // mid 기준 좌측이 정렬되어있다.
        if (nums[left] &amp;lt;= nums[mid]) {
            // 좌측 정렬 안에 값이 존재하는지 확인
            if (nums[left] &amp;lt;= target &amp;amp;&amp;amp; target &amp;lt; nums[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        } else { // mid 기준 우측이 정렬되어있다.
            // 우측 정렬 안에 값이 존재하는지 확인
            if (nums[mid] &amp;lt; target &amp;amp;&amp;amp; target &amp;lt;= nums[right]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }

    return -1;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Binary Search를 어떻게 진행할 건지를 판단하는게 핵심&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통은 모두 정렬된 상태에서 Binary Search를 진행하지만 이 문제는 정렬이 되어있지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정렬된 구간을 통해 값을 구해나가는 방식으로 문제를 접근해야 함.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>binary search</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Search in Rotated Sorted Array</category>
      <category>이분탐색</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/213</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Search-in-Rotated-Sorted-Array-JavaScript#entry213comment</comments>
      <pubDate>Wed, 17 Dec 2025 17:16:31 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Remove Duplicates from Sorted Array ll (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Remove-Duplicates-from-Sorted-Array-ll-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/remove-duplicates-from-sorted-array-ii/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an integer array&amp;nbsp;nums&amp;nbsp;sorted in&amp;nbsp;&lt;b&gt;non-decreasing order&lt;/b&gt;, remove some duplicates&amp;nbsp;&lt;a href=&quot;https://en.wikipedia.org/wiki/In-place_algorithm&quot;&gt;&lt;b&gt;in-place&lt;/b&gt;&lt;/a&gt;&amp;nbsp;such that each unique element appears&amp;nbsp;&lt;b&gt;at most twice&lt;/b&gt;. The&amp;nbsp;&lt;b&gt;relative order&lt;/b&gt;&amp;nbsp;of the elements should be kept the&amp;nbsp;&lt;b&gt;same&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Since it is impossible to change the length of the array in some languages, you must instead have the result be placed in the&amp;nbsp;&lt;b&gt;first part&lt;/b&gt;&amp;nbsp;of the array&amp;nbsp;nums. More formally, if there are&amp;nbsp;k&amp;nbsp;elements after removing the duplicates, then the first&amp;nbsp;k&amp;nbsp;elements of&amp;nbsp;nums&amp;nbsp;should hold the final result. It does not matter what you leave beyond the first&amp;nbsp;k&amp;nbsp;elements.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return&amp;nbsp;k&amp;nbsp;after placing the final result in the first&amp;nbsp;k&amp;nbsp;slots of&amp;nbsp;nums.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Do&amp;nbsp;&lt;b&gt;not&lt;/b&gt;&amp;nbsp;allocate extra space for another array. You must do this by&amp;nbsp;&lt;b&gt;modifying the input array&amp;nbsp;&lt;a href=&quot;https://en.wikipedia.org/wiki/In-place_algorithm&quot;&gt;in-place&lt;/a&gt;&lt;/b&gt;&amp;nbsp;with O(1) extra memory.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Custom Judge:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The judge will test your solution with the following code:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;int[] nums = [...]; // Input array
int[] expectedNums = [...]; // The expected answer with correct length

int k = removeDuplicates(nums); // Calls your implementation

assert k == expectedNums.length;
for (int i = 0; i &amp;lt; k; i++) {
    assert nums[i] == expectedNums[i];
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If all assertions pass, then your solution will be&amp;nbsp;&lt;b&gt;accepted&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: nums = [1,1,1,2,2,3]
Output: 5, nums = [1,1,2,2,3,_]
Explanation: Your function should return k = 5, with the first five elements of nums being 1, 1, 2, 2 and 3 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: nums = [0,0,1,1,1,1,2,3,3]
Output: 7, nums = [0,0,1,1,2,3,3,_,_]
Explanation: Your function should return k = 7, with the first seven elements of nums being 0, 0, 1, 1, 2, 3 and 3 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= nums.length &amp;lt;= 3 * 104&lt;/li&gt;
&lt;li&gt;104 &amp;lt;= nums[i] &amp;lt;= 104&lt;/li&gt;
&lt;li&gt;nums&amp;nbsp;is sorted in&amp;nbsp;&lt;b&gt;non-decreasing&lt;/b&gt;&amp;nbsp;order.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 포인터를 이용하여 문제를 풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 최대 2개의 연속된 수를 가질 수 있으므로 시작은 2부터 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 현재 요소가 유효한지 확인&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nums[i] !== nums[index - 2]&lt;/li&gt;
&lt;li&gt;현재 요소(nums[i])가 nums[index - 2]와 같다면 3개 이상 존재하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 배열 업데이트&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;nums[index] = nums[i]; index++;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;1590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EqP2o/dJMcaaRohJp/057ECrKHcVK5ukJHMJtvdK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EqP2o/dJMcaaRohJp/057ECrKHcVK5ukJHMJtvdK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EqP2o/dJMcaaRohJp/057ECrKHcVK5ukJHMJtvdK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEqP2o%2FdJMcaaRohJp%2F057ECrKHcVK5ukJHMJtvdK%2Fimg.jpg&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;450&quot; height=&quot;643&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;1590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function(nums) {
    let index = 2;

    for (let i = 2; i &amp;lt; nums.length; i++) {
        if (nums[i] !== nums[index - 2]) {
            nums[index] = nums[i];
            index++;
        }
    }

    return index;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 원소를 최대 2개만 보이도록 하는 로직을 생각하는게 핵심&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Remove Duplicates from Sorted Array ll</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/212</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Remove-Duplicates-from-Sorted-Array-ll-JavaScript#entry212comment</comments>
      <pubDate>Wed, 17 Dec 2025 16:55:24 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Set Matrix Zeroes (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Set-Matrix-Zeroes-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/set-matrix-zeroes/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/set-matrix-zeroes/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given an&amp;nbsp;m x n&amp;nbsp;integer matrix&amp;nbsp;matrix, if an element is&amp;nbsp;0, set its entire row and column to&amp;nbsp;0's.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You must do it&amp;nbsp;&lt;a href=&quot;https://en.wikipedia.org/wiki/In-place_algorithm&quot;&gt;in place&lt;/a&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w5EKn/dJMcafLVa2r/hsCQpwq6KtQaESvYXgFAzK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w5EKn/dJMcafLVa2r/hsCQpwq6KtQaESvYXgFAzK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w5EKn/dJMcafLVa2r/hsCQpwq6KtQaESvYXgFAzK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw5EKn%2FdJMcafLVa2r%2FhsCQpwq6KtQaESvYXgFAzK%2Fimg.jpg&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;641&quot; height=&quot;241&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: matrix = [[1,1,1],[1,0,1],[1,1,1]]
Output: [[1,0,1],[0,0,0],[1,0,1]]

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eAugdA/dJMb99ZeD4a/9GZCTLnrq6Y0nR5DNhdVMK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eAugdA/dJMb99ZeD4a/9GZCTLnrq6Y0nR5DNhdVMK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eAugdA/dJMb99ZeD4a/9GZCTLnrq6Y0nR5DNhdVMK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeAugdA%2FdJMb99ZeD4a%2F9GZCTLnrq6Y0nR5DNhdVMK%2Fimg.jpg&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;791&quot; height=&quot;241&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
Output: [[0,0,0,0],[0,4,5,0],[0,3,1,0]]

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;m == matrix.length&lt;/li&gt;
&lt;li&gt;n == matrix[0].length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= m, n &amp;lt;= 200&lt;/li&gt;
&lt;li&gt;231 &amp;lt;= matrix[i][j] &amp;lt;= 231 - 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Follow up:&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;A straightforward solution using&amp;nbsp;O(mn)&amp;nbsp;space is probably a bad idea.&lt;/li&gt;
&lt;li&gt;A simple improvement uses&amp;nbsp;O(m + n)&amp;nbsp;space, but still not the best solution.&lt;/li&gt;
&lt;li&gt;Could you devise a constant space solution?&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&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;각 row를 돌면서 0의 index (j)를 set에 저장, 해당 row에 0이 있다면 zero를 true로 바꿈&lt;/li&gt;
&lt;li&gt;zero가 true라면 해당 라인은 0으로 채워야 하기 때문에 row의 모든 col을 0으로 변환&lt;/li&gt;
&lt;/ul&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;각 row의 0 index를 저장해 두었고 해당 index의 위, 아래도 0으로 변환해야 하기 때문에 matrix를 돌면서 해당 col index가 set에 속해 있다면 0으로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 matrix 반환&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} matrix
 * @return {void} Do not return anything, modify matrix in-place instead.
 */
var setZeroes = function(matrix) {
    const set = new Set();

    for (let i = 0; i &amp;lt; matrix.length; i++) {
        let zero = false;
        for (let j = 0; j &amp;lt; matrix[i].length; j++) {
            if (matrix[i][j] === 0) {
                set.add(j);
                zero = true;
            }
        }

        if (zero) {
            for (let j = 0; j &amp;lt; matrix[i].length; j++) {
                matrix[i][j] = 0;
            }
        }
    }

    for (let i = 0; i &amp;lt; matrix.length; i++) {
        for (let j = 0; j &amp;lt; matrix[i].length; j++) {
            if (set.has(j)) {
                matrix[i][j] = 0;
            }
        }
    }

    return matrix;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0으로 변환하는 부분을 2개로 나누어 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 구현 문제&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>set</category>
      <category>Set Matrix Zeros</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/211</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Set-Matrix-Zeroes-JavaScript#entry211comment</comments>
      <pubDate>Tue, 16 Dec 2025 18:27:09 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Simplify Path (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Simplify-Path-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/simplify-path/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/simplify-path/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;You are given an&amp;nbsp;absolute&amp;nbsp;path for a Unix-style file system, which always begins with a slash&amp;nbsp;'/'. Your task is to transform this absolute path into its&amp;nbsp;&lt;b&gt;simplified canonical path&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The&amp;nbsp;rules&amp;nbsp;of a Unix-style file system are as follows:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A single period&amp;nbsp;'.'&amp;nbsp;represents the current directory.&lt;/li&gt;
&lt;li&gt;A double period&amp;nbsp;'..'&amp;nbsp;represents the previous/parent directory.&lt;/li&gt;
&lt;li&gt;Multiple consecutive slashes such as&amp;nbsp;'//'&amp;nbsp;and&amp;nbsp;'///'&amp;nbsp;are treated as a single slash&amp;nbsp;'/'.&lt;/li&gt;
&lt;li&gt;Any sequence of periods that does&amp;nbsp;&lt;b&gt;not match&lt;/b&gt;&amp;nbsp;the rules above should be treated as a&amp;nbsp;&lt;b&gt;valid directory or&lt;/b&gt;&amp;nbsp;&lt;b&gt;file&amp;nbsp;name&lt;/b&gt;. For example,&amp;nbsp;'...' and&amp;nbsp;'....'&amp;nbsp;are valid directory or file names.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The simplified canonical path should follow these&amp;nbsp;rules:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;The path must start with a single slash&amp;nbsp;'/'.&lt;/li&gt;
&lt;li&gt;Directories within the path must be separated by exactly one slash&amp;nbsp;'/'.&lt;/li&gt;
&lt;li&gt;The path must not end with a slash&amp;nbsp;'/', unless it is the root directory.&lt;/li&gt;
&lt;li&gt;The path must not have any single or double periods ('.'&amp;nbsp;and&amp;nbsp;'..') used to denote current or parent directories.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return the&amp;nbsp;&lt;b&gt;simplified canonical path&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;path = &quot;/home/&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;&quot;/home&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The trailing slash should be removed.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;path = &quot;/home//foo/&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;&quot;/home/foo&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multiple consecutive slashes are replaced by a single one.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 3:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;path = &quot;/home/user/Documents/../Pictures&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;&quot;/home/user/Pictures&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A double period&amp;nbsp;&quot;..&quot;&amp;nbsp;refers to the directory up a level (the parent directory).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 4:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;path = &quot;/../&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;&quot;/&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Going one level up from the root directory is not possible.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 5:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Input:&lt;/b&gt;&amp;nbsp;path = &quot;/.../a/../b/c/../d/./&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Output:&lt;/b&gt;&amp;nbsp;&quot;/.../b/d&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Explanation:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;...&quot;&amp;nbsp;is a valid name for a directory in this problem.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= path.length &amp;lt;= 3000&lt;/li&gt;
&lt;li&gt;path&amp;nbsp;consists of English letters, digits, period&amp;nbsp;'.', slash&amp;nbsp;'/'&amp;nbsp;or&amp;nbsp;'_'.&lt;/li&gt;
&lt;li&gt;path&amp;nbsp;is a valid absolute Unix path.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'/' 를 기준으로 문자열을 나눈 뒤, 나눈 문자열을 가지고 최종 path를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 빈 문자열이거나 .(현재 디렉토리) 인 경우 처리가 필요없으므로 continue&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ..(이전 디렉토리)인 경우 answer(현재 경로)의 길이가 1이상일 경우 맨 위의 원소를 뺌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 아닌 경우 answer에 경로 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 answer를 '/'로 이어 붙이면 됨&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {string} path
 * @return {string}
 */
var simplifyPath = function(path) {
    const p = path.split('/');
    const answer = [];

    for (let i = 0; i &amp;lt; p.length; i++) {
        if (p[i] === '' || p[i] === '.') continue;
        
        if (p[i] === '..') {
            if (answer.length &amp;gt; 0) {
                answer.pop();
            }
        } else {
            answer.push(p[i]);
        }
    }

    return '/' + answer.join('/'); 
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stack을 이용한 구현 문제&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Simplify Path</category>
      <category>Stack</category>
      <category>구현</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/210</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Simplify-Path-JavaScript#entry210comment</comments>
      <pubDate>Mon, 15 Dec 2025 18:32:38 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Minimum Path Sum (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Minimum-Path-Sum-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/minimum-path-sum/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/minimum-path-sum/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given a&amp;nbsp;m x n&amp;nbsp;grid&amp;nbsp;filled with non-negative numbers, find a path from top left to bottom right, which minimizes the sum of all numbers along its path.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Note:&lt;/b&gt;&amp;nbsp;You can only move either down or right at any point in time.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;242&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpf5BX/dJMcaaX7Df1/yvB8cRDku6JvhIOrxtbs00/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpf5BX/dJMcaaX7Df1/yvB8cRDku6JvhIOrxtbs00/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpf5BX/dJMcaaX7Df1/yvB8cRDku6JvhIOrxtbs00/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcpf5BX%2FdJMcaaX7Df1%2FyvB8cRDku6JvhIOrxtbs00%2Fimg.jpg&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;242&quot; height=&quot;242&quot; data-origin-width=&quot;242&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: grid = [[1,3,1],[1,5,1],[4,2,1]]
Output: 7
Explanation: Because the path 1 &amp;rarr; 3 &amp;rarr; 1 &amp;rarr; 1 &amp;rarr; 1 minimizes the sum.

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Input: grid = [[1,2,3],[4,5,6]]
Output: 12

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;m == grid.length&lt;/li&gt;
&lt;li&gt;n == grid[i].length&lt;/li&gt;
&lt;li&gt;1 &amp;lt;= m, n &amp;lt;= 200&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= grid[i][j] &amp;lt;= 200&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP를 이용하여 풀이&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;1. 첫 번째 row는 이전 row의 값을 더해주면 됨, 첫 번째 col은 이전 col의 값을 더해주면 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 오른쪽, 아래로 이동이 가능하므로 위, 왼쪽에서 올 수 있는 최단거리의 값을 자리에 더해줌.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 최종적으로 m - 1, n - 1좌표는 최단 거리가 저장되어있음.&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number[][]} grid
 * @return {number}
 */
var minPathSum = function(grid) {
    const m = grid.length;
    const n = grid[0].length;

    for (let i = 0; i &amp;lt; m; i++) {
        for (let j = 0; j &amp;lt; n; j++) {
            if (i === 0) {
                if (j &amp;gt; 0) {
                    grid[i][j] += grid[i][j - 1];
                }
            } else if (j === 0) {
                if (i &amp;gt; 0) {
                    grid[i][j] += grid[i - 1][j];
                }
            } else {
                grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]);
            }
        }
    }

    return grid[m - 1][n - 1];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP를 활용해 각 자리를 어떠한 방식으로 관리를 할 것인가가 중요&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>dp</category>
      <category>Dynamic Programming</category>
      <category>JavaScript</category>
      <category>LeetCode</category>
      <category>Minimum Path Sum</category>
      <category>다이나믹 프로그래밍</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/209</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Minimum-Path-Sum-JavaScript#entry209comment</comments>
      <pubDate>Thu, 11 Dec 2025 18:11:36 +0900</pubDate>
    </item>
    <item>
      <title>LeetCode Unique Paths (JavaScript)</title>
      <link>https://kuh97.tistory.com/entry/LeetCode-Unique-Paths-JavaScript</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;링크 :&lt;span&gt; &lt;a href=&quot;https://leetcode.com/problems/unique-paths/description/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://leetcode.com/problems/unique-paths/description/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 설명&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;There is a robot on an&amp;nbsp;m x n&amp;nbsp;grid. The robot is initially located at the&amp;nbsp;&lt;b&gt;top-left corner&lt;/b&gt;&amp;nbsp;(i.e.,&amp;nbsp;grid[0][0]). The robot tries to move to the&amp;nbsp;&lt;b&gt;bottom-right corner&lt;/b&gt;&amp;nbsp;(i.e.,&amp;nbsp;grid[m - 1][n - 1]). The robot can only move either down or right at any point in time.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given the two integers&amp;nbsp;m&amp;nbsp;and&amp;nbsp;n, return&amp;nbsp;the number of possible unique paths that the robot can take to reach the bottom-right corner.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The test cases are generated so that the answer will be less than or equal to&amp;nbsp;2 * 109.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 1:&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt9mgV/dJMcadHf8Nr/VJRkpkV8JcSy23xc0iP0ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt9mgV/dJMcadHf8Nr/VJRkpkV8JcSy23xc0iP0ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt9mgV/dJMcadHf8Nr/VJRkpkV8JcSy23xc0iP0ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt9mgV%2FdJMcadHf8Nr%2FVJRkpkV8JcSy23xc0iP0ck%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;400&quot; height=&quot;183&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Input: m = 3, n = 7
Output: 28

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Example 2:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Input: m = 3, n = 2
Output: 3
Explanation: From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -&amp;gt; Down -&amp;gt; Down
2. Down -&amp;gt; Down -&amp;gt; Right
3. Down -&amp;gt; Right -&amp;gt; Down

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constraints:&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;1 &amp;lt;= m, n &amp;lt;= 100&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. (시간 초과) BFS를 활용하여 문제 풀이 -&amp;gt; 0,0 부터 오른쪽, 아래 가능한 경로를 stack에 쌓으면서 최종 목적지 까지 가는 횟수를 카운트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 2가지의 경우의 수가 존재하고 m x n 행을 전부 탐색해야 하므로 O(2^(m+n))의 시간복잡도를 가짐&lt;/p&gt;
&lt;pre id=&quot;code_1765331311795&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    const move = [[1, 0], [0, 1]];
    let answer = 0;

    const stack = [[0, 0]];

    while (stack.length &amp;gt; 0) {
        let [x, y] = stack.pop();

        if ((x === n - 1) &amp;amp;&amp;amp; (y === m - 1)) {
            answer++;
            continue;
        }

        for (let i = 0; i &amp;lt; move.length; i++) {
            const mx = x + move[i][0];
            const my = y + move[i][1];

            if (mx &amp;lt; n &amp;amp;&amp;amp; my &amp;lt; m) {
                stack.push([mx, my]);
            }
        }
    }

    return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 2차원 배열을 한번만 순회하여 정답을 구하는 방법으로 O(m + 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;현재 위치(x, y)기준 왼쪽(x - 1, y), 위(x, y - 1)의 값을 더한 값이 현재 위치의 도달 가능 횟수임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 (m - 1, n - 1)을 리턴해주면 됨.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMd8wF/dJMcadN0Uar/9r9HbZp4Z1pR35Ore6glAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMd8wF/dJMcadN0Uar/9r9HbZp4Z1pR35Ore6glAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMd8wF/dJMcadN0Uar/9r9HbZp4Z1pR35Ore6glAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMd8wF%2FdJMcadN0Uar%2F9r9HbZp4Z1pR35Ore6glAK%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;500&quot; height=&quot;214&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1735724827079&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function(m, n) {
    let arr = Array.from({length: m}, () =&amp;gt; Array.from({length: n}, () =&amp;gt; 1));

    for (let i = 1; i &amp;lt; m; i++) {
        for (let j = 1; j &amp;lt; n; j++) {
            arr[i][j] = arr[i - 1][j] + arr[i][j - 1];
        }
    }

    return arr[m - 1][n - 1];
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS를 이용해 쉽게 해결할 순 있지만 시간복잡도 초과 문제가 발생하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 좀 더 생각해보면 각 위치가 어떠한 방식으로 생성이 될 수 있는지 파악할 수 있다.&lt;/p&gt;</description>
      <category>코딩테스트/LeetCode</category>
      <category>BFS</category>
      <category>FOR</category>
      <category>LeetCode</category>
      <category>O(2^(m + n))</category>
      <category>O(m + n)</category>
      <category>Unique paths</category>
      <category>코딩테스트</category>
      <author>의현</author>
      <guid isPermaLink="true">https://kuh97.tistory.com/208</guid>
      <comments>https://kuh97.tistory.com/entry/LeetCode-Unique-Paths-JavaScript#entry208comment</comments>
      <pubDate>Wed, 10 Dec 2025 10:56:00 +0900</pubDate>
    </item>
  </channel>
</rss>