자바스크립트 코딩테스트 강좌, 가장 큰 정사각형 찾기

안녕하세요! 이번 글에서는 “가장 큰 정사각형 찾기” 문제를 JavaScript로 해결하는 방법을 자세히 알아보겠습니다. 이 문제는 동적 프로그래밍(Dynamic Programming) 접근 방식을 활용하여 효율적으로 해결할 수 있습니다. 실제 기업의 코딩 테스트에서 자주 출제되는 유형이므로, 이 문제를 통해 동적 프로그래밍의 개념을 확고히 하고, 실전에서의 활용 방법에 대해 알아보도록 하겠습니다.

문제 설명

주어진 2차원 배열 matrix가 있을 때, ‘1’로 이루어진 가장 큰 정사각형의 변의 길이를 구하는 문제입니다. 예를 들어, 아래와 같은 입력 행렬이 주어진다면:

1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0

위 행렬에서 가장 큰 정사각형의 변의 길이는 2입니다. 즉, ‘1’로 이루어진 최대 크기는 2×2입니다.

문제 해결 접근

이 문제는 여러 가지 방법으로 접근할 수 있지만, 동적 프로그래밍을 사용하는 것이 가장 효율적입니다. 동적 프로그래밍을 사용하면 이전에 계산한 값을 재사용하여 불필요한 계산을 줄일 수 있습니다. 다음은 이 문제를 해결하기 위한 단계별 접근 방법입니다.

1단계: 동적 프로그래밍 테이블 설정

먼저, 기존 행렬과 동일한 크기의 2차원 배열인 dp를 생성합니다. 이 배열은 각 위치에 ‘1’로 이루어진 정사각형의 최대 변의 길이를 저장할 것입니다.

2단계: 경계 조건 처리

행렬의 첫 번째 행과 첫 번째 열은 경계 조건으로 설정합니다. 이 경우, 해당 위치가 ‘1’이라면 최대 정사각형의 변 길이는 1입니다. 만약 ‘0’이라면 어디에도 정사각형이 존재하지 않으므로 0으로 설정합니다.

3단계: DP 배열 채우기

이후, 행렬을 순회하면서 각 위치의 값에 따라 DP 배열을 업데이트합니다. 현재 위치 matrix[i][j]가 ‘1’이라면, 해당 위치에서 정사각형의 변의 길이를 다음과 같이 구합니다:

dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1;

여기서 Math.min() 함수는 위쪽, 왼쪽, 그리고 왼쪽 대각선에서 최소값을 구하는 함수입니다. 이 최소값은 정사각형을 만들기 위해 필요한 최소한의 길이를 나타냅니다.

4단계: 결과 처리

마지막으로, DP 배열에서 가장 큰 값을 찾으면 그 값이 정사각형의 변의 길이가 됩니다.

알고리즘 구현

이제 앞서 설명한 접근 방식을 바탕으로 실제 알고리즘을 JavaScript로 구현해보겠습니다.

function maximalSquare(matrix) {
    if (matrix.length === 0) return 0;

    const rows = matrix.length;
    const cols = matrix[0].length;
    const dp = Array.from({ length: rows }, () => Array(cols).fill(0));
    let maxSide = 0;

    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (matrix[i][j] === '1') {
                if (i === 0 || j === 0) {
                    dp[i][j] = 1; // 첫 번째 열이나 행일 경우는 1
                } else {
                    dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1;
                }
                maxSide = Math.max(maxSide, dp[i][j]);
            }
        }
    }

    return maxSide * maxSide; // 정사각형의 넓이 반환
}

성능 및 복잡도 분석

이 알고리즘의 시간 복잡도는 O(m*n)이며, 공간 복잡도는 O(m*n)입니다. 여기서 m은 행렬의 행 수, n은 열 수입니다. DP 배열을 사용하기 때문에 공간이 추가로 필요하지만, 이를 최적화하여 O(n)으로 줄일 수 있습니다.

최적화: 공간 복잡도 감소

경우에 따라 DP 배열의 공간 복잡도를 O(n)으로 줄일 수 있습니다. 이전 DP 배열을 저장하기 위해 두 개의 배열을 사용할 수 있으며, 현재 상태를 업데이트할 때는 이전 상태만 필요합니다.

