자바스크립트 코딩테스트 강좌, 트리 순회하기

개요

코딩테스트에서는 다양한 자료구조와 알고리즘 문제가 출제됩니다. 그 중 트리는 자주 등장하는 자료구조입니다.
트리 구조는 컴퓨터 과학에서 매우 중요한 역할을 하며, 파일 시스템, 데이터베이스 등의 다양한 분야에서 활용됩니다.
이 강좌에서는 자바스크립트를 이용하여 트리를 순회하는 방법에 대해 배워보겠습니다.

트리 구조란?

트리(Tree)란 노드(Node)와 엣지(Edge)로 구성된 비선형 데이터 구조로, 계층적인 관계를 표현하는 데 최적화되어 있습니다.
트리는 루트 노드(root node)와 자식 노드(child node), 부모 노드(parent node), 잎 노드(leaf node) 등의 개념을 가지고 있습니다.

트리의 주요 특징은 다음과 같습니다:

  • 트리는 하나의 루트를 가지고 있으며, 자식 노드들이 이 루트로부터 연결됩니다.
  • 노드는 0개 이상의 자식 노드를 가질 수 있습니다.
  • 리프 노드는 자식이 없는 노드입니다.

트리의 순회 방법

트리를 순회하는 방법에는 여러 가지가 있으며, 가장 일반적으로 사용되는 방식은 다음과 같습니다:

  • 전위 순회(Pre-order Traversal)
  • 중위 순회(In-order Traversal)
  • 후위 순회(Post-order Traversal)
  • 레벨 순회(Level-order Traversal)

각각의 순회 방법은 노드를 방문하는 순서가 다릅니다. 각 방법에 대해 자세히 살펴보겠습니다.

전위 순회(Pre-order Traversal)

전위 순회의 방식은 다음과 같습니다:

  1. 현재 노드를 방문한다.
  2. 왼쪽 서브트리를 전위 순회한다.
  3. 오른쪽 서브트리를 전위 순회한다.

예를 들어, 다음과 같은 트리 구조가 있다고 가정해봅시다.

                공공
                ├── 사용자 1
                │   ├── 사용자 1.1
                │   └── 사용자 1.2
                └── 사용자 2
                    ├── 사용자 2.1
                    └── 사용자 2.2
                

전위 순회 결과는 “공공, 사용자 1, 사용자 1.1, 사용자 1.2, 사용자 2, 사용자 2.1, 사용자 2.2″입니다.

중위 순회(In-order Traversal)

중위 순회의 방식은 다음과 같습니다:

  1. 왼쪽 서브트리를 중위 순회한다.
  2. 현재 노드를 방문한다.
  3. 오른쪽 서브트리를 중위 순회한다.

예를 들어, 같은 트리 구조에서 중위 순회 결과는 “사용자 1.1, 사용자 1, 사용자 1.2, 공공, 사용자 2.1, 사용자 2, 사용자 2.2″입니다.

후위 순회(Post-order Traversal)

후위 순회의 방식은 다음과 같습니다:

  1. 왼쪽 서브트리를 후위 순회한다.
  2. 오른쪽 서브트리를 후위 순회한다.
  3. 현재 노드를 방문한다.

같은 트리 구조에서 후위 순회 결과는 “사용자 1.1, 사용자 1.2, 사용자 1, 사용자 2.1, 사용자 2.2, 사용자 2, 공공”입니다.

레벨 순회(Level-order Traversal)

레벨 순회의 방식은 다음과 같습니다:

  1. 루트 노드를 방문한다.
  2. 현재 노드의 자식 노드들을 방문한다.
  3. 모든 자식 노드를 방문한 후, 다음 깊이로 이동한다.

같은 트리 구조에서 레벨 순회 결과는 “공공, 사용자 1, 사용자 2, 사용자 1.1, 사용자 1.2, 사용자 2.1, 사용자 2.2″입니다.

프로그래밍 문제: 이진 트리 순회

다음과 같은 이진 트리 구조가 주어졌을 때, 다양한 순회 방법을 이용하여 트리를 순회하는 함수를 작성하시오.
이진 트리는 다음과 같은 노드로 구성됩니다:

            class TreeNode {
                constructor(value) {
                    this.value = value;
                    this.left = null;
                    this.right = null;
                }
            }
            

