<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>YLog</title>
    <link>https://yeonnim01.tistory.com/</link>
    <description>프론트엔드 개발자 지망생 입니다 </description>
    <language>ko</language>
    <pubDate>Thu, 11 Jun 2026 21:33:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Yeonnim</managingEditor>
    <item>
      <title>[프로그래머스] 프로세스 (Lv. 2 JavaScript)</title>
      <link>https://yeonnim01.tistory.com/16</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42587&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42587&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781175785690&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42587&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dsPHYD/dJMb9jgFCyM/6QMzZVEBTzmrKPK31FkN7k/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/o1Mmj/dJMb9dHwtJ6/BerYi1Ykc8gej6iP4Gn64k/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42587&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42587&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dsPHYD/dJMb9jgFCyM/6QMzZVEBTzmrKPK31FkN7k/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/o1Mmj/dJMb9dHwtJ6/BerYi1Ykc8gej6iP4Gn64k/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 주어진 프로세스 실행 대기 큐에서 특정 프로세스가 실행되는 순서를 알아내는 것이 목표. 이 때 주어진 우선순위에 따라 높은 우선순위의 프로세스를 먼저 실행해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 조건:&lt;/b&gt;&lt;/p&gt;
&lt;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;pre id=&quot;code_1781175812920&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 실행 대기 큐(Queue)에서 대기중인 프로세스 하나를 꺼냅니다.
2. 큐에 대기중인 프로세스 중 우선순위가 더 높은 프로세스가 있다면 방금 꺼낸 프로세스를 다시 큐에 넣습니다.
3. 만약 그런 프로세스가 없다면 방금 꺼낸 프로세스를 실행합니다.
  3.1 한 번 실행한 프로세스는 다시 큐에 넣지 않고 그대로 종료됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주어지는 입력은 큐의 프로세스들의 실행 우선순위를 나타내는 배열, 확인하고자 하는 프로세스의 위치 인덱스&lt;/li&gt;
