나를 기록하다
article thumbnail
반응형

1. 4. 슬라이딩 윈도우

  • 슬라이딩 윈도우 알고리즘은 2개의 포인터로 범위를 지정한 다음 범위(window)를 유지한 채로 이동(sliding)하며 문제를 해결한다. 투 포인터 알고리즘과 매우 비슷하다.

1.1. [❌][백준 12891] DNA 비밀번호

12891번: DNA 비밀번호

백준(baekjoon) 12891 DNA 비밀번호

1.1.1. 1단계 - 문제 분석하기

  • P와 S의 길이가 1,000,000으로 매우 큼 → O(n)의 시간 복잡도 알고리즘으로 문제를 해결해야 함.
  • 부분 문자열의 길이가 P → 슬라이딩 윈도우의 개념을 이용하면 문제를 쉽게 해결 가능

슬라이딩 윈도우(sliding window)

  • 길이가 P인 윈도우를 지정하여 배열 S의 시작점에 놓는다.
  • 윈도우를 오른쪽으로 밀면서 윈도우에 잡힌 값들이 조건에 맞는지 탐색
  • 배열 S의 길이만큼만 탐색을 진행하면 되므로 O(n)의 시간 복잡도로 문제 해결 가능

1.1.2. 2단계 - 손으로 풀어 보기

  • S 배열과 비밀번호 체크 배열을 저장

S배열과 비밀번호 체크 배열

  • 윈도우에 포함된 문자로 현재 상태 배열을 만든다. 그런 다음 현재 상태 배열과 비밀번호 체크 배열을 비교하여 유효 비밀번호인지 판단

유효한 비밀번호 판단 방법

  • 윈도우를 한 칸씩 이동하며 현재 상태 배열을 업데이트
  • 비밀번호 체크 배열과 비교하여 비밀번호 유효성 판단
  • 현재 상태 배열을 업데이트할 때는 빠지는 문자열, 신규 문자열만 보고 업데이트하는 방식으로 진행

문자열 업데이트 로직

  • 위 그림의 경우 한 칸 이동하여 C가 빠지고, G가 추가되어 현재 상태 배열을 1, 2, 2, 3에서 1, 1, 3, 3으로 업데이트
  • 고민해야할 사항
    • 실제 문자열과 관련된 배열 처리를 어떻게 할 것인지
    • 비밀번호 유효성 검사를 보다 바르게 할 수 있는 방법이 있는지

1.1.3. 3단계 - 슈도코드 작성하기

<code />
// 데이터 저장 S(문자열 크기) P(부분 문자열 크기) checkArr(비밀번호 체크 배열) // 변수 선언 myArr(현재 상태 배열) checkSecret(몇 개의 문자와 관련된 개수를 충족했는지 판단하는 변수) P 범위(0 ~ P - 1)만큼 S 배열에 적용하고, 유효한 비밀번호인지 판단 for(i를 P에서 S까지 반복) { j 선언(i - P) // 이 부분은 함수로 별도 구현 한 칸씩 이동하면서 제거되는 문자열과 새로 들어오는 문자열을 처리 유효한 비밀번호인지(checkSecret == 4) 판단해 결괏값에 업데이트 } 결괏값 출력 별도 함수 Add(문자 더하기 함수) { 새로 들어온 문자를 myArr에 업데이트하거나 checkSecret 값 변경 } Remove(문자 빼기 함수) { 제거되는 문자를 myArr에 업데이트하거나 checkSecret 값 변경 }
  • 핵심은 유효한 비밀번호 검사 시에 기존 검사 결과에 새로 들어온 문자열, 제거되는 문자열만 반영하여 확인하는 것

1.1.4. 4단계 - 코드 구현하기

<code />
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; import java.util.StringTokenizer; public class Main { static int myArr[]; static int checkArr[]; static int checkSecret; public static void main(String[] args) throws NumberFormatException, IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int s = Integer.parseInt(st.nextToken()); // 임의로 만든 DNA 문자열 길이 int p = Integer.parseInt(st.nextToken()); // 비밀번호로 사용할 부분 문자열 길이 int result = 0; checkArr = new int[4]; // A,C,G,T의 최소 개수 myArr = new int[4]; // 현재 부분 문자열 내의 A, C, G, T의 최소 개수 char A[] = new char[s]; checkSecret = 0; // 만족한 조건의 수 A = br.readLine() .toCharArray(); st = new StringTokenizer(br.readLine()); for (int i = 0 ; i < 4 ; i++) { checkArr[i] = Integer.parseInt(st.nextToken()); if (checkArr[i] == 0) { checkSecret++; } } for (int i = 0 ; i < p ; i++) { // 부분 문자열 처음 받을 때 세팅 Add(A[i]); } if (checkSecret == 4) { result++; } // 슬라이딩 윈도우 for (int right = p ; right < s ; right++) { int left = right - p; Add(A[right]); Remove(A[left]); if(checkSecret == 4) { result++; } } System.out.println(result); br.close(); } private static void Remove(char c) { switch (c) { case 'A': if (myArr[0] == checkArr[0]) checkSecret--; myArr[0]--; break; // >=가 안되는 이유: 만족하는 순간에 딱 한번만 checkSecret을 더함 // >=을 쓰면 계속 더해지기 때문에 안됨. case 'C': if (myArr[1] == checkArr[1]) checkSecret--; myArr[1]--; break; case 'G': if (myArr[2] == checkArr[2]) checkSecret--; myArr[2]--; break; case 'T': if (myArr[3] == checkArr[3]) checkSecret--; myArr[3]--; break; } } private static void Add(char c) { switch (c) { case 'A': myArr[0]++; if (myArr[0] == checkArr[0]) checkSecret++; break; // >=가 안되는 이유: 만족하는 순간에 딱 한번만 checkSecret을 더함 // >=을 쓰면 계속 더해지기 때문에 안됨. case 'C': myArr[1]++; if (myArr[1] == checkArr[1]) checkSecret++; break; case 'G': myArr[2]++; if (myArr[2] == checkArr[2]) checkSecret++; break; case 'T': myArr[3]++; if (myArr[3] == checkArr[3]) checkSecret++; break; } } }

 

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!