function maximalSquare(matrix) {
    if (matrix.length === 0) return 0;

    const rows = matrix.length;
    const cols = matrix[0].length;
    let dp = Array(cols).fill(0);
    let maxSide = 0, prev = 0;

    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            let temp = dp[j]; // 이전 값을 저장
            if (matrix[i][j] === '1') {
                if (i === 0 || j === 0) {
                    dp[j] = 1;
                } else {
                    dp[j] = Math.min(dp[j], dp[j-1], prev) + 1;
                }
                maxSide = Math.max(maxSide, dp[j]);
            } else {
                dp[j] = 0; // '0'일 경우 0 설정
            }
            prev = temp; // 이전 값을 업데이트
        }
    }

    return maxSide * maxSide; // 정사각형의 넓이 반환
}

마치며

오늘은 자바스크립트 코딩테스트에서 자주 등장하는 “가장 큰 정사각형 찾기” 문제를 동적 프로그래밍을 통해 해결하는 방법을 알아보았습니다. 이 문제는 동적 프로그래밍의 기본 개념을 이해하는 데 도움을 주며, 실전에서의 코드 작성 능력을 향상시키는 데 큰 도움이 될 것입니다. 다양한 문제를 풀어보며 알고리즘과 데이터 구조에 대한 이해도를 높이는 것도 잊지 마세요!

앞으로 더 많은 알고리즘 문제를 다룰 예정이니 많은 관심 부탁드립니다. 감사합니다!

자바스크립트 코딩테스트 강좌, 시간 복잡도 활용하기

문제 설명

주어진 배열 arr에서 특정 num 값과 가장 가까운 수를 찾는 문제입니다.
가장 가까운 수가 여러 개 있을 경우, num에 가까운 값이 더 낮을 경우를 우선합니다.

입력

  • 정수 배열 arr (-106arr[i] ≤ 106, 1 ≤ arr.length ≤ 105)
  • 정수 num (-106num ≤ 106)

출력

가장 가까운 수를 반환합니다.

예제

예제 1:
입력: arr = [1, 2, 3, 4, 5], num = 3
출력: 3

예제 2:
입력: arr = [1, 2, 4, 5], num = 3
출력: 2

예제 3:
입력: arr = [5, 10, 15], num = 12
출력: 10

풀이 과정

이 문제를 해결하기 위해서 다음과 같은 단계를 따릅니다.

1단계: 문제 이해하기

우선 num과 가장 가까운 수를 찾는 문제이므로, 각 요소와 num의 차이인 절댓값을 계산하여 가장 작은 값을 찾는 것이 핵심입니다. 이때, 같은 차이 값을 가진 여러 개의 수가 있을 경우 더 작은 값을 반환해야 합니다.

2단계: 접근 방법 선정하기

가장 간단한 방법은 배열을 순회하며 각 요소의 절댓값 차이를 계산하는 것입니다. 하지만 이 방식은 O(n) 시간복잡도를 가지므로, 이보다 빠른 알고리즘을 고려해야 합니다.

3단계: 정렬을 통한 이진 탐색 활용하기

입력 배열을 정렬하면 이진 탐색을 통해 num 값에 가장 가까운 수를 효율적으로 찾을 수 있습니다. 정렬된 배열에서 특정 값 num 의 인덱스를 찾기 위해 Array.prototype.sort()를 사용하고, 이에 이어서 binarySearch() 함수를 구현합니다.

4단계: 코드 구현하기

다음은 위에서 설명한 내용을 바탕으로 작성한 자바스크립트 코드입니다.


function findClosest(arr, num) {
    // 배열 정렬
    arr.sort((a, b) => a - b);
    
    let left = 0;
    let right = arr.length - 1;
    let closest = arr[0];

    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        
        // 절댓값 차이 비교
        if (Math.abs(arr[mid] - num) < Math.abs(closest - num) ||
            (Math.abs(arr[mid] - num) === Math.abs(closest - num) && arr[mid] < closest)) {
            closest = arr[mid];
        }
        
        // 이진 탐색의 방향 결정
        if (arr[mid] < num) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return closest;
}

// 예제 테스트
console.log(findClosest([1, 2, 3, 4, 5], 3)); // 3
console.log(findClosest([1, 2, 4, 5], 3));    // 2
console.log(findClosest([5, 10, 15], 12));      // 10