&lt;li&gt;priorities 의 길이는 1 이상 100 이하 (최대 N=100)&lt;/li&gt;
&lt;li&gt;priorities 의 원소는 1 이상 9 이하의 정수 (우선순위는 1~9의 범위)&lt;/li&gt;
&lt;li&gt;location 은 0 이상 (대기 큐에 있는 프로세스 수 - 1) 이하&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로세스를 꺼낼 때 마다 매번 큐의 남은 우선순위를 체크하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 dequeue를 할 때마다 큐를 순회하면서 우선순위를 체크해야한다. 시간 복잡도를 우선 고려해보자면, 최악의 경우에 주어진 location이 큐의 맨 뒤 프로세스라면 최대 N번 프로세스를 실행해야 한다. 이때 이 프로세스를 실행(dequeue) 할 때 마다 큐의 남은 우선순위를 체크하기 위해 순회를 해야하므로 N * N회 연산을 해야하므로, 시간 복잡도는 O(N^2)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 주어진 priorities 의 크기가 최대 100 이므로 N = 100 일 때 최대 연산횟수는 10,000회가 된다. 이 정도의 규모라면 시간 초과에서는 문제가 생기지 않을 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 구조 설계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에서는 프로세스들을 꺼내고 다시 맨 뒤로 보내는 상황이 존재하므로, 초기 큐의 순서가 뒤바뀔 수 있기 때문에, 목표로 하는 특정 위치의 프로세스를 추적하기 위해 큐에 삽입하는 데이터 구조를 { index, priority } 객체 형태로 저장하였다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;    const queue = new Queue();
    priorities.forEach((priority, index) =&amp;gt; {
        queue.enqueue({index: index, priority: priority});
    });

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;핵심 로직&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;    while (queue.size() &amp;gt; 0) {
        let isPass = false;
        const item = queue.dequeue(); // {0, 2}
        for (let i = queue.front; i &amp;lt; queue.rear; i++) {
            if (item.priority &amp;lt; queue.storage[i].priority) {
                queue.enqueue(item);
                isPass = true;
                break;
            }
        }
        if (!isPass) answer++
        if (!isPass &amp;amp;&amp;amp; item.index === location) break;

    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐에서 front에 있는 항목을 하나 dequeue한다. 그리고 남은 큐를 순회하면서 dequeue한 프로세스의 우선순위보다 큐에 더 우선순위가 높은 항목이 하나라도 존재한다면, 해당 프로세스를 euqueue 하여 맨 뒤로 보낸다. 방금 꺼낸 프로세스를 실행하지 않고 다시 큐에 넣었다는 것을 기억하기 위해 isPass 플래그를 true로 변경하고, 순회를 즉시 탈출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 만약에 isPass가 false라면 즉, 꺼낸 프로세스를 다시 큐에 넣지 않고 실행했다면, 실행 횟수인 answer 를 1 증가 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 만약 이 실행한 프로세스의 원래 item.index가 location과 동일하다면 while문을 종료하고 누적된 answer 를 반환한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; ️ 트러블슈팅&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;JavaScript 배열 shift()의 성능 이슈를 고려해 큐(Queue) 직접 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 배열을 이용해 JS의 Array 내장 메서드인 shift(), push()를 통해 구현하는 것을 생각했다. 하지만 자바스크립트의 shift() 메서드는 배열의 첫 요소를 추출한 후 남은 모든 요소의 인덱스 재정렬이 포함되기 때문에 O(N)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제에서 큐의 요소가 빠졌다가 다시 뒤로 넘겨지는 연산이 빈번히 발생한다. 비록 주어진 N의 크기가 최대 100이기 때문에 위 방법으로 구현하여도 이번 문제에서는 성능 문제가 발생하지 않을 수도 있지만, N의 크기가 커질 경우 시간 초과 문제가 생길수도 있을것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 직접 큐를 클래스로 구현하여, 삽입과 삭제 연산의 비용을 O(1)이 되도록 하여 효율성을 극대화하였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;매번 큐를 전체 순회해야 하는 비효율성 개선 고민&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제에서는 큐에서 dequeue할 때 마다 for문으로 우선순위를 순회하면서 체크하는 방식을 택했는데 이 방식 외에 더 효율적인 방법이 또 있을지 문제를 해결하고 알아보았다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Queue {
    constructor() {
        this.storage = {};
        this.front = 0;
        this.rear = 0;
    }
    
    size() {
        return this.rear - this.front;
    }
    
    enqueue(value) {
        this.storage[this.rear] = value;
        this.rear += 1;
    }
    
    dequeue() {
        if (this.size() === 0) return null;
        const temp = this.storage[this.front];
        delete this.storage[this.front];
        this.front += 1;
        return temp;

    }
    
}

function solution(priorities, location) {
    var answer = 0;
    const queue = new Queue();
    priorities.forEach((priority, index) =&amp;gt; {
        queue.enqueue({index: index, priority: priority});
    });

    while (queue.size() &amp;gt; 0) {
        let isPass = false;
        const item = queue.dequeue(); // {0, 2}
        for (let i = queue.front; i &amp;lt; queue.rear; i++) {
            if (item.priority &amp;lt; queue.storage[i].priority) {
                queue.enqueue(item);
                isPass = true;
                break;
            }
        }
        if (!isPass) answer++
        if (!isPass &amp;amp;&amp;amp; item.index === location) break;

    }
    
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최적화된 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class Queue {
    constructor() {
        this.storage = {};
        this.front = 0;
        this.rear = 0;
    }
    
    size() {
        return this.rear - this.front;
    }
    
    enqueue(value) {
        this.storage[this.rear] = value;
        this.rear += 1;
    }
    
    dequeue() {
        if (this.size() === 0) return null;
        const temp = this.storage[this.front];
        delete this.storage[this.front];
        this.front += 1;
        return temp;

    }
    
}

function solution(priorities, location) {
    var answer = 0;
    const queue = new Queue();
    priorities.forEach((priority, index) =&amp;gt; {
        queue.enqueue({index: index, priority: priority});
    });
    
    // 이 배열의 맨 앞은 항상 '현재 큐에 남아있는 최고 우선순위'를 의미
    const sortedQueue = [...priorities].sort((a, b) =&amp;gt; b - a); 
    let maxIndex = 0; // // 현재 가장 높은 우선순위를 가리키는 포인터

    while (queue.size() &amp;gt; 0) {
        const item = queue.dequeue();
        // // 현재 꺼낸 아이템의 우선순위가 남아있는 최고 우선순위보다 낮다면
        if (item.priority &amp;lt; sortedQueue[maxIndex]) {
            queue.enqueue(item); // 맨 뒤로 보내기
        } else {
		        // 현재 꺼낸 아이템이 최고 우선순위라면 실행
            answer++;
            
            maxIndex++;
            
            // 방금 실행한 프로세스가 우리가 찾던 목표 위치라면 종료
            if (item.index === location) {
                break;
            }
        }

    }
    
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 방식은 큐에서 프로세스를 하나 꺼낼 때 마다 뒤에 더 큰 값이 있는지 확인하기 위해 큐 내부를 순회하므로 O(N^2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 이미 내림차순으로 정렬한 sortedQueue 배열을 생성하는데 O(N log N)이 걸리고 다음으로 while 루프를 돌면서 sortedQueue의 maxIndex 위치의 값과 비교만 하면 되므로, O(1)의 시간이 소요된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 while 문에서 원소들이 큐를 나갔다 들어왔다만 수행할 뿐 내부에서 전체를 다시 순회하는 연산이 사라지므로, 큐 연산 자체는 O(N)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 시간 복잡도는 O(N) + O(N log N)으로 최적화 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;꼬리질문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q1. 최적화 풀이에서 정렬 배열(sortedQueue)을 썼는데, 만약 우선순위의 범위가 1 ~ 9가 아니라 대단히 크고 데이터 수가 훨씬 많다면 어떤 방식이 더 유리했을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 내림차순 정렬O(N log N)을 이용해 최댓값을 추적했다. 만약 실시간으로 데이터가 추가 및 삭제되거나 데이터 규모가 매우 크다면, 현재 최댓값을 항상 O(1) 혹은 O(log N)으로 유지해 주는 최대 힙(Max Heap) 기반의 우선순위 큐(Priority Queue) 자료구조를 직접 구현해 적용하는 것이 정석적인 확장 방향이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이번 문제에서는 큐를 직접 구현하지 않고 배열의 메서드를 이용한다면 더 쉽게 풀이할 수 있던 문제였습니다. 해결하면서 큐를 다시 복습하면서 shift() 메서드가 가지는 성능 면에서의 우려점을 고려해서 해본 풀이였습니다. 그래도 다음에 이 경험이 도움이 되지 않을까? 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘도 파이팅입니다!&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>프로그래머스</category>
      <category>프로세스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/16</guid>
      <comments>https://yeonnim01.tistory.com/16#entry16comment</comments>
      <pubDate>Thu, 11 Jun 2026 20:04:05 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 타겟 넘버 (Lv. 2 JavaScript)</title>
      <link>https://yeonnim01.tistory.com/15</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43165&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/43165&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780796614457&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43165&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cNDPYR/dJMb89ylpb4/TkuukcWirSy4vY1RB99Dyk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/4LocQ/dJMb8VNDosv/30n5LpmoqMrk1wYTkPKVXK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43165&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43165&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cNDPYR/dJMb89ylpb4/TkuukcWirSy4vY1RB99Dyk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/4LocQ/dJMb8VNDosv/30n5LpmoqMrk1wYTkPKVXK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; n개의 음이 아닌 정수가 주어지면, 이 정수들를 더하거나 빼서(순서를 바꾸지 않고) 주어진 타겟 넘버를 만들 수 있는 경우의 수를 반환해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 조건:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주어지는 숫자의 개수 n은 2 이상 20 이하이다.&lt;/li&gt;
&lt;li&gt;각 숫자는 1 이상 50 이하의 자연수&lt;/li&gt;
&lt;li&gt;타겟 넘버는 1 이상 1000 이하의 자연수&lt;/li&gt;
&lt;li&gt;주어진 숫자 배열의 순서는 바꾸지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&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;
&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;두 가지 선택지가 매 단계마다 반복되므로 이를 시각화하면 이진 트리 형태로 나타낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 노드인 0부터 시작해서 주어진 숫자의 개수만큼 depth를 내려가며 모든 합산 결과를 탐색하는 DFS 알고리즘을 적용할 수 있다. 예를 들어, numbers = [4, 1, 2, 1], target = 4 일 때의 트리 구조는 아래와 같이 모든 숫자를 다 사용할 때까지 뻗어나간다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;[Depth 0]                      0 (시작)
                            /     \
[Depth 1]                +4         -4
                        /  \       /  \
[Depth 2]             +1    -1   +1    -1
                     / \    / \  / \   / \
[Depth 3]           +2 -2  +2 -2 ...  ...
                   / \
[Depth 4]         +1  -1  &amp;lt;- 이 상태(Leaf Node)에서 최종 합산 값이 target과 같은지 체크&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트리의 깊이(depth)는 현재까지 연산에 사용한 숫자의 개수를 의미&lt;/li&gt;
&lt;li&gt;탐색 종료 조건은 depth가 주어진 숫자 배열의 크기와 동일할 경우 (모든 숫자를 다 사용)&lt;/li&gt;
&lt;li&gt;answer 카운트 증가 조건은 종료 조건에 도달 했을 때 최종 합산이 target과 동일한지에 따라&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 숫자를 현재 총 합산 값에 더하는 경우와 빼는 경우를 재귀 함수를 통해 호출하게 한다. 근데 이게 왜 그런지를 설명 못하겠네 예시에 대해서 직접 트리 그림으로 설명한다면 좋을텐데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 트리에서 내려가면서 더하고 뺀 합산 값을 적은 트리 좌측 노드로 내려간다면 +, 우측 노드로 내려가면 -를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 depth가 숫자 배열의 크기만큼의 깊이 즉, 주어진 숫자를 다 사용하였고, current의 값이 타겟 넘버와 동일하다면 answer 값을 증가시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 재귀를 통해 트리를 구성하면 정답을 찾을수 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;[Depth = 0]                   0
[Depth = 1]              1        -1
[Depth = 2]         2        
[Depth = 3]      3 (target넘버와 동일하나 depth가 조건을 충족 X)      
[Depth = 4]    4   2  
[Depth = 5]  5  3 (조건 만족 answer++)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 코드 구현은 아래와 같이 진행하였음.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;    let current = 0;
    const permutation = (current, depth) =&amp;gt; {
        if (numbers.length &amp;lt; depth) return;
        if ((numbers.length === depth) &amp;amp;&amp;amp; (current === target)) {
            answer++;
        }
        const number = numbers[depth] === undefined ? 0 : numbers[depth];

        permutation(current + number, depth + 1);
        permutation(current - number, depth + 1);

    }
    permutation(current, 0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;permutation(current, depth) 함수는 DFS 알고리즘을 기반으로 하며, 이진 트리에서 leaf node까지 탐색을 완료했다면 다시 위로 올라가는 재귀적 구조이다.&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;이진 트리의 분기&lt;br /&gt;&lt;/b&gt;매 호출마다 현재 깊이의 주어진 배열의 숫자를 총합(current)에 더하는 경우(+)와 빼는 경우(-)의 두 갈래로 나뉘며 permutation 을 재귀 호출한다. 이는 이진 트리에서 왼쪽 자식 노드와 오른쪽 자식 노드로 깊어지며 탐색하는 DFS 동작과 같다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조건 검사와 탐색 종료&lt;br /&gt;&lt;/b&gt;depth 가 numbers.length 와 같다는 것은 주어진 숫자 배열의 원소들을 모두 탐색에 사용했다는 의미이며 이진 트리에서 바닥 노드 (leaf node)에 도달했다는 것을 의미하기도 한다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;이 때 최종 누적된 current 의 값이 target 과 같은지를 검사한다. 만약 같다면 answer 의 값을 1 증가시킨다.&lt;br /&gt;조건을 만족하든 하지 않든 리프 노드에 도달했다면 해당 분기의 탐색은 종료되며, 함수가 반환되면서 이전 갈림길 부모 노드로 돌아가게 된다. (백트래킹)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 처음 호출한 permutation(current, 0) 가 종료되었다는 것은 이진 트리에 대한 DFS 탐색이 최종 완료되었음을 의미한다. 이 과정에서 current와 타겟 넘버가 같을 경우에 answer 값을 증가 시키기 때문에 이진 트리에 대한 탐색이 완료되면 결과 값을 알 수 있는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; ️ 트러블슈팅&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;function solution(numbers, target) {
    var answer = 0;
    let current = 0;
    
    const permutation = (current, depth) =&amp;gt; {
        if (numbers.length === depth) {
            return ;
        }
        const number = numbers[depth];
        permutation(current + number, depth + 1);
        permutation(current - number, depth + 1);

        console.log(`추출: ${number}, 현재: ${current}, 깊이: ${depth}, 결과: ${answer}`)
        if ((numbers.length &amp;gt; depth) &amp;amp;&amp;amp; (current + number === target) || (current - number === target)) {
            answer++;
        }

    }
    
    permutation(current, 0);
    
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 시도에서 있었던 문제점은 탐색이 끝나지 않은 시점에서도 (depth가 배열크기보다 작다면) 타겟 넘버와 일치하는 지 검사하고 answer 값을 증가 시켜 잘못 구현하여 문제가 있었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 원소를 사용하는 조합/순열이나 DFS 문제에서는 반드시 최종 깊이에 도달했을 때 결과를 확인해야 한다는 점을 인지하지 못한 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 최종 타겟 넘버 체크 로직에서 불필요하게 current +- number 와 같은 조건을 걸었는데, 애초에 permutation을 재귀적으로 호출 할때 current 값에 number를 더해 인자로 넘겨주기에 굳이 이렇게 검사할 필요가 없었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;function solution(numbers, target) {
    var answer = 0;
    let current = 0;
    
    const permutation = (current, depth) =&amp;gt; {
        if (numbers.length &amp;lt; depth) return;
        if ((numbers.length === depth) &amp;amp;&amp;amp; (current === target)) {
            answer++;
        }
        const number = numbers[depth] === undefined ? 0 : numbers[depth];
        
        permutation(current + number, depth + 1);
        permutation(current - number, depth + 1);

    }
    
    permutation(current, 0);
    
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;복습&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFS의 구현 방법&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS를 구현하는 방법은 크게 두 가지가 있다.&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;명시적 스택: Array 나 Stack 자료구조를 직접 선언하여 반복문으로 구현하는 방법&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제에서 사용한 것 처럼 재귀함수로 구현하는 방법은 컴퓨터 내부적으로 스택 메모리 구조를 사용한다. 함수가 호출될 때마다 메모리에 쌓아두고 return을 만나면 다시 쌓아둔 메모리 주소로 돌아가 하나씩 빠져나오는 원리이다. 이를 코드 상으로 명시적으로 그대로 가져오면 직접 const stack = [] 과 같이 만들고 while 문을 돌리는 방식으로 DFS를 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 함수 구현의 경우 재귀가 너무 깊어지면 일반적인 JS 기준 1만번 이상 일경우 스택 오버플로우가 발생할 수 있다. 명시적 스택의 경우 메모리 제한이 시스템 스택에 비해 널널하다. 탐색해야 할 데이터가 방대하고 깊이가 극단적으로 깊을 때 사용한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트리 DFS vs 그래프 DFS&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료 구조의 형태에 따라 DFS의 디테일한 점이 달라진다. 트리는 그래프의 한 종류로 모든 트리는 그래프이지만 결정적인 차이는 사이클(순환 경로)가 존재하는지 (이미 방문한 곳을 다시 방문할 수 있는가?) 이다.&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;트리에서의 DFS&lt;/b&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 주어진 데이터(array)를 바탕으로 깊이(depth)를 제어하며 탐색
function treeDFS(depth, currentResult, array) {
    // 1. Base Case: 트리의 가장 바닥(Leaf Node)에 도달했을 때 (종료 조건)
    if (depth === array.length) {
        // 결과 처리 로직 (최종 값 비교, 정답 카운트 등)
        return;
    }

    // 2. Recursive Case: 갈라지는 선택지(자식 노드들)를 각각 호출
    // 예: 선택지 A를 고르고 다음 깊이로 이동
    treeDFS(depth + 1, currentResult + array[depth], array);
    
    // 예: 선택지 B를 고르고 다음 깊이로 이동
    treeDFS(depth + 1, currentResult - array[depth], array);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;b&gt;특징:&lt;/b&gt; 부모에서 자식으로만 내려가는 일방 통행 수직 구조이다. 사이클이 없으므로 이미 방문한 노드를 기억할 필요가 없다. 즉, visited 배열이 불필요하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그래프에서의 DFS&lt;/b&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// graph: 각 노드의 연결 관계를 담은 2차원 배열/인접 리스트
// node: 현재 방문한 노드 번호
// visited: 방문 여부를 체크하는 불리언 배열 [false, false, ...]
function graphDFS(node, graph, visited) {
    // 1. 현재 노드를 방문 처리
    visited[node] = true;
    
    // 필요한 비즈니스 로직 수행 (ex. 카운트 증가, 노드 출력 등)

    // 2. 현재 노드와 연결된 인접 노드들을 하나씩 확인 (반복문)
    for (let i = 0; i &amp;lt; graph[node].length; i++) {
        const nextNode = graph[node][i];

        // 3. 아직 방문하지 않은 인접 노드가 있다면 해당 노드로 더 깊이 탐색 (재귀)
        if (!visited[nextNode]) {
            graphDFS(nextNode, graph, visited);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;b&gt;특징:&lt;/b&gt; 탐색하다가 이전에 방문했던 노드로 다시 돌아가는 무한 루프에 빠질 수 있기 때문에 방문 여부를 반드시 확인해야한다. 즉, visited 배열이 필수&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;꼬리질문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q1. 만약 N의 개수가 50개 이상으로 커진다면 이 풀이(DFS)를 그대로 사용할 수 있을까요?&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 개라고 했을 때, 총 선택의 가짓수는 2^N이 된다. 따라서 시간 복잡도는 O(2^N)이 된다. 이 문제에서는 N의 최대값이 20이기 때문에 2^20 = 약 100만번의 연산이 수행되어 DFS로 충분히 통과가 가능하다.&lt;/li&gt;
&lt;li&gt;하지만 N의 개수가 50개 이상으로 커진다면, 2^50은 대략 10^15를 넘어서기 때문에 시간 초과가 발생하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 문제에 이어 DFS 문제에 마주했는데 스스로 힘으로 해결하진 못했고 AI에게 약간의 힌트를 받아서 현재 구조를 생각해내어 구현을 진행했습니다. 이번에 문제를 풀이하고 복습을 진행하면서 DFS 문제를 만났을 때 어떻게 접근하면 좋을지에 대해 생각해볼 수 있었던 것 같습니다. 다음에는 완전 스스로 힘으로 풀이 가능하기를 바라면서! 앞으로도 파이팅입니다!&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: #666666;&quot;&gt;&lt;i&gt;본 게시물은 AI의 도움을 받아 작성되었습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>타겟 넘버</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/15</guid>
      <comments>https://yeonnim01.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 7 Jun 2026 10:46:50 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 소수 찾기 (Lv. 2 JavaScript)</title>
      <link>https://yeonnim01.tistory.com/14</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42839&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42839&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780730037286&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42839&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZnFqW/dJMb8SXFzR9/1CKDwKkZD8DLHTHtuPgPFk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/G6Hy6/dJMb8YXToCa/gYduAFYTagT2x0D3otR4W1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42839&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42839&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZnFqW/dJMb8SXFzR9/1CKDwKkZD8DLHTHtuPgPFk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/G6Hy6/dJMb8YXToCa/gYduAFYTagT2x0D3otR4W1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 한 자리 숫자가 적힌 종이들이 주어졌을 때(문자열 numbers) 이 한자리 수들로 만들 수 있는 소수가 몇 개인지 반환해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 조건:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;numbers는 &amp;ldquo;17&amp;rdquo; 이러한 형태로 주어지며, 이는 숫자 &amp;lsquo;1&amp;rsquo;, &amp;lsquo;7&amp;rsquo; 으로 봐야한다.&lt;/li&gt;
&lt;li&gt;numbers의 길이는 1이상 7이하의 문자열. 즉, 한자리 숫자가 1~7개 주어진다.&lt;/li&gt;
&lt;li&gt;numbers는 0~9까지 숫자로만 이루어져있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&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;주어진 numbers로 만들 수 있는 숫자 조합을 먼저 파악&lt;/li&gt;
&lt;li&gt;이게 소수인지 판별하는 방식으로 구현하고자 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 numbers의에서 만들 수 있는 모든 자리 수 (1 ~ N자리)를 구해야 하므로, DFS 알고리즘을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자들을 하나씩 붙여가면서 가장 깊은 자릿수에 방문하면 이전으로 백트래킹한 후 다시 다른 조각들을 이어붙이는 수직적인 탐색 방식의 DFS를 통해 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS 구현을 위해 이미 방문한 숫자는 다음 탐색에서 제외해야 하므로 visited 배열을 통해 방문 체크를 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 흐름으로 탐색을 진행한다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;[0층] &quot;&quot; (시작)
  │
  ├── [1층] &quot;1&quot;  ─── [2층] &quot;17&quot; ─── [3층] &quot;173&quot; (➡️ 복귀)
  │                    │
  │                    └── [2층] &quot;1&quot;로 복귀 후 다음 조각 '3' 선택 ➡️ [2층] &quot;13&quot; ─── [3층] &quot;137&quot; (➡️ 복귀)
  │
  ├── [1층] &quot;1&quot; 갈래 완전 종료 후 &quot;&quot;로 복귀 ➡️ 다음 조각 '7' 선택 ➡️ [1층] &quot;7&quot; ... (반복)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;    const permutation = (current, depth) =&amp;gt; {
        if (depth &amp;gt; 0) set.add(parseInt(current));
        if (depth === arr.length) return;
        
        for (let i=0; i &amp;lt; arr.length; i++) {
            if (visited[i] === false) {
                visited[i] = true;
                permutation(current + arr[i], depth + 1);
                visited[i] = false;
            }
            
        }
    }
   permutation(&quot;&quot;, 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;173&amp;rdquo;를 예시로 상세한 실행 흐름을 추적하면 아래 표와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; 현재 함수&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; i (루프 인덱스)&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; visited 상태&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; set 상태&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt; 진행 상황 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;&amp;rsquo;, 0)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[T, F, F]&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;visited[0] = true 체크 후 permutation(&amp;rsquo;1&amp;rsquo;, 1) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;1&amp;rsquo;, 1)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[T, F, F]&lt;/td&gt;
&lt;td&gt;[1]&lt;/td&gt;
&lt;td&gt;set에 &amp;lsquo;1&amp;rsquo; 추가,&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;visited[0]이 true 즉, 이미 방문한 상태이므로 다음 인덱스로 넘어감&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;[T, T, F]&lt;/td&gt;
&lt;td&gt;[1]&lt;/td&gt;
&lt;td&gt;visited[1] = true 체크 후 permutation(&amp;rsquo;17&amp;rsquo;, 2) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;17&amp;rsquo;, 2)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[T, T, F]&lt;/td&gt;
&lt;td&gt;[1, 17]&lt;/td&gt;
&lt;td&gt;set에 &amp;lsquo;17&amp;rsquo; 추가,&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;visited[0]이 true 즉, 이미 방문한 상태이므로 다음 인덱스로 넘어감&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;[T, T, F]&lt;/td&gt;
&lt;td&gt;[1, 17]&lt;/td&gt;
&lt;td&gt;visited[1]이 true 즉, 이미 방문한 상태이므로 다음 인덱스로 넘어감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;[T, T, T]&lt;/td&gt;
&lt;td&gt;[1, 17]&lt;/td&gt;
&lt;td&gt;visited[2] = true 체크 후 permutation(&amp;rsquo;173&amp;rsquo;, 3) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;173&amp;rsquo;, 3)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[T, T, T]&lt;/td&gt;
&lt;td&gt;[1, 17, 173]&lt;/td&gt;
&lt;td&gt;set에 &amp;lsquo;173&amp;rsquo; 추가,&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;visited[0]이 true 즉, 이미 방문한 상태이므로 다음 인덱스로 넘어감&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;[T, T, T]&lt;/td&gt;
&lt;td&gt;[1, 17, 173]&lt;/td&gt;
&lt;td&gt;visited[1]이 true 즉, 이미 방문한 상태이므로 다음 인덱스로 넘어감&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;[T, T, T]&lt;/td&gt;
&lt;td&gt;[1, 17, 173]&lt;/td&gt;
&lt;td&gt;visited[2]이 true 즉, 이미 방문한 상태. 루프 종료 후&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;17&amp;rsquo;, 2)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;[T, T, F]&lt;/td&gt;
&lt;td&gt;[1, 17, 173]&lt;/td&gt;
&lt;td&gt;visited[2] = false로 복구.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;루프 종료&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;1&amp;rsquo;, 1)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;[T, F, F]&lt;/td&gt;
&lt;td&gt;[1, 17, 173]&lt;/td&gt;
&lt;td&gt;visited[1] = false복구 후 다음 인덱스로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;1&amp;rsquo;, 1)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;[T, F, T]&lt;/td&gt;
&lt;td&gt;[1, 17, 173]&lt;/td&gt;
&lt;td&gt;visited[2] = true 체크 후 permutation(&amp;rsquo;13&amp;rsquo;, 2) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;13&amp;rsquo;, 2)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[T, F, T]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13]&lt;/td&gt;
&lt;td&gt;set에 &amp;lsquo;13&amp;rsquo; 추가.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;visited[0]이 true 즉, 이미 방문한 상태이므로 다음 인덱스로 넘어감&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;[T, T, T]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13]&lt;/td&gt;
&lt;td&gt;visited[1] = true 체크 후 permutation(&amp;rsquo;137&amp;rsquo;, 3) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;137&amp;rsquo;, 3)&lt;/td&gt;
&lt;td&gt;0~2&lt;/td&gt;
&lt;td&gt;[T, T, T]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13, 137]&lt;/td&gt;
&lt;td&gt;set에 &amp;lsquo;137&amp;rsquo; 추가.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이후 visited[0~2]가 true 이미 방문한 상태이므로 루프 종료&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;13&amp;rsquo;, 2)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;[T, T, F]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13, 137]&lt;/td&gt;
&lt;td&gt;visited[2] = false복구 후 루프 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;1&amp;rsquo;, 1)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;[T, F, F]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13, 137]&lt;/td&gt;
&lt;td&gt;visited[1] = false복구 후 루프 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;&amp;rsquo;, 0)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[F, F, F]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13, 137]&lt;/td&gt;
&lt;td&gt;visited[0] = false복구 후 다음 인덱스로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;&amp;rsquo;, 0)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;[F, T, F]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13, 137]&lt;/td&gt;
&lt;td&gt;visited[1] = true 체크 후 permutation(&amp;rsquo;7&amp;rsquo;, 1) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permutation(&amp;rsquo;7&amp;rsquo;, 1)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;[T, T, F]&lt;/td&gt;
&lt;td&gt;[1, 17, 173, 13, 137, 7]&lt;/td&gt;
&lt;td&gt;set에 &amp;lsquo;7&amp;rsquo; 추가.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이후&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;visited[0] = true 체크 후 permutation(&amp;rsquo;71&amp;rsquo;, 2) 호출&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;hellip; 이후 반복&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;number가 1 이하인 경우 제외&lt;/li&gt;
&lt;li&gt;2부터 시작해서 주어진 number의 제곱근까지 루프문을 돌면서 i로 나누었을 때 나머지가 존재하지 않는다면. (딱 나누어진다면) 소수가 아니라고 판별한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;function isPrime(number) {
    if (number &amp;lt;= 1) return false;
    
    for (let i = 2; i&amp;lt;=Math.sqrt(number); i++) {
        if (number % i === 0) return false;
    }
    return true;
}

