자바스크립트 코딩테스트 강좌, 수 정렬하기

개요

코딩테스트에서 자주 등장하는 문제 중 하나는 주어진 수를 정렬하는 것입니다.
본 강좌에서는 JavaScript로 수를 정렬하는 방법에 대해 알아보겠습니다.
수 정렬하기 문제는 기본적인 정렬 알고리즘을 공부하는 데 매우 유용합니다.
우리는 여러 가지 알고리즘을 이용해 수를 정렬할 수 있으며, 각각의 알고리즘은
시간 복잡도와 공간 복잡도가 다릅니다.

문제 설명

문제

주어진 수의 배열을 입력으로 받아서, 정렬된 배열을 반환하는 함수를 작성하시오.
예를 들어, 입력 배열이 [3, 1, 2]이라면,
출력은 [1, 2, 3]이어야 합니다.

입력

  • 첫 번째 줄에 정수 N 이 주어진다. (1 ≤ N ≤ 100,000)
  • 두 번째 줄에 N개의 정수 A1, A2, ..., AN이 하나의 공백으로 구분되어 주어진다.
    (-1,000,000 ≤ Ai ≤ 1,000,000)

출력

정렬된 수를 한 줄에 출력해야 하며, 각 수는 하나의 공백으로 구분되어야 한다.

예제

    입력:
    5
    5 4 3 2 1
    
    출력:
    1 2 3 4 5
    

문제 해결 과정

1단계: 요구사항 분석

주어진 문제를 해결하기 위해서는 입력받은 배열을 정렬하여
출력하는 것이 목표입니다. 이를 위해 우리는 클래식한 정렬 알고리즘인
퀵 정렬(Quick Sort)과 병합 정렬(Merge Sort)을 사용할 수 있습니다.
JavaScript 내장 함수를 사용할 수도 있지만, 이번에는 알고리즘을 이해하기 위해
직접 구현해보는 것이 중요합니다.

2단계: 알고리즘 선택

정렬 알고리즘 중 퀵 정렬과 병합 정렬은 일반적으로 고속 정렬 알고리즘으로 널리 사용됩니다.
아래에서 각 알고리즘의 장단점을 간단히 정리하겠습니다.

퀵 정렬(Quick Sort)

  • 평균 시간 복잡도: O(N log N)
  • 최악 시간 복잡도: O(N2) (비효율적인 피벗 선택 시)
  • 공간 복잡도: O(log N)
  • 장점: in-place 정렬이 가능하여 메모리 사용이 적다.
  • 단점: 안정적인 정렬이 아니다.

병합 정렬(Merge Sort)

  • 평균 및 최악 시간 복잡도: O(N log N)
  • 공간 복잡도: O(N)
  • 장점: 안정적인 정렬이다.
  • 단점: 추가적인 메모리가 필요하다.

3단계: 퀵 정렬 구현

우리는 퀵 정렬을 이용하여 수 배열을 정렬하는 함수를 구현할 것입니다.
아래는 JavaScript로 작성된 퀵 정렬의 구현 코드입니다.


    function quickSort(arr) {
        if (arr.length <= 1) return arr;
        const pivot = arr[arr.length - 1];
        const left = [];
        const right = [];
        
        for (let i = 0; i < arr.length - 1; i++) {
            if (arr[i] < pivot) {
                left.push(arr[i]);
            } else {
                right.push(arr[i]);
            }
        }
        return [...quickSort(left), pivot, ...quickSort(right)];
    }
    

4단계: 병합 정렬 구현

다음으로, 병합 정렬 알고리즘을 구현해보겠습니다. 아래에 병합 정렬의 구현 코드를 제공합니다.


    function mergeSort(arr) {
        if (arr.length <= 1) return arr;
        const mid = Math.floor(arr.length / 2);
        const left = mergeSort(arr.slice(0, mid));
        const right = mergeSort(arr.slice(mid));
        
        return merge(left, right);
    }
    
    function merge(left, right) {
        const result = [];
        let i = 0, j = 0;
        
        while (i < left.length && j < right.length) {
            if (left[i] < right[j]) {
                result.push(left[i]);
                i++;
            } else {
                result.push(right[j]);
                j++;
            }
        }
        return result.concat(left.slice(i)).concat(right.slice(j));
    }
    