5단계: 시간 복잡도 분석

위 코드는 먼저 배열을 정렬합니다. 정렬의 시간 복잡도는 O(n log n)이며, 이후 이진 탐색을 이용한 탐색 과정에서는 O(log n)의 시간이 소요됩니다. 전체 시간 복잡도는 O(n log n)로 평가할 수 있습니다.

결론

이 문제를 통해 시간 복잡도를 고려하여 효율적인 알고리즘을 선택하는 것이 얼마나 중요한지를 배웠습니다. 주어진 문제에 대해 다양한 접근 방식을 시도하고, 그 결과에 따라 적절한 알고리즘을 선택하는 연습이 필요합니다. 앞으로의 코딩테스트에서도 이러한 원칙을 잘 활용하시길 바랍니다!

자바스크립트 코딩테스트 강좌, 깊이 우선 탐색

문제: 미로 탐색

주어진 2차원 배열에서 출발점 (시작점)에서 도착점까지의 경로가 존재하는지를 확인하는 문제입니다. 배열의 값은 0 (통과 가능)과 1 (통과 불가능)으로 주어집니다. 출발점은 (0, 0)이고, 도착점은 (N-1, M-1)입니다. 상하좌우로만 이동할 수 있으며, 경로가 존재하면 true, 존재하지 않으면 false를 반환해야 합니다.

문제 예시

입력

    [
        [0, 0, 1, 0],
        [1, 0, 1, 0],
        [0, 0, 0, 0],
        [0, 1, 1, 0]
    ]
    

출력

true

설명

위의 예시에서 출발점 (0, 0)에서 시작하여 (1, 1), (2, 1), (2, 2), (2, 3), (3, 3)으로 경로를 따라 가면 도착점 (3, 3)에 도달할 수 있습니다.

문제 해결 과정

1. 알고리즘의 개요

이 문제는 깊이 우선 탐색(DFS) 알고리즘을 통해 해결할 수 있습니다. DFS는 노드를 방문할 때 가능한 한 깊게 들어갔다가 더 이상 갈 수 없게 되면 다른 경로로 돌아오는 방식입니다. 우리는 재귀를 사용하여 구현할 것입니다. DFS를 적용하여 미로를 탐색하고, 출발점에서 도착점까지 도달 가능한지를 확인합니다.

2. DFS 알고리즘 구현

DFS를 구현하기 위해서는 다음과 같은 단계가 필요합니다:

  1. 현재 위치가 경계 내에 있는지 확인합니다.
  2. 현재 위치가 통과 불가능한 지점인지 확인합니다.
  3. 현재 위치가 도착지인지 확인합니다.
  4. 현재 위치를 방문 처리합니다.
  5. 상하좌우 방향으로 재귀적으로 DFS를 호출합니다.
  6. 모든 경로를 탐색한 후 방문 처리를 해제합니다.

3. 코드 구현

다음은 자바스크립트를 이용한 DFS 알고리즘을 구현한 코드입니다:


function isPathExists(maze) {
    const rows = maze.length;
    const cols = maze[0].length;

    function dfs(x, y) {
        // 경계 조건
        if (x < 0 || y < 0 || x >= rows || y >= cols) return false;
        // 통과 불가능한 지점
        if (maze[x][y] === 1) return false;
        // 도착지
        if (x === rows - 1 && y === cols - 1) return true;

        // 현재 위치 방문 처리
        maze[x][y] = 1;

        // 상하좌우 DFS 탐색
        const found = dfs(x + 1, y) || dfs(x - 1, y) || dfs(x, y + 1) || dfs(x, y - 1);

        // 방문 처리 해제
        maze[x][y] = 0;

        return found;
    }

    return dfs(0, 0);
}

// 예제 테스트
const maze = [
    [0, 0, 1, 0],
    [1, 0, 1, 0],
    [0, 0, 0, 0],
    [0, 1, 1, 0]
];

console.log(isPathExists(maze)); // 출력: true

4. 코드 설명