입력 예시:

            const root = new TreeNode(1);
            root.left = new TreeNode(2);
            root.right = new TreeNode(3);
            root.left.left = new TreeNode(4);
            root.left.right = new TreeNode(5);
            

문제

위의 트리를 전위 순회, 중위 순회, 후위 순회, 레벨 순회하는 함수를 각각 작성하시오.

문제 풀이 과정

1. 전위 순회 구현

전위 순회를 수행하기 위해서는 재귀적 접근이 필요합니다. 아래는 이를 구현한 코드입니다:

            function preOrderTraversal(node) {
                if (node === null) return;
                console.log(node.value); // 현재 노드 방문
                preOrderTraversal(node.left); // 왼쪽 서브트리 방문
                preOrderTraversal(node.right); // 오른쪽 서브트리 방문
            }
            

위 코드는 노드를 순회하면서 현재 노드를 먼저 방문한 후 왼쪽과 오른쪽 노드를 순회합니다.

2. 중위 순회 구현

중위 순회도 재귀적으로 구현합니다. 아래는 중위 순회 코드입니다:

            function inOrderTraversal(node) {
                if (node === null) return;
                inOrderTraversal(node.left); // 왼쪽 서브트리 방문
                console.log(node.value); // 현재 노드 방문
                inOrderTraversal(node.right); // 오른쪽 서브트리 방문
            }
            

이 코드는 왼쪽 서브트리를 먼저 방문한 후 현재 노드를 방문합니다.

3. 후위 순회 구현

후위 순회 역시 재귀적으로 구현합니다. 다음은 구현된 코드입니다:

            function postOrderTraversal(node) {
                if (node === null) return;
                postOrderTraversal(node.left); // 왼쪽 서브트리 방문
                postOrderTraversal(node.right); // 오른쪽 서브트리 방문
                console.log(node.value); // 현재 노드 방문
            }
            

후위 순회에서는 두 자식 서브트리를 방문한 후에 현재 노드를 방문합니다.

4. 레벨 순회 구현

레벨 순회는 큐 자료구조를 이용하여 구현합니다. 큐를 사용하면 각 노드를 층층이 방문할 수 있습니다. 다음은 레벨 순회 코드입니다:

            function levelOrderTraversal(root) {
                if (root === null) return;
                const queue = [root]; // 큐 초기화
                while (queue.length > 0) {
                    const current = queue.shift(); // 큐에서 노드 제거
                    console.log(current.value); // 현재 노드 방문
                    if (current.left) queue.push(current.left); // 왼쪽 자식 추가
                    if (current.right) queue.push(current.right); // 오른쪽 자식 추가
                }
            }
            

큐를 통해 각 노드를 레벨별로 차례대로 방문할 수 있습니다.

결론

이 강좌에서는 자바스크립트를 활용하여 트리를 순회하는 다양한 방법에 대해 알아보았습니다.
트리 순회는 많은 프로그래밍 문제에서 기본적인 부분을 차지하므로, 충분히 연습하는 것이 중요합니다.
위에서 다룬 전위, 중위, 후위, 레벨 순회 알고리즘을 잘 이해하고 구현하는 것이 코딩테스트에서 좋은 성과를 낼 수 있는 방법입니다.

앞으로도 연습을 통해 다양한 알고리즘 문제를 해결해보세요. 연습과 반복은 최고의 스승입니다!

자바스크립트 코딩테스트 강좌, 계단 수 구하기

안녕하세요, 오늘은 자바스크립트 코딩테스트 준비에 유용한 알고리즘 문제 중 하나인 “계단 수 구하기”를 풀어보겠습니다. 이 문제는 동적 프로그래밍(Dynamic Programming)과 조합적으로 접근할 수 있는 흥미로운 문제입니다. 이 글에서는 문제 설명, 풀이 과정, 그리고 최적화 방안을 포함하여 자세히 설명하겠습니다.

문제 설명

계단 수는 n 자리 수 중, 인접한 두 자리의 차이가 1인 수를 의미합니다. 예를 들어, 123, 321과 같이 인접한 자리의 숫자 차이가 1일 경우 이 수는 계단 수입니다. 주어진 n에 대해 n 자리 계단 수를 구하는 프로그램을 작성하세요.

