[슬라이딩 윈도우] 문자열 교환 - 백준 1522번
풀이
EX) aabbaaabaaba , (n = 12)
a를 연속으로 한다 = b를 연속으로 한다.
최솟값 -> 그리디 or 모든 경우의 수를 구하고 그 중 최솟값
a의 갯수만큼 연속되어야 한다 -> 8개
aaaaaaaabbbb 이렇게 만들어야 한다.
모든 경우는 똑같다. 하지만 어떻게 묶어야 교체 횟수가 적은지 찾는다.
[aabbaaab]aaba -> 3개 교체
[abbaaaba]abaa -> 3개 교체
[bbaaabaa]baaa -> 3개 교체
[baaabaab]aaab -> 3개 교체
[aaabaaba]aabb -> 2개 교체
[aabaabaa]abba -> 2개 교체
[abaabaaa]bbaa -> 2개 교체
[baabaaab]baaa -> 3개 교체
[aabaaabb]aaab -> 3개 교체
[abaaabba]aaba -> 3개 교체
[baaabbaa]abaa -> 3개 교체
[aaabbaaa]baab -> 2개 교체
= 12번 (n)
-> 최소 2번 교체가 필요
- 8개씩 묶을 때 length를 넘어가는 경우 앞자리로 돌아와야 한다.
- length를 넘지 않는 경우
- substring (i, 8) : i번 index부터 8개씩 묶기 - 넘어가는 경우
- substr(i) : i번부터 ~ 끝까지
-
- substr(0, (i + aCount) - s.length()) : 앞에꺼를 붙이기
- (i + aCount) - s.length(): 남은 개수
- substr(0, (i + aCount) - s.length()) : 앞에꺼를 붙이기
- count함수를 통해 window 내 바꿔야 할 ‘b’의 개수를 체크 = 교체 회수
- min 함수로 최솟값 구하기
최적화
1. a의 개수 세기
기존 코드
int aCount = 0;
for(int i = 0; i < s.length(); i++) {
if(s[i] == 'a') aCount++;
}
새 코드
b의 개수를 샐 때 처럼 count
함수를 사용한다.
int aCount = count(s.begin(), s.end(), 'a');
어차피 같은 O(n) 이지만 코드 간결성이 좋다!
2. 원형 문자열 처리
기존 코드
if(i + aCount > s.length()) {
window = s.substr(i);
window += s.substr(0, (i + aCount) - s.length());
} else {
window = s.substr(i, aCount); // 0 ~ 8 -> 1 ~ 8
}
문자열을 수동으로 처리해야 하기 때문에 코드가 더 복잡하고 추가적인 복사 작업이 발생합니다.
새코드
s += s; // 문자열을 두 번 이어붙여 원형 처리
코드는 더 단순해지고 처리 속도가 빨라집니다.
ex) aabbaaabaaba
⇒ aabbaaabaaba-aabbaaabaaba
3. 슬라이딩 윈도우
기존코드
for(int i = 0; i < s.length(); i++) {
if(i + aCount > s.length()) {
window = s.substr(i);
window += s.substr(0, (i + aCount) - s.length());
} else {
window = s.substr(i, aCount); // 0 ~ 8 -> 1 ~ 8
}
int result = count(window.begin(), window.end(), 'b');
min_value = min(min_value, result);
}
매번 window
문자열을 슬라이싱하고 count
함수를 사용하여 ‘b’의 개수를 세고 있습니다. 이 과정은 매번 O(aCount)
시간 복잡도가 발생하며, 이 작업을 s.length()
만큼 반복합니다. 결과적으로 전체 시간 복잡도는 O(n * aCount)
새코드
// 첫 번째 윈도우에서 b의 개수 세기
int bCount = 0;
for(int i = 0; i < aCount; i++) {
if(s[i] == 'b') bCount++;
}
int min_value = bCount;
// 슬라이딩 윈도우로 최소 교환 횟수 찾기
for(int i = 1; i < s.length() / 2; i++) {
if(s[i - 1] == 'b') bCount--;
if(s[i + aCount - 1] == 'b') bCount++;
min_value = min(min_value, bCount);
}
첫 번째 윈도우에서 b의 개수를 구한다. ⇒ min_value를 구하기 위함
- aCount로 구하는 이유는 aCount == 슬라이딩 윈도우의 크기
i가 0번째의 경우는 구했으니 i = 1 부터 윈도우를 슬라이딩하기 시작한다.
s는 두개를 합쳤으니 s.length()의 절반까지 실행, i가 (s.length() / 2 + 1)이 된다면 원점으로 돌아온다.
- aabbaaabaaba[aabbaaab]aaba
[aabbaaab] 의 경우 b의 개수는 3개 → 슬라이딩 후 → [abbaaaba]
- b의 개수는 맨 앞이 ‘b’라면 window 안의 개수가 하나 줄어들고
if(s[i - 1] == 'b') bCount--;
- 맨 뒤에 새로 들어온 글자가 ‘b’라면 window 안의 b의 개수는 하나 늘어난다.
if(s[i + aCount - 1] == 'b') bCount++;
⇒ O(n) 시간 안에 처리
코드
https://github.com/KimGyeongLock/CodingTest/blob/main/1522.cpp