5단계: 입력 처리 및 출력

이제 정렬 함수들을 완성했으므로, 입력을 처리하고
결과를 출력하는 부분을 구현하겠습니다. JavaScript에서는
prompt를 사용하여 입력을 받을 수 있으며,
결과는 console.log를 사용하여 출력합니다.


    const N = parseInt(prompt("정수의 개수를 입력하세요:"));
    const nums = prompt("정수를 입력하세요:").split(" ").map(Number);
    const sorted = quickSort(nums); // 혹은 mergeSort(nums);
    
    console.log(sorted.join(" "));
    

결론

이번 강좌에서는 자바스크립트를 이용하여 주어진 수 배열을 정렬하는
문제를 해결했습니다. 정렬 알고리즘의 구현뿐만 아니라,
각 알고리즘의 특징 및 성능에 대해서도 알아보았습니다.
퀵 정렬과 병합 정렬을 직접 구현함으로써 정렬 알고리즘에 대한
이해를 깊게 할 수 있었습니다.
코딩테스트에서는 정렬 문제 외에도 다양한 문제들이 출제되니,
이러한 알고리즘들을 다양하게 연습해보는 것을 추천합니다.

문제 해결 및 연습

아래의 문제들을 풀어보며, 더 많은 연습을 해보세요!

  • 주어진 수 배열에서 중복된 수를 제거한 후 정렬하기
  • 구간 정렬: 주어진 구간 내에서만 정렬하기
  • 정렬된 두 배열을 합치는 문제

참고 자료

자바스크립트 코딩테스트 강좌, 구간 합

안녕하세요! 이번 강좌에서는 자바스크립트 코딩 테스트에서 자주 출제되는 “구간 합” 문제를 심도 있게 다뤄보겠습니다. 구간 합 문제는 주어진 수열에서 임의의 구간의 합을 효율적으로 구하는 문제로, 다양한 최적화 기법을 통해 해결할 수 있습니다. 우리가 다룰 구간 합 문제는 특히 배열의 크기가 클 때 시간이 많이 소요될 수 있기 때문에, 보다 효율적인 알고리즘을 설계하는 것이 중요합니다.

문제 소개

다음은 구간 합에 관한 문제입니다:

문제 설명

정수로 이루어진 배열 arr와 여러 쌍의 정수 (l, r)이 주어집니다. 각 쌍은 구간의 시작점 l과 끝점 r을 나타냅니다. arr[l] + arr[l + 1] + ... + arr[r]의 합을 구하는 프로그램을 작성하시오. 배열의 길이와 쌍의 개수는 다음과 같습니다:

  • 1 ≤ arr.length ≤ 106
  • 1 ≤ l, rarr.length
  • 0 ≤ arr[i] ≤ 109

예를 들어, 배열 arr = [1, 2, 3, 4, 5]이고, 쌍 (1, 3)이 주어졌다면, 구간 합은 arr[1] + arr[2] + arr[3] = 2 + 3 + 4 = 9입니다.

문제 접근 방법

이 문제를 효율적으로 해결하기 위해서는 단순히 반복문을 통해 각 구간의 합을 계산하는 것은 적절하지 않습니다. 이유는 최악의 경우 O(N * M)의 시간 복잡도를 가지게 되므로, 데이터의 크기가 크면 지수적으로 시간이 늘어날 수 있습니다. 대신에 더 효과적인 접근 방법을 사용할 수 있습니다.

전처리 기법: 누적 합 배열

구간 합 문제를 해결하기 위한 방법 중 하나는 누적 합 배열(Prefix Sum Array)을 만드는 것입니다. 누적 합 배열을 사용하면, 구간의 합을 O(1)로 계산할 수 있습니다. 누적 합 배열의 정의는 다음과 같습니다:

prefix[i] = arr[0] + arr[1] + ... + arr[i-1]

