[백준] 10816 - 숫자 카드 2 (java)

zl존석동

·

2022. 1. 24. 18:17

 

백준 10816번 숫자 카드 2 자바 풀이


 

 

 

 

10816번: 숫자 카드 2

첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,

www.acmicpc.net

 

 

 

문제 간단 요약

 

 

숫자 중복이 가능한 숫자 카드들이 50만개 이하로 주어진다.

 

찾아야 할 숫자 목록도 50만개 이하로 주어진다.

 

주어진 숫자카드에 찾아야 할 숫자가 몇 개 있는지를 출력한다.

 

 

 

 

주의점

 

 

최악의 경우 숫자카드가 50만개 주어지고

 

찾아야 할 숫자 목록도 50만개가 주어질 수 있다.

 

기본적으로 숫자카드 50만개에 대해 반복을 돌면서 찾아야 할 숫자 하나하나에 대해 비교를 해야한다.

 

제한 시간이 1초이기 때문에 한 번의 비교에 2000번 이상 연산하면 안된다고 이해했다! 

 

 

 

 

나의 풀이 1 - Map

 

 

Map으로 풀면 더럽게 쉽다.

 

카드의 숫자Key로 하고 그 숫자의 개수Value 로 설정해 Map에 넣어주고 찾기만 하면 된다.

 

풀이는 생략!

 

 

 

나의 풀이 1 코드

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int len = Integer.parseInt(br.readLine());
        //존재하는 숫자카드의 숫자들을 맵에 저장
        Map<Integer, Integer> map = new HashMap<>();
        StringTokenizer st = new StringTokenizer(br.readLine(), " ");
        for (int i = 0; i < len; i++) {
            int key = Integer.parseInt(st.nextToken());
            map.put(key, map.getOrDefault(key, 0) + 1);
        }
        br.readLine();
        // 주어진 숫자 배열
        int[] resultArr = Arrays.stream(br.readLine().split(" ")).mapToInt(v->Integer.parseInt(v)).toArray();
        StringBuilder sb = new StringBuilder();
        // 주어진 숫자에 대한 숫자 카드를 몇 개 가지고 있나
        Arrays.stream(resultArr).forEach(v -> sb.append(map.getOrDefault(v,0)).append(" "));
        System.out.println(sb.toString());
    }
}

 

 

 

 

 

 

나의 풀이 2 - Binary Search

 

 

기존의 이분 탐색처럼 활용하려고 해도 문제에서는 해당되는 값에 대한 개수를 요구하고 있다.

 

 

1. 해당값을 이분 탐색으로 찾고 앞뒤로 같은 값의 수를 탐색하여 구한다?

 

 

- 효율적이어 보일 수 있지만 안된다.

 

- 50만개 입력이 주어지고 모든 숫자가 다 같다고 생각하면 시간 복잡도가 O(n) + O(log2(N)) 이 되지 않을까?

 

- 물론 모든 값이 같다면 이분탐색 자체가 의미가 없어질 것이기에 결국 탐색을 위한 시간 복잡도는 O(n) 이 될 것이다.

 

- 알고리즘 스터디 팀원의 풀이를 참고해 그렇게 풀어보았는데 70% 대에서 시간초과가 났다.

 

 

 

2. 해당 값으로 시작하는 인덱스해당 값보다 큰 것들 중 가장 작은 수의 인덱스를 이용

 

 

- 하나의 값에 대해 이분 탐색을 두번 시행하여 O(log2(N)) * 2 가 되지 않을까 생각한다.

 

 

다음과 같은 입력이 들어왔을 때 

 

InputArray
[6 , 3 , 3 , 10 , 10 , 10 , 3 , -10 , 7 , 3]

 

 

이분 탐색을 하기 때문에 당연히 정렬을 먼저 해주어야 한다. 

 

Sort(InputArray)
[-10 , 3 , 3 , 3 , 3 , 6 , 7 , 10 , 10 , 10]

 

 

 

여기서 만약 3의 개수를 찾아야 한다면

 

3이 시작하는 인덱스와  3보다 크지만 가장 작은 6에 대한 인덱스들을 각각 구한다.

 

두 인덱스의 차가 3이라는 값의 개수인 5 - 1 = 4 를 나타내며 문제의 요구에 대한 답이다.

 

 

 

과정 1. 해당 값으로 시작될 때의 인덱스 찾기

 

 

3의 개수를 구해야 한다고 생각해보자. 여기서 구해야 하는 위치는 -10 바로뒤에 있는 3의 위치이다.

 

 

초기 상태는 다음과 같을 것이다.

 

val(M) = 6 > 3 으로 M 부터 R 까지의 값은 필요없다.

 

 

R 을 M 으로 설정해주고 M 을 다시 구한다.

 

 

val(M) = 3 으로 찾아야 하는 값과 같다 하더라도 정답이 될 수는 없다.

 

현재의 관심사는 3으로 시작하는 부분의 위치이고 

 

val(M)이 3이고 그 뒤로는 3이있던 다른 값이 있던 알 바가 아니며 앞에 3이 더 있는지 없는지 아직 알 수 없다.

 