입력

정수 n (1 ≤ n ≤ 1000)

출력

n 자리의 계단 수의 개수를 1,000,000,000으로 나눈 나머지를 출력합니다.

문제 풀이 전략

이 문제를 해결하기 위해서는 동적 프로그래밍 접근 방식을 사용할 수 있습니다. 계단 수는 다음과 같이 상태를 정의하여 해결할 수 있습니다:

  • dp[i][j]: i자리 계단 수 중, j로 끝나는 수의 개수

계단 수를 구성하는 규칙을 다음과 같이 세울 수 있습니다:

  • j가 0일 때(0으로 시작하는 수 없음): dp[i][0] = dp[i-1][1]
  • j가 9일 때: dp[i][9] = dp[i-1][8]
  • 그 외의 경우: dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]

동적 프로그래밍 테이블 초기화

이제 dp 테이블을 초기화해보겠습니다. 1자리 수의 경우, 숫자는 0부터 9까지 가능하므로 dp[1][0] 부터 dp[1][9]까지 각각 1로 초기화합니다.

풀이 코드


function countStairNumbers(n) {
    const MOD = 1000000000;
    const dp = Array.from({ length: n + 1 }, () => Array(10).fill(0));

    // 1자리 수 초기화
    for (let j = 0; j < 10; j++) {
        dp[1][j] = 1;
    }

    // dp 테이블 채우기
    for (let i = 2; i <= n; i++) {
        for (let j = 0; j < 10; j++) {
            if (j > 0) dp[i][j] += dp[i - 1][j - 1]; // j-1에서 이동
            if (j < 9) dp[i][j] += dp[i - 1][j + 1]; // j+1에서 이동
            dp[i][j] %= MOD; // modulo 연산
        }
    }

    // n자리 모든 계단 수의 합
    let result = 0;
    for (let j = 0; j < 10; j++) {
        result += dp[n][j];
    }

    return result % MOD;
}

// 함수 호출 예시
console.log(countStairNumbers(3)); // 24

시간 복잡도

위 코드의 시간 복잡도는 O(n)이며, 공간 복잡도는 O(n)입니다. 각 자리 수의 조합을 통해 결과를 도출하기 때문에 n이 커질수록 시간과 공간을 효율적으로 사용할 수 있습니다.

최적화 방안

현재 구현된 코드에서 메모리 사용량을 줄이기 위해 dp 배열을 2차원에서 1차원으로 변경할 수 있습니다. 각 i에 대해 이전의 dp 상태만 필요하기 때문에, 이를 이용해 최적화할 수 있습니다.


function countStairNumbersOptimized(n) {
    const MOD = 1000000000;
    const dp = Array(10).fill(0);
    const temp = Array(10).fill(0);

    // 1자리 수 초기화
    for (let j = 0; j < 10; j++) {
        dp[j] = 1;
    }

    for (let i = 2; i <= n; i++) {
        for (let j = 0; j < 10; j++) {
            temp[j] = 0;
            if (j > 0) temp[j] += dp[j - 1]; // j-1에서 이동
            if (j < 9) temp[j] += dp[j + 1]; // j+1에서 이동
            temp[j] %= MOD; // modulo 연산
        }
        for (let j = 0; j < 10; j++) {
            dp[j] = temp[j]; // 다음 단계로 업데이트
        }
    }

    // n자리 모든 계단 수의 합
    let result = 0;
    for (let j = 0; j < 10; j++) {
        result += dp[j];
    }

    return result % MOD;
}

마치며

이번 글에서는 “계단 수 구하기” 문제를 통해 자바스크립트로 동적 프로그래밍을 활용하여 문제를 해결하는 방법을 배웠습니다. 초기화, dp 테이블 구성, 그리고 최적화 과정을 자세히 설명하였으며, 다양한 테크닉을 사용하여 알고리즘의 효율성을 높일 수 있는 방법도 소개하였습니다. 알고리즘 문제를 풀 때는 항상 다양한 접근 방식을 고민하고 최적화할 방안을 모색해 보세요. 감사합니다!

자바스크립트 코딩테스트 강좌, 가장 빠른 버스 노선 구하기