따라서, 구간 (l, r)의 합은 다음과 같이 계산할 수 있습니다:

sum(l, r) = prefix[r + 1] - prefix[l]

이를 통해 각 쌍의 구간 합을 O(1) 시간 안에 구할 수 있습니다. 전체 알고리즘의 시간 복잡도는 O(N + M)이 되며, 여기서 N은 배열의 크기, M은 쌍의 개수입니다.

구현 단계

이제 누적 합 배열을 사용하여 문제를 해결하는 자바스크립트 코드를 구현해보겠습니다.

1단계: 누적 합 배열 생성


function createPrefixSum(arr) {
    const prefix = new Array(arr.length + 1).fill(0);
    for (let i = 0; i < arr.length; i++) {
        prefix[i + 1] = prefix[i] + arr[i];
    }
    return prefix;
}

2단계: 구간 합 계산


function rangeSum(prefix, l, r) {
    return prefix[r + 1] - prefix[l];
}

3단계: 메인 함수 구현


function calculateRangeSums(arr, queries) {
    const prefix = createPrefixSum(arr);
    const results = [];
    for (let [l, r] of queries) {
        results.push(rangeSum(prefix, l - 1, r - 1)); // 1-based to 0-based
    }
    return results;
}

// 예시 사용
const arr = [1, 2, 3, 4, 5];
const queries = [[1, 3], [2, 5], [0, 2]];
const results = calculateRangeSums(arr, queries);
console.log(results); // [9, 14, 6]

결과 분석

위 코드는 주어진 배열과 쿼리를 기반으로 매 쿼리의 구간 합을 효율적으로 계산하여 결과를 출력하는 프로그램입니다. 결과적으로 구간 합을 빠르게 구할 수 있으며, 배열의 크기나 쿼리 개수에 따라 성능이 저하되지 않습니다.

시간 복잡도

본 알고리즘의 시간 복잡도는 다음과 같습니다:

  • 누적 합 배열 생성: O(N)
  • 각 쿼리 처리: O(1) (M개 쿼리 처리 전부 합치면 O(M))

결과적으로 전체 시간 복잡도는 O(N + M)이며, 이는 매우 효율적인 수준입니다.

결론

이제 구간 합 문제를 누적 합 배열을 사용하여 효율적으로 해결하는 방법을 배웠습니다. 코딩 테스트에서 구간 합 문제는 자주 출제되는 주제이므로, 이와 같은 최적화 기법을 이해하고 활용하는 것이 중요합니다. 연습을 통해 다양한 유형의 문제를 해결해 보시기 바랍니다!

추가 연습 문제

다음과 같은 변형 문제를 연습해보세요:

  • 구간 합 대신 구간의 최대값을 구하는 문제
  • 구간의 곱을 구하는 문제 (단, 나누기 연산을 고려해야 할 수도 있음)
  • 다양한 쿼리(예: 구간 증가, 감소 등)가 주어질 때 어떻게 처리할 것인지 고민해보기

위 내용을 바탕으로 다양한 문제를 해결해 보시길 바랍니다. 감사합니다!

자바스크립트 코딩테스트 강좌, 선분의 교차 여부 구하기

여러분 안녕하세요! 오늘은 자바스크립트 코딩 테스트에서 자주 등장하는 문제 중 하나인 ‘선분의 교차 여부 구하기’에 대해 다뤄보겠습니다. 이 문제는 면접에서 자주 출제되며, 기하학적인 개념을 이해하고 코드로 구현하는 능력을 키우는데 큰 도움이 됩니다.

문제 설명

주어진 두 개의 선분이 교차하는지 여부를 판단하는 문제입니다. 각 선분은 두 점으로 정의되며, 선분 A는 점 A1(x1, y1)과 A2(x2, y2)로, 선분 B는 점 B1(x3, y3)와 B2(x4, y4)로 표시됩니다. 우리는 이러한 두 선분이 서로 교차하는지 여부를 판단해야 합니다.

입력

  • A1: (x1, y1)
  • A2: (x2, y2)
  • B1: (x3, y3)
  • B2: (x4, y4)

출력

