
변수의 역할을 나누면 헷갈리지 않는다.
| 변수 | 역할 |
|---|---|
| k | 멈추는 기준 |
| maxPts | 카드 한 장의 최댓값 |
| n | 버스트 기준선 (게임 진행에는 관여하지 않고 결과 판정에만 쓰임) |
n=21, k=17, maxPts=10 이면 블랙잭 딜러 규칙과 같다.
dp[i] = 게임 도중 점수가 정확히 i 가 될 확률
점수 i 에 오려면 직전 점수 j 에서 카드 i - j 를 뽑아야 한다.
i - maxPts ~ i - 1 범위 (카드가 1 ~ maxPts 이므로)1 / maxPtsdp[i] = (조건을 만족하는 dp[j] 의 합) / maxPts
멈추는 칸은 k ~ n 이므로 답 = dp[k] + ... + dp[n] 이다.
매번 합을 새로 구하면 O(n · maxPts) 라서, 합을 슬라이딩 윈도우(windowSum) 로 유지한다.
if (k === 0 || n >= k - 1 + maxPts) return 1;
k === 0: 한 장도 뽑지 않고 0점에서 끝난다. 반복문은 "0점에서 반드시 한 장 뽑는다"를 전제로 하므로 따로 걸러야 한다.n >= k - 1 + maxPts: 최대로 나올 수 있는 점수는 k - 1(뽑기 직전 최대)에 maxPts(가장 큰 카드)를 더한 값이다. 최악의 경우에도 n 이하라면 버스트할 수 없다.function new21Game(n: number, k: number, maxPts: number): number {
if (k === 0 || n >= k - 1 + maxPts) return 1;
const dp = Array(n + 1).fill(0);
dp[0] = 1;
let windowSum = 1;
let probability = 0;
for (let i = 1; i <= n; i++) {
dp[i] = windowSum / maxPts;
if (i < k) windowSum += dp[i];
else probability += dp[i];
if (i - maxPts >= 0 && i - maxPts < k) windowSum -= dp[i - maxPts];
}
return probability;
};
i < k: 아직 더 뽑으므로 다음 칸의 출발점으로 윈도우에 넣는다.i >= k: 여기서 멈추므로 윈도우에 넣지 않고 결과에 더한다.< k 인 칸만 뺀다.n=1, k=0, maxPts=3 에서 빠른 반환이 없으면 0점에서 카드를 뽑는 것처럼 계산돼 1/3 이 나온다. 정답은 1이다.