문제 소개

당신은 A 지점에서 B 지점까지 가는 가장 빠른 버스 노선을 찾는 프로그램을 작성해야 합니다. 여러 개의 버스 노선이 있고 각각의 노선은 정해진 정류소를 지나며, 정류소 간의 이동 시간은 모두 다르게 설정되어 있습니다. 최종 목표는 특정한 A 지점에서 B 지점까지의 가장 빠른 경로를 찾고, 해당 경로의 소요시간을 반환하는 것입니다.

문제 설명

주어진 노선은 다음과 같이 표현됩니다. 각 노선은 정류소 간의 이동 시간을 가지며, 정류소는 다음과 같은 형태로 표현됩니다:

        [
            {busRoute: "1", stops: [{stop: "A", time: 0}, {stop: "B", time: 5}, {stop: "C", time: 10}]},
            {busRoute: "2", stops: [{stop: "A", time: 0}, {stop: "D", time: 3}, {stop: "B", time: 8}]},
            {busRoute: "3", stops: [{stop: "B", time: 0}, {stop: "C", time: 4}, {stop: "E", time: 6}]},
            {busRoute: "4", stops: [{stop: "D", time: 0}, {stop: "E", time: 2}, {stop: "B", time: 7}]},
        ]
    

입력

첫 번째 매개변수로는 버스 노선의 배열, 두 번째 매개변수로는 A 지점과 B 지점이 주어집니다.

출력

가장 빠른 경로의 소요 시간을 출력합니다. 경로가 없다면 -1을 출력합니다.

예제

        입력: 
        const routes = [
            {busRoute: "1", stops: [{stop: "A", time: 0}, {stop: "B", time: 5}, {stop: "C", time: 10}]},
            {busRoute: "2", stops: [{stop: "A", time: 0}, {stop: "D", time: 3}, {stop: "B", time: 8}]},
            {busRoute: "3", stops: [{stop: "B", time: 0}, {stop: "C", time: 4}, {stop: "E", time: 6}]},
            {busRoute: "4", stops: [{stop: "D", time: 0}, {stop: "E", time: 2}, {stop: "B", time: 7}]},
        ];
        const start = "A";
        const end = "B";

        출력: 
        5
    

해결 방법

이 문제를 해결하기 위해 우리는 그래프 탐색 알고리즘을 사용할 수 있습니다. 정류소는 노드로, 정류소 간 시간은 그 노드들 간의 엣지의 가중치로 표현할 수 있습니다. 적절한 알고리즘으로는 BFS(너비 우선 탐색)나 Dijkstra 알고리즘이 있습니다. 이 문제에서는 Dijkstra 알고리즘이 더 효과적입니다, 왜냐하면 모든 간선의 가중치가 다를 수 있어서 가장 짧은 경로를 찾기 위해 최적화된 방법이 필요하기 때문입니다.

Dijkstra 알고리즘 개요

Dijkstra는 가중치가 있는 그래프에서 최단 경로를 찾기 위한 알고리즘으로, 현재 노드에서 인접 노드로 이동할 때의 비용을 계속 업데이트하면서 최종 목표 노드로 가는 경로를 탐색합니다. 이를 위해 우리는 각 정류소와 비용을 기록할 수 있는 우선순위 큐를 사용합니다.

