alorithm/Baekjoon

[JAVA/백준] 12891번 : DNA 비밀번호

Hannana. 2023. 5. 25. 02:47
반응형

[문제]
평소에 문자열을 가지고 노는 것을 좋아하는 민호는 DNA 문자열을 알게 되었다. DNA 문자열은 모든 문자열에 등장하는 문자가 {‘A’, ‘C’, ‘G’, ‘T’} 인 문자열을 말한다. 예를 들어 “ACKA”는 DNA 문자열이 아니지만 “ACCA”는 DNA 문자열이다. 이런 신비한 문자열에 완전히 매료된 민호는 임의의 DNA 문자열을 만들고 만들어진 DNA 문자열의 부분문자열을 비밀번호로 사용하기로 마음먹었다.

하지만 민호는 이러한 방법에는 큰 문제가 있다는 것을 발견했다. 임의의 DNA 문자열의 부분문자열을 뽑았을 때 “AAAA”와 같이 보안에 취약한 비밀번호가 만들어 질 수 있기 때문이다. 그래서 민호는 부분문자열에서 등장하는 문자의 개수가 특정 개수 이상이여야 비밀번호로 사용할 수 있다는 규칙을 만들었다.

임의의 DNA문자열이 “AAACCTGCCAA” 이고 민호가 뽑을 부분문자열의 길이를 4라고 하자. 그리고 부분문자열에 ‘A’ 는 1개 이상, ‘C’는 1개 이상, ‘G’는 1개 이상, ‘T’는 0개 이상이 등장해야 비밀번호로 사용할 수 있다고 하자. 이때 “ACCT” 는 ‘G’ 가 1 개 이상 등장해야 한다는 조건을 만족하지 못해 비밀번호로 사용하지 못한다. 하지만 “GCCA” 은 모든 조건을 만족하기 때문에 비밀번호로 사용할 수 있다.

민호가 만든 임의의 DNA 문자열과 비밀번호로 사용할 부분분자열의 길이, 그리고 {‘A’, ‘C’, ‘G’, ‘T’} 가 각각 몇번 이상 등장해야 비밀번호로 사용할 수 있는지 순서대로 주어졌을 때 민호가 만들 수 있는 비밀번호의 종류의 수를 구하는 프로그램을 작성하자. 단 부분문자열이 등장하는 위치가 다르다면 부분문자열이 같다고 하더라도 다른 문자열로 취급한다.



<Input>
첫 번째 줄에 민호가 임의로 만든 DNA 문자열 길이 |S|와 비밀번호로 사용할 부분문자열의 길이 |P| 가 주어진다. (1 ≤ |P| ≤ |S| ≤ 1,000,000)

두번 째 줄에는 민호가 임의로 만든 DNA 문자열이 주어진다.

세번 째 줄에는 부분문자열에 포함되어야 할 {‘A’, ‘C’, ‘G’, ‘T’} 의 최소 개수가 공백을 구분으로 주어진다. 각각의 수는 |S| 보다 작거나 같은 음이 아닌 정수이며 총 합은 |S| 보다 작거나 같음이 보장된다.


<Output>
첫 번째 줄에 민호가 만들 수 있는 비밀번호의 종류의 수를 출력해라.

 


<examples>
-입력1

9 8
CCTGGATTG
2 0 1 1


-출력1

0

 

-입력2

4 2
GATA
1 0 0 1

-출력2

2



[풀이]

