Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 백준_구간합구하기
- 백준1546
- Java
- 백준1253
- 백준11286
- 백준_11659
- 백준11724
- 백준1260
- 백준11003
- 백준11720
- 정렬알고리즘
- 백준1517
- 백준11399
- 백준2751
- 백준17298
- 백준13023
- 백준2750
- 백준2023
- 자바
- stack
- 백준1377
- 백준1874
- 백준
- 버블소트
- 백준1940
- 백준12891
- 백준10998
- 구간합
- 백준2018
- 백준1427
Archives
- Today
- Total
HOONDOC
[백준]12891 DNA 비밀번호 - 자바/Java 본문
12891번: DNA 비밀번호
풀이
💡 메모리 : 26536KB, 시간 360ms
- 만족스러운 코드는 아니다.
- 일단 줄이 길어지는 기능 단위의 코드들은 메소드로 구분하여 구현했다. 이를 통해 코드의 가독성은 처음보다 개선되었다.
- 하지만 알고리즘에 맞추어 코드를 작성하다보니, 조건 분기가 잘못 설정되어 슬라이딩 윈도우의 마지막 회차에서 conCheck()메소드가 동작하지 않고 끝나게 되었다.
- 결국 for문 바깥쪽에서 if(conCheck()) ans++; 로 문제가 요구하는 답은 해결할 수 있었다.
- 이번에 배운 건, ‘알고리즘에 맞춰 코드를 작성’ 하는게 아니라
- ‘전체적인 코드 플로우를 구상하고, 이에 맞추어 알고리즘의 반환, 진행을 커스터마이징 하는 것’이 더 깔끔한 가독성있고 깔끔한 코드를 만든다는 것이다.
//틀린 코드입니다.
for(int i = 0; i<S-P+1; i++) { //투포인터 알고리즘
int[] conTmp = new int[4]; // 검사할 array
for(int j = i; j<i+P; j++) {
char dnaPiece = dnaArray[j];
switch(dnaPiece) {
case 'A' : conTmp[0]++; break;
case 'C' : conTmp[1]++; break;
case 'G' : conTmp[2]++; break;
case 'T' : conTmp[3]++; break;
}
}
//조건 검사
if(con[0] > conTmp[0] || con[1] > conTmp[1] || con[2] > conTmp[2] || con[3] > conTmp[3]) continue;
ans++;
}
for(int i = 0; i<S-P+1; i++) { //투포인터 알고리즘
int[] conTmp = new int[4]; // 검사할 array
for(int j = i; j<i+P; j++) {
char dnaPiece = dnaArray[j];
switch(dnaPiece) {
case 'A' : conTmp[0]++; break;
case 'C' : conTmp[1]++; break;
case 'G' : conTmp[2]++; break;
case 'T' : conTmp[3]++; break;
}
}
//조건 검사
if(con[0] > conTmp[0] || con[1] > conTmp[1] || con[2] > conTmp[2] || con[3] > conTmp[3]) continue;
ans++;
}
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static char[] dnaArray;
static int[] conWindow;
static int[] con;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
StringBuilder sb = new StringBuilder();
int S = sc.nextInt(); // 문자열의 길이
int P = sc.nextInt(); // 부분문자열의 길이
String dna = sc.next();
dnaArray = dna.toCharArray();
con = new int[4]; //A C G T의 조건 개수
conWindow = new int[4]; //슬라이딩 윈도우로 체크할 조건 개수
for(int i = 0; i<4; i++) con[i] = sc.nextInt();
int ans = 0;
firstEdit(P);
for(int i = P; i<S; i++) {
if(conCheck()) {
ans++;
}
plusEdit(dnaArray[i]);
minusEdit(dnaArray[i-P]);
}
if(conCheck()) ans++;
sb.append(ans);
System.out.print(sb.toString());
sc.close();
}
static void firstEdit(int P) {
for(int i = 0; i<P; i++) {
switch(dnaArray[i]) {
case 'A' : conWindow[0]++;
break;
case 'C' : conWindow[1]++;
break;
case 'G' : conWindow[2]++;
break;
case 'T' : conWindow[3]++;
break;
}
}
}
static void plusEdit(char dnaPiece) {
switch(dnaPiece) {
case 'A' : conWindow[0]++;
break;
case 'C' : conWindow[1]++;
break;
case 'G' : conWindow[2]++;
break;
case 'T' : conWindow[3]++;
break;
}
}
static void minusEdit(char dnaPiece) {
switch(dnaPiece) {
case 'A' : conWindow[0]--;
break;
case 'C' : conWindow[1]--;
break;
case 'G' : conWindow[2]--;
break;
case 'T' : conWindow[3]--;
break;
}
}
static boolean conCheck() {
for(int i = 0; i<4; i++) {
if(con[i] > conWindow[i]) return false;
}
return true;
}
}
코드 리뷰
- 특이하게 Map을 사용하여 풀이했다.
- map1은 부분 문자열의 성립 조건 ( 2 0 1 1 형태로 주어지는 ), map2는 슬라이딩 윈도우를 하면서 증감되는 ‘현재 범위의 DNA 문자열 카운트 횟수’ 이다.
- Map의 특성상 key값을 DNA 값으로, value를 카운팅 횟수로 설정해두면 확실히 덜 헷갈리고 코드는 간결해진다.isPossiblePassword() 에서 등장한이 코드는 뭔지 모르겠다. Map과 관련된 함수인 것 같은데, 아마도 위의 코드 동작 과정은(2) 불러온 key 값들은 x로 하나씩 iteration된다.(4) map2.get(x) ≥ map1.get(x) 는 각 A G C T key값에 대한 value 크기 비교로, 문제에서 요구하는 비밀번호로 사용할 조건을 충족시키는지, .count() == 4는 해당 조건을 만족하는 개수가 4개이면 true, 아니면 false를 반환한다.
- 로 보인다. Map에 이런 메소드가 있는지 처음 알았다. 익혀두면 유용하게 쓰일 듯.
- (3) → 이 후에는 조건과 반환 형태가 결정된다.
- (1) map2의 모든 key 값을 불러온다.
- map2.keySet().stream().filter(x -> map2.get(x)>=map1.get(x)).count() == 4;
- DNA 염기서열을 String으로 한번에 받고, 필요할 때마다 .charAt()함수로 꺼내 쓰는 것도 나쁘지 않아 보인다. 근데 이러면 메소드 호출이 많아져서 시간이 오래 걸리지 않나?
💡 풀이자 : kwonsg514, 메모리 : 167788KB, 시간 820ms
import java.io.*;
import java.util.*;
public class Main {
static Map<Character, Integer> map1 = new HashMap<>();
static Map<Character, Integer> map2 = new HashMap<>();
static int count = 0;
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());
String str = br.readLine();
st = new StringTokenizer(br.readLine());
map1.put('A', Integer.parseInt(st.nextToken()));
map1.put('C', Integer.parseInt(st.nextToken()));
map1.put('G', Integer.parseInt(st.nextToken()));
map1.put('T', Integer.parseInt(st.nextToken()));
map2.put('A', 0);
map2.put('C', 0);
map2.put('G', 0);
map2.put('T', 0);
//Initial setting
for(int i=0; i<=P-2; i++)
if(isACGT(str.charAt(i))) map2.put(str.charAt(i), map2.get(str.charAt(i))+1);
int lp = 0; int rp = P-2;
while (rp<S-1){
rp++;
if(isACGT(str.charAt(rp))) map2.put(str.charAt(rp), map2.get(str.charAt(rp))+1);
if (isPossiblePassword()) count++;
if(isACGT(str.charAt(lp))) map2.put(str.charAt(lp), map2.get(str.charAt(lp))-1);
lp++;
}
System.out.println(count);
}
// Methods
static boolean isACGT(char c) {
return (c == 'A' || c == 'C' || c == 'G' || c == 'T');
}
static boolean isPossiblePassword() {
return map2.keySet().stream().filter(x -> map2.get(x)>=map1.get(x)).count() == 4;
}
}
- 로직은 나랑 비슷하다.
- 다만 이 사람은 비트 마스킹이라던가, 아스키 코드를 좀 더 잘 활용하는 것으로 보인다.
💡 풀이자 : jeuk817 메모리 : 20528KB, 시간 188ms
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int s;
static int p;
static String dnaString;
static int condition = 0;
final static int FULLFILLED_CONDITION = (1 << ('A' & 31)) + (1 << ('C' & 31)) + (1 << ('G' & 31)) + (1 << ('T' & 31));
static int[] acgtCondition = new int['Z' + 1];
static int[] currCondition = new int['Z' + 1];
static char[] acgt = {'A', 'C', 'G', 'T'};
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
/* set input */
s = Integer.parseInt(st.nextToken());
p = Integer.parseInt(st.nextToken());
dnaString = br.readLine();
st = new StringTokenizer(br.readLine(), " ");
for(int i = 0; i < 4; i++) {
char alphabet = acgt[i];
acgtCondition[alphabet] = Integer.parseInt(st.nextToken());
if(acgtCondition[alphabet] == 0) condition |= 1 << (alphabet & 31);
}
/* /set input */
// set current condition
for(int i = 0; i < p; i++) {
currCondition[dnaString.charAt(i)]++;
}
checkCurrCondition();
// count available passwords
int count = condition == FULLFILLED_CONDITION ? 1 : 0;
int startIdx = 0, endIdx = p - 1;
while(++endIdx < s) {
// remove
char removeAlphabet = dnaString.charAt(startIdx++);
if(currCondition[removeAlphabet] == acgtCondition[removeAlphabet])
condition &= ~(1 << (removeAlphabet & 31));
currCondition[removeAlphabet]--;
// add
char addAlphabet = dnaString.charAt(endIdx);
currCondition[addAlphabet]++;
if(currCondition[addAlphabet] == acgtCondition[addAlphabet])
condition |= 1 << (addAlphabet & 31);
if(condition == FULLFILLED_CONDITION) count++;
}
System.out.println(count);
br.close();
}
static void checkCurrCondition() {
for(int i = 0; i < 4; i++) {
char alphabet = acgt[i];
if(currCondition[alphabet] >= acgtCondition[alphabet]) condition |= 1 << (alphabet & 31);
else condition &= ~(1 << (alphabet & 31));
}
}
}
Conclusion
- Map에 관련된 흥미로운 메소드를 알게 되었다.
- 숫자와 알파벳에 관련된 아스키 코드는 기본적으로 외워두면 쓸모가 많을 것 같다.
'문제 풀이 > BAEKJOON' 카테고리의 다른 글
[백준]1874 스택 수열 - 자바/Java (0) | 2022.12.30 |
---|---|
[백준]11003 최솟값 찾기 - 자바/Java (2) | 2022.12.30 |
[백준]1253 좋다 - 자바/Java (0) | 2022.12.29 |
[백준]1940 주몽 - 자바/Java (0) | 2022.12.29 |
[백준]2018 수들의 합5 - 자바/Java (0) | 2022.12.28 |