코드 구현

        function getFastestBusRoute(routes, start, end) {
            // Priority queue for processing the stops
            const pq = new MinPriorityQueue();
            const distances = {};
            const parents = {};

            // Initialize distances and priority queue
            for (const route of routes) {
                for (const stop of route.stops) {
                    distances[stop.stop] = Infinity;
                    parents[stop.stop] = null;
                }
            }

            // Starting point
            distances[start] = 0;
            pq.enqueue(start, 0);

            while (!pq.isEmpty()) {
                const currentStop = pq.dequeue().element;

                // If we reach the target stop, return the distance
                if (currentStop === end) {
                    return distances[currentStop];
                }

                for (const route of routes) {
                    for (let i = 0; i < route.stops.length - 1; i++) {
                        const stop1 = route.stops[i].stop;
                        const stop2 = route.stops[i + 1].stop;
                        const time = route.stops[i + 1].time - route.stops[i].time;

                        if (stop1 === currentStop) {
                            const newTime = distances[stop1] + time;
                            if (newTime < distances[stop2]) {
                                distances[stop2] = newTime;
                                parents[stop2] = stop1;
                                pq.enqueue(stop2, newTime);
                            }
                        }
                    }
                }
            }
            return -1; // If there's no path to the end
        }

        // Example usage
        const routes = [
            {busRoute: "1", stops: [{stop: "A", time: 0}, {stop: "B", time: 5}, {stop: "C", time: 10}]},
            {busRoute: "2", stops: [{stop: "A", time: 0}, {stop: "D", time: 3}, {stop: "B", time: 8}]},
            {busRoute: "3", stops: [{stop: "B", time: 0}, {stop: "C", time: 4}, {stop: "E", time: 6}]},
            {busRoute: "4", stops: [{stop: "D", time: 0}, {stop: "E", time: 2}, {stop: "B", time: 7}]},
        ];
        const start = "A";
        const end = "B";
        console.log(getFastestBusRoute(routes, start, end)); // Output: 5
    

결론

이번 강좌에서는 주어진 버스 노선 정보를 바탕으로 가장 빠른 경로를 찾는 알고리즘 문제를 해결하는 방법을 배웠습니다. Dijkstra 알고리즘은 가중치가 있는 그래프에서 최단 경로를 찾아주는 매우 유용한 방법입니다. 각 단계에서 실제로 어떻게 탐색을 진행하는지 이해하고, 이를 코딩으로 구현하는 과정에서 많은 도움이 되었기를 바랍니다.

추가 연습 문제

이번 문제를 통해 Dijkstra 알고리즘을 익혔다면, 다음과 같은 변형 문제들도 시도해 보세요:

  • 특정 노선만을 사용하여 경로를 찾아야 하는 문제
  • 정류소 간의 이동 시간이 아닌 이동 거리로 경로를 찾는 문제
  • 최소 경로가 아닌 최다 경로를 찾는 문제

참고 자료

자바스크립트 코딩테스트 강좌, 케빈 베이컨의 6단계 법칙

안녕하세요! 이번 포스트에서는 자바스크립트 코딩테스트의 하나의 주제로 “케빈 베이컨의 6단계 법칙”에 대해 알아보고, 문제를 해결하는 과정을 상세하게 설명드리겠습니다. 이 강의에서는 알고리즘을 설계하고 구현하는 과정, 그리고 자바스크립트 문법 및 함수를 효율적으로 활용하는 방법에 대해 배워보겠습니다.

1. 케빈 베이컨의 6단계 법칙이란?

케빈 베이컨의 6단계 법칙은 영화 배우들 간의 관계를 설명하는 이론입니다. 이 이론에 따르면, 두 사람 간의 관계는 최대 6단계의 연결을 통해 이루어질 수 있다는 것입니다. 즉, 어떤 배우 A가 배우 B와 관계를 맺고, B가 C와 또 다른 관계를 맺는 식으로 연결되면, A와 C 사이의 관계는 6단계 이내에 있을 것이라는 믿음입니다.

2. 문제 설명

이번 문제는 그래프 이론을 기반으로 한 문제입니다. 주어진 배우 목록과 그들 간의 관계를 통해, 특정한 배우와 다른 배우 간의 연결 고리를 찾고, 몇 단계로 연결되어 있는지를 계산하는 것입니다.

문제 정의

    주어진 배우 목록과 그들의 관계를 그래프로 나타내고, 
    주어진 두 배우 간의 최단 경로를 구하여, 
    그 간의 단계 수를 반환하는 함수를 작성하세요.
    
    제한 조건:
    - 각 배우는 문자열로 표현되며, 두 배우가 함께 출연한 영화에서 직접 연결됩니다.
    - 그래프의 간선은 무방향이며, 이 때문에 BFS(너비 우선 탐색)를 사용하여 최단 경로를 찾는 방식을 사용할 수 있습니다.
    
    예제 입력:
    actors = [
        ['A', 'B'],
        ['B', 'C'],
        ['C', 'D'],
        ['D', 'E'],
        ['A', 'E']
    ]
    startActor = 'A'
    targetActor = 'D'
    
    예제 출력:
    3 (A -> B -> C -> D)
    

