✍️문제 설명

전화번호부에 적힌 전화번호 중, 한 번호가 다른 번호의 접두어인 경우가 있는지 확인하려 합니다.

전화번호가 다음과 같을 경우, 구조대 전화번호는 영석이의 전화번호의 접두사입니다.

  • 구조대 : 119
  • 박준영 : 97 674 223
  • 지영석 : 11 9552 4421

전화번호부에 적힌 전화번호를 담은 배열 phone_book 이 solution 함수의 매개변수로 주어질 때, 어떤 번호가 다른 번호의 접두어인 경우가 있으면 false를 그렇지 않으면 true를 return 하도록 solution 함수를 작성해주세요.

제한 사항
  • phone_book의 길이는 1 이상 1,000,000 이하입니다.
  • 각 전화번호의 길이는 1 이상 20 이하입니다.
  • 같은 전화번호가 중복해서 들어있지 않습니다.

조건으로 얻을 수 있는 정보

1. 배열의 길이가 `10^6` 이므로, 시간복잡도 `O(n x logn)`이 되도록 구현하여야 1초(10^8) 내에 실행 가

2. `O(n^2)` 방법은 시간 내에 처리 불가능하지만, 시간이 부족하면 이를 채택 (실전)

 

1️⃣ Brute False

누구나 가장 먼저 생각나는 방법으로, 각 phone_number를 순회하면서 해당 번호를 접두어로 사용하고 있는 phone_number가 있는지 판단하는 방법이다.

 

- 최소한의 최적화를 위해, 비교하는 두 phone_number를 문자열이 아닌 숫자로 변환하여 비교하였다.

is_jupdo : 문자열 비교

is_jupdo2 : 숫자 비

// #시도1. Brute false 문자열 비교, 시간초과
function is_jupdo(target, diff) {
  const target_len = target.length;

  for (let i = 0; i < target_len; i++) {
    if (target[i] !== diff[i]) {
      return false;
    }
  }
  return true;
}

// #시도2. Brute False 숫자 비교, 시간초과
function is_jupdo2(target, diff, power_10) {
  const target_len = target.length;
  const diff_len = diff.length;

  if (Math.floor(diff / power_10[diff_len - target_len]) - target === 0) {
    return true;
  }
  return false;
}
function solution(phone_book) {
  const len = phone_book.length;
  const power_10 = [1];

  // 10의 거듭제곱 값 넣기
  for (let i = 1; i <= 20; i++) {
    power_10[i] = power_10[i - 1] * 10;
  }

  phone_book.sort((a, b) => a.length - b.length);

  for (let i = 0; i < len - 1; i++) {
    const target = phone_book[i];
    for (let j = i + 1; j < len; j++) {
      const diff = phone_book[j];
      // Compare
      if (is_jupdo2(target, diff, power_10)) {
        return false;
      }
    }
  }
  return true;
}

 

효율성 테케 실행 결과 역시나 실패

 

2️⃣Hash Table

1. phone_number 값을 객체 (Hash Table)에 저장한다. (아래 그림 참고)

2. 각 phone_number를 순회하며, 생성가능한 모든 접두어가 객체(Hash Table)에 존재하는지 확인한다.

Hash Table 구조

// 시도3: Hash Table만들기
function solution(phone_book) {
  const table = {};
  for (const phone of phone_book) {
    table[phone] = true;
  }

  for (const phone of phone_book) {
    const len = phone.length;
    for (let i = 1; i < len; i++) {
      const substr = phone.substring(0, i);
      if (table[substr] === true) {
        return false;
      }
    }
  }

  return true;
}

 

효율성 테케 실행 결과 성공

 


분석

1️⃣Brute False

  • solution 함수에서는 전화번호를 길이 순으로 정렬한 후, 각 번호와 그 다음 번호들 간의 비교를 반복한다.
  • 여기서 이중 반복문을 사용하므로, 시간 복잡도는 최악의 경우 O(n^2)다.
  • 또한, is_jupdo 호출은 두 문자열의 길이에 비례하여 O(m) 시간이 소요되며, 이는 두 문자열 길이의 최대값에 의해 결정된다.
  • 따라서 전체 시간 복잡도는 `O(n^2×m)`로, 전화번호의 수가 많거나 각 번호의 길이가 길면 성능이 최악이다.

2️⃣Hash Table

  • 해시 테이블에 각 전화번호를 저장한 후, 각 전화번호의 접두사가 테이블에 있는지 확인한다. (O(n))
  • 각 phone_number의 길이-1 만큼 hash table에 접두어가 있는지 확인한다. (O(m))
  • 따라서, 이 방법의 시간 복잡도는 `O(n×m)`로, n은 전화번호 개수이고, m은 각 전화번호의 길이이다.

'PS' 카테고리의 다른 글

[파이썬] 코테 꿀팁  (2) 2024.10.10
Javascript PS 꿀팁 저장소  (0) 2024.06.29

문제의 발단

부동소수점에 대해 복습하던 중, 웹 개발 주력 언어인 Javascript의 소수점 처리 방식이 궁금해졌다.

Typescript를 사용해본 독자들은 number 원시 타입에 익숙할 것이다.

그렇다면 number 타입은 어느 범위의 숫자를 지원할까?


Number

Javascript가 최대 및 최소로 표현할 수 있는 숫자는 MAX_VALUE, MIN_VALUE 속성으로 파악 가능하다.

console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);

JS Max, Min Value

 