package Baekjoon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class P12891_DNA비밀번호 {
    static int charArr[];//최소한 포함되어야 할 알파벳을 담은 배열
    static int myArr[];
    static int checkSecret; //---> 전역 변수로 선언
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int S = Integer.parseInt(st.nextToken());
        int P = Integer.parseInt(st.nextToken());
        int count = 0;
        char A[] = new char[S];//입력받을 배열
        charArr = new int[4];//꼭 들어가야 할 4자리 수를 담은 배열
        myArr = new int[4];//checkArr과 비교할, 내 슬라이딩윈도우 범위 안에 4자리 수 체크한 배열
        checkSecret=0;
        A = br.readLine().toCharArray();
        st = new StringTokenizer(br.readLine());
        for (int i=0;i<4;i++){
            charArr[i] = Integer.parseInt(st.nextToken());
            if(charArr[i]==0){
                checkSecret++; //받아야 되는 수가 0개라면 이미 충족했으므로 ++
            }
        }
        for (int i=0;i<P;i++){//(1) P 범위까지(최초)
            Add(A[i]);
        }

        if (checkSecret==4){
            count++;
        }
        for (int i=P;i<S;i++){//(2) P 이후부터(한칸 씩 이동)
            int j = i-P;
            Add(A[i]);
            Remove(A[j]);
            if (checkSecret==4){
                count++;
            }
        }
        System.out.println(count);
        br.close();
    }

    private static void Add(char c) {
        switch (c){
        case 'A':
            myArr[0]++;//카운트
            if(myArr[0] == charArr[0])//정한 숫자에 들어맞으면
                checkSecret++;//조건 충족한다고 ++
            break;
        case 'C':
            myArr[1]++;
            if(myArr[1] == charArr[1])
                checkSecret++;
            break;
        case 'G':
            myArr[2]++;
            if(myArr[2] == charArr[2])
                checkSecret++;
            break;
        case 'T':
            myArr[3]++;
            if(myArr[3] == charArr[3])
                checkSecret++;
            break;
        }
    }

    private static void Remove(char c) {
        switch (c){
        case 'A':
            if(myArr[0] == charArr[0])
                checkSecret--;
            myArr[0]--;
            break;
        case 'C':
            if(myArr[1] == charArr[1])
                checkSecret--;
            myArr[1]--;
            break;
        case 'G':
            if(myArr[2] == charArr[2])
                checkSecret--;
            myArr[2]--;
            break;
        case 'T':
            if(myArr[3] == charArr[3])
                checkSecret--;
            myArr[3]--;
            break;
        }
    }

}

 


 

슬라이딩 윈도우를 이용한 풀이이다.

특정 조건을 모두 충족하는지 확인하기 위해서 Add 함수를 별도로 생성하여 

switch-case문으로 체크해주고, 전부 조건을 충족 시에 최종 카운트에 +1 되도록 로직을 짜주었다.

그리고 범위를 조금씩 이동해가며 받은 문자열 내 문자들을 경우의 수를 모두 따져야하기 때문에

슬라이딩 윈도우 로직을 사용하여 배열 값을 업데이트해가며 체크해주었다. 그리고 범위가 바뀌면서 범위에 포함되지 않게 된 원소들은 체크에서 다시 빼는 Remove 함수를 별도로 구성하였다.

 

이렇게 입력 받은 문자열을 돌면서, 체크-카운트를 반복해주다가

순회가 끝났을 때 최종적으로 카운트가 몇번 되었는지 출력하여

주어진 조건으로 얼마나 많은 비밀번호를 만들 수 있는지 확인한다.

 

개인적으로 앱/웹에 들어가는 패스워드 보안 로직을 짤 때

입력값 검증 로직을 꼭 넣는데, 그게 생각이 났다.

이런 원리로 검증이 되기도 하는구나 하고 신기했다.

 

 

 

반응형

'alorithm > Baekjoon' 카테고리의 다른 글

[JAVA/백준] 11286번 : 절댓값 힙  (0) 2023.05.26
[JAVA/백준] 17298번 : 오큰수  (0) 2023.05.26
[JAVA/백준] 1253번 : 좋다(GOOD)  (0) 2023.05.25
[JAVA/백준] 1940번 : 주몽  (0) 2023.05.25
[JAVA/백준] 2018번 : 수들의 합 5  (0) 2023.05.25