https://www.acmicpc.net/problem/2531

 

2531번: 회전 초밥

첫 번째 줄에는 회전 초밥 벨트에 놓인 접시의 수 N, 초밥의 가짓수 d, 연속해서 먹는 접시의 수 k, 쿠폰 번호 c가 각각 하나의 빈 칸을 사이에 두고 주어진다. 단, 2 ≤ N ≤ 30,000, 2 ≤ d ≤ 3,000, 2 ≤

www.acmicpc.net

코드설명

슬라이딩 윈도우(Sliding Window) + 투포인터(Two Pointer) + 원형(Circular) + HashMap(해시맵) 활용하는 문제입니다.

 

1. 일반적인 투포인터에 단순이 직선을 처리하는것이 아닌, 원형으로 돌면서 적절한 구간에서 종료조건을 넣어야합니다.

여기서 적절한 구간은,

- start는 똑같이 start < N 으로 처리하지만,

- end같은경우 end < N + K로 처리하여 K 접시를 먹으면서 end % N을 처리하여 원형조건을 해결합니다.

 

헷갈리는점은 원형큐라고 모든 구간을 원형으로 다시 처리하는것이 아닌,

start < N 으로 처리하여 , start 는 N까지 처리하고, End를 처리함으로써 중복된 구간은 다시 처리하지 않습니다.

 

2. HashMap을 활용하여 먹은 음식의 번호를 가지고 있습니다.

- 처음에 풀어낼시 entry.getValue() 처리를 하여서 각 음식의 종류가 아닌 각 음식의 개수를 더하여 틀렸었습니다.

- 아래 주석의 예시코드를 보고 해당 오류를 찾아내어 기록해보았습니다.

//먹는 그릇의 종류를 확인
for(Map.Entry<Integer, Integer> entry : hashmap.entrySet()) {
    //그릇의 value가 0이 아닐경우 해당 조건의 음식을 한번이라도 먹었으므로 1개처리. 
    // 이전에 잘못하고 entry.getValue를 더해서 해당 문제 오류 복기함
    // foodKind += entry.getValue();
    // 예시 코드
    //2 2 2 2
    //1
    //1
    if(entry.getValue() != 0) {
    	
        foodKind += 1;	
    }
}

 

3. HashSet은 사용불가하고, HashMap을 사용해야 빠르게 처리할 수 있습니다.

이유는, 중복된 음식들이 존재하기 때문입니다. HashSet을 사용한다면 중복된 음식이 있을경우, 슬라이딩 되면서 개수가 존재하지 않기에 해당 Window(창)에 해당 재료가 존재함에도 삭제되겠지요.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;


public class Main {
	public static int N, d, k, c;
	public static int[] arr;
	public static int answer = 0;
	public static HashMap<Integer, Integer> hashmap = new HashMap<>();
    public static void main(String[] args) throws IOException{
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	
    	StringTokenizer st = new StringTokenizer(br.readLine());
    	N = Integer.parseInt(st.nextToken());
    	d = Integer.parseInt(st.nextToken());
    	k = Integer.parseInt(st.nextToken());
    	c = Integer.parseInt(st.nextToken());
    	
    	arr = new int[N];
    	for(int i=0;i<N;i++) {
    		st = new StringTokenizer(br.readLine());
    		arr[i] = Integer.parseInt(st.nextToken());
    	}
    	
    	
    	slidingWindowWithTwoPoitner();
    	
		System.out.println(answer);
    }
    
    public static void slidingWindowWithTwoPoitner() {
    	int start = 0;
    	int end = 0;
    	int foodKind = 0;
    	
    	int endCircularIdx = 0;
    	
    	while(start < N) {
    		foodKind = 0;
    		
    		endCircularIdx = end % N;
    		
    		//슬라이딩 윈도우
    		while(start + k > end && end < N + k) {
    			endCircularIdx = end % N;
    			hashmap.put(arr[endCircularIdx], hashmap.getOrDefault(arr[endCircularIdx], 0) + 1);
    			end += 1;
    		}
    		
        	//먹는 그릇의 종류를 확인
        	for(Map.Entry<Integer, Integer> entry : hashmap.entrySet()) {
        		//그릇의 value가 0이 아닐경우 해당 조건의 음식을 한번이라도 먹었으므로 1개처리. 
        		// 처음 풀시 잘못하고 entry.getValue를 더해서 해당 문제 오류발견         		
        		if(entry.getValue() != 0) {
        			foodKind += 1;	
        		}
        	}
        	
        	//쿠폰번호가 존재하는지 확인
        	if(hashmap.get(c) == null || hashmap.get(c) == 0) {
        		foodKind += 1;
        	}
        	answer = Math.max(answer,  foodKind);
        	//초밥의 가짓수 한개 뺴줌
        	hashmap.put(arr[start], hashmap.get(arr[start]) - 1);
        	
        	//start 이동처리
        	start += 1;
    	}
    }
}

 

정답코드2입니다.

이 코드에서 가장 중요점은, 이 부분입니다.

for(int i=k-1; i<N + k - 1; i++) {

처음에는 N까지하는것 아니야? 라고 생각했습니다만, 슬라이딩 윈도우 기법과 원형 시프트배열을 곰곰히 생각해보면 이유를 알 수 있습니다.

이유는, 마지막 위치 N-1번째 에서 다시 0, 1, 2 ..의 초밥 구간도 고려해야하기 때문입니다.

사실상 여기서 i는 새로운 초밥을 구매하고, 이전 (i-(k-1) 번쨰 초밥은 구간에서 제거하는 슬라이딩 윈도우기법을 사용하므로 i는 <N이 아닌, < (N + K - 1)로 처리해야 모든 구간을 탐색한다는 점을 이해할 수 있습니다. 여기서 -1을 하는 이유는 arr[0]이 두번 처리되지 않게 하기 위함입니다.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
 
public class Main {
	static int N, M, S, P, A, B, X, L, R, C, n, k, m, l, K, D, T, d, c;
	static int answer = 0;
	static int[] arr = new int[30001];
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		N = Integer.parseInt(st.nextToken());
		d = Integer.parseInt(st.nextToken());
		k = Integer.parseInt(st.nextToken());
		c = Integer.parseInt(st.nextToken());
		
		for(int i=0;i<N; i++) {
			st = new StringTokenizer(br.readLine());
			arr[i] = Integer.parseInt(st.nextToken());
		}
		
		answer = slidingWindow();
		
		System.out.println(answer);
	}

	static int slidingWindow() {
		HashMap<Integer, Integer> hashmap = new HashMap<>();
		hashmap.put(c, 1);
		for(int i=0; i<k-1; i++) {
			hashmap.put(arr[i%N], hashmap.getOrDefault(arr[i%N], 0)+1);
		}
		
		for(int i=k-1; i<N + k - 1; i++) {
			hashmap.put(arr[i%N], hashmap.getOrDefault(arr[i%N], 0)+1);
			hashmap.put(c, hashmap.getOrDefault(c, 0) + 1);
			answer = Math.max(answer, hashmap.size());
			hashmap.put(c,  hashmap.get(c) - 1);
			if(hashmap.get(c) == 0) hashmap.remove(c);
			
			if(hashmap.get(arr[(i-(k-1))%N]) > 1) {
				hashmap.put(arr[(i-(k-1))%N], hashmap.get(arr[(i-(k-1))%N]) - 1);
			}else if(hashmap.get(arr[(i-(k-1))%N]) == 1 ) {
				hashmap.remove(arr[(i-(k-1))%N]);
			}
			
		}
		
		return answer;
	}
}

+ Recent posts