즉, 해당 값을 넘어가면 오차가 발생하거나 infinity로 판단된다.

부동소수점에 대한 자세한 내용은 CS 파트에서 다루겠다.

그렇다면, 범위를 늘리려면 어떻게 해야할까? → `Bigint` 자료형을 사용해보자.


Bigint

BigIntNumber 원시 값이 안정적으로 나타낼 수 있는 최대치인 2^53 - 1보다 큰 정수를 표현할 수 있는 내장 객체이다.

 

BigInt는 정수 리터럴의 뒤에 `n`을 붙이거나(10n) 함수 `BigInt()`를 호출해 생성할 수 있다.

const theBiggestInt = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// ↪ 9007199254740991n

const hugeString = BigInt("9007199254740991");
// ↪ 9007199254740991n

속성

수학 연산자

BigInt는 대개 일반 숫자와 큰 차이 없이 사용할 수 있다.

console.log(1n + 2n); // 3

 

주의점

  1. 나눗셈 연산자에서는 소수부가 없다.
  2. `BigInt` 값과 일반 숫자를 섞어서 사용할 수 없다
    형 변환을 통해 자료형을 동일하게 설정해주어야 한다.
  3. `+` 연산자를 사용하면, `Number` 범위 외 비트는 잘려나간다.
    → 따라서, `bigint`는 `+` 연산자를 지원하지 않는다.
// #1
console.log(5n / 2n); // 2

// #2. 
console.log(1n + 2); // Error: Cannot mix BigInt and other types

// 형 변환
alert(1n + BigInt(2)); // 3
alert(Number(1n) + 2); // 3

// #3. + 연산자 사용 불가
alert( +2n ); // 에러

비교 연산자

  1. `>` `<` 모두 사요 가능하며, `Number`와 동일하게 작용한다.
  2. `Number` 자료형과 비교하는 경우, `==` 는 작동하나, `===` 는 false를 도출한다.
// #1. > <
console.log( 2n > 1n ); // true
console.log( 2n > 1 ); // true

// #2. == and ===
console.log( 1 == 1n ); // true
console.log( 1 === 1n ); // false

논리 연산자

`Number`와 동일하게 작동한다.

if (0n) {
  // 실행 X
}

console.log( 1n || 2 ); // 1 (truthy)
console.log( 0n || 2 ); // 2 (falsy)

Typescript

version 3.2 부터 `bigint`를 사용가능하다.

const num: bigint = 10n;

const num2: bigint = Bigint(10);

 

 


결론

그렇다면 항상 Bigint를 사용하는 것이 좋을까?

Bigint는 Number에 비해 연산과정이 오래걸린다.

시간적 소요는 성능에 영향을 끼치므로, 꼭 필요한 경우에만 사용하도록 하자.

기존에 Stack을 다룰 때, 값들을 Stack에 넣어서 사용하는 간단한 문제만 풀어보다가 index를 스택에 저장하여 사용하는 방식의 문제가 있어서 기록합니다.

Problem

✏️ 문제 설명

temperatures 배열의 각 원소에 대해 자신보다 큰 값이 나오는 Index 차이를 알아내어, 해당 값을 output 배열에 넣어 반환.

 

Approach 1. O(n^2) : Brute False

변수선언 `output[]`: 정답을 저장할 배열

  1. `stack[]` : 순회한 값들을 저장할 배열
  2. `temperatures[]` 의 뒤에서부터 순회한다.
    • 각 temperature에 대해 스택의 앞에서 부터 순회하며, temperature보다 큰 값을 구할 때 까지 count 한다.
    • 큰 값이 나오면, 해당 값을 `ouput[]`의 맨 앞에 추가한다.
var dailyTemperatures1 = function (temperatures) {
  const output = [];
  let stack = [];
  while (temperatures.length > 0) {
    const target = temperatures.pop();
    let cnt = 0;
    let flag = false;
    for (const num of stack) {
      if (num <= target) cnt++;
      else {
        cnt++;
        flag = true;
        break;
      }
    }
    stack.unshift(target);
    if (flag) {
      output.unshift(cnt);
    } else {
      output.unshift(0);
    }
  }
  return output;
};

Time Out Error!

n ≤ 10^5 이므로, 10^4 * 10^4 = 1초 원칙에 의거 시간이 부족함.

Approach 2. O(n) : Stack (Monotonic Decreasing)

  1. 변수선언
    `idx_stack[]` : output이 정해지지 않은 temperatures의 index를 저장한 배열
    `ouput[]` : 결과값을 저장할 배열
  2. `temperatures []`순회
    각 temperature에 대해, `idx_stack[]` 의 뒤에서 부터, 자신보다 값이 작은게 오면, pop() 하여 output에 index 차이를 저장
var dailyTemperatures = function (temperatures) {
  const len = temperatures.length;

  const idx_stack = [];
  const output = new Array(len).fill(0);

  for (let i = 0; i < temperatures.length; i++) {
    const target = temperatures[i];
    while (
      idx_stack.length > 0 &&
      target > temperatures[idx_stack[idx_stack.length - 1]]
    ) {
      const idx = idx_stack.pop();
      output[idx] = i - idx;
    }
    idx_stack.push(i);
  }
  return output;
};

공유하고 싶었던 내용

  1. Stack을 다룰 때, index만을 다룰 수 있다는 유연함
  2. Monotonic 배열에서 while 문을 통한 배열 checking

+ Recent posts