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

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

문제 설명

주어진 두 개의 선분이 교차하는지 여부를 판단하는 문제입니다. 각 선분은 두 점으로 정의되며, 선분 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) 시간에 구간 합을 계산할 수 있는 방법을 배웠습니다. 이러한 접근 방식은 다른 비슷한 문제를 해결하는 데에도 적용할 수 있으므로, 알고리즘 공부에 많은 도움이 될 것입니다.

자바스크립트 코딩테스트 강좌, 타임머신으로 빨리 가기

코딩 시험은 점점 더 많은 기업들에서 필수적으로 진행되고 있으며, 자바스크립트는 웹 개발에서 가장 인기 있는 언어 중 하나입니다.
이 강좌에서는 자바스크립트를 사용하여 코딩 시험에서 흔히 나오는 알고리즘 문제를 풀어보겠습니다.
오늘의 주제는 ‘타임머신으로 빨리 가기’라는 문제입니다.

문제 설명

당신은 타임머신을 조작할 수 있는 과학자입니다. 타임머신은 특정 시간으로 이동할 수 있으며,
두 개의 정수 a, b가 주어졌을 때, a에서 b로 이동하는 데 걸리는 최소 시간 (이동 횟수를 의미)
을 계산해야 합니다. 타임머신은 다음과 같은 규칙을 따릅니다:

  • 현재 위치에서 1을 더하거나 1을 뺄 수 있습니다.
  • 현재 위치를 2배로 만들 수 있습니다.

주어진 a와 b에 대해 최소한 몇 번의 이동으로 a에서 b로 이동할 수 있는지를 계산하는 함수를 작성하세요.

입력 형식

– 두 개의 정수 a (0 ≤ a ≤ 105), b (0 ≤ b ≤ 105)가 주어집니다.

출력 형식

– a에서 b로 이동하기 위한 최소 이동 수를 정수로 출력합니다.

예제

        입력: a = 2, b = 9
        출력: 4
    
        입력: a = 5, b = 22
        출력: 7
    

문제 풀이 접근 방법

이 문제를 해결하기 위해 우리는 BFS(너비 우선 탐색)를 사용할 것입니다. BFS는 최단 경로를 찾는 데 적합한 알고리즘으로,
주어진 상태에서 가능한 모든 상태를 탐색하여 목표 상태에 도달하는 가장 최소한의 이동 경로를 찾는 방식입니다.
이 경우, 각각의 상태는 타임머신이 위치할 수 있는 시간을 나타냅니다.

1단계: BFS 알고리즘 사용 준비

BFS를 구현하기 위해, 우리는 큐를 사용할 것입니다. 먼저, 시작 위치인 a를 큐에 추가합니다.
그리고 현재 위치에서 가능한 모든 이동을 큐에 추가하면서 목표 위치인 b에 도달할 때까지 반복합니다.
각 위치에서 가능한 이동은 다음과 같습니다:

  • 현재 위치에서 1을 더하기
  • 현재 위치에서 1을 빼기
  • 현재 위치를 2배로 만들기

목표 위치에 도달할 때까지 각 이동의 횟수를 기록할 것이며, 목표에 도달했을 때의 이동 횟수를 출력합니다.

2단계: 코드 구현


function minimumMoves(a, b) {
    if (a >= b) return a - b; // a가 b보다 크거나 같으면 정해진 시간 만큼 차감하는 것으로 이동 횟수 결정
    const queue = [[a, 0]]; // [현재 위치, 이동 수]
    const visited = new Set(); // 방문한 위치 기록
    visited.add(a); // 시작 위치를 방문 처리

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

        // 가능한 모든 이동
        const nextMoves = [current - 1, current + 1, current * 2]; 

        for (let next of nextMoves) {
            if (next === b) return moves + 1; // 목표 위치에 도달하면 이동 수 반환
            if (next < 0 || next > 100000 || visited.has(next)) continue; // 유효 범위 내에서 방문하지 않은 경우만
            visited.add(next); // 다음 위치 방문 처리
            queue.push([next, moves + 1]); // 다음 위치와 이동 수 큐에 추가
        }
    }
}
    

3단계: 알고리즘 복잡도 분석

알고리즘의 시간 복잡도는 O(n) 입니다.
최악의 경우 모든 가능한 위치를 탐색해야 하므로 O(n)에 해당하고,
공간 복잡도도 O(n)입니다. 큐와 방문 기록을 위한 공간을 고려하면 이러한 복잡도가 발생합니다.

4단계: 최적화 가능성

이 알고리즘은 이미 BFS를 기반으로 하여 최단 경로를 탐색하고 있으므로
추가적인 최적화는 필요하지 않습니다. 그러나 상황에 따라
DFS(깊이 우선 탐색)를 적용할 수도 있지만, 이 문제에서는 BFS가 더 효과적입니다.

5단계: 결론

‘타임머신으로 빨리 가기’ 문제를 통해 BFS의 원리를 간단히 이해하고,
자바스크립트를 활용하여 문제를 해결하는 방법을 배웠습니다.
이런 방식으로 다양한 문제를 해결할 수 있으며,
코딩테스트에서 좋은 성적을 얻기 위해 기본적인 알고리즘을 숙지하는 것이 중요합니다.

추가 자료

– BFS 알고리즘에 대한 심화 연구 및 다양한 문제 풀이를 연습하기 위해 다음의 자료를 추천드립니다.

자바스크립트 코딩테스트 강좌, 오일러 피 함수 구현하기

