호우동의 개발일지

Today :

article thumbnail

문제 이해 단계

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

 

16472번: 고냥이

고양이는 너무 귀엽다. 사람들은 고양이를 너무 귀여워했고, 결국 고양이와 더욱 가까워지고 싶어 고양이와의 소통을 위한 고양이 말 번역기를 발명하기로 했다. 이 번역기는 사람의 언어를 고

www.acmicpc.net

소문자로 이루어진 알파벳 문자열이 주어진다.

또한 입력으로 N이 주어지는데,  서로 다른 종류의 알파벳을 인식할 수 있는 개수이다.

구해야 하는 것은 N개의 서로 다른 종류의 알파벳을 인식할 수 있을 때,
주어진 문자열에서 인식할 수 있는 연속된 문자열 중 최대 길이를 구하는 문제

 

 


문제 접근 단계

늘 그렇듯, 제한사항부터 알아보자.입력 N은 최대 26까지,
문자열의 길이는 최대 100,000까지 가능하다.

이중 반복으로 100,000 * 26을 모두 살펴보아도 시간초과가 나지는 않는다.

해당 문자열의 최대 길이를 구한다고 했을 때,
N개의 서로 다른 종류의 알파벳을
1.. 2.. K.. N-1.. N
이렇게 1부터 올려보자.

N = 1 일 때, 인덱스 0부터 탐색해 보자.

왼쪽 위부터 오른쪽 아래순으로 읽으면 된다
왼쪽 위부터 오른쪽 아래순으로 읽으면 된다.

N = 1 일 때, 인덱스 0부터 나올 수 있는 일부분을 그려봤다.
보다시피 길이가 2일 때가 최대이다.

N = 2 일 때를 보면

N = 2 일 때의 각 인덱스 문자열 길이
N = 2 일 때의 각 인덱스의 문자열 길이

이렇게 cacc가 최대길이 4가 된다. 

그런데 여기서 그치지 않고 N = 3 이 된다고 생각해 보자.

N = 3 일 때의 각 인덱스 문자열 길이
N = 3 일때의 각 인덱스의 문자열 길이

이렇게 될 것이다.

문자열의 최댓값을 구할 때 왼쪽에서 오른쪽으로만 무조건 이동한다.

이 점을 보았을 때, 이는 왼쪽에서 오른쪽을 탐색하면서
중복을 확인하는 방법을 사용하면 될 것 같다.

이때 생각난 것이 투포인터였다.
정답 문자열의 시작점과 마지막점을 두 개의 포인터로 잡는다.

시작점을 옮겨가며, 다른 알파벳을 인식할 수 있는 기회 N을 가진 채로 끝 포인트를 옮겨간다.

이미 인식할 수 있는 알파벳이면 인식 기회 N을 유지한 채 끝 포인트를 옮긴다.
새로운 알파벳이면 인식 기회를 N-1 하고 끝 포인트를 옮긴다.

해당 과정을 반복하다가 N이 0이 되면 반복을 종료하고,
시작위치와 끝위치 사이 길이를 구한다.

이 과정을 모든 문자에 위치를 시작점으로 하여 실행하여 최댓값을 구한다.

자세한 것은 코드를 통해 설명하겠다.


문제 구현 단계


#include <iostream>
#include <string>
#include <cstring>

using namespace std;

int main(){
    cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
    int N;
    cin >> N;
    string str;
    cin >> str;

    int ans = 0;
    // 시작 위치를 인덱스 0부터 끝까지 하나씩 올림
    for(int i = 0; i < str.length(); i++){
        int word[28]; // 해당 알파벳의 개수
        memset(word,0,sizeof(word)); // 시작 위치를 옮길때마다 초기화
        int right = i; // 끝 위치(right)를 왼쪽 위치와 동일하게 초기화
        int cnt = 0; // 현재 인식한 다른 알파벳의 개수

        // 끝 위치가 문자열의 길이를 넘거나, 인식할 수 있는 알파벳의 갯수를 넘었을 때
        while(right < str.length() && cnt <= N){
            int num = str[right]-'a'; // 개수를 위해 'a'를 뻬줌
            word[num]++;
            if(word[num] == 1) cnt++; // 새롭게 생긴 알파벳이란 뜻
            if(cnt > N) break;
            ans = max(ans,right-i+1); // 개수
            right++; // 끝 위치를 옮김
        }
        if(right == str.length()) break; // 끝 위치가 마지막까지 온 것이라면 종료
    }
    cout << ans;
}

투포인터를 구현한 함수이다.

이중반복문의 바깥은 시작위치를 나타내는데, 0부터 문자열의 마지막 인덱스까지 간다.

word 배열은 안에 알파벳의 개수를 저장한다.
시작위치를 바꿀 때마다 초기화해줘야 한다.

내부에 있는 반복문은 끝 위치를 바꾸는 것인데, 매번 +1씩 뒤로 옮겨준다.
이때 새로 생긴 알파벳인지 아닌지에 따라 cnt를 더해주는지 말지 결정해 준다.

이후 반환할 때는 끝위치 - 시작위치 +1을 해줘 길이를 반환한다.

여기서 if(right == str.length()) break;
으로 끝위치가 문자열의 끝에 다다랐을 때 아예 종료한다.

그 이유는 해당 상태가 됐다는 것은 cnt <= N인 상태에서 온 것이기에,
더 이상 볼 필요도 없이 여태까지 했던 것에 최소이기 때문이다.

여기까지가 설명의 끝이다.

 

 


시행착오

처음에는 DP인 줄 알았다.
확신했다. 근데 아무리 풀어봐도 안되더라. 실패했다.

이거 두포인터라던데 와.. 보니까 당연히 투포인터더라.

화나서 시간복잡도 찾아보니까
O(N).. 투포인터가 좋긴 좋네..근데 생각보다 구현이 어려웠다..
투 포인터도 많이 풀어보긴 해야지..

생각보다 많이 쓰이네

 

생활비..