function solution(numbers) {
    var answer = 0;
    const set = new Set(); // 만들 수 있는 숫자 조합을 저장할 Set 집합 (중복을 허용 X)
    const arr = numbers.split(&quot;&quot;) // 숫자의 각 자리를 분해하여 배열로 변환
    const visited = new Array(arr.length).fill(false);
    
    const permutation = (current, depth) =&amp;gt; {
        if (depth &amp;gt; 0) set.add(parseInt(current));
        if (depth === arr.length) return;
        
        for (let i=0; i &amp;lt; arr.length; i++) {
            if (visited[i] === false) {
                visited[i] = true;
                permutation(current + arr[i], depth + 1);
                visited[i] = false;
            }
            
        }
    }
    
    permutation(&quot;&quot;, 0);
    
    set.forEach((num) =&amp;gt; {
        if (isPrime(num)) {
            answer++;
        }
    })
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최적화된 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;function isPrime(number) {
    if (number &amp;lt;= 1) return false;
    if (number === 2) return true;
    if (number % 2 === 0) return false; // 짝수인 경우 제외
    
    for (let i = 3; i&amp;lt;=Math.sqrt(number); i+=2) {
        if (number % i === 0) return false;
    }
    return true;
}

function solution(numbers) {
    var answer = 0;
    const set = new Set(); // 만들 수 있는 숫자 조합을 저장할 Set 집합 (중복을 허용 X)
    const arr = numbers.split(&quot;&quot;) // 숫자의 각 자리를 분해하여 배열로 변환
    const visited = new Array(arr.length).fill(false);
    
    const permutation = (current, depth) =&amp;gt; {
        if (depth &amp;gt; 0) set.add(parseInt(current));
        if (depth === arr.length) return;
        
        for (let i=0; i &amp;lt; arr.length; i++) {
            if (visited[i] === false) {
                visited[i] = true;
                permutation(current + arr[i], depth + 1);
                visited[i] = false;
            }
            
        }
    }
    
    permutation(&quot;&quot;, 0);
    
    set.forEach((num) =&amp;gt; {
        if (isPrime(num)) {
            answer++;
        }
    })
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수 판별 함수에서 짝수인 경우를 제외하면 소수 판별 속도를 더 빠르게 할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;복습&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;완전 탐색&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전 탐색은 문제의 해를 찾기 위해 가능한 모든 케이스를 빠짐없이 탐색하는 알고리즘으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100%의 정답률을 보장하지만, 입력 데이터의 크기에 따라 연산량이 무지막지하게 늘어나는 문제가 존재한다. 따라서 대용량 데이터 처리에는 적합하지 않고 주로 탐색 범위가 좁을 때 최적의 선택이다.&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;단순 Brute Force: 반복문과 조건문만으로 처음부터 끝까지 다 뒤지는 방식&lt;/li&gt;
&lt;li&gt;비트마스크(Bitmask): 이진수 비트 연산을 이용해 모든 경우의 수를 표현하고 탐색하는 방식&lt;/li&gt;
&lt;li&gt;재귀 함수를 이용한 순열/조합: DFS 등을 활용해 모든 가능한 순서를 조합하는 방식&lt;/li&gt;
&lt;li&gt;BFS/DFS: 그래프나 트리 형태의 상태 공간을 탐색하는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전 탐색의 시간 복잡도는 경우의 수에 따라 O(2^n), O(N!)(순열) 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제에서는 주어진 종이 조각의 개수(numbers)가 최대 7개로 제한되었기 때문에 완전 탐색을 사용하는 것이 적합한 문제였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFS (완전 탐색)&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS는 그래프나 트리 자료구조에서 시작 정점으로부터 연결된 한 갈래를 선택해서 최대한 깊숙히 바닥 노드까지 파고들어 탐색한 뒤, 더 이상 갈 곳이 없으면 내려온 길을 되돌아가 다음 갈래를 선택하는 알고리즘이다. 이는 Call Stack이나 LIFO를 이용해 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도는 일반적으로 그래프 탐색 시 정점(V)과 간선(E)를 모두 확인하므로 O(V+E)이다. 이번 문제에서는 조각을 뽑는 가짓수와 순열의 가짓수를 고려해 O(N*N!)의 시간 복잡도를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS에서 주요 관련 개념이 있는데&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;백트래킹:&lt;/b&gt; 해를 찾는 도중에 막힐 경우(바닥 노드에 도달하는 등), 이전 상태로 되돌아가서 다른 경로를 다시 시도하는 알고리즘이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가지치기(Pruning):&lt;/b&gt; 백트래킹 과정에서 이 경로는 더 들어가봤자 어차피 해를 구할 수 없다라고 판단되는 갈래를 미리 차단하여 연산량을 줄이는 최적화 기법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이번 문제에서 DFS를 사용하는 이유는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;permutation(&quot;&quot;) 으로 시작하면서 빈 문자열에서 조각 하나를 고를 때마다 트리의 아래 depth로 내려간다. &amp;ldquo;1&amp;rdquo;에서 &amp;ldquo;17&amp;rdquo;이 되는 것은 트리의 자식 노드로 이동하는 셈이다.&lt;/li&gt;
&lt;li&gt;DFS는 Call Stack을 이용해서 내가 지금 어떤 조각들을 거쳐서 이 깊이까지 내려왔는지를 기억한다. &amp;ldquo;173&amp;rdquo; 이라는 바닥 노드에 도달했을 때 메모리 스택에는 [&amp;rdquo;1&amp;rdquo;, &amp;ldquo;7&amp;rdquo;, &amp;ldquo;3&amp;rdquo;]이라는 경로가 수직으로 쌓여있다.&lt;/li&gt;
&lt;li&gt;만약 BFS(넓이 우선)으로 구현하려 했다면, &amp;lsquo;1&amp;rsquo;고르고 &amp;lsquo;7&amp;rsquo; 고르고 &amp;lsquo;3&amp;rsquo; 고른 뒤에 다시 위로 올라가서 &amp;lsquo;17&amp;rsquo;, &amp;lsquo;13&amp;rsquo;을 고르는 식으로 가로로 움직여야 한다. 이는 이전 층의 상태를 모두 큐에 저장하여야 하므로 메모리 낭비가 심하다. DFS는 한 놈을 끝까지 파고들어 숫자를 완성하고 돌아오는 방식이기 때문에 이번 문제와 같이 순열 조립에 최적화된 형태라고 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이번 문제를 떠나서 앞으로 어떤 문제를 풀이하려 할 때 아 이 때는 DFS가 적합하다! 라고 판단하는 기준은 어떨까?&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이번 문제에서 visited 배열을 사용한 것 처럼 과거 선택에 대한 기록을 남기는 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;가장 먼 곳이나 바닥을 확인해야 결론이 나는 경우
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;경로 탐색 문제에서 &amp;lsquo;미로의 출구까지 갈 수 있는가?&amp;rsquo;, &amp;lsquo;트리의 리프 노드(끝)까지 도달했을 때 총합이 얼마인가?&amp;rsquo; 와 같이 중간 과정보다 끝까지 가봐야 정답 조건을 충족하는 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;제약 조건 N의 크기가 매우 작을 때 (완전 탐색 기반의 경우!)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;완전 탐색 기반의 DFS는 시간 복잡도가 O(2^N) 이나 O(N!)으로 큰 편이다. 간혹 문제에서 N의 조건이 10 이하, 15이하와 같이 범위가 극단적으로 작은 경우 완전 탐색으로 구현하는 문제라는 힌트라고 볼 수 있다.&lt;/li&gt;
&lt;/ol&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;&quot;미로가 주어졌을 때 출구까지 갈 수 있는가?&quot;처럼 정해진 지도(그래프)가 이미 존재할 때 사용한다. 이는 중복 방문을 막는 것이 목적으로 따라서 visited[] 를 true에서 false로 탐색이 완전 끝날 때 까지 바꾸지 않는다.&lt;/li&gt;
&lt;li&gt;이는 정점만 훑으면 되므로 N이 비교적 크다. ****(N &amp;ge; 100,000)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;꼬리질문&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q. DFS를 재귀 함수(Recursion) 대신 다르게 구현하는 방법이 있나요?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 함수는 기본적으로 컴퓨터 내부의 Call Stack을 사용하는 방식이다. 따라서 개발자가 명시적으로 스택을 선언하고 직접 반복문을 돌리면서 데이터를 Push/Pop 하는 방식으로도 동일하게 DFS를 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Q. 이 문제에서 '백트래킹(Backtracking)'과 'DFS'의 차이점은 무엇인가요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DFS는 트리나 그래프의 모든 노드를 빠짐없이 방문하는 탐색 알고리즘 자체를 의미한다.&lt;/li&gt;
&lt;li&gt;백트래킹은 탐색을 진행하다가 조건을 만족하지 않으면 더 깊이 들어가지 않고 이전 상태로 되돌아가 나오는 알고리즘 설계 패러다임이다.&lt;/li&gt;
&lt;li&gt;이번 풀이에서 모든 숫자를 다 만들기 때문에 가지치기를 하지는 않았지만, 하나의 조각을 쓰고 난 뒤 복구하는 과정 (visited[i] = false)에 백트래킹의 핵심 메커니즘이 녹아들어 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 공부를 시작하면서 처음으로 완전 탐색 문제 풀이를 시도해보면서 어려움이 있었던 것 같습니다. 스스로 문제를 해결하려고 고민했을 때 결국 해결책을 찾지 못하고 AI의 도움을 받아 학습하면서 문제를 해결했습니다. 이번 풀이를 통해 DFS 개념에 대해 복습하게 되었고, 여기서는 완전 탐색을 위해 DFS 알고리즘을 활용해보았는데, 이 경험을 통해 앞으로 비슷한 유형에서는 직접 더 빠르게 해결할 수 있으면 좋겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이팅!&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>소수 찾기</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/14</guid>
      <comments>https://yeonnim01.tistory.com/14#entry14comment</comments>
      <pubDate>Sat, 6 Jun 2026 16:15:37 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 더 맵게 (Lv. 2 JavaScript)</title>
      <link>https://yeonnim01.tistory.com/13</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42626&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780385058631&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bqshGw/dJMb83kAd36/u0mpap79KD5SczMp9WCBY0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/tERGc/dJMb86n4Pkh/CvK13MMraFpuNlzgvehKh0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42626&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bqshGw/dJMb83kAd36/u0mpap79KD5SczMp9WCBY0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/tERGc/dJMb86n4Pkh/CvK13MMraFpuNlzgvehKh0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 주어진 음식의 스코빌 지수가 K 이상이 되도록 섞어야하는 최소 횟수를 반환해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 조건:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 때 두 음식을 섞었을 때 스코빌 지수는 다음과 같다. 섞은 음식의 스코빌 지수 = 가장 맵지 않은 음식의 스코빌 지수 + (두 번째로 맵지 않은 음식의 스코빌 지수 * 2)&lt;/li&gt;
&lt;li&gt;주어진 음식의 수는 2이상 1,000,000 이하이다.&lt;/li&gt;
&lt;li&gt;K는 0 이상 1,000,000,000 이하이다.&lt;/li&gt;
&lt;li&gt;스코빌 지수는 0 이상 1,000,000 이하이다.&lt;/li&gt;
&lt;li&gt;만약에 모든 스코빌 지수를 K 이상으로 만들 수 없다면 -1을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&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이 항상 루트 노드인 heap[0]에 위치하게 된다.&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;힙의 루트 노드 (heap[0])가 K보다 작고, 힙에 남은 음식의 수가 2개 이상일 경우에 계속해서 음식을 섞는다.&lt;/li&gt;
&lt;li&gt;가장 작은 값과 두 번째로 작은 값을 꺼내어 poll() 주어진 연산 공식에 따라 계산하여 새로운 스코빌 지수의 음식을 다시 힙에 넣는다. add() . 이 과정이 끝날 때 마다 answer 변수의 값을 1 증가 시킨다.&lt;/li&gt;
&lt;li&gt;반복문이 끝나고 난 후에도 루트 노드의 값이 K보다 작다면 조건에 의해 -1을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scoville이 다음과 같이 주어졌을 때 [1, 2, 3, 9, 10, 12] 최소 힙의 변경 과정은 아래 그림과 같다.&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. 처음 힙의 초기 상태&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nta9e/dJMcac4ngxm/VyDm5DmxdyKQ4aobTMjF7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nta9e/dJMcac4ngxm/VyDm5DmxdyKQ4aobTMjF7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nta9e/dJMcac4ngxm/VyDm5DmxdyKQ4aobTMjF7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnta9e%2FdJMcac4ngxm%2FVyDm5DmxdyKQ4aobTMjF7k%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;418&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;418&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;2. 가장 맵지 않은 음식인 루트 노드를 &lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;poll() 및 bubbledown() &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;539&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDD0iL/dJMcajbhPiY/M944ITk72qN4agtw6ube40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDD0iL/dJMcajbhPiY/M944ITk72qN4agtw6ube40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDD0iL/dJMcajbhPiY/M944ITk72qN4agtw6ube40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDD0iL%2FdJMcajbhPiY%2FM944ITk72qN4agtw6ube40%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;539&quot; height=&quot;420&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;420&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;루트 노드인 1을 꺼내고 마지막 노드인 12를 루트 노드로 올린다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yzM4c/dJMcacwzUfJ/fzp1ZR5kAlA4E50XKisPU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yzM4c/dJMcacwzUfJ/fzp1ZR5kAlA4E50XKisPU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yzM4c/dJMcacwzUfJ/fzp1ZR5kAlA4E50XKisPU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyzM4c%2FdJMcacwzUfJ%2Ffzp1ZR5kAlA4E50XKisPU1%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;486&quot; height=&quot;400&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트로 올린 노드 12를 자식 노드인 2와 3과 비교하고 루트 노드보다 작고 이 중 2가 더 작으므로 2와 위치를 변경한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ALGFA/dJMcaaFyIoP/LGRDLtTtSKhThjpfDJCJr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ALGFA/dJMcaaFyIoP/LGRDLtTtSKhThjpfDJCJr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ALGFA/dJMcaaFyIoP/LGRDLtTtSKhThjpfDJCJr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FALGFA%2FdJMcaaFyIoP%2FLGRDLtTtSKhThjpfDJCJr0%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;640&quot; height=&quot;402&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;402&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;노드 12가 이동한 후에도 자식 노드들보다 더 큰 값이므로 자식 노드들 중 작은 값인 9와 위치를 변경한다. 이렇게 첫 번째 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;1&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;poll()&lt;/span&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;3. 두 번째로 스코빌이 작은 값을 꺼내는 과정도 동일하게 진행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v8dky/dJMcaaZNMTK/9tgTzySwDnxjWKR1TxNQAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v8dky/dJMcaaZNMTK/9tgTzySwDnxjWKR1TxNQAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v8dky/dJMcaaZNMTK/9tgTzySwDnxjWKR1TxNQAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv8dky%2FdJMcaaZNMTK%2F9tgTzySwDnxjWKR1TxNQAk%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;640&quot; height=&quot;412&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출한 가장 작은 스코빌 지수의 값은 순서대로 1, 2이다. 주어진 섞은 음식의 스코빌 지수 계산을 진행하면 1 + (2 * 2) = 5가 된다. 따라서 두 음식을 섞어 만든 새로운 음식 (스코빌 지수 5)를 힙에 삽입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5를 힙의 마지막(heap[heap.size() - 1])에 삽입하고 bubbleup() 을 통해 위치를 정렬한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 첫 번째 루프가 종료되면서 answer 값을 1 증가시킨다. 여전이 K=7 보다 루트 노드의 값이 작기 때문에 동일한 방식으로 루프를 진행한다.&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;a href=&quot;https://chamdom.blog/heap-using-js/&quot;&gt;https://chamdom.blog/heap-using-js/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780385422777&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;[자료구조] JavaScript로 힙(Heap) 구현하기&quot; data-og-description=&quot;힙이란? 힙(heap) 은 완전 이진 트리의 일종 으로 특정한 규칙을 따라 부모 노드와 자식 노드 사이의 값이 정렬된다. 힙은 주로 우선순위 큐(Priority Queue) &amp;hellip;&quot; data-og-host=&quot;chamdom.blog&quot; data-og-source-url=&quot;https://chamdom.blog/heap-using-js/&quot; data-og-url=&quot;https://chamdom.blog/heap-using-js/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/B58fJ/dJMb9kT98b5/KHA5trQ2pykTf0xV4UJG8k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://chamdom.blog/heap-using-js/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chamdom.blog/heap-using-js/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/B58fJ/dJMb9kT98b5/KHA5trQ2pykTf0xV4UJG8k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400');&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;[자료구조] JavaScript로 힙(Heap) 구현하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;힙이란? 힙(heap) 은 완전 이진 트리의 일종 으로 특정한 규칙을 따라 부모 노드와 자식 노드 사이의 값이 정렬된다. 힙은 주로 우선순위 큐(Priority Queue) &amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chamdom.blog&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class MinHeap {
    constructor() {
        this.heap = [];
    }
    
    size() {
        return this.heap.length;
    }
    
    getRoot() {
        return this.heap[0];
    }
    
    swap(idx1, idx2) {
        [this.heap[idx1], this.heap[idx2]] = [this.heap[idx2], this.heap[idx1]];
    }
    
    add(value) {
        this.heap.push(value);
        this.bubbleup();
    }
    
    bubbleup() {
        let index = this.heap.length - 1;
        let parentIndex = Math.floor((index - 1) / 2)
        while (
            parentIndex &amp;gt;= 0
            &amp;amp;&amp;amp; this.heap[index] &amp;lt; this.heap[parentIndex]) {
            this.swap(index, parentIndex);
            index = parentIndex;
            parentIndex = Math.floor((index - 1) / 2)
        }
    }
    
    poll() {
        if (this.heap.length === 1) {
            return this.heap.pop();
        }
        
        const value = this.heap[0];
        this.heap[0] = this.heap.pop(); // 마지막노드를 루트 노드로
        this.bubbledown();
        return value;
    }
    
    bubbledown() {
        let index = 0;
        let leftIndex = index * 2 + 1;
        let rightIndex = index * 2 + 2;
        
        while(
            (leftIndex &amp;lt; this.heap.length 
	            &amp;amp;&amp;amp; this.heap[leftIndex] &amp;lt; this.heap[index]) 
	            || (rightIndex &amp;lt; this.heap.length 
		            &amp;amp;&amp;amp; this.heap[rightIndex] &amp;lt; this.heap[index])) {
                let smallIndex = leftIndex;
                if (this.heap[rightIndex] 
	                &amp;amp;&amp;amp; this.heap[smallIndex] &amp;gt; this.heap[rightIndex]) {
                    smallIndex = rightIndex;
                }
                this.swap(index, smallIndex);
                index = smallIndex;
                leftIndex = index * 2 + 1;
                rightIndex = index * 2 + 2;
        
            }
        
    }
}

function solution(scoville, K) {
    var answer = 0;
    const heap = new MinHeap();
    scoville.forEach((item) =&amp;gt; {
        heap.add(item);
    })
    
    let lowest = 0;
    let secondLowest = 0;
    while (heap.size() &amp;gt; 1 &amp;amp;&amp;amp; heap.getRoot() &amp;lt; K) {
        lowest = heap.poll();
        secondLowest = heap.poll();
        const mixScoville = lowest + (secondLowest * 2);
        heap.add(mixScoville);
        answer++;
    }
    
    if (heap.getRoot() &amp;lt; K) {
        answer = -1;
    }
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최적화된 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class MinHeap {
    constructor() {
        this.heap = [];
    }
    
    size() {
        return this.heap.length;
    }
    
    getRoot() {
        return this.heap[0];
    }
    
    swap(idx1, idx2) {
        [this.heap[idx1], this.heap[idx2]] = [this.heap[idx2], this.heap[idx1]];
    }
    
    add(value) {
        this.heap.push(value);
        this.bubbleup();
    }
    
    bubbleup() {
        let index = this.heap.length - 1;
        let parentIndex = Math.floor((index - 1) / 2)
        while (
            parentIndex &amp;gt;= 0
            &amp;amp;&amp;amp; this.heap[index] &amp;lt; this.heap[parentIndex]) {
            this.swap(index, parentIndex);
            index = parentIndex;
            parentIndex = Math.floor((index - 1) / 2)
        }
    }
    
    poll() {
        if (this.heap.length === 1) {
            return this.heap.pop();
        }
        
        const value = this.heap[0];
        this.heap[0] = this.heap.pop(); // 마지막노드를 루트 노드로
        this.bubbledown();
        return value;
    }
    
    bubbledown() {
        let index = 0;

        
        while(true) {
            let leftIndex = index * 2 + 1;
            let rightIndex = index * 2 + 2;
            let smallIndex = index;
            
            if (leftIndex &amp;lt; this.heap.length 
            &amp;amp;&amp;amp; this.heap[smallIndex] &amp;gt; this.heap[leftIndex]) 
            {
                smallIndex = leftIndex;
            }
            
            if (rightIndex &amp;lt; this.heap.length 
            &amp;amp;&amp;amp; this.heap[smallIndex] &amp;gt; this.heap[rightIndex]) 
            {
                smallIndex = rightIndex;
            }
            
            if (smallIndex === index) break;
            
            this.swap(index, smallIndex);
            index = smallIndex;
        }
    }
}

function solution(scoville, K) {
    var answer = 0;
    const heap = new MinHeap();
    scoville.forEach((item) =&amp;gt; {
        heap.add(item);
    })
    
    let lowest = 0;
    let secondLowest = 0;
    while (heap.size() &amp;gt; 1 &amp;amp;&amp;amp; heap.getRoot() &amp;lt; K) {
        lowest = heap.poll();
        secondLowest = heap.poll();
        const mixScoville = lowest + (secondLowest * 2);
        heap.add(mixScoville);
        answer++;
    }
    
    if (heap.getRoot() &amp;lt; K) {
        answer = -1;
    }
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bubbledown() 메서드 코드를 조금 더 직관적이게 리팩토링하였고, 기존 조건문에서는 this.heap[rightIndex] 의 유무를 판단하였는데, 만약 오른쪽 자식 노드가 없다면 undefined가 된다. JS에서는 존재하지 않는 인덱스를 비교해도 런타임 에러가 아닌 false를 반환하기 때문에 왼쪽 자식만 있을 경우에 연산 로직이 꼬일 우려가 있어 rightIndex의 값이 현재 힙의 크기보다 작은지를 판단하도록 수정함.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;복습&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최소 힙 개념 복습&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 힙은 완전 이진 트리를 기반으로 하는 자료구조로, 최댓값이나 최솟값을 빠르게 찾아내기 위해 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선순위 조건: 부모 노드의 키 값이 자식 노드의 키 값보다 항상 작거나 같아야 한다 (A&amp;le; B)&lt;/li&gt;
&lt;li&gt;최소 힙의 루트 노드(heap[0])에는 트리 전체에서 가장 작은 값이 위치한다.&lt;/li&gt;
&lt;li&gt;최소 힙은 오직 부모와 자식 간의 대소 관계만 보장할 뿐, 왼쪽 자식과 오른쪽 자식 간의 크기 순서는 정해져 있지 않다. (즉, 배열의 인덱스 순서대로 완벽히 정렬된 상태가 아니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이진 탐색 트리와의 차이점은 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 최소 힙 (Min Heap) 이진 탐색 트리 (BST)&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;&lt;b&gt;트리 형태&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;완전 이진 트리 (빈틈없이 채워짐)&lt;/td&gt;
&lt;td&gt;일반 이진 트리 (한쪽으로 치우칠 수 있음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;정렬 방향&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;상하 관계 (부모 &amp;le; 자식)&lt;/td&gt;
&lt;td&gt;좌우 관계 (왼쪽자식 &amp;lt; 부모 &amp;lt; 오른쪽자식)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;목적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최솟값/최댓값을 O(1)만에 탐색&lt;/td&gt;
&lt;td&gt;임의의 데이터를 O(log N)만에 탐색/정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 배열을 이용해 완전 이진 트리를 표현할 수 있는 이유는 힙은 중간에 빈 노드가 존재하지 않는다. 따라서 연결 리스트가 아니라 일반 1차원 배열로 효율적으로 구현이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 노드의 인덱스를 0으로 시작할 때, 임의의 노드 인덱스가 i라면 다음 공식이 성립한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 노드의 인덱스: Math.floor((i - 1) / 2)&lt;/li&gt;
&lt;li&gt;왼쪽 자식 노드의 인덱스: i * 2 + 1&lt;/li&gt;
&lt;li&gt;오른쪽 자식 노드의 인덱스: i * 2 + 2&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배열을 정렬 sort() 하여 가장 작은 값 두 개를 추출하고 추가하는 방식으로 구현 했을 때의 문제점&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 힙 자료구조를 택하지 않더라도 주어진 배열을 정렬하고 가장 작은 두 개의 값을 추출해서 섞고 새로운 값을 추가하는 방식을 반복한다면 문제 자체는 해결이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 효율성 테스트에서 문제가 생기는데 자바스크립트의 sort() 메서드는 Timsort 방식으로 O(N log N)의 시간 복잡도를 가진다. 최악의 경우 모든 음식을 다 섞어야 하므로 원소가 1개가 남을때 까지 N번 루프를 돌게 되므로 최종 시간 복잡도는 O(N^2 log N)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 문제에서 N의 최대 값은 100만개이므로, 효율성 테스트에서 무조건 시간 초과가 발생하게 된다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최소값 추출(poll()): 루트 노드를 빼고 맨 뒤 노드를 위로 올린 뒤 자식들과 비교해나간다. 이 때 높이만큼만 내려가므로 log N이 소요된다.&lt;/li&gt;
&lt;li&gt;두 번째 최소값 추출 역시 마찬가지이다.&lt;/li&gt;
&lt;li&gt;새 음식을 삽입하는 add() 역시 트리 높이만큼만 올라가므로 O(log N)의 시간 복잡도를 갖는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 루프를 한 번 돌때 시간 복잡도가 O(log N)이고 이를 최대 N번 반복하면 O(N log N)의 시간 복잡도를 갖기 때문에 정렬하여 진행하는 것 보다 더 효율적이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&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>더 맵게</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/13</guid>
      <comments>https://yeonnim01.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 2 Jun 2026 16:31:51 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 가장 큰 수 (Lv. 2 JavaScript)</title>
      <link>https://yeonnim01.tistory.com/12</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42746&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42746&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780287440044&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42746&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dEPmyO/dJMb9frMrEc/5rNyevr2SCbYZwbu3HRxxk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/RJDjP/dJMb9efk1M5/kbT7kgBoj6fOqp0J7ZqyC0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42746&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42746&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dEPmyO/dJMb9frMrEc/5rNyevr2SCbYZwbu3HRxxk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/RJDjP/dJMb9efk1M5/kbT7kgBoj6fOqp0J7ZqyC0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 배열로 주어진 정수들을 이어 붙여서 만들 수 있는 가장 큰 수 조합을 알아내는 것이 목표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심조건:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;numbers 배열의 길이는 1 이상 100,000 이하&lt;/li&gt;
&lt;li&gt;numbers의 원소 즉, 주어지는 정수의 크기는 0 이상 1000 이하&lt;/li&gt;
&lt;li&gt;문자열로 return 해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 정수 배열 numbers 의 요소들을 재배치하여 만들 수 있는 가장 큰 수를 반환하는 문제로, 예를 들어 [6, 10, 2]로 주어진 경우 만들 수 있는 조합은 6102, 6210, 2106&amp;hellip; 등등이 있다 여기서 만들 수 있는 가장 큰 수는 6210 일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서 JS의 정렬 메서드 sort()를 사용한다. 이 때 정렬할 두 요소를 앞뒤로 이어붙인 값 중에 더 큰 경우를 조건으로 정렬을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 [6, 10, 2]의 케이스의 경우&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;610&amp;rdquo;과 &amp;ldquo;106&amp;rdquo; 중 더 큰 값은 610이므로 6을 앞에 배치하고&lt;/li&gt;
&lt;li&gt;다음 &amp;ldquo;102&amp;rdquo;와 &amp;ldquo;210&amp;rdquo;중에서는 210이 더 크므로 2를 앞에 배치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식으로 정렬하면 가장 큰 수 조합의 정답인 &amp;ldquo;6210&amp;rdquo;을 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때 주의 해야 할 예외 케이스는 주어진 numbers 배열이 [0, 0, 0]과 같이 0으로만 구성된 경우에는 정답이 000으로 나오게 되므로 이 예외 케이스를 고려해서 최종 반환할 때 answer를 숫자로 변환했을 때 값이 0이라면 &amp;ldquo;0&amp;rdquo;으로 반환하도록 처리한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; ️ 트러블슈팅&lt;/b&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;예를 들어 [8, 82, 819]의 경우 모두 맨 앞자리의 수는 8로 같은데 이 경우에는 어떤 기준으로 이 세 숫자를 정렬해야할까? 고민해봤고 &amp;ldquo;만약에 앞자리가 동일하면 그 다음 자릿수를 비교하는 식으로 해보자&amp;rdquo; 생각했으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 문제점이 8과 82를 비교할때 앞자리 수가 같으므로 다음 자리수를 비교하면 8은 undefined이고 82는 2인데 undefined와 2를 비교할 수는 없기 때문이다. 이러한 예외 케이스까지 처리하려면 매우 복잡해졌고 코드의 직관성도 떨어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기에다 이 방식으로 정렬하는 경우 성능 면에서도 좋지 않은데, 핵심 조건에 의하면 numbers의 크기는 최대 100,000 (N=100,000) 배열 내부 원소의 크기는 최대 1,000 (D=4)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 최대 숫자가 네자리까지 있다는 것인데 모든 numbers의 요소들을 정렬할 때마다 최대 4번의 자릿수를 매번 비교 연산하면 시간 복잡도는 O(D * N log N) 이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function solution(numbers) {
    var answer = '';
    const strNumbers = numbers.map((num) =&amp;gt; num.toString()).sort((a, b) =&amp;gt; {
        return (b+a) - (a+b)
    });

    strNumbers.forEach((num) =&amp;gt; {
        answer += num;
    })

    return Number(answer) !== 0 ? answer : &quot;0&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최적화된 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function solution(numbers) {
    var answer = '';
    const strNumbers = numbers.map((num) =&amp;gt; num.toString()).sort((a, b) =&amp;gt; {
        return (b+a) - (a+b)
    });

    answer = strNumbers.join(&quot;&quot;)

    return Number(answer) !== 0 ? answer : &quot;0&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;forEach를 활용해 문자열을 순회하며 누적하는 방식 대신 join() 고차 함수를 사용하는 방법이 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;복습&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sort()&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sort() 함수에 대해 복습 내용 정리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sort() 함수의 기본 정렬 기준은 &amp;lsquo;유니코드(문자열)&amp;rsquo; 기준으로 정렬을 한다.&lt;/li&gt;
&lt;li&gt;const numbers = [1, 2, 10, 20]; numbers.sort(); console.log(numbers); // Expected: [1, 2, 10, 20] -&amp;gt; Actual: [1, 10, 2, 20]&lt;/li&gt;
&lt;li&gt;sort() 함수 내부에 콜백함수를 정의하여 정렬 기준을 커스텀할 수 있다. 콜백함수는 sort((a, b) &amp;rArr; return b- a) 와 같이 비교하는 함수를 정의 해야한다.&lt;/li&gt;
&lt;li&gt;자바스크립트에서(V8 엔진) sort() 함수는 Timsort 방식을 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 때 Timsort 알고리즘이란 MergeSort와 Insertion Sort의 장점을 결합한 하이브리드 정렬 알고리즘으로 퀵 정렬의 단점인 최악의 경우 O(N^2) 까지 성능이 떨어지는 경우를 보완하는데&lt;/li&gt;
&lt;li&gt;MergeSort 방식을 기반으로 하기 때문에 최악에 상황에서도 O(N logN) 보장한다.&lt;/li&gt;
&lt;li&gt;또한 동일한 값의 상대적인 순서가 유지되는 Stable Sort 특징을 가진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고자료:&lt;/b&gt; Tim sort에 대해 알아보자 &lt;a href=&quot;https://d2.naver.com/helloworld/0315536&quot;&gt;https://d2.naver.com/helloworld/0315536&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;join()&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;join() 메서드는 배열의 모든 요소를 연결해 하나의 문자열로 만들어 준다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;join(): 인자를 생략하면 기본값으로 쉼표(,)를 사용해 연결한다.&lt;/li&gt;
&lt;li&gt;join(&amp;rsquo;&amp;rsquo;): 빈 문자열을 인자로 넘겨주면 요소 사이에 아무것도 넣지 않고 연결한다.&lt;/li&gt;
&lt;li&gt;join(&amp;rsquo;-&amp;rsquo;): 지정한 구분자를 요소 사이에 삽입한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 문제에서 forEach 사용보다 join()이 더 최적화된 코드라고 볼 수 있을까? 이는 forEach의 콜백 함수 내부에서 answer += num 연산으로 문자를 더해주고 있는데, 이 방식은 자바스크립트의 특성 상 문자열은 불변(Immutable) 객체이다. 즉, += 연산을 할 때 마다 기존 문자열이 수정되는 것이 아니라 메모리 상에 새로운 문자열 객체가 생성되고 버려지는 과정을 반복한다. 만약 배열의 크기가 N=100,000 이라면 총 100,000번의 위 과정을 반복하는 오버헤드가 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 join(&amp;rsquo;&amp;rsquo;) 의 경우 자바스크립트 내부 엔진에서 배열의 길이를 미리 계산한 후 한 번의 메모리 할당으로 문자열을 결합하기 때문에 성능면에서 이러한 경우 더 최적화된다고 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제를 해결하면서 아쉬움이 많이 남았습니다. &amp;lsquo;두 요소를 앞 뒤로 이어 붙여서 크기를 비교하여 정렬한다&amp;rsquo; 라는 접근을 결국 스스로 해내지 못하고 힌트를 얻고 해결한 점이 많이 아쉬웠습니다. 스스로 한없이 부족하다는 점을 계속해서 느끼고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그렇다고 포기하지 말고 꾸준히 나아가는게 중요하다 생각하고 이번에는 매일 꾸준히 알고리즘 문제 풀이에 정진해야겠습니다. 모두 파이팅!&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>가장 큰 수</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/12</guid>
      <comments>https://yeonnim01.tistory.com/12#entry12comment</comments>
      <pubDate>Mon, 1 Jun 2026 13:18:17 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 올바른 괄호 (Lv. 2, JavaScript)</title>
      <link>https://yeonnim01.tistory.com/11</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12909&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780032473632&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/x6ZcE/dJMb89ykosy/tyA9uB2gHBEUd1m1L94Qg1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bGCqKs/dJMb84X5vHZ/hkkpAkoZ4WK3gR1pK2sPM1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12909&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/x6ZcE/dJMb89ykosy/tyA9uB2gHBEUd1m1L94Qg1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bGCqKs/dJMb84X5vHZ/hkkpAkoZ4WK3gR1pK2sPM1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 괄호로 이루어진 문자열이 주어졌을 때 괄호가 짝지어서 닫힌 형태로만 존재해야 한다. 이를 확인해 true 혹은 false 를 반환하는 것이 목표&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 조건:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;괄호가 있다면 무조건 짝지어서 닫힌 형태여야 한다. &lt;b&gt;()&lt;/b&gt; 예를들어 &lt;b&gt;()()&lt;/b&gt; 혹은 &lt;b&gt;(())&lt;/b&gt; 와 같이 되어야 하며, &lt;b&gt;((())&lt;/b&gt; 나 &lt;b&gt;)()()&lt;/b&gt; 는 조건이 부합하지 않는다.&lt;/li&gt;
&lt;li&gt;문자열 s의 길이는 100,000 이하의 자연수&lt;/li&gt;
&lt;li&gt;문자열 s는 &lt;b&gt;&amp;lsquo;(&amp;rsquo;&lt;/b&gt; 또는 &lt;b&gt;&amp;lsquo;)&amp;rsquo;&lt;/b&gt; 로만 이루어져 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;풀이 과정 (나만의 풀이 전략)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 s에서 순서대로 볼 때 &lt;b&gt;&amp;lsquo;(&amp;rsquo;&lt;/b&gt; 가 1회 이상 연속으로 나온다면 이후에는 &lt;b&gt;&amp;lsquo;)&amp;rsquo;&lt;/b&gt;가 &lt;b&gt;&amp;lsquo;(&amp;rsquo;&lt;/b&gt;의 개수와 동일하게 연속으로 등장해야 괄호의 규칙에 맞게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들면 ((( 3개 라면 동일하게 ))) 3개여야 하고,&lt;b&gt; (( ) (( )))&lt;/b&gt; 이 경우에도&lt;b&gt; &amp;lsquo;(&amp;rsquo;&lt;/b&gt;가 4회 &lt;b&gt;&amp;lsquo;)&amp;rsquo;&lt;/b&gt; 역시 4개로 조건을 충족한다.&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;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;) 가 ( 보다 먼저 나오는 경우에는 무조건 false&lt;/li&gt;
&lt;li&gt;( 로 끝나는 경우 무조건 false&lt;/li&gt;
&lt;li&gt;( 가 나온 횟수보다 ) 가 바로 뒤이어 더 많이 나올 경우에도 false&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 케이스를 제외하면, 두 괄호의 개수가 동일하면 true를 반환한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; ️ 트러블슈팅&lt;/b&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;1차 코드&lt;/p&gt;
&lt;pre id=&quot;code_1780032565360&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(s){
    var answer = true;
    
    // 문자열 s를 배열로 변환
    let arr = s.split(&quot;&quot;);
    
    // 두 괄호의 개수가 다르다면
    let leftCount = arr.filter((s) =&amp;gt; s === '(').length;
    let rightCount = arr.filter((s) =&amp;gt; s === ')').length;
    
    if (leftCount !== rightCount) {
        answer = false;
    }
    
    
    // case 1. ( 가 먼저 나올 경우 무조건 올바르지 않은 경우
    // case 2. ( 로 끝나는 경우 무조건 올바르지 않은 경우
    if (arr[0] === ')' || arr[arr.length - 1] === '(')  {
        return false;
    }

    leftCount = 0;
    rightCount = 0;
    
    // O(N)
    while (arr.length &amp;gt; 0) {
        char = arr.shift();
        if (char === '(') {
		        // case 3. (를 추출했을 때 )가 나온 개수 보다 작다면
            if (leftCount &amp;lt; rightCount) {
                return false;
            }
            leftCount++;
            continue;
        }
        
        if (char === ')') {
            rightCount++;
        }
    }
    
    

    

    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;기존 코드의 시간 복잡도는 문자열의 길이만큼 반복문을 돌도록 설정했으므로, 최악의 경우 O(N)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 시간복잡도를 줄일 수 있는 방법이 있을까? 해서 두 번째로 도입한 방법은 정규표현식을 활용하여 &amp;lsquo;(&amp;rsquo; 묶음과 &amp;lsquo;)&amp;rsquo; 묶음으로 배열을 재구성한다. 이러면 &lt;b&gt;[&amp;rsquo;(((&amp;rsquo;, &amp;lsquo;))&amp;rsquo;, &amp;lsquo;(&amp;rsquo;, &amp;lsquo;)))&amp;rsquo;]&lt;/b&gt; 이러한 형태로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 다음 배열을 순회하면서 문자열의 길이에 따라 Case 3를 검증하면 어떨까? 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 특정 테스트 케이스를 통과하지 못했고 결론적으로 오히려 성능면에서 좋지 않다는 점인데, match 메서드의 시간 복잡도가 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;결국 다시 처음 1차 코드 (문자열 순회)에서 문제점을 찾을 수 있었는데, 기본적인 O(N) 시간복잡도는 유지하되, shift() 메서드를 사용하는 것이 문제였다. 배열의 메서드를 사용하는 것이 생각보다 시간 복잡도 측면에서 손실이 있었고 이를 배열을 순회할때마다 하니 시간이 오래걸렸던게 문제였다&amp;hellip; 생각보다 너무 간단한 문제에 끙끙대고 있었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  코드 (Code)&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function solution(s){
    var answer = true;
    
    // 문자열 s를 배열로 변환
    let arr = s.split(&quot;&quot;);
    
    // case 1. ( 가 먼저 나올 경우 무조건 올바르지 않은 경우
    // case 2. ( 로 끝나는 경우 무조건 올바르지 않은 경우
    if (arr[0] === ')' || arr[arr.length - 1] === '(')  {
        return false;
    }
    
    // 두 괄호의 개수가 다르다면
    let leftCount = arr.filter((s) =&amp;gt; s === '(').length;
    let rightCount = arr.filter((s) =&amp;gt; s === ')').length;
    
    if (leftCount !== rightCount) {
        return false;
    }
    
    // O(N)
    leftCount = 0;
    rightCount = 0;

    for (let index = 0; index &amp;lt; arr.length; index++) {
        if (arr[index] === '(') {
            // case 3. (를 추출했을 때 )가 나온 개수 보다 작다면
            if (leftCount &amp;lt; rightCount) {
                return false;
            }
            leftCount++;
        } else if (arr[index] === ')') {
            rightCount++;
        }
    }

    
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;function solution(s){
    var answer = true;
    
    // case 1. ( 가 먼저 나올 경우 무조건 올바르지 않은 경우
    // case 2. ( 로 끝나는 경우 무조건 올바르지 않은 경우
    if (s[0] === ')' || s[s.length - 1] === '(')  {
        return false;
    }
    
    const arr = s.match(/\\)+|\\(+/g)
	
    leftCount = 0;
    
    // case 3 점검
    for (word of arr) {
        if (word[0] === '(') {
            leftCount += word.length;
        } else if (word[0] === ')') {
            leftCount -= word.length;
            if (leftCount &amp;lt; 0) {
                return false;
            }
        }
    }
    
    
    return leftCount === 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최적화된 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function solution(s){
    var answer = true;
    
    // case 1. ( 가 먼저 나올 경우 무조건 올바르지 않은 경우
    // case 2. ( 로 끝나는 경우 무조건 올바르지 않은 경우
    if (s[0] === ')' || s[s.length - 1] === '(')  {
        return false;
    }
    
    // O(N)
    let leftCount = 0;
    let rightCount = 0;

    for (let index = 0; index &amp;lt; s.length; index++) {
        if (s[index] === '(') {
            leftCount++;
        } else if (s[index] === ')') {
            rightCount++;
            // case 3. (를 추출했을 때 )가 나온 개수 보다 작다면
            if (leftCount &amp;lt; rightCount) {
                return false;
            }
        }
    }

    
    
    return leftCount === rightCount;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 배열로 split을 통해 변환할 필요가 없다. 따라서 문자열을 그대로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for문 이전에 filter을 통해 불필요하게 두 괄호의 개수를 비교할 필요 없이 for문에서 case 3를 걸러낸다면 마지막에 최종 return 직전에 leftCount와 rightCount를 비교하면 되므로, 더 간단하게 작성 가능하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;❓ &lt;b&gt;꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;복습 (추가 학습)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 문제가 스택&amp;amp;큐 알고리즘 관련 문제라는 점을 생각한다면 leftCount와 rightCount를 별도로 두는게 아니라 스택의 성질을 활용하여 leftCount만 두고도 해결이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;(&amp;rsquo;가 나왔을 때 스택의 push처럼 count를 1 증가 시키고 &amp;lsquo;)&amp;rsquo;가 나온다면 pop 처럼 count를 1 감소시키는 방식으로 진행하고, 스택이 비어있는 상태에서 pop을 시도하려하면 문제가 된다고 보면, count가 0일 때 &amp;lsquo;)&amp;rsquo;를 만나 감소시키려 한다면 괄호의 규칙에 어긋난다고 볼 수도 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택의 특징을 잘 알고 있다면 쉽게 해결할 수 있는 문제였기에 생각보다 오랜 시간을 투자해서 많이 아쉬움이 남습니다.&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>올바른 괄호</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/11</guid>
      <comments>https://yeonnim01.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 29 May 2026 14:31:02 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 기능개발 (Lv. 2, JavaScript)</title>
      <link>https://yeonnim01.tistory.com/10</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42586&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773649448133&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Vnh4n/dJMb8T9YfCx/JurGYwBaVIoHbQd1bKQS71/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/Qwpyt/dJMb8Z3p3AL/5w8tksmJ7pRfRh5H5m7bMK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42586&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Vnh4n/dJMb8T9YfCx/JurGYwBaVIoHbQd1bKQS71/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/Qwpyt/dJMb8Z3p3AL/5w8tksmJ7pRfRh5H5m7bMK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;목표&lt;/b&gt;: 배포되어야하는 순서대로 적힌 작업 배열 progresses 와 개발 속도가 적힌 정수 배열 speeds 주어지면 각 배포마다 몇 개의 기능이 배포되는지를 반환해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;핵심조건&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;작업 진도가 100%가 되어야 배포를 진행할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;작업의 개수는 100개 이하 및 작업 진도는 100 미만, 속도는 100 이하이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;progresses 배열에서 뒷 작업이 먼저 끝나더라도 배열의 제일 앞 작업이 끝나야 배포가 진행된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배포는 하루에 한 번만 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각 작업들은 다른 작업 진행에 영향받지 않고 병렬적으로 처리된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  풀이 과정 (나만의 풀이 전략)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 배열에서 첫 번째 요소부터 작업이 끝나기 위해 필요한 날짜 계산이 필요할 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 50px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; progresses&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; speeds&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; return &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[93, 30, 55]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[1, 30, 5]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[2, 1]&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같이 주어졌을 때 progresses의 첫 번째 작업이 끝나기 위해 필요한 기간은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;100% - 93%(현재 진행률) = 7%, 이는 총 4일이 소요된다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그럼 4일동안 작업이 진행되었을 때 첫 번째 작업의 뒷 순서의 작업들의 진행률을 계산해보자&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;30 + 30 * 4 = 150%, 55 + 5 * 4 = 75 으로 첫 번째 작업이 완료되고 배포될 때 두번째 작업 역시 배포되어야한다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이런 방식으로 계산한다면 다음 테스트 케이스의 경우에도&lt;/span&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;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; progresses &lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; speeds &lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; return &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[95, 90, 99, 99, 80, 99]&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[1, 1, 1, 1, 1, 1]&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[1, 3, 2]&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;첫 번째 작업이 끝나기 위해 필요한 기간은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;100% - 95% = 5%, 총 5일이 소요된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그럼 모든 작업들의 작업률은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[100, 95, 104, 104, 85, 104]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 첫 번째 작업은 완료되었으므로 배포를 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그런데 두 번째 작업은 아직 95%로 완료되지 않았으므로, 3,4,6번째 작업이 완료되었지만 배포는 진행하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 5일차에는 하나의 작업만 배포를 진행하고&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다시 5일 뒤에 작업률은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[100, 109, 109, 90, 109] 으로 3개의 작업이 배포될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 다시 10일 뒤에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[100, 119]으로 2개의 작업이 배포되는 것이다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;progresses 배열의 첫 번째 작업이 완료되기 위해 필요한 기간을 계산한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;progresses 배열의 작업들의 진도에 작업속도 * 위에서 계산한 기간 만큼 더한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 progresses 배열이 비어있을 때까지 순회&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;progresses 배열의 첫 번째 작업 진도가 100 이상이라면 배포 카운트를 +1 하고, progresses와 speeds 배열의 첫 번째 항목을 제거한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;첫 번째 작업 진도가 100 미만이라면 순회 종료 (break)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;answer 배열에 배포 카운트를 push 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;progresses 배열이 빌 때까지 1-4 과정을 반복&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; ️ 트러블슈팅&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;처음 제출한 코드는 반복 시퀀스의 3번 과정을 진행할 때 while문이 아닌 for문을 사용했었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;for (let i=0; i &amp;lt; progresses.length; i++) {
      if (progresses[0] &amp;gt;= 100) {
          deployCount++;
          progresses.shift();
          speeds.shift();
      } else {
          break;
      }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 경우 오답이라고 채점되었는데, 이는 for문과 while문의 동작방식의 차이를 이해하지 못한 부분에서 발생한 문제였다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;for (let i=0; i &amp;lt; progresses.length; i++) 루프가 시작되고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만약 if (progresses[0] &amp;gt;= 100) 조건이 참이된다면 배열의 첫 번째 요소를 제거 (shift)하게 된다. 이렇게 된 후에 배열의 모든 요소들이 앞으로 한 칸씩 당겨지게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 루프가 다음 회차로 넘어가면 i는 1이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 때 문제는 i번 인덱스 값은 루프가 돌때마다 1씩 증가하는데, 루프의 종료 조건인 i &amp;lt; progresses.length; 는 점점 작아지게 되면서 의도와 맞지 않게 동작하게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를 들어&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;progresses = [100, 100, 100] 인 경우에&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;i = 0일 때 progresses[0]은 100이므로, 조건문이 참이므로 deployCount를 상승시키고 두 배열을 shift() 한다. 배열은 [100, 100] 이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;i = 1일 때, progresses.length = 2이므로 마찬가지로 조건문을 통과하고 shift() 하면 배열은 [100]이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;i = 2일 때, progresses.lengh = 1 이므로 배열에 아직 [100] 으로 100이 남아있는데도 루프르 종료해버려서 잘못된 deployCount가 계산이 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 인덱스 i를 사용하지 않는 while (progresses.length &amp;gt; 0) 문을 사용해야 shift()를 해서 배열의 길이가 줄어들더라도 문제가 생기지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  코드 (Code)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;function solution(progresses, speeds) {
    let answer = [];
    while (progresses.length &amp;gt; 0) {
        let deployCount = 0;
        let day = 0;
        day = Math.ceil(((100 - progresses[0]) / speeds[0]))
        
        for (let i=0; i &amp;lt; progresses.length; i++) {
            progresses[i] = progresses[i] + speeds[i] * day;
        }
        while (progresses.length &amp;gt; 0) {
            if (progresses[0] &amp;gt;= 100) {
                deployCount++;
                progresses.shift();
                speeds.shift();
            } else {
                break;
            }
        }

        answer.push(deployCount);
    }
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;최적화된 코드&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function solution(progresses, speeds) {
	let answer = [];
	let days = progresses.map((p, i) =&amp;gt; Math.ceil((100 - p) / speeds[i]));
	
	let maxDay = days[0];
	let count = 1;
	
	for (let i = 1; i &amp;lt; days.length; i++) {
		// 기준일보다 빨리 작업이 끝났다면 배포
		if (days[i] &amp;lt;= maxDay) {
			count++;
		} else {
			// 기준일보다 오래걸리면 이전까지 쌓인 기능 배포 후에 기준일을 갱신한다.
			answer.push(count);
			count = 1;
			maxDay = days[i];
		}
	}
	answer.push(count); // 마지막 남은 그룹 추가
	
	return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;진행 상황을 매번 shift()하고 업데이트 하는 방법 대신, 각 기능이 완료되는데 소요되는 기간을 먼저 계산한 후 배열을 한 번만 순회하는 방법으로 해결이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 방법의 경우 원본 배열을 수정하는 작업이 없기 때문에 더 빠른 성능을 보인다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;복습&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;이 문제가 왜 스택/큐 문제인지&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제의 핵심은 먼저 들어온 작업이 끝난 후에 배포되면서 먼저 나가아한다는 점이므로, 큐의 핵심 원리인 FIFO를 담고있는 문제이다. 뒤의 작업이 아무리 빨리 끝나더라도 큐의 앞의 작업이 나갈 때 까지 나갈 수 없다는 점이 문제의 핵심.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;JS의 Array 인스턴스의 주요 메서드들&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;요소 추가 및 제거&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;push(&amp;hellip;items): 배열의 끝에 요소를 추가하고 변경된 길이를 반환하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;pop(): 배열의 끝 요소를 제거하고,그 요소를 반환하는 메서드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;unshift(&amp;hellip;items): 배열의 &lt;b&gt;앞&lt;/b&gt;에 요소를 추가하는 메서드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;shift(): 배열의 앞 요소를 제거하는 메서드. 제거한 후 나머지 요소들을 앞으로 당기는 작업이 포함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;splice(start, deleteCount, &amp;hellip;items): 특정 위치의 요소를 제거하거나, 교체/추가하는 메서드&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순회 및 탐색&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;forEach(callback): 배열의 각 요소에 대해 함수를 실행하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;map(callback): 배열의 각 요소에 함수를 적용하여 새로운 배열을 생성하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;filter(callback): 조건에 맞는 요소만 추려서 새로운 배열을 생성하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;reduce(callback, initValue): 배열을 순회하며 하나의 결과값을 만드는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;find(callback) / findIndex(callback): 조건에 맞는 첫 번째 요소나 그 인덱스를 반환하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열 변형 및 정렬&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;sort(compareFunc): 배열을 정렬하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;reverse(): 배열의 순서를 뒤집는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;slice(start, end): 배열의 일부분을 복사하여 새로운 배열을 생성하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;join(seperator): 배열의 모든 요소를 문자열로 합치는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;concat(): 두 개 이상의 배열을 병합하여 새로운 배열을 반환하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;논리 판단 메서드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;includes(value): 배열이 특정 값을 포함하는지 여부를 반환하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;every(callback): 배열의 모든 요소가 조건을 만족하는지 여부를 반환하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;some(callback): 배열에서 하나라도 조건을 만족하는지를 반환하는 메서드.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;꼬리질문&lt;/span&gt;&lt;/b&gt;&lt;/h3&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Q. shift() 메서드 대신 성능을 개선할 방법이 또 있을까?&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열의 인덱스를 가리키는 pointer 변수를 만들어서 실제 요소를 지우는 방법 대신 포인터 인덱스만 옆으로 옮겨가며 검사하는 방법&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  마무리하며&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번에는 답 먼저 보지않고 해결한 문제였습니다&amp;hellip; 휴&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다만 성능 면에서는 shift() 메서드는 배열에서 항목을 제거하고 나머지 항목들을 다 앞으로 당기는 작업이 포함되기 때문에 O(N)의 시간 복잡도를 가지기 때문에 최악의 경우에 O(N^2)의 시간복잡도를 가지는 문제가 존재한다는 점이 아쉬운 해결 방안이었습니다. 또한, for문의 경우 shift() 메서드를 사용할 경우 배열의 원본이 수정되기 때문에 i 인덱스가 일치하지 않는 문제를 고려하지 않아 해결 시간이 더 걸렸던 문제였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이런 부분들이 아쉬웠던 것 같습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>기능개발</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/10</guid>
      <comments>https://yeonnim01.tistory.com/10#entry10comment</comments>
      <pubDate>Mon, 16 Mar 2026 17:29:26 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 의상 (Lv. 2, JavaScript)</title>
      <link>https://yeonnim01.tistory.com/9</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42578&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42578&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772691620142&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42578&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bxKK6d/dJMb9kmazsj/cMdeZJFIsRt9HNhh8BkKhK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/dGmtvm/dJMb9jOkJ9E/ZlQ9a4rUuLvhSKsCwNJxJk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42578&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42578&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bxKK6d/dJMb9kmazsj/cMdeZJFIsRt9HNhh8BkKhK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/dGmtvm/dJMb9jOkJ9E/ZlQ9a4rUuLvhSKsCwNJxJk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.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;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; [[의상 이름, 의상 종류], &amp;hellip;] 배열이 주어지면 서로 다른 옷의 조합의 수를 구해야 한다.&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;/li&gt;
&lt;li&gt;케이스들에 의상이 겹치더라도 다른 종류에서 다른 의상을 입었거나 추가로 더 착용한 경우에는 별도의 케이스로 본다.&lt;/li&gt;
&lt;li&gt;같은 이름의 의상은 존재하지 않는다.&lt;/li&gt;
&lt;li&gt;의상의 수는 1 이상 30 이하&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 구조화&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 의상의 종류를 키로 하여 주어진 2차원 배열을 Map 객체로 만들어야 하지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코니가 가진 의상들이 다음과 같이 주어졌다면&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[[&quot;yellow_hat&quot;, &quot;headgear&quot;], [&quot;blue_sunglasses&quot;, &quot;eyewear&quot;], [&quot;green_turban&quot;, &quot;headgear&quot;], [&quot;blue_tshirt&quot;, &quot;top&quot;]] 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;const newMap = new Map();
    
    for (const element of clothes) {
        if (newMap.has(element[1])) {
            let arr = newMap.get(element[1]);
            arr.push(element[0])
            newMap.set(element[1], arr)
        } else {
            newMap.set(element[1], [element[0]])
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 2차원 배열을 순회하면서 의상의 종류를 키로하는 Map 객체로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 이러한 형태로 객체를 구성을 하였다. 그런 다음은 어떻게 해야할까?&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
	&quot;headgear&quot;: [&quot;yellow_hat&quot;, &quot;green_turban&quot;],
	&quot;eyewear&quot;: [&quot;blue_sunglasses&quot;]
	&quot;top&quot;: [&quot;blue_tshirt&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;경우의 수 계산&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 의상 종류의 의상 목록에서 선택할 수 있는 경우의 수는 &lt;b&gt;value.length + 1&lt;/b&gt; 이 된다. 이때 +1은 의상을 입지 않는 경우를 포함한다.&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;headgear:&lt;/b&gt; yellow_hat, green_turban, 안 입음 &amp;rarr; &lt;b&gt;3가지&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;eyewear:&lt;/b&gt; blue_sunglasses, 안 입음 &amp;rarr; &lt;b&gt;2가지&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;top:&lt;/b&gt; blue_tshirt, 안 입음 &amp;rarr; &lt;b&gt;2가지&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 3 * 2 * 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;따라서 3 * 2 * 2 - 1 = 총 11개의 경우의 수&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;newMap.forEach((value, key) =&amp;gt; {
        if (answer &amp;gt; 0) {
            answer *= value.length + 1;
        } else {
            answer += value.length + 1
        }
    })
    
    answer -= 1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; ️ 트러블슈팅&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제점&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 각 의상 종류별로 1가지만 고르는 경우, 2가지 고르는 경우, 3가지 고르는 경우 등 모든 조합을 직접 구해서 더하는 방식으로 접근하려다 보니 너무 복잡해지고 어떤 알고리즘으로 접근해야할지도 정해지지 못했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의상 종류에서 선택할 수 있는 선택지에 &amp;ldquo;옷을 입지 않는 선택지&amp;rdquo;를 추가하면 각 의상 종류에서 선택할 수 있는 경우의 수를 각각 곱함으로써 모든 경우의 수를 구할 수 있다는 점을 알게되면서 해결할 수 있었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;function solution(clothes) {
    var answer = 0;
    const newMap = new Map();
    
    for (const element of clothes) {
        if (newMap.has(element[1])) {
            let arr = newMap.get(element[1]);
            arr.push(element[0])
            newMap.set(element[1], arr)
        } else {
            newMap.set(element[1], [element[0]])
        }
    }
    
    newMap.forEach((value, key) =&amp;gt; {
        if (answer &amp;gt; 0) {
            answer *= value.length + 1;
        } else {
            answer += value.length + 1
        }
    })
    
    answer -= 1
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최적화된 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;function solution(clothes) {
    let answer = 1;
    const newMap = new Map();
    
    for (const element of clothes) {
        if (newMap.has(element[1])) {
            let arr = newMap.get(element[1]);
            arr.push(element[0])
            newMap.set(element[1], arr)
        } else {
            newMap.set(element[1], [element[0]])
        }
    }
    
    newMap.forEach((value, key) =&amp;gt; {
        answer *= value.length + 1;
    })
    
    return answer - 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;answer의 초기 값을 1로 설정하면, forEach 문에서 복잡하게 if 문을 작성할 필요 없이 바로 배열 길이 + 1을 계속 곱할 수 있게 할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;복습&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;경우의 수와 곱의 법칙&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;독립 시행의 곱의 법칙:&lt;/b&gt; 각 의상 선택이 서로 영향을 주지 않으므로 모든 경우의 수를 곱한다는 점.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민해도 떠오르지 않아 결국 AI에게 힌트를 얻어서 해결했던 문제였습니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;  아주 작은 힌트: &quot;안 입는 것도 선택이다&quot;
코니가 &quot;안경&quot; 카테고리에서 [검정 안경, 투명 안경]을 가지고 있다면, 코니가 안경에 대해 할 수 있는 선택지는 몇 가지일까요?

1번: 검정 안경을 쓴다.

2번: 투명 안경을 쓴다.

3번: ???
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나올 수 있는 모든 경우의 수를 구하는 것이기 때문에 경우의 수를 곱하는 쉬운 길이었는데, 너무 복잡하게 또 생각했던 것 같습니다. 결국 해당 문제는 수학에서의 조합, 독립시행에 대한 지식이 선행되어야 하는 문제였습니다.&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>의상</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/9</guid>
      <comments>https://yeonnim01.tistory.com/9#entry9comment</comments>
      <pubDate>Thu, 5 Mar 2026 15:21:03 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 전화번호 목록 (Lv. 2, JavaScript)</title>
      <link>https://yeonnim01.tistory.com/8</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  &lt;b&gt;문제 설명&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42577&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772612963002&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bRhsNh/dJMb9dHlKxz/9XmFX2ytO4uSNzvq2p2aJ1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/Fm59q/dJMb8VNs3qt/QoDKHRvM3GDK3vyJ2IwQ11/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42577&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bRhsNh/dJMb9dHlKxz/9XmFX2ytO4uSNzvq2p2aJ1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/Fm59q/dJMb8VNs3qt/QoDKHRvM3GDK3vyJ2IwQ11/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;핵심조건&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전화번호부에는 같은 번호가 중복으로 들어있지 않다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전화번호의 길이는 1 이상 20 이하&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;접두어의 길이는 전화번호의 길이 조건만 충족한다면 제한이 없다 (내 해석)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주어진 테스트 케이스에 의하면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[&quot;12&quot;,&quot;123&quot;,&quot;1235&quot;,&quot;567&quot;,&quot;88&quot;] 이렇게 주어지면 &amp;ldquo;12&amp;rdquo;를 접두어인 번호는 &amp;ldquo;123&amp;rdquo;, &amp;ldquo;1235&amp;rdquo;이 될거고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;ldquo;123&amp;rdquo;이 접두어인 경우도 &amp;ldquo;1235&amp;rdquo; 이렇게 된다. 접두어는 하나라는 조건이 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단 문제의 목표는 전화번호부가 주어졌을 때 접두어인 경우가 하나라도 있다면 false를 리턴해주는 것이므로, 모든 접두어 케이스를 다 찾을 필요는 없다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;첫 번째 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열을 순회하면서 접두어인 케이스를 찾는 방법은 어떨까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 경우에는 배열을 이중으로 순회해야한다. 즉 이중 for문 형태가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 케이스처럼 곧바로 &amp;ldquo;12&amp;rdquo;를 접두어를 갖는 번호가 바로 다음 번호인 &amp;ldquo;123&amp;rdquo; 인 경우도 있겠지만, 최악의 경우 배열을 모두 순회해야할 수도 있다. 이 경우에는 시간 복잡도는 O(N^2)이 될 것이기 때문에 적절한 방법은 아닐 것이므로, 이 방법은 패스&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 번째 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해시 테이블 자료구조를 활용하는 방법으로 접근해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Map 객체를 활용하여 접두어 - 번호(key-value) 형태로 구성해본다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;Map(5) {
  '12' =&amp;gt; [],
  '123' =&amp;gt; [],
  '1235' =&amp;gt; [],
  '567' =&amp;gt; [],
  '88' =&amp;gt; []
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같은 형태로 Map 객체를 생성하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;phone_book 배열을 다시 순회하면서 phone_book의 요소가 Map의 key에 포함되는지를 정규표현식을 활용하여 알아내고 동일한 경우는 제외하고 key에 포함되는 경우라면 map.set을 통해 배열에 삽입하도록 한다?&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 방법 역시 동일하게 배열과 map 객체를 함께 순회하면서 정규 표현식으로 포함된다면 set하는 방식이기 때문에 똑같이 O(N^2)의 시간복잡도 문제에 직면한다&amp;hellip;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;세 번째 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;동일하게 Map 객체를 생성하고난 후&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주어진 전화번호부를 순회하면서 전화번호의 앞 숫자부터 하나하나 확인하면서 Map 객체에 해당 번호가 포함되어있는지를 체크하는 방식으로 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 때 검사하는 번호 자기 자신도 맵에 들어있으므로 이 경우는 제외한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;for (const phone of phone_book) {
        let arr = &quot;&quot;
        for (const num of phone) {
            arr += num
            if (arr !== phone &amp;amp;&amp;amp; newMap.has(arr)) {
                return false;
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전화번호 문자열을 내부에서 순회하면서 앞의 숫자부터 확인하기 위해 arr 이라는 변수를 선언하여 앞 번호 숫자부터 추가하면서 arr이 전화번호 phone과 같은지(자기 자신을 비교하는걸 방지) 그리고 해시 맵이 arr 숫자를 포함하는지 체크한다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를들어&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[&quot;119&quot;, &quot;97674223&quot;, &quot;1195524421&quot;] 의 경우에는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;ldquo;119&amp;rdquo;의 경우는 arr은 먼저 &amp;ldquo;1&amp;rdquo; 이는 해시맵에 없으므로 패스, 다음은 &amp;ldquo;11&amp;rdquo; 이도 없으므로 패스 &amp;ldquo;119&amp;rdquo; 이는 해시맵에 존재하나 &amp;ldquo;119&amp;rdquo;와 &amp;ldquo;119&amp;rdquo;를 비교하는건 동일한 번호를 비교하는 것이므로 패스&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다음으로 &amp;ldquo;1195524421&amp;rdquo;는 동일한 방법으로 진행하다가 &amp;ldquo;119&amp;rdquo;의 경우 해시 맵에 존재하고 arr &amp;ldquo;119&amp;rdquo;는 phone &amp;ldquo;1195524421&amp;rdquo;과 같지 않으므로 즉, &amp;ldquo;1195524421&amp;rdquo;는 접두어로 &amp;ldquo;119&amp;rdquo;를 가지는 경우라고 판명된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이를 확인하면서 false 를 리턴하면서 solution 함수는 종료된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;+ 또 다른 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해시 맵을 활용하지 않는 방법으로 phone_book 배열을 sort()를 통해 정렬한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정렬하게 되면 전화번호의 숫자가 사전식으로 정렬되므로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[&quot;119&quot;, &quot;97674223&quot;, &quot;1195524421&quot;] &amp;rarr; [&quot;119&quot;, &quot;1195524421&quot;, &quot;97674223&quot;] 이렇게 정렬된다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 되면 배열을 순회하면서 바로 인접한 옆의 요소와 비교하면서 접두어로 시작하는지를 확인하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;결국 문제의 목표는 접두어로 시작되는 전화번호의 경우가 있는가? 여부 이기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;function solution(phone_book) {
    phone_book.sort();

    for (let i = 0; i &amp;lt; phone_book.length - 1; i++) {
        if (phone_book[i + 1].startsWith(phone_book[i])) {
            return false;
        }
    }

    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;function solution(phone_book) {
    const newMap = new Map();
    
    for (const phone of phone_book) {
        newMap.set(phone, true);
    }
    
    for (const phone of phone_book) {
        let arr = &quot;&quot;
        for (const num of phone) {
            arr += num
            if (arr !== phone &amp;amp;&amp;amp; newMap.has(arr)) {
                return false;
            }
        }
    }
    return true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다른 해결 방법 코드&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;function solution(phone_book) {
    phone_book.sort();

    for (let i = 0; i &amp;lt; phone_book.length - 1; i++) {
        if (phone_book[i + 1].startsWith(phone_book[i])) {
            return false;
        }
    }

    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;복습&lt;/b&gt;&lt;/span&gt;&lt;/h3&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;String.prototype.startsWith(searchString, position)&lt;/b&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;어떤 문자열의 문자로 시작하는지 확인하여 결과를&amp;nbsp;true&amp;nbsp;혹은&amp;nbsp;false로 반환한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;position은 발견될 것으로 예상되는 위치의 인덱스 값을 넣어준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;String.prototype.substring(indexStart, indexEnd)&lt;/b&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;string의 시작 인덱스부터 종료 인덱스 전까지 문자열 부분을 반환한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;indexEnd는 옵셔널 생략된 경우 문자열 끝까지 추출한다. indexStart와 같을 경우 빈 문자열 반환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Array.prototype.sort([compareFunction])&lt;/b&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;compareFunction 콜백함수는 옵셔널하며, 이를 제공해주지 않으면 배열 요소를 문자열로 변환하고 사전식으로 오름차순 정렬합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주어진 배열을 그대로 가지고 접두어를 가지는 경우를 찾는 방법과 문제의 의도에 맞게 해시 맵을 활용하여 해결하는 두 가지 해결 방법이 존재했습니다. 물론 이 답을 결국 스스로 찾지 못하고 정답을 보고 말았다는 점이 너무나도 아쉬운 부분으로 남습니다&amp;hellip;  두 번째 방법론의 경우에는 해시 맵을 활용한다고 했으나 해시 맵의 장점인 요소 조회에 드는 시간 복잡도가 O(1) 이라는 부분을 전혀 활용하지 못한 방법이었던 게 문제였던 것 같습니다. &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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한, 해결 방법이 이중 for문 형태를 취하기는 하지만 전화번호부의 길이 N은 1 이상 1,000,000 이하지만 전화번호 자체 L은 1 이상 20 이하 이기 때문에 배열 자체를 이중으로 순회하는 방법은 O(N^2) 이지만, 배열 순회 내에서 전화 번호를 순회하여 포함되는지 확인은 O(N * L) 이기 때문에 후자가 훨씬 성능 면에서 좋다는 점이 키 포인트인 것 같습니다. 무조건 이중 for문이라고 피하는게 아니라 주어진 조건을 잘 확인해서 시간 복잡도를 계산하는 자세를 갖춰야 되겠다고 느꼈습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>전화번호 목록</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/8</guid>
      <comments>https://yeonnim01.tistory.com/8#entry8comment</comments>
      <pubDate>Wed, 4 Mar 2026 17:28:52 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 완주하지 못한 선수 (Lv. 1, JavaScript)</title>
      <link>https://yeonnim01.tistory.com/7</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  문제 설명&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42576&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772522196494&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCIdpJ/dJMb8QejNbq/2d1R2MXUzIe56M4dKvQaQ0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/oeDnG/dJMb9lk4JML/P2bT14VOBEkCKj1HZeNQwk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42576&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCIdpJ/dJMb8QejNbq/2d1R2MXUzIe56M4dKvQaQ0/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/oeDnG/dJMb9lk4JML/P2bT14VOBEkCKj1HZeNQwk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&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;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;목표: 마라톤에 참여한 선수들 중 완주하지 못한 선수의 이름을 반환해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단 한 명의 선수를 제외하고는 모든 선수가 완주를 했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;참가자의 이름에 동명이인이 있을 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  풀이 과정 (나만의 풀이 전략)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주어지는 인풋은 전체 참가자 명단 participant 와 완주한 참가자 명단 completion 이다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;첫 번째 방법&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇다면 전체 참가자 명단에서 완주한 참가자 명단의 이름을 제외하면 되지 않을까?&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;function solution(participant, completion) {
    var answer = '';
    completion.forEach((comp) =&amp;gt; {
        participant.splice(participant.indexOf(comp), 1);
    })
    answer = participant[0]
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;완주한 참가자 수 배열을 순회하면서 참가자 수 배열에서 완주한 사람의 인덱스를 indexOf를 통해 알아내어 splice(완주한 사람 인덱스, 1명) 을 통해 participant 배열에서 제외하도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 되면 최종적으로 완주하지 못한 1명만 남은 participant 배열이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;두 번째 방법&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;function solution(participant, completion) {
    let answer = ''
    const newMap = new Map();
    
    participant.forEach((part) =&amp;gt; {
        if (!newMap.get(part)) {
            newMap.set(part, 1)
        } else {
            newMap.set(part, newMap.get(part) + 1)
        }
    })
    
    completion.forEach((comp) =&amp;gt; {
        if (newMap.get(comp)) {
            newMap.set(comp, newMap.get(comp) - 1)
        }
    })
    
    newMap.forEach((value, key) =&amp;gt; {
        if (value === 1) {
            answer = key;
            return ;
        }
    })
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해당 문제가 해시 자료구조에 관한 문제이므로 map 객체를 활용하여 문제를 해결하고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우선 전체 참가자 배열을 순회하면서 newMap 객체에 set 하는데 이미 map 객체에 있다면 value를 1 증가시키고 없다면 1로 set 하도록 한다. 이렇게 하면 map 객체에 { 참가자이름, 인원수 } 같은 형태로 저장된다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다음으로는 완주한 참가자 배열을 순회하면서 newMap 객체에서 완주한 사람의 이름이 존재하는지 확인하고 존재한다면 value를 1 감소하도록 한다. 이렇게하면 최종적으로 map 객체에는 value가 1인 참가자 한명만이 남게 되고 해당 참가자가 완주하지 못한 마지막 1명이 되므로 answer를 구할 수 있게 된다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 두 가지 방법은 정확성 테스트에서는 통과할 수 있었으나, 효율성 테스트에서 통과할 수 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;첫 번째 방법은 모든 효율성 테스트 케이스를 시간 초과로 통과하지 못했고, 두 번째 방법은 1-2 효율성 테스트 케이스만 통과하고 나머지 3개 테스트 케이스는 시간 초과로 통과하지 못했다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;세 번째 방법&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막 answer를 구하는 방법을&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;    participant.forEach((part) =&amp;gt; {
        if (newMap.get(part) &amp;gt; 0) {
            answer = part;
            return ;
        }
    })
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;participant 배열을 순회하는 방식으로 수정했을 때 효율성 테스트를 통과하는 모습을 보였다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt; ️ 트러블슈팅&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;completion.forEach : 완주자 명단만큼 순회하므로 O(N)의 시간 복잡도&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;participant.indexOf(comp) : 마찬가지로 전체 명단에서 특정 참가자의 인덱스를 알아내기 위해 전체를 순회하므로 O(N)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;participant.splice(...) : splice의 경우 배열의 특정 요소를 삭제하면 나머지 뒤의 요소들을 앞으로 당기는 작업이 필요하기 때문에 삭제하려는 요소가 배열의 끝이라면 O(1)의 시간 복잡도이지만, 요소가 중간에 위치할 경우 O(N)의 시간 복잡도를 가지게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;결과적으로 전체 시간 복잡도를 계산해보면 N * (N + N) = 2N^2 &amp;rarr; O(N^2)의 시간 복잡도를 가지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열 자체를 순회하는건 2, 3번째 방법 동일하나 splice의 시간복잡도가 최대 O(N)일 수 있어 효율성 면에서 가장 안 좋은 풀이 방법이 되었던 것으로 보인다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다음으로 두 번째 풀이의 경우에는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;동일하게 participant나 completion배열을 순회하는 것은 같으나 forEach 내부에서 map 객체의 메서드인 set과 get을 사용하였는데, 이는 내부적으로 해시 테이블을 사용하기 때문에 O(1)의 시간 복잡도를 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막 세 번째 풀이와의 차이점은&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Map.forEach 의 경우 Map 객체에 저장된 모든 키-값(Entry)를 꺼내와야한다. 다만 participant.forEach의 경우 단순 배열의 값을 순회하여 Map.get(part)로 확인하기 때문에 비교적 더 빠르기 때문이다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제 제출 후 성능을 더 최적화하는 방법에 대해 알아 보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;코드 상에서 answer를 찾을 경우 return을 통해 break를 시도하도록 작성하였는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;forEach의 경우에는 return을 만나더라도 멈추지 않고 끝까지 모든 데이터를 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 방법을 시도하기 위해서는 forEach 보다는 for&amp;hellip; of나 기본 for문을 사용하거나 아니면 forEach 문에서 try catch문을 통해 예외처리를 하는 방법 혹은 some나 every 메서드를 사용하는 방법이 있다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  코드 (Code)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;function solution(participant, completion) {
    let answer = ''
    const newMap = new Map();
    
    participant.forEach((part) =&amp;gt; {
        if (!newMap.get(part)) {
            newMap.set(part, 1)
        } else {
            newMap.set(part, newMap.get(part) + 1)
        }
    })
    
    completion.forEach((comp) =&amp;gt; {
        if (newMap.get(comp)) {
            newMap.set(comp, newMap.get(comp) - 1)
        }
    })
    
    participant.forEach((part) =&amp;gt; {
        if (newMap.get(part) ) {
            answer = part;
            return ;
        }
    })
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;최적화된 코드&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;function solution(participant, completion) {
    let answer = ''
    const newMap = new Map();
    
    participant.forEach((part) =&amp;gt; {
        if (!newMap.get(part)) {
            newMap.set(part, 1)
        } else {
            newMap.set(part, newMap.get(part) + 1)
        }
    })
    
    completion.forEach((comp) =&amp;gt; {
        if (newMap.get(comp)) {
            newMap.set(comp, newMap.get(comp) - 1)
        }
    })
    
    participant.some((part) =&amp;gt; {
        if (newMap.get(part) &amp;gt; 0) {
            answer = part;
            return true;
        }
        return false;
    })
    
    return answer;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;❓ 꼬리 질문 및 복습 (Self-Q&amp;amp;A)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;복습&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;배열 탐색&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Array.prototype.indexOf(item)&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열에서 주어진 요소를 찾을 수 있는 첫 번째 인덱스를 반환하고, 찾을 수 없는 경우 -1을 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;작동 원리는 배열의 0번 인덱스부터 끝까지 하나씩 대조해 봅니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시간 복잡도는 O(N)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Array.prototype.splice(start, deleteCount, [item1, item2, &amp;hellip;])&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열의 특정 지점에서 요소를 삭제하거나 추가합니다. start는 삭제하거나 추가할 배열의 인덱스, deleteCount는 삭제할 요소의 개수를 의미합니다. 0 이하라면 제거하지 않고 생략한다면 start부터 모든 요소를 제거합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;item은 배열에 추가할 요소를 지정합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;작동 원리는 배열의 중간 요소를 제거하거나 추가할 경우 빈자리를 채우기 위해 뒤에 있는 요소가 모두 이동을 해야합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 시간 복잡도는 O(N)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Map 객체&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Map&lt;/b&gt;&amp;nbsp;객체는 키-값 쌍과 키의 원래 삽입 순서를 기억합니다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;set(key, value): 데이터를 저장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;get(key): 데이터를 가져옵니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;has(key): 데이터가 있는지 확인합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내부적으로 해시 테이블 자료 구조를 사용하기 때문에 시간 복잡도는 O(1)입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;꼬리질문&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Q:&lt;/b&gt; Map은 내부적으로 데이터를 어떻게 저장하기에 인덱스가 없어도 값을 바로 찾을 수 있을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Q:&lt;/b&gt; 일반 Object와 Map의 성능 차이는 언제 발생할까? 데이터의 개수가 많을 때 Map이 유리한 이유는?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Q:&lt;/b&gt; 첫 번째 풀이(배열 수정)와 두 번째 풀이(Map 생성) 중 메모리를 더 많이 사용하는 것은 무엇일까?&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;  마무리하며&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 문제를 해결하면서도 마주했던 문제점은 제가 JS 기본 문법들에 대해 너무 머리속에 든게 없구나 였습니다. splice 메서드, 나 indexOf 메서드, map 객체에 대해서 잘 알지 못하여 방법론 자체를 생각하는데 너무나 오랜 시간이 걸리고 생각하더라도 이를 구현하는데 애를 먹는 등 문제가 생겼고, 추가로 각 메서드들에 대한 시간 복잡도에 대해 알고 있지 못하니 시간 초과가 발생하는 문제 역시 발생했습니다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제를 해결하면서 여러 고민이 생기는 것 같습니다. 기본 JS 문법에 대해 어느정도 학습하고 문제를 푸는게 좋을까? 아니면 지금처럼 문제를 학습하면서 박치기하면서 학습하는게 좋을까? 고민이 필요한 문제 같다고 느꼈습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>알고리즘/프로그래머스</category>
      <category>완주하지 못한 선수</category>
      <category>프로그래머스</category>
      <author>Yeonnim</author>
      <guid isPermaLink="true">https://yeonnim01.tistory.com/7</guid>
      <comments>https://yeonnim01.tistory.com/7#entry7comment</comments>
      <pubDate>Tue, 3 Mar 2026 16:20:04 +0900</pubDate>
    </item>
  </channel>
</rss>