코드의 주요 부분을 설명하겠습니다:

  • 경계 조건 체크: 현재 위치가 배열의 경계를 초과하지 않는지 확인합니다.
  • 통과 불가능한 지점 검사: 현재 위치의 값이 1인지 확인하여 통과 가능한 지점인지 검사합니다.
  • 도착지 도달: 현재 위치가 도착점 (N-1, M-1)이라면 true를 반환합니다.
  • 방문 처리: 현재 위치를 방문 처리하여 중복 탐색을 방지합니다.
  • 재귀 호출: 상하좌우 방향으로 DFS를 재귀적으로 호출하여 경로를 탐색합니다.
  • 방문 해제: 탐색이 끝난 후 방문 처리를 해제합니다.

5. 시간 복잡도 분석

이 알고리즘의 시간 복잡도는 O(N * M)입니다. 여기서 N은 행의 수, M은 열의 수입니다. 모든 셀을 한 번씩 방문할 수 있기 때문입니다. 하지만 재귀에 따른 스택 공간 오버헤드로 인해 최악의 경우 필요할 수 있는 추가 메모리도 존재합니다.

결론

이번 강좌에서는 깊이 우선 탐색(DFS)을 통해 미로 탐색 문제를 해결하는 방법을 배웠습니다. DFS는 복잡한 그래프 구조에서도 유용하게 사용될 수 있으며, 다양한 문제에 적용할 수 있습니다. DFS의 특징과 동작 방식을 이해했다면, 다양한 문제에 응용해보는 것이 좋습니다. 다음 강좌에서는 너비 우선 탐색(BFS) 알고리즘에 대해 알아보겠습니다. 이 강좌가 도움이 되었기를 바랍니다.

자바스크립트 코딩테스트 강좌, 부녀회장이 될 테야

문제 정의

오늘의 문제는 “부녀회장이 될 테야”입니다. 이 문제는 층과 호에 대한 정보가 주어지면, 특정층의 특정호에 사는 사람의 수를 계산하는 문제입니다.

부녀회장은 다음과 같은 규칙으로 관리합니다. 각 호에는 한 가구가 살고 있으며, 각 층의 1호에는 항상 1명이 살고 있습니다. 이후 각 호의 인원 수는 그 아래 층의 동일한 호 인원과 그 아래 층의 바로 왼쪽 호 인원 수의 합으로 계산됩니다.

문제 설명

입력:
– 첫 번째 입력 값은 테스트 케이스의 수 T (1 <= T <= 1000)입니다.
– 각 테스트 케이스는 두 개의 정수 K (0 <= K <= 14)와 N (1 <= N <= 14)로 구성됩니다.
K는 층의 수, N은 해당 층의 호수를 나타냅니다.

출력:
각 테스트 케이스마다 KN호의 사람 수를 출력해야 합니다.

예시 입력 및 출력

입력:
2
1 3
2 3
출력:
3
6

문제 풀이 과정

1. 결합 함수 이해하기

이 문제의 핵심은 동적 계획법(Dynamic Programming)을 이용하여 인원 수를 계산하는 것입니다. 기본적으로 아래층의 호수의 인원 수를 이용해 현재 층의 사람 수를 계산합니다. 계산 과정은 다음과 같습니다.

계산 방법

def count_people(K, N):
    if K == 0:
        return N
    if N == 1:
        return 1
    return count_people(K-1, N) + count_people(K, N-1)

2. 반복문을 통한 진행

재귀 함수는 코드가 복잡해질 수 있기 때문에 반복문을 통해 효율적으로 계산하는 방법을 사용하겠습니다. 먼저 K층의 사람 수를 미리 저장한 2차원 배열을 생성합니다.

3. 구현

function countPeople(T, cases) {
    const results = [];
    const dp = Array.from(Array(15), () => Array(15).fill(0));

    for (let i = 0; i <= 14; i++) {
        dp[0][i] = i;  // 0층의 경우 i명
    }
    
    for (let k = 1; k <= 14; k++) {
        dp[k][1] = 1;  // 1호의 경우 무조건 1명
        for (let n = 2; n <= 14; n++) {
            dp[k][n] = dp[k-1][n] + dp[k][n-1];  // 위층과 왼쪽 호수 사람 수를 합함
        }
    }

    for (let i = 0; i < T; i++) {
        let k = cases[i][0];
        let n = cases[i][1];
        results.push(dp[k][n]);
    }
  
  return results;
}

