문제 개요
정수 N과 K가 주어집니다. 0과 1로만 구성된 길이 N의 이진 문자열 중에서, 인접한 1이 정확히 K번 나타나는 문자열의 개수를 구하는 것이 목표입니다.
예를 들어 N=3, K=2라면, 길이 3의 모든 이진 문자열 중에서 인접한 1이 두 번 나타나는 문자열의 개수를 세어야 합니다.
- 111 — 인접한 1이 두 번 나타납니다 (K번).
- 011, 110 — 인접한 1이 한 번만 나타납니다.
해결 아이디어: 다이나믹 프로그래밍(DP)
이 문제는 이전에 계산한 결과값을 저장해 두는 방식, 즉 다이나믹 프로그래밍으로 효율적으로 해결할 수 있습니다.
3차원 배열 count[x][y][z]를 사용합니다. 여기서 x는 문자열의 길이(N), y는 인접한 1의 개수(K), z는 문자열의 마지막 자릿수(0 또는 1)를 의미합니다.
기저 조건
N=1일 때 가능한 문자열은 "0"과 "1"뿐이며, 이 경우 인접한 1의 개수는 항상 0입니다. 따라서 어떤 K에 대해서도 N=1이면 개수는 0입니다.
count[1][K][0] = count[1][K][1] = 0
마지막 자릿수가 0인 경우
길이 N-1이면서 인접한 1이 K개인 모든 문자열 뒤에 0을 붙이면 길이 N의 새로운 문자열이 됩니다. 끝에 0을 추가해도 인접한 1의 개수는 변하지 않습니다.
count[N][K][0] = count[N-1][K][0] + count[N-1][K][1]
마지막 자릿수가 1인 경우
두 가지 경우를 고려해야 합니다.
- 길이 N-1이고 0으로 끝나며 인접한 1이 K개인 문자열 + 마지막 1 →
count[N-1][K][0] - 길이 N-1이고 1로 끝나며 인접한 1이 K-1개인 문자열 + 마지막 1 →
count[N-1][K-1][1]
count[N][K][1] = count[N-1][K][0] + count[N-1][K-1][1]
따라서 조건을 만족하는 전체 문자열의 개수는 다음과 같습니다.
정답 = count[N][K][0] + count[N][K][1]
입력 / 출력 예제
예제 1
N=4, K=2
출력:
Count of strings: 2
설명 — 길이 4의 문자열 중에서 인접한 1이 정확히 두 번 나타나는 것은 1110과 0111뿐입니다.
1110 - "11 10", 그리고 "1 11 0" (어느 위치의 인접한 1이 세어지는지 보여주기 위해 구분) 0111 - "0 11 1", 그리고 "01 11". K=2, 인접한 1의 등장 횟수 = 2회
예제 2
N=3, K=1
출력:
Count of strings: 2
설명 — 길이 3의 문자열 중에서 인접한 1이 한 번만 나타나는 것은 110과 011입니다. 참고로 111에서는 인접한 1이 두 번 나타나므로 제외됩니다.
알고리즘 접근 방법
- 정수 N과 K는 각각 문자열의 길이와 인접한 1이 나타나야 하는 횟수를 저장합니다.
- 함수
stringcount(int n, int k)는 n과 k를 매개변수로 받아 인접한 1이 K번 나타나는 문자열의 개수를 반환합니다. - 배열
count[i][j][0/1]은 길이 i이고 인접한 1이 j개이며 0 또는 1로 끝나는 문자열의 개수를 저장합니다. - 초기 조건은
count[1][0][0] = 1;,count[1][0][1] = 1;입니다. - 길이 2(i=2)부터 n까지 반복하면서, 각 j(0 ≤ j ≤ k)에 대해 이전 결과를 바탕으로
count[i][j][0]과count[i][j][1]을 갱신합니다. - j-1 ≥ 0이면(인접한 1의 개수가 1보다 클 때) 1로 끝나는 문자열의 개수를
count[i][j][1] += count[i-1][j-1][1]로 갱신합니다. - 마지막에
count[n][k][0]과count[n][k][1]을 더하여 결과를 저장합니다. - 계산된 결과값을 원하는 개수로 반환합니다.
C++ 구현 코드
#include <bits/stdc++.h>
using namespace std;
int stringcount(int n, int k){
// 길이=n이고 인접한 1의 개수=k인 문자열의 개수
int count[n + 1][k + 1][2]={0};
// count[n][k][0] -- 길이=n, 인접한 1의 개수=k, 마지막 문자가 0인 문자열
// count[n][k][1] -- 길이=n, 인접한 1의 개수=k, 마지막 문자가 1인 문자열
// n = 1이고 k = 0인 초기 조건
count[1][0][0] = 1;
count[1][0][1] = 1;
for (int i = 2; i <= n; i++) {
// 인접한 1의 개수는 i-1을 초과할 수 없음
for (int j = 0; j <= k; j++) {
count[i][j][0] = count[i - 1][j][0] + count[i - 1][j][1];
count[i][j][1] = count[i - 1][j][0];
if (j - 1 >= 0)
count[i][j][1] = count[i][j][1] + count[i - 1][j - 1][1];
}
}
int result = count[n][k][0] + count[n][k][1];
return result;
}
int main(){
int N = 6, K = 3;
cout << "Strings of length 6 and 3 adjacent 1's in each :" << stringcount(N,K);
return 0;
}
실행 결과
Strings of length 6 and 3 adjacent 1's in each :7
마무리
이처럼 3차원 DP 배열을 활용하면 길이 N의 이진 문자열 중에서 인접한 1이 정확히 K번 나타나는 문자열의 개수를 O(N×K) 시간 복잡도로 효율적으로 계산할 수 있습니다. 완전 탐색으로는 2^N개의 문자열을 일일이 확인해야 하지만, DP를 사용하면 이전 상태의 결과를 재활용하므로 N이 커져도 빠르게 답을 구할 수 있다는 점이 이 기법의 가장 큰 장점입니다.