교차하는 경우 true를, 그렇지 않은 경우 false를 반환해야 합니다.

예제

입력 예제

    A1: (1, 1), A2: (4, 4)
    B1: (1, 4), B2: (4, 1)
  

출력 예제

    true
  

문제 접근 방식

이 문제를 해결하기 위해서는 몇 가지 기하학적인 개념과 알고리즘을 활용해야 합니다. 특히, 선분의 교차 여부를 판단하기 위한 두 가지 방법을 소개합니다. 첫 번째 방법은 사각형의 넓이를 이용한 방법이고, 두 번째는 벡터의 외적(Cross Product) 개념을 이용한 방법입니다.

1. 외적을 이용한 교차 여부 판단

선분이 교차하는지 여부를 판단하기 위해 벡터의 외적을 사용합니다. 두 방향 벡터 A와 B간의 외적이 0보다 크면 한 방향에 있고, 0보다 작으면 반대 방향에 있음을 알 수 있습니다.

직선의 방정식

    Let's define line segments A and B:
    A: [(x1, y1), (x2, y2)]
    B: [(x3, y3), (x4, y4)]
  

외적 공식

선분 AB와 AC에 대해 다음과 같은 외적 공식을 사용할 수 있습니다:

    cross(A, B) = (A.x * B.y - A.y * B.x)
  

2. 선분과 한 선의 교차 여부 판단

선분이 주어졌을 때, 한 선이 특정 선분을 교차하는지 여부를 판단하기 위해서는 또 다른 기법을 사용할 수 있습니다. 이 기본적으로 각 선분의 끝점을 포함한 직선을 읽어들여 교차 여부를 계산하는 것입니다.

구현 코드

이제 위에서 설명한 방식으로 실제 코드를 구현해 보겠습니다.


    function isIntersect(A1, A2, B1, B2) {
      const orientation = (p, q, r) => {
        const val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
        if (val === 0) return 0; // collinear
        return (val > 0) ? 1 : 2; // clock or counterclock wise
      };
      
      const onSegment = (p, q, r) => {
        return (
          q[0] <= Math.max(p[0], r[0]) &&
          q[0] >= Math.min(p[0], r[0]) &&
          q[1] <= Math.max(p[1], r[1]) &&
          q[1] >= Math.min(p[1], r[1])
        );
      };
      
      const o1 = orientation(A1, A2, B1);
      const o2 = orientation(A1, A2, B2);
      const o3 = orientation(B1, B2, A1);
      const o4 = orientation(B1, B2, A2);
      
      if (o1 !== o2 && o3 !== o4) return true;
      if (o1 === 0 && onSegment(A1, B1, A2)) return true;
      if (o2 === 0 && onSegment(A1, B2, A2)) return true;
      if (o3 === 0 && onSegment(B1, A1, B2)) return true;
      if (o4 === 0 && onSegment(B1, A2, B2)) return true;

      return false;
    }

    // Example usage
    const A1 = [1, 1],
          A2 = [4, 4],
          B1 = [1, 4],
          B2 = [4, 1];

    console.log(isIntersect(A1, A2, B1, B2)); // true
  

코드 설명

우선, orientation 함수는 주어진 세 점(p, q, r)에 대한 방향성을 판별합니다. 이를 통해 선분 A, B의 교차 여부를 판단할 수 있습니다.

그 다음, onSegment 함수는 점 q가 선분 pr 위에 있는지를 판단합니다. 교차 여부를 확인한 후, 특정 경우 (세 점이 모두 일치하는 경우)에 대해 추가적인 판단을 진행합니다.

시간 복잡도

이 알고리즘의 시간 복잡도는 O(1)입니다. 단 한 번의 비교를 통해 교차 여부를 확인할 수 있기 때문입니다.

결론

이번 강좌에서는 자바스크립트를 이용해 선분의 교차 여부를 판단하는 알고리즘을 알아보았습니다. 기하학적 문제에 대한 이해와 코딩 능력을 키울 수 있었기를 바랍니다. 면접 준비에 도움이 되었길 바라며, 다음 강좌에서 또 만나요!