R 을 M 으로 땡겨와 준다.

 

 

 

아직까지는 val(M) 이 3이라 하더라도 우리는 알 수 있지만 컴퓨터는 처음으로 나온 3인지 알 수 없다.

 

R 을 M으로 다시 땡겨온다.

 

 

이제 val(M) < 3 으로 개수를 찾아야 하는 값보다 작아졌다.

 

이 때는 L 을 M + 1 로 설정해준다.

 

M 에 위치하는 값보다 찾아야 하는 값이 클 경우 M 위치를 포함해 앞에 있는 것들에 대한 정보는 필요없어지게 되기 때문이다.

 

 

R == L 이되는 시점. 즉 이분탐색을 끝 마쳤을 때 L 의 위치를 리턴하면 된다. => 1

 

 

 

 

과정 2. 해당 값보다 크면서도 가장 작은 값의 인덱스 구하기

 

 

여기서는 6에 대한 위치를 구해야 한다!

 

 

 

val(M) = 6 > 3 이다.

 

이미 3보다 큰 값이 M 에 위치하기 때문에 6보다 더 큰 값들에 대해서는 관심이 없어진다.

 

R 을 M 으로 설정!

 

 

val(M) = 3 = 찾아야 하는 값이다.

 

우리가 찾고 싶은 것은 찾아야하는 3보다 바로 큰 값의 위치이다.

 

앞에서 시작하는 위치를 찾는 로직과 유사하지만 다른 것은 M을 기준으로 어디에 관심이 있는지이다.

 

앞에서는 가장 맨 앞에 있는 3에 관심이 있었기에 val(M) == 3 이라 하더라도 R 을 땡겨왔지만

 

여기서는 val(M) > 3 인 것 중 최소값에 관심이 있기에 L 을 M 보다 1개 큰 쪽으로 땡겨와야 한다.

 

 

 

val(M) 이 여전히 3과 같다.

 

이때 L 을  M + 1의 위치로 땡겨주면 L == R == M == 6의 위치가 되고 

 

결과적으로 L 의 위치를 리턴하면 찾아야 하는 값보다 크지만 가장 작은 값의 위치를 얻을 수 있다. => 5

 

 

 

 

과정 3.  각 인덱스의 차 구하기

 

과정 2의 결과 - 과정 1의 결과 = 5 - 1 = 4 로 3이 4개 존재함을 구할 수 있다.

 

 

 

 

찾아야 하는 값이 없다면?

 

 

결국 위의 과정1과 과정2의 로직은 찾아야 하는 값이 중앙 인덱스에 나타났냐 아니냐의 차이만 있다.

 

따라서 요구하는 값이 존재하지 않으면 같은 로직을 돌고 같은 인덱스를 리턴하여 0 이라는 결과를 얻게 될 것이다.

 

 

 

 

 

나의 풀이 2 코드

 

 

Map 으로는 5분도 안걸려서 풀었는데

 

두 부분으로 나누어서 이분탐색을 한다는 것을 생각하는데 좀 오래걸려서 어려웠던 것 같다.

 

메소드 이름이 거지같긴 하지만 각각 위의 과정1과정2 라고 생각하면 될 것 같다!

 

import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;
// 숫자카드2 이분탐색으로 풀기
public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        int count1 = Integer.parseInt(br.readLine());
        StringTokenizer st1 = new StringTokenizer(br.readLine());

        int[] have = new int[count1];
        for (int i = 0; i < count1; i++) {
            have[i] = Integer.parseInt(st1.nextToken());
        }

        int count2 = Integer.parseInt(br.readLine());
        StringTokenizer st2 = new StringTokenizer(br.readLine());

        int[] correct = new int[count2];
        for (int i = 0; i < count2; i++) {
            correct[i] = Integer.parseInt(st2.nextToken());
        }

        Arrays.sort(have);
        int[] result = new int[count2];

        for (int i = 0; i < count2; i++) {
            int currentValue = correct[i];
            int countOfCurrentValue = 
            	getMinIndexGreaterThanCurrentValue(currentValue, have) 
                - getMaxIndexLessThanCurrentValue(currentValue, have);
            result[i] += countOfCurrentValue;
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < result.length; i++) {
            sb.append(result[i]).append(" ");
        }

        bw.write(sb.toString());
        bw.flush();
        bw.close();
    }

    public static int getMaxIndexLessThanCurrentValue(int curVal, int[] arr) {
        int left = 0;
        int right = arr.length;
        while (left < right) {
            int mid = (left + right) / 2;
            if (curVal <= arr[mid]) {
                right = mid;
                continue;
            }
            left = mid + 1;
        }

        return left;
    }

    public static int getMinIndexGreaterThanCurrentValue(int curVal, int[] arr) {
        int left = 0;
        int right = arr.length;
        while (left < right) {
            int mid = (left + right) / 2;
            if (curVal < arr[mid]) {
                right = mid;
                continue;
            }
            left = mid + 1;
        }
        return left;
    }
}