const T = 2;
const cases = [[1, 3], [2, 3]];
console.log(countPeople(T, cases)); // 출력 결과: [3, 6]

결론

우리가 구현한 함수는 T개의 테스트 케이스에 대해 정확하고 빠르게 각 층과 호수의 인원 수를 계산합니다. 이 문제는 동적 계획법의 기본 개념을 잘 보여주는 예제입니다. 동적 계획법을 적절히 사용하면, 같은 유형의 문제를 효율적으로 해결할 수 있습니다.

이 알고리즘을 통해 코딩 테스트를 준비하면서 프로그래밍의 여러 측면을 강화할 수 있습니다. 이번 강좌를 통해 자바스크립트 코딩 테스트에 대한 이해도를 높이길 바랍니다.

자바스크립트 코딩테스트 강좌, 회의실 배정하기

2023년 10월 10일

문제 설명

회의실을 효율적으로 배정하는 것은 현대 사무 환경에서 중요한 문제입니다. 주어진 회의 목록에 대해 각 회의의 시작 시간과 끝 시간이 주어지면, 중복되지 않도록 최대한 많은 회의를 회의실에 배정하는 알고리즘을 작성하는 문제입니다.

입력:

  • meetings: 2차원 배열로 각 배열은 [startTime, endTime]으로 구성되어 있습니다.

출력:

  • 배정할 수 있는 최대 회의 수

예시

예를 들어, 아래와 같은 회의 목록이 있다고 가정합시다.


            [[0, 30], [5, 10], [15, 20]]
        

여기서 배정할 수 있는 최대 회의 수는 2입니다. (회의 [0, 30]은 [5, 10] 및 [15, 20]과 겹치지 않음).

문제 접근 방법

이 문제는 그리디 알고리즘을 통해 해결할 수 있습니다. 회의를 끝나는 시간 기준으로 정렬한 후, 가장 일찍 끝나는 회의에 대해 회의를 배정하는 방식을 사용할 것입니다. 이 방식을 통해 회의실의 자원을 최대한 활용할 수 있습니다.

  1. 우선, 회의 목록을 끝나는 시간 기준으로 정렬합니다.
  2. 첫 번째 회의를 선택하고, 이후 회의가 시작되는 시간이 현재 선택된 회의의 끝나는 시간보다 클 경우 새로운 회의를 선택합니다.
  3. 이 과정을 회의 목록의 끝까지 반복합니다.

자바스크립트 구현

이제 위의 접근 방법을 바탕으로 자바스크립트 코드로 구현해 보겠습니다.


function maximumMeetings(meetings) {
    // 회의의 끝나는 시간으로 정렬
    meetings.sort((a, b) => a[1] - b[1]);
    
    let count = 0;
    let lastEndTime = 0;

    for (let i = 0; i < meetings.length; i++) {
        // 현재 회의의 시작 시간이 마지막으로 선택된 회의의 끝나는 시간보다 클 경우
        if (meetings[i][0] >= lastEndTime) {
            count++;
            lastEndTime = meetings[i][1]; // 마지막으로 선택한 회의의 끝나는 시간 업데이트
        }
    }

    return count;
}

// 테스트를 위한 예시
const testMeetings = [[0, 30], [5, 10], [15, 20]];
console.log(maximumMeetings(testMeetings)); // 출력: 2
        

복잡도 분석

이 알고리즘의 시간 복잡도는 O(n log n)입니다. 이는 회의를 정렬하는 데에 소요되는 시간 때문입니다. 정렬된 회의 목록을 통해 최대 회의 수를 세는 과정은 O(n)입니다. 따라서 전체 시간 복잡도는 O(n log n)입니다. 공간 복잡도는 O(1)로, 정렬된 리스트를 사용하더라도 추가 공간을 필요로 하지 않습니다.

결론

“회의실 배정하기” 문제는 그리디 알고리즘을 통해 효율적으로 해결할 수 있는 대표적인 문제입니다. 이 문제를 통해 회의의 중복을 피하고 자원을 최대한 활용하는 방법을 배울 수 있습니다. 자바스크립트로 구현한 위의 코드는 이 문제를 해결하는 데 도움을 줄 수 있습니다. 이를 바탕으로 더 나아가 다양한 알고리즘 문제를 해결하는 데 필요한 기초를 다질 수 있을 것입니다.