자바스크립트 코딩테스트 강좌, 줄 세우기

문제 설명

주어진 학생들의 키 정보가 있습니다. 이들 중에서 단 하나의 학생을 지정하여
해당 학생이 가장 앞에 서도록 줄을 세워야 합니다.
이 때, 한 학생의 키보다 작은 학생은 그 뒤에 서야 하며,
같은 키를 가진 학생이 여러 명이라도 순서는 변하지 않아야 합니다.
이러한 조건을 만족하도록 줄을 세우는 알고리즘을 작성하세요.

입력 형식

    학생들의 키를 담고 있는 배열 (예: [160, 170, 165, 180, 175])
    지정할 학생의 키 (예: 170)
    

출력 형식

    줄 세운 학생들의 키 배열 (예: [170, 160, 165, 180, 175])
    

문제 해결 과정

1단계: 문제 이해하기

문제의 핵심은 주어진 배열에서 지정한 키를 가진 학생을 가장 앞에 배치하고, 나머지 학생들은 키에 따라 정렬하되
원래의 순서를 유지하는 것이다. 이 문제는 주로 안정 정렬을 활용하여 해결할 수 있다.
우리가 구현할 알고리즘은 다음과 같은 작업을 포함한다.

2단계: 간단한 예제 분석

예를 들어, 입력으로 [160, 170, 165, 180, 175]170이 주어진다면,
줄 세운 결과는 [170, 160, 165, 180, 175]가 되어야 한다. 이때 주의할 점은
같은 키를 가진 학생이 여러 명일 때, 그 순서를 유지하는 것이다.

3단계: 해결 전략 구상

해결 방법은 다음과 같다.

  1. 주어진 배열에서 주어진 키와 같은 학생을 찾아 그 학생을 결과 배열의 첫 번째 요소로 추가한다.
  2. 나머지 학생들은 같은 키를 가진 학생을 제외한 채로 원래 순서를 유지하면서 결과 배열에 추가한다.
  3. 최종적으로 수정된 배열을 반환한다.

4단계: 자바스크립트 코드 구현

위의 전략을 바탕으로 자바스크립트 함수를 작성해보겠다. 이 함수는 두 개의 매개변수를 받아,
지정된 키를 가진 학생을 가장 앞으로 보내는 역할을 한다.

function lineUpStudents(students, targetHeight) {
    // 결과를 저장할 배열을 선언
    let result = [];

    // targetHeight에 해당하는 학생을 먼저 추가
    const targetStudents = students.filter(height => height === targetHeight);
    result.push(...targetStudents);

    // 나머지 학생들을 추가 (원래 순서 유지)
    const otherStudents = students.filter(height => height !== targetHeight);
    result.push(...otherStudents);

    return result;
}
        

5단계: 코드 테스트 및 검증

작성한 함수가 잘 작동하는지 확인하기 위해 몇 가지 테스트 케이스를 실행해 보겠다.

console.log(lineUpStudents([160, 170, 165, 180, 175], 170)); // [170, 160, 165, 180, 175]
console.log(lineUpStudents([160, 160, 165, 170, 180], 160)); // [160, 160, 165, 170, 180]
console.log(lineUpStudents([180, 170, 160, 150], 160)); // [160, 180, 170, 150]
        

6단계: 복잡도 분석

이 알고리즘의 시간 복잡도는 O(n)이다. 주어진 배열을 한 번씩 모두 돌기 때문이다.
공간 복잡도 또한 O(n)으로, 결과 배열을 따로 생성하기 때문이다.

결론

이 강좌에서는 주어진 학생들의 키 정보를 기반으로 한 줄 세우기 알고리즘을
자바스크립트를 이용해 해결하는 방법을 알아보았다.
학생의 키를 기준으로 안정적인 정렬을 유지하는 것이 이 문제의 핵심이었다.
이와 같은 문제들은 실제 코딩 테스트에 자주 출제된다는 점에서 매우 중요하다.
다양한 변형 문제를 풀어보면서 알고리즘적 사고를 키워나가는 것도 좋은 공부가 될 것이다.