안녕하세요, 여러분! 오늘은 자바스크립트로 오일러 피 함수(𝜙(n))를 구현하는 방법에 대해 자세히 설명드리겠습니다. 오일러 피 함수는 주어진 정수 n보다 작거나 같은 정수 중 n과 서로 소인 정수의 개수를 나타냅니다. 이 문제는 수론 관련 알고리즘 문제에서 매우 자주 등장하며, 다양한 코딩 테스트에서 유용하게 활용될 수 있습니다.

오일러 피 함수란?

오일러 피 함수 𝜙(n)은 n과 서로 소인 1부터 n까지의 양의 정수의 개수를 반환합니다. 다시 말하면, 두 수 a와 b가 서로 소라는 것은 두 수의 최대공약수(GCD)가 1일 때를 의미합니다.

예를 들어:

  • 𝜙(1) = 1 (1과 서로 소인 수는 1 하나뿐)
  • 𝜙(2) = 1 (2보다 작고 2와 서로 소인 수는 1)
  • 𝜙(3) = 2 (3보다 작고 3과 서로 소인 수는 1, 2)
  • 𝜙(4) = 2 (4보다 작고 4와 서로 소인 수는 1, 3)
  • 𝜙(5) = 4 (5보다 작고 5와 서로 소인 수는 1, 2, 3, 4)

문제 정의

이제 코딩 테스트 문제를 정의해보겠습니다.

문제: 주어진 정수 n에 대해 오일러 피 함수를 계산하는 함수를 작성하라.
입력: 정수 n (1 ≤ n ≤ 106)
출력: 오일러 피 함수 𝜙(n)의 값

문제 해결 접근법

오일러 피 함수를 계산하는 방법에는 여러 가지가 있습니다. 가장 효율적인 방법은 에라토스테네스의 체를 변형하여 𝜙(n) 값을 한 번에 구하는 것입니다. 이 방법의 시간 복잡도는 O(n log log n)입니다.

오일러 피 함수의 성질

𝜙(n)을 계산하기 위해 필요한 중요한 성질은 다음과 같습니다:

  • 𝜙(p) = p – 1, 여기서 p는 소수
  • 𝜙(p1 * p2 * … * pk) = p1 * p2 * … * pk * (1 – 1/p1) * (1 – 1/p2) * … * (1 – 1/pk)
  • 𝜙(n) = n * (1 – 1/p1) * (1 – 1/p2) * … * (1 – 1/pk), 여기서 pi는 n의 소인수들입니다.

자바스크립트 코드 구현

위의 성질을 바탕으로 오일러 피 함수를 구현하는 코드를 작성해보겠습니다.


function eulerPhi(n) {
    // 배열 선언 및 초기화
    const phi = Array.from({ length: n + 1 }, (_, i) => i);
    
    // 에라토스테네스의 체를 사용한 소수 찾기
    for (let p = 2; p <= n; p++) {
        if (phi[p] === p) { // p가 소수인 경우
            for (let k = p; k <= n; k += p) {
                // 𝜙(k) 값 업데이트
                phi[k] = Math.floor(phi[k] * (1 - 1 / p));
            }
        }
    }
    
    // n에 대한 phi(n) 반환
    return phi[n];
}

// 테스트 케이스
console.log(eulerPhi(1));  // 1
console.log(eulerPhi(2));  // 1
console.log(eulerPhi(3));  // 2
console.log(eulerPhi(4));  // 2
console.log(eulerPhi(5));  // 4

코드 설명

코드는 다음과 같이 작동합니다:

  1. 우선 n + 1 크기의 배열 phi를 생성하고, 각 요소를 그 자체로 초기화합니다. phi[i]는 i를 초기값으로 가집니다.
  2. 2부터 n까지 반복하면서, 각 수 p가 소수인지 검사합니다. 이를 위해, phi[p]가 p와 같은 경우 소수로 간주합니다.
  3. p가 소수일 경우, p의 배수를 찾아 phi[k] 값을 업데이트합니다. 업데이트는 𝜙(k) = 𝜙(k) * (1 – 1/p)로 수행합니다.
  4. 최종적으로, phi[n] 값을 반환하여 n에 대한 오일러 피 함수의 값을 계산합니다.

복잡도 분석

위 코드의 시간 복잡도는 O(n log log n)입니다. 이는 에라토스테네스의 체를 사용하는 방법 때문입니다. 공간 복잡도는 O(n)입니다, 배열 phi를 저장하기 위해 n의 크기만큼의 공간이 필요합니다.

결론

지금까지 자바스크립트로 오일러 피 함수를 구현하는 방법에 대해 알아보았습니다. 이 방법은 알고리즘 테스트 및 수론에 매우 유용하며, 효율적으로 오일러 피 값을 계산할 수 있습니다. 이 코드를 활용하여 다양한 문제를 해결해 보시기 바랍니다.

더 나아가기

비슷한 문제를 풀어보고 싶다면, 최대공약수나 최소공배수를 사용하는 문제를 해결해보는 것 또한 좋은 연습이 될 것입니다. 또한 수론의 다른 개념인 소수 판별, 소수 생성기 등을 연구해보는 것도 추천드립니다. 수학적 사고를 통해 알고리즘에 대한 이해도를 높여보세요!

참고 자료

이 포스팅이 도움이 되셨기를 바랍니다. 궁금한 점이나 추가적인 질문이 있으시다면 댓글로 남겨주세요! 감사합니다!