코딩테스트 대비/백준 알고리즘

BOJ - 10816 숫자 카드 2

JellyApple 2024. 11. 17. 23:49

1. 문제 : https://www.acmicpc.net/problem/10816

2. 문제 유형 : 이진 탐색, 해시맵

3. 문제 티어: 실버4

4. 문제 풀이

처음 생각한 문제 풀이 방법은 배열을 처음부터 끝까지 순회하면서 같은 값이 있을 시 카운트를 추가하고 그 추가한 것을 새로운 결괏값 배열에 담는 과정을 반복하는 것이었다.

 

따라서 처음 코드는 아래와 같았다.

const fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");
let N = Number(input[0]);
let first_arr = input[1].split(" ").map(Number);
let M = Number(input[2]);
let second_arr = input[3].split(" ").map(Number);

// 비교 후 count 하는 함수 생성
function compare(arr, targets){
    // 결과를 저장할 배열
    let result = [];
    // arr length 만큼 반복문 돌림
    for(let target of targets) {
        let count = 0;
        for(let num of arr){
            if(num === target) count++;
        }
        result.push(count);
    }
    // 만약 arr에서 i랑 target이 같으면 count ++ 
    
    // result 배열에 push 후 다시 처음으로 돌아감
}
let output = compare(first_arr, second_arr);
console.log(output.join(" "));

 

= 시간 초과가 난 코드이며 이를 위해 새로운 방법을 찾던 중 해시맵(딕셔너리)을 사용하여 구현하는 방법을 참고 하였다.

 

1) first_arr의 숫자들을 한 번씩만 순회하여 숫자의 갯수를 저장한다.(O(N))
  -> 딕셔너리를 사용해 각 숫자의 등장 횟수를 기록
2) second_arr의 숫자를 확인할 때, 딕셔너리에서 즉시 값을 가져온다.( O(1))

 -> 따라서 전체 시간 복잡도는 O(N+M)

 

// 해시맵을 사용한 버전
const fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");
let N = Number(input[0]);
let first_arr = input[1].split(" ").map(Number);
let M = Number(input[2]);
let second_arr = input[3].split(" ").map(Number);

// 해시맵(딕셔너리 함수)
function countWithMap(arr, targets) {
    let countMap = new Map(); // 맵 생성
    for(let num of arr){
        // 첫 번째 배열 순회 후 num과 그에 따른 value 값 저장 만약 value가 없으면 0 + 1
        countMap.set(num, (countMap.get(num) || 0 ) + 1);
    }
    // 결과를 담을 result 배열
    let result = [];
    // 두 번째 배열 순회하면서 첫 번째 배열 순회 시 저장했던 갯수 가져옴
    for(let target of targets) {
        result.push(countMap.get(target) || 0);
     }
     return result;
 }
 
let output = countWithMap(first_arr, second_arr);
console.log(output.join(" "));

 

 

* 이진 탐색 사용한 풀이

추가로 이진 탐색을 사용한 풀이도 있었다.

함수에 관한 설명은 아래와 같다.

1) Lower Bound 함수
 - 배열에서 특정 값 이상인 첫 위치를 찾는 함수.

 - 예를 들어 배열 [1,2,2,3,4] 에서 target = 2인 경우
  lowerBount(arr,2) 는 1(인덱스)을 반환함 (2가 처음 등장하는 위치)

 

2) Upper Bound 함수
 - 배열에서 특정 값보다 큰 위치를 찾는 함수

 - 예를 들어 배열 [1,2,2,3,4]에서 target = 2인 경우
 upperBound(arr,2)는 3(인덱스)를 반환함( 3이 처음 등장하는 위치)

 

3) 등장 횟수 계산

 - 특정 값의 개수 = upperBound - lowerBound로 계산할 수 있다.

 - 예를 들어 배열 [1,2,2,3,4]에서 : 
          target = 2 일 때 upperBound (2) = 3 , lowerBound(2) = 1 

          => 따라서 2의 갯수는 3- 1 = 2

 

4) 정답 코드

const fs = require("fs");
let input = fs.readFileSync("/dev/stdin").toString().split("\n");
let n = Number(input[0]);
let arr1 = input[1].split(" ").map(Number);
let m = Number(input[2]);
let arr2 = input[3].split(" ").map(Number);
let result = [];

// arr1 오름차순 정렬
arr1.sort((a,b) => a-b);


// lowerBound 함수 (특정 값 이상이 처음 등장하는 위치)
function lowerBound(arr,target) {
   let start = 0;
   let end = arr.length;
   while(start< end) {
       let mid = Math.floor((start+end)/2);
       if(arr[mid]>=target){
           end = mid;
       } else {
           start = mid + 1;
        }
     }
     return start;
}

// upperBound 함수 (특정 값보다 큰 값이 처음 등장하는 위치 반환)
function upperBound(arr,target){
   let start = 0;
   let end = arr.length;
   while(start<end){
       let mid = Math.floor((start+ end) / 2);
       if(arr[mid]> target){
           end = mid;
       } else {
           start = mid + 1;
       }
   }
   return end;
}

// m 만큼 순회하면서 lower , upper 구해서 빼준 후 result 배열에 넣어줌
for(let i=0;i<m;i++){
    let lower = lowerBound(arr1, arr2[i]);
    let upper = upperBound(arr1, arr2[i]);
    result.push(upper-lower);
}
console.log(result.join(" "));

'코딩테스트 대비 > 백준 알고리즘' 카테고리의 다른 글

BOJ - 15649 N과 M(1)  (0) 2024.11.20
BOJ - 18353 병사 배치하기  (0) 2024.11.20
BOJ - 1654 랜선 자르기  (0) 2024.11.17
BOJ - 2805 나무 자르기  (0) 2024.11.17
BOJ - 2512 예산  (0) 2024.10.15