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

문제

먹을 수 있는 초밥의 가짓수의 최댓값을 구해야 하는 문제이다. 여기서 무조건 초밥은 연속으로 가져가야 하기 때문에 k개의 연속된 요소들이 옮겨가면서 먹을 수 있는 가짓수를 구하면 되는 슬라이딩 윈도우 문제이다.

하나의 연속된 수열을 가지면서 계속 옆으로 옮겨가면서 구하면 될 것 같았지만

const INPUT_FILE = process.platform === "linux" ? "/dev/stdin" : "./inputs.txt";
const inputs = require("fs")
  .readFileSync(INPUT_FILE)
  .toString()
  .trim()
  .split("\n");
const [N, d, k, c] = inputs[0].split(" ").map(Number);
const sushi = inputs.slice(1).map(Number);
 
let max = 0;
for (let i = 0; i < N; i++) {
  const ateSushi = {};
  let cnt = 0;
  for (let j = 0; j < k; j++) {
    const sushiNum = sushi[(i + j) % N];
    if (!ateSushi[sushiNum]) {
      ateSushi[sushiNum] = 1;
      cnt++;
    }
  }
  if (!ateSushi[c]) cnt++;
  max = Math.max(max, cnt);
}
console.log(max);
 

이런 식으로 무식하게 도는 방법은 당연히 먹히지 않는다. 여기서 머리를 한번 더 굴려야 할 필요성이 있다. 슬라이딩 윈도우가 시간초과가 뜬다면 이를 더 효율적으로 사용할 수 있는 방법을 찾아야 한다.

슬라이딩 윈도우를 최적화하는 방법은 이전 값을 최대한 기억하면서 옮겨가는것이다. 따라서 맨 처음 k크기의 윈도우를 미리 만들어 놓고, 오른쪽으로 옮겨가면서 기존의 값에서 빼지는 초밥과 더해지는 초밥에 대해서만 계산을 하면 된다.

const INPUT_FILE = process.platform === "linux" ? "/dev/stdin" : "./inputs.txt";
const inputs = require("fs")
  .readFileSync(INPUT_FILE)
  .toString()
  .trim()
  .split("\n");
const [N, d, k, c] = inputs[0].split(" ").map(Number);
const sushi = inputs.slice(1).map(Number);
 
let max = 0;
let current = new Array(d + 1).fill(0);
let count = 0;
 
for (let i = 0; i < k; i++) {
  if (current[sushi[i]] === 0) count++;
  current[sushi[i]]++;
}
 
max = count;
 
for (let i = 0; i < N; i++) {
  if (max <= count) {
    if (current[c] === 0) max = count + 1;
    else max = count;
  }
 
  current[sushi[i]]--;
  if (current[sushi[i]] === 0) count--;
 
  const next = sushi[(i + k) % N];
  if (current[next] === 0) count++;
  current[next]++;
}
 
console.log(max);
 

이런 식으로 기존의 값은 최대한 기억하면서 이전 값을 빼고, 새로 들어올 값을 더한다. 여기서 next가 sushi[(i + k) % N]인 이유는 배열을 초과하더라도 회전초밥이기 때문에 다시 처음 요소로 가서 계산을 해야 하기 때문이다. 그렇게 추가되는 것과 빠지는 것을 계산함과 동시에 최댓값을 갱신시켜야 한다. 이 때, 이전에 먹었던 초밥 중에 쿠폰에서 제공하는 초밥이 없었다면 1개를 더해줘야 한다.

메모리가 참…많이 쓰인다