자바스크립트 코딩테스트 강좌, 구간 합 구하기 2

문제 설명

구간 합 문제는 알고리즘 문제에서 자주 등장하는 유형 중 하나입니다. 이번 강좌에서는 구간 합을 계산하는 두 번째 문제를 다룹니다. 주어진 배열에서 두 점의 구간 합을 효율적으로 계산하는 방법에 대해 배우겠습니다.

문제:
정수 배열 A와 쿼리 배열 queries가 주어질 때, 각 쿼리 (l, r)에 대해 배열 Al 번째부터 r 번째까지의 구간 합을 계산하시오.

배열의 인덱스는 0부터 시작하며, 배열의 크기는 N이고 쿼리의 개수는 Q입니다.

입력 형식

1. 첫 번째 줄에 배열 A의 크기 N (1 ≤ N ≤ 106)이 주어진다.

2. 두 번째 줄에 배열 A의 원소들이 주어진다. (A[i]는 정수, -109A[i] ≤ 109)

3. 세 번째 줄에 쿼리의 개수 Q (1 ≤ Q ≤ 105)가 주어진다.

4. 다음 Q줄에는 각 쿼리 (l, r)이 주어진다. (0 ≤ lr < N)

출력 형식

각 쿼리마다 구간 합을 한 줄에 하나씩 출력한다.

예제

    입력:
    5
    1 2 3 4 5
    3
    0 2
    1 4
    2 2

    출력:
    6
    14
    3
    

풀이 과정

구간 합을 효율적으로 구하기 위한 첫 번째 단계는 전치합 (prefix sum) 배열을 만드는 것입니다. 전치합 배열은 배열의 각 인덱스까지의 원소들의 합을 저장하는 배열로, 이를 이용하면 구간 합을 O(1) 시간에 계산할 수 있습니다.

전치합 배열 만들기

전치합 배열 prefix는 다음과 같이 정의합니다:

prefix[0] = A[0],

prefix[i] = prefix[i-1] + A[i] (for 1 ≤ i < N)

이렇게 전치합 배열을 만들면, 구간 합 A[l] + ... + A[r]는 다음과 같이 구할 수 있습니다:

sum(l, r) = prefix[r] - prefix[l - 1] (단, l > 0)

위의 경우, l = 0일 때는 sum(0, r) = prefix[r]로 처리합니다.

구현

이제 전치합 배열을 만들고, 각 쿼리에 대해 구간 합을 계산하는 코드를 작성해보겠습니다. 아래는 자바스크립트로 구현한 코드입니다:

    
    function rangeSum(A, queries) {
        const N = A.length;
        const prefix = new Array(N);
        prefix[0] = A[0];
        
        // 전치합 배열 생성
        for (let i = 1; i < N; i++) {
            prefix[i] = prefix[i - 1] + A[i];
        }

        const result = [];
        // 쿼리 처리
        for (const [l, r] of queries) {
            if (l === 0) {
                result.push(prefix[r]);
            } else {
                result.push(prefix[r] - prefix[l - 1]);
            }
        }

        return result;
    }

    // 예제 입력
    const A = [1, 2, 3, 4, 5];
    const queries = [[0, 2], [1, 4], [2, 2]];
    console.log(rangeSum(A, queries)); // [6, 14, 3]
    
    

시간 복잡도 분석

전치합 배열을 만드는 과정은 O(N) 시간이 소요되며, 각 쿼리의 시간을 O(1)로 처리할 수 있습니다. 따라서 전체 알고리즘의 시간 복잡도는 O(N + Q)입니다. 이는 주어진 문제의 입력 조건을 만족하는 효율적인 솔루션입니다.

결론

이번 강좌에서는 구간 합을 구하는 방법에 대해 알아보았습니다. 문제를 해결하기 위해 전치합 배열을 활용하여 O(1) 시간에 구간 합을 계산할 수 있는 방법을 배웠습니다. 이러한 접근 방식은 다른 비슷한 문제를 해결하는 데에도 적용할 수 있으므로, 알고리즘 공부에 많은 도움이 될 것입니다.