3. 해결 방법

이 문제를 해결하기 위해서는 다음과 같은 단계로 진행합니다.

3.1. 데이터 구조 선택

우선, 모든 배우들 간의 관계를 저장할 데이터 구조가 필요합니다. 우리는 이 관계를 Map 또는 Object를 사용하여 그래프로 표현할 것입니다. 각 배우는 키가 되고, 그에 해당하는 값은 함께 출연한 배우들의 배열이 됩니다.

3.2. 그래프 구성

주어진 배우 목록을 통해 위에서 선택한 데이터 구조에 배우와 배우 간의 관계를 추가합니다.

3.3. 최단 경로 찾기

BFS 알고리즘을 사용하여 시작 배우와 목표 배우 간의 최단 경로를 찾습니다. BFS는 최단 거리 문제를 해결하는 데 유용한 알고리즘으로, 수준별로 노드를 탐색하기 때문에 최단 경로를 보장합니다.

4. 자바스크립트 구현

이제 위에서 설명한 내용을 바탕으로 자바스크립트 코드로 구현해 보겠습니다.

    function findShortestPath(actors, startActor, targetActor) {
        // 그래프 생성
        const graph = {};
        
        for (const [actorA, actorB] of actors) {
            if (!graph[actorA]) graph[actorA] = [];
            if (!graph[actorB]) graph[actorB] = [];
            graph[actorA].push(actorB);
            graph[actorB].push(actorA);
        }
        
        // BFS 알고리즘
        const queue = [[startActor, 0]];
        const visited = new Set();
        visited.add(startActor);
        
        while (queue.length > 0) {
            const [currentActor, steps] = queue.shift();
            
            // 목표 배우에 도달했을 경우
            if (currentActor === targetActor) {
                return steps;
            }
            
            for (const neighbor of graph[currentActor]) {
                if (!visited.has(neighbor)) {
                    visited.add(neighbor);
                    queue.push([neighbor, steps + 1]);
                }
            }
        }
        
        // 연결이 없음
        return -1;
    }
    
    // 예제 실행
    const actors = [['A', 'B'], ['B', 'C'], ['C', 'D'], ['D', 'E'], ['A', 'E']];
    const startActor = 'A';
    const targetActor = 'D';
    
    console.log(findShortestPath(actors, startActor, targetActor)); // 출력: 3
    

5. 코드 분석

위 코드를 통해 우리는 주어진 배우 간의 관계를 기반으로 그래프를 구성하고, BFS를 통해 최단 경로를 찾아내는 과정을 구현했습니다. 이 부분을 좀 더 상세히 분석해 보겠습니다.

5.1. 그래프 구현

그래프는 객체 형태로 구성되며, 키는 배우 이름이고, 값은 그와 연결된 배우들의 배열입니다. 우리는 곧바로 양방향 관계를 갱신하여, 서로 연결된 배우를 추가합니다.

5.2. BFS 동작 원리

BFS는 큐를 사용하여 구현되었습니다. 시작 배우를 큐에 추가하고, 방문한 배우는 Set에 추가하여 중복 방문을 피합니다. 큐가 비워질 때까지 반복적으로 탐색을 진행하며, 목표 배우를 찾으면 그간의 단계 수를 반환합니다.

5.3. 시간 복잡도

이 알고리즘의 시간 복잡도는 O(V + E)로, V는 배우의 수, E는 간선의 수입니다. 이로 인해 효율적으로 작동하며, 대규모 데이터에서도 빠른 시간 내에 결과를 도출할 수 있습니다.

6. 결론

이번 포스트에서는 “케빈 베이컨의 6단계 법칙”을 활용한 알고리즘 문제를 해결하는 과정을 살펴보았습니다. 알고리즘 설계, 데이터 구조의 선택, 구현 및 분석까지의 전체 과정을 풀어봤습니다. 이러한 문제는 실제 코딩테스트에서도 빈번하게 출제되므로, 연습을 통해 숙달하는 것이 중요합니다.

앞으로도 다양한 알고리즘과 문제 해결 방법을 다루는 포스트를 계속해서 올리도록 하겠습니다. 항상 코딩 연습을 잊지 마시고, 질문이 있으시면 언제든지 댓글로 남겨주세요!

7. 추가 참고 자료

자바스크립트 코딩테스트 강좌, 경로 찾기

코딩 테스트는 많은 개발자들이 치르며, 다양한 알고리즘 문제를 통해 자신의 능력을 평가받습니다. 이번 글에서는 ‘경로 찾기’ 문제를 통한 알고리즘 문제 해결 과정을 자세히 살펴보겠습니다. 이 문제는 DFS(Depth-First Search) 또는 BFS(Breadth-First Search)와 같은 그래프 탐색 기법을 사용하는 데에 적합합니다.

문제 설명

주어진 2D 격자에서 시작점에서 도착점으로 가는 경로를 찾아야 합니다. 격자의 각 셀은 이동할 수 있는지 없는지를 나타내며, 아래는 문제 조건입니다:

  • 격자는 N x M 크기를 가진다.
  • 각 셀은 0 또는 1로 표시되며, 0은 이동할 수 있는 경로, 1은 막힌 경로를 뜻한다.
  • 시작점은 (0, 0)이고, 도착점은 (N-1, M-1)이다.
  • 상하좌우로 이동할 수 있다.

예시 입력

    4 4
    0 0 1 0
    0 1 0 0
    0 1 1 0
    0 0 0 0
    

예시 출력

    Yes
    

문제 해결 과정

이 문제를 해결하기 위해 우리가 사용할 알고리즘은 BFS입니다. BFS는 각 노드를 차례대로 탐색할 수 있어 최단 경로를 찾기에 유리합니다. 이를 수행하기 위해 아래와 같은 단계로 문제를 해결합니다:

1. 격자 공간 생성

입력받은 격자 정보를 배열로 변환합니다. 이를 통해 각 셀에 대한 접근과 이동을 용이하게 합니다.

2. BFS 초기화

시작점 (0, 0)을 큐에 추가합니다. 추가적으로 방문한 노드를 추적하기 위해 추가 배열을 사용합니다.

3. BFS 탐색 수행

큐가 비어있지 않은 동안 다음을 반복합니다:

  • 큐에서 노드를 꺼내 현재 위치를 확인합니다.
  • 현재 위치가 도착점 (N-1, M-1)인지 확인합니다. 맞으면 ‘Yes’를 출력하고 탐색을 종료합니다.
  • 상하좌우로 이동할 수 있는 모든 셀에 대해 다음을 수행합니다:
    • 셀의 경계 조건 및 방문 여부를 확인합니다.
    • 셀의 값이 0이고, 미방문 상태라면 큐에 추가하고 방문 상태로 변경합니다.

모든 탐색이 끝난 후에도 도착점에 도달하지 못했다면 ‘No’를 출력합니다.

4. 코드 구현

    function canReachDestination(grid) {
        const N = grid.length;
        const M = grid[0].length;
        const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]];
        const queue = [[0, 0]];
        const visited = Array.from({length: N}, () => Array(M).fill(false));

        visited[0][0] = true;

        while (queue.length > 0) {
            const [x, y] = queue.shift();

            // 도착점 확인
            if (x === N - 1 && y === M - 1) {
                return "Yes";
            }

            for (const [dx, dy] of directions) {
                const nx = x + dx;
                const ny = y + dy;

                // 경계조건과 방문여부 확인
                if (nx >= 0 && ny >= 0 && nx < N && ny < M && 
                    !visited[nx][ny] && grid[nx][ny] === 0) {
                    visited[nx][ny] = true;
                    queue.push([nx, ny]);
                }
            }
        }
        return "No";
    }

    const grid = [
        [0, 0, 1, 0],
        [0, 1, 0, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0]
    ];
    console.log(canReachDestination(grid)); // 결과: "Yes"
    

결론

이번 문제를 통해 BFS 알고리즘을 활용한 경로 찾기 문제를 해결해 보았습니다. 실제 코딩 테스트에서는 이러한 문제를 자주 만나게 되므로, 여러 알고리즘을 연습하여 다양한 문제에 대한 해결 능력을 기르는 것이 중요합니다. 다음 강좌에서는 다른 유형의 알고리즘 문제를 다뤄보도록 하겠습니다. 감사합니다!