안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 DP 문제 내려가기 자바 풀이를 진행하도록 하겠습니다.

문제 출처: https://www.acmicpc.net/problem/2096

 

2096번: 내려가기

첫째 줄에 N(1 ≤ N ≤ 100,000)이 주어진다. 다음 N개의 줄에는 숫자가 세 개씩 주어진다. 숫자는 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 중의 하나가 된다.

www.acmicpc.net

 

1. 풀이 소스

 

import java.io.*;
import java.util.StringTokenizer;

import static java.lang.Integer.parseInt;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        int n = parseInt(br.readLine());
        int[][] map = new int[n][3];
        
        int[][][] dp = new int[n][3][2]; // 최대, 최소 저장 
        
        for (int row = 0; row < n; row++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int col = 0; col < 3; col++) {
                map[row][col] = parseInt(st.nextToken());        
            }
        }
        
        dp[0][0][0] = dp[0][0][1] = map[0][0]; // 초기화는 map에 있는 값으로 설정 
        dp[0][1][0] = dp[0][1][1] = map[0][1];
        dp[0][2][0] = dp[0][2][1] = map[0][2];
        
        for (int row = 1; row < n; row++) { // 왼쪽은 왼, 중앙 / 중앙은 왼, 중, 오 / 오른쪽은 중, 오 순서로 dp 진행 
            // 최대 
            dp[row][0][0] = Math.max(dp[row - 1][0][0], dp[row - 1][1][0]) + map[row][0];
            dp[row][1][0] = Math.max(Math.max(dp[row - 1][0][0], dp[row - 1][1][0]), dp[row - 1][2][0]) + map[row][1];
            dp[row][2][0] = Math.max(dp[row - 1][1][0], dp[row - 1][2][0]) + map[row][2];
        
            // 최소 
            dp[row][0][1] = Math.min(dp[row - 1][0][1], dp[row - 1][1][1]) + map[row][0];
            dp[row][1][1] = Math.min(Math.min(dp[row - 1][0][1], dp[row - 1][1][1]), dp[row - 1][2][1]) + map[row][1];
            dp[row][2][1] = Math.min(dp[row - 1][1][1], dp[row - 1][2][1]) + map[row][2];    
        }
        
        int max = Math.max(Math.max(dp[n - 1][0][0], dp[n - 1][1][0]), dp[n - 1][2][0]);
        int min = Math.min(Math.min(dp[n - 1][0][1], dp[n - 1][1][1]), dp[n - 1][2][1]);
    
        System.out.println(max + " " + min);
    }
}

 

2. 풀이 중점 사항

 

문제에서 요구하는 것은 최대, 최소를 출력하여야 하므로 dp를 3차원 배열로 선언하여 각각 최대, 최소를 매 상황에 맞게 구하는 것이 중요한 포인트였습니다.!

N이 최대 10만이고, 최대 숫자는 9 이므로 max의 범위는 90만이므로 int 3차원 배열로 설정하였습니다.

 

이상으로 백준 DP 문제 내려가기 풀이를 마치도록 하겠습니다. 감사합니다.!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 DP 문제 암호코드 자바 풀이를 진행하도록 하겠습니다.

문제 출처: https://www.acmicpc.net/problem/2011

 

2011번: 암호코드

나올 수 있는 해석의 가짓수를 구하시오. 정답이 매우 클 수 있으므로, 1000000으로 나눈 나머지를 출력한다. 암호가 잘못되어 암호를 해석할 수 없는 경우에는 0을 출력한다.

www.acmicpc.net

 

1. 풀이 소스 

 

import java.io.*;

public class Main {
    static final int MOD = 1000000;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String encode = br.readLine();

        if (encode.charAt(0) == '0') {
            System.out.println("0");
            return;
        }

        int len = encode.length();
        long[] dp = new long[len + 1];
        dp[0] = dp[1] = 1; // 하나로 초기화

        // dp는 1번 인덱스, 문자열은 0번
        for (int i = 2; i <= len; i++) {
            char now = encode.charAt(i - 1); // 현재 문자 판단
            char pre = encode.charAt(i - 2); // 이전 문자
            if (now == '0') {
                // 120 이 있는 경우, 20은 가능한 문자이므로 1이 있었던 위치의 dp 활용 (두개가 하나이므로)
                if (pre == '1' || pre == '2') dp[i] = dp[i - 2];
                else break;
            }
            else {
                // 앞 문자가 0인 경우 무조건 주어진 숫자 하나만 선택 가능하므로 그대로  ex) 03 -> 3
                if (pre == '0') dp[i] = dp[i - 1];
                else {
                    int num = (pre - '0') * 10 + (now - '0'); // 숫자로 만들기
                    if (1 <= num && num <= 26) dp[i] = (dp[i - 2] + dp[i - 1]) % MOD;
                    else dp[i] = dp[i - 1]; // 28의 경우 알파벳 변환 불가하므로 한개 선택 => 그대로
                }
            }
        }
        System.out.println(dp[len] % MOD);
    }
}

 

2. 풀이 중점 과정

 

해당 문제는, 문자열의 인덱스와 DP의 인덱스를 설정하는 과정이 중요하였습니다. 이 부분을 해결하지 못하여 코드를 참조하여 작성하였습니다.

 

풀이할 때, 예외 처리가 중요하였는데, 고려해야 할 사항은 다음과 같습니다.

  • 시작이 0인 경우 불가
  • 10, 20을 만들 수 있는 위치의 '0'이 아닌 경우 불가 ex) 30, 300
  • 이전 문자와 현재 문자로 숫자를 만들었을 때, 26이 넘어가는 경우 각각 개별적인 선택만 가능 ex) 27
  • 이전 숫자가 0인 경우는 현재 숫자는 2개를 만들 수 없고 선택해야 함 ( 위에서, 10, 20을 만들 수 있는 위치의 '0'이 아닌 경우에서 2개가 연속된 0인지 판단하는 로직이 있으므로 여기서는 선택)
  • 만약 2개의 문자열을 만들 수 있다면 하나만 선택하기 위해 dp[i - 1] + 두 개를 문자로 묶어 하나로 만들기 위한 dp[i - 2]로 점화식을 만들 수 있습니다.

문자열은 0부터 시작하고, dp는 1부터 시작하여 

문자의 길이가 2라면, 문자열 인덱스는 0, 1 까지 이고,

dp는 dp[2]가 문자 길이 2에 대한 값을 나타내도록 하였습니다.

 

자세한 풀이는 주석으로 처리하였습니다. 이상으로 암호코드 자바 풀이를 마치도록 하겠습니다. 감사합니다.

 

풀이 참고 자료: https://iamheesoo.github.io/blog/algo-boj2011

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 동물원 자바 풀이를 진행하도록 하겠습니다.

문제출처: https://www.acmicpc.net/problem/1309

 

1309번: 동물원

첫째 줄에 우리의 크기 N(1≤N≤100,000)이 주어진다.

www.acmicpc.net

 

1. 풀이 소스

 

import java.util.*;
import java.io.*;

public class Main {

    static final int MOD = 9901;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int n = Integer.parseInt(br.readLine());

        int[][] dp = new int[n][3]; // [x][0]: 모두 선택 안함, [x][1]: 왼쪽만 넣음, [x][2]: 오른쪽만 넣음

        Arrays.fill(dp[0], 1); // 0번째에서는 모두 선택 안하거나, 왼쪽에만 넣거나 오른쪽에만 넣을 수 있음

        for (int i = 1; i < n; i++) {
            // 모두 선택안하고 시작한 경우, 모두 선택 X, 왼쪽만 넣은 경우, 오른쪽만 넣은 경우 모두 넣고, 이번에 선택 안함
            dp[i][0] = (dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2]) % MOD;
            dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % MOD; // 이전에 모두 선택 안한 것, 오른쪽을 선택한 것을 선택하고 이번은 왼쪽을 선택
            dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % MOD; // 이전에 모두 선택 안한 것, 왼쪽만 선택한 경우를 가져온 후 오른쪽만 선택
        }

        int sum = 0;
        for (int i = 0; i < 3; i++) sum += dp[n - 1][i];
        sum %= MOD;

        System.out.println(sum);

    }

 

2. 풀이 중점 사항

 

해당 문제는 x by y 배열에서, i번째의 행에서 사자를 모두 놓지 않는지, 왼쪽만 놓는지, 오른쪽에만 놓는지를 구분하기 위해 
이차원 배열의 dp로 0, 1, 2 세 가지를 나누는 과정이 중점이었습니다.

 

int[][] dp = new int[n][3]; // [x][0]: 모두 선택 안함, [x][1]: 왼쪽만 넣음, [x][2]: 오른쪽만 넣음

 

하나의 사례를 예시로 들면 다음과 같습니다. (O: 사자 선택,  X: 사자 선택 하지 않음)

 

1) X X       <-- 모두 선택 X

 

2) X X           X X         X X        첫 번째 행에서 모두 선택하지 않은 경우 

    O X           X O        X X         왼쪽의 모습처럼 왼쪽만 선택, 오른쪽만 선택, 모두 선택하지 않는 경우로 나눌 수 있습니다.

 

dp[i][0] = (dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2]) % MOD;

이를 코드로 구현하면, 모두 선택하지 않은 2차원 배열에서 0 인덱스는 모두 선택 X + 왼쪽만 선택 + 오른쪽만 선택  % MOD로 할 수 있습니다.

 

1) X O

 

2) X O          X O               첫번째 행에서 오른쪽만 고른 경우는 

    O X          X X                그 다음 행에서 왼쪽만 선택하거나, 모두 선택하지 않는 경우로 나눌 수 있습니다.

 

dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % MOD; // 이전에 모두 선택 안한 것, 오른쪽을 선택한 것을 선택하고 이번은 왼쪽을 선택
dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % MOD; // 이전에 모두 선택 안한 것, 왼쪽만 선택한 경우를 가져온 후 오른쪽만 선택

 

이를 코드로 구현하면 다음과 같이 현재 주어진 인덱스 행에서 왼쪽만 선택 혹은 오른쪽만 선택하는 두 가지로 나눠서 dp를 계산할 수 있습니다.

 

최종적으로 마지막 sum을 구하고 %MOD 연산을 하면 답을 구할 수 있습니다.

이상으로 동물원 자바 풀이를 마치도록 하겠습니다. 감사합니다.!!! 

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 Java vs C++ 자바 풀이를 진행하도록 하겠습니다.

문제출처:https://www.acmicpc.net/problem/3613

 

3613번: Java vs C++

Java 예찬론자 김동규와 C++ 옹호가 김동혁은 서로 어떤 프로그래밍 언어가 최고인지 몇 시간동안 토론을 하곤 했다. 동규는 Java가 명확하고 에러가 적은 프로그램을 만든다고 주장했고, 동혁이는

www.acmicpc.net

 

1. 풀이 소스

 

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        String str = br.readLine();
        
        // 시작이 대문자이거나 _, 마지막이 _ 불가
        if (str.charAt(0) <= '_' || str.charAt(str.length() - 1) == '_') {
            System.out.println("Error!");
            return;
        }
        
        // __ 불가 _A 불가 A_불가
        boolean upperCase = false; // 만약 대문자가 있다면 c++로만 바꿀 수 있음
        boolean toJava = false; // false라면 c++, true라면 자바로 바꾸기
        char pre = str.charAt(0); // 이전 문자 파악
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            
            if (ch <= 'Z') {
                if (toJava) { // _A로 끝나면 안되므로 _ 나왔는지 판단 (toJava라면 _이 나옴)
                    System.out.println("Error!");
                    return;
                } 
                upperCase = true; // 대문자 true 설정
            }
            
            if (ch == '_') { // 만약 _가 있는데 대문자가 섞여 있다면 불가
                if (upperCase || pre == '_') {
                    System.out.println("Error!");
                    return;
                }
                toJava = true;
            }
            pre = ch;
        }
        
        
        if (toJava) { // 자바로 바꿔야 함
            boolean nextUpper = false; // 다음이 대문자가 나와야함
            for (int i = 0; i < str.length(); i++) {
                char ch = str.charAt(i);
                if (nextUpper) {
                    sb.append(String.valueOf(ch).toUpperCase()); // 문자열 대문자로 변경
                    nextUpper = false; // 초기화
                } else {
                    if (str.charAt(i) == '_') nextUpper = true; // 다음 문자열 대문자로 설정
                    else sb.append(ch);
                }
            }
        }
        else { // c++로 바꿔야 함
            for (int i = 0; i < str.length(); i++) {
                char ch = str.charAt(i);
                if (ch <= 'Z') { // 만약 대문자라면
                    sb.append("_"); // "_소문자"로 바꾸기
                    sb.append(String.valueOf(ch).toLowerCase());
                } else sb.append(ch);
            }            
        }
        
        System.out.println(sb);
    }
}

 

2. 풀이 중점 사항

 

해당 문제는 반례를 생각해 내는 것이 중요한 문제였습니다.

안 되는 경우

  • _ 로 시작   ex)  "_a"
  • 대문자로 시작 ex) "Ab"
  • _로 끝남 ex) "ab_"
  • 대문자와 _이 같이 쓰임 ex) "aB_b"
  • __이 두 개 이상이 연속으로 쓰임 ex) "a__b"

 

// 시작이 대문자이거나 _, 마지막이 _ 불가
if (str.charAt(0) <= '_' || str.charAt(str.length() - 1) == '_') {
    System.out.println("Error!");
    return;
}

시작이 대문자이거나 "_"인 경우, 마지막 문자열이 "_"로 끝나는 경우는 처음에 확인이 가능합니다.

 

// __ 불가 _A 불가 A_불가
boolean upperCase = false; // 만약 대문자가 있다면 c++로만 바꿀 수 있음
boolean toJava = false; // false라면 c++, true라면 자바로 바꾸기
char pre = str.charAt(0); // 이전 문자 파악
for (int i = 0; i < str.length(); i++) {
    char ch = str.charAt(i);
    
    if (ch <= 'Z') {
        if (toJava) { // _A로 끝나면 안되므로 _ 나왔는지 판단 (toJava라면 _이 나옴)
            System.out.println("Error!");
            return;
        } 
        upperCase = true; // 대문자 true 설정
    }
    
    if (ch == '_') { // 만약 _가 있는데 대문자가 섞여 있다면 불가
        if (upperCase || pre == '_') {
            System.out.println("Error!");
            return;
        }
        toJava = true;
    }
    pre = ch;
}

 

모든 문자가 전부 소문자와 대문자로만 이루어져 있다면, 이는 Java의 변수명 형태로 작성된 것입니다.

따라서, c++로 변경 가능합니다.

반면, 모든 문자가 소문자와 "_"로만 쓰인 경우는 Java로 변경이 가능합니다.

이와 같이 O(n)으로 탐색하며 어떠한 문자로 변경 가능한지 판단합니다.

 

if (toJava) { // 자바로 바꿔야 함
    boolean nextUpper = false; // 다음이 대문자가 나와야함
    for (int i = 0; i < str.length(); i++) {
        char ch = str.charAt(i);
        if (nextUpper) {
            sb.append(String.valueOf(ch).toUpperCase()); // 문자열 대문자로 변경
            nextUpper = false; // 초기화
        } else {
            if (str.charAt(i) == '_') nextUpper = true; // 다음 문자열 대문자로 설정
            else sb.append(ch);
        }
    }
}
else { // c++로 바꿔야 함
    for (int i = 0; i < str.length(); i++) {
        char ch = str.charAt(i);
        if (ch <= 'Z') { // 만약 대문자라면
            sb.append("_"); // "_소문자"로 바꾸기
            sb.append(String.valueOf(ch).toLowerCase());
        } else sb.append(ch);
    }            
}

 

이후 다시 O(n)으로 바꿔야 하는 방향으로 문자를 바꿀 수 있습니다.

자세한 풀이는 주석으로 작성하였습니다.

이상으로 Java vs C++ 자바 풀이를 마치도록 하겠습니다.

감사합니다.!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 크로아티아 알파벳 자바 풀이를 진행하고자 합니다.

 

문제 출처: https://www.acmicpc.net/problem/2941

 

2941번: 크로아티아 알파벳

예전에는 운영체제에서 크로아티아 알파벳을 입력할 수가 없었다. 따라서, 다음과 같이 크로아티아 알파벳을 변경해서 입력했다. 크로아티아 알파벳 변경 č c= ć c- dž dz= đ d- lj lj nj nj š s= ž z=

www.acmicpc.net

 

1. 풀이 소스

 

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        Set<String> set = new HashSet<>(List.of("c=", "c-", "dz=", "d-", "lj", "nj", "s=", "z="));
        
        int result = 0; // 결과 출력
        String str = br.readLine();
        String tmp = ""; // 문자열
        
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);            
            tmp += c; // 문자열 추가 하기
            
            if (tmp.length() == 3) { // 3개인 경우 dz 이후 문자
                if (tmp.equals("dz=")) {
                    result++; // 하나의 문자
                    tmp = ""; // 문자를 모두 소모한 경우이므로
                } else {
                    result += 2; // 앞의 두개가 불가한 문자이므로
                    tmp = String.valueOf(c); // 새로운 c 설정
                }
            }
            
            if (tmp.length() == 2) {
                if (tmp.equals("dz")) continue; // dz=일수 있으므로 넘기기
                if (set.contains(tmp)) tmp = "";
                else tmp = String.valueOf(c);
                result++; // 만약 set에 있다면 하나로 쳐서 ++, 만약 아니라면, 이전 길이에 대한 ++,                
            }
        }
        
        if (tmp.length() >= 1) { // 한개 혹은 두개에서 끝난 경우
            result += tmp.length(); // 문자열이 있다는 것은 크로아티아 알파벳 만족 불가
        }
        
        System.out.print(result);
    }
}

 

2, 풀이 중점 사항

 

정의해야 할 문자를 미리 set에 컬렉션 생성자로 생성하여 tmp 문자가 같은지 판단하는 방법으로 문제를 해결할 수 있었습니다.

실수하기 좋은 위치가 마지막에 한 번 더 tmp.length() >= 1 인지 확인하는 로직입니다.

 

예를 들어 tmp가 "" 아닌 상태에서 종료될 수 있습니다. 특정 문자 하나가 남아있거나, dz의 특성으로 continue 하여 넘어간 것일 수 있습니다. 따라서, tmp의 길이가 1 이상이라면 남아있는 문자열 개수를 더해주는 것이 중요하였습니다.

이상으로 크로아티아 알파벳 자바 풀이를 마치도록 하겠습니다. 

감사합니다!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다. 

이번 포스팅은 백준의 문자열 문제 - 문자열 게임 2 자바풀이를 진행하도록 하겠습니다.

문제출처: https://www.acmicpc.net/problem/20437

 

20437번: 문자열 게임 2

첫 번째 문자열에서 3번에서 구한 문자열은 aqua, 4번에서 구한 문자열은 raquator이다. 두 번째 문자열에서는 어떤 문자가 5개 포함된 문자열을 찾을 수 없으므로 -1을 출력한다.

www.acmicpc.net

 

1. 풀이 소스

 

import java.util.*;
import java.io.*;
import static java.lang.Integer.parseInt;
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = parseInt(br.readLine());
        
        StringGame stringGame = new StringGame();
        for (int i = 0; i < t; i++) {
            stringGame.addStr(br.readLine());
            stringGame.addTarget(parseInt(br.readLine()));
        }
        stringGame.find();
    }
}

class StringGame {
    List<String> strs = new ArrayList<>(); // 문자열을 담을 리스트
    List<Integer> targets = new ArrayList<>(); // k를 담을 타겟 리스트
    Map<Character, List<Integer>> map = new HashMap<>(); // 해시맵
    
    void addStr(String str) {
        strs.add(str);
    }
    
    void addTarget(int k) {
        targets.add(k);
    }
    
    void init() {
        for (char c = 'a'; c <= 'z'; c++) map.put(c, new ArrayList<>());
    } // 미리 각 문자에 대한 어레이 리스트 생성하여 맵에 저장
    
    void find() {
        init();
        StringBuilder sb = new StringBuilder();

        // T: 100  W: 10000   idx: 10000 / 26 (k = 1) , 삭제 10000 / 26,  알파벳 26
        // O(T * W + T * 10000 / 26 * 2 * 26) = O(300만)
        for (int i = 0; i < strs.size(); i++) { // 문자열 순회
            String str = strs.get(i);
            int k = targets.get(i);
            int min = Integer.MAX_VALUE; // 먼저 최소값 맥스로 설정
            int max = -1; // max는 문자열 길이이므로 1 이상 나올 수 있으므로 -1로 최소화 설정
            
            for (int j = 0; j < str.length(); j++) { // 각 문자에 대한 charAt으로 탐색
                List<Integer> idxs = map.get(str.charAt(j));
                idxs.add(j); // 인덱스 추가하기
            }
            
            Set<Character> set = map.keySet(); // 맵에서 키셋을 가져오기
            
            for (Character ch : set) { // 키셋을 순회하며 ch 가져옴
                List<Integer> idxs = map.get(ch); // 각 맵에 저장되어 있는 인덱스를 저장한 리스트 가져오기
                
                if (idxs.size() >= k) { // k보다 같거나 크다면, k개 이상이 알파벳이 존재한다는 의미
                    for (int idx = 0; idx + (k - 1) < idxs.size(); idx++) { // 현재 인덱스부터 k - 1 큰 인덱스까지
                        min = Math.min(min, idxs.get(idx + k - 1) - idxs.get(idx) + 1); // 1 ~ 3까지의 길이는 3이므로 + 1
                        max = Math.max(max, idxs.get(idx + k - 1) - idxs.get(idx) + 1);
                    }
                } 
                idxs.clear(); // 저장된 인덱스 모두 제거하기
            }
            
            if (min == Integer.MAX_VALUE && max == -1) sb.append(-1).append("\n");
            else sb.append(min).append(" ").append(max).append("\n");
        }
        
        System.out.print(sb);
    }
}

 

2. 풀이 중점사항

 

시간 복잡도 계산

해당 문제는 1초라는 시간이 주어졌고, 1024M라는 충분한 메모리가 제시되어 있습니다.

저는 각 문자열을 탐색하면서 map에 저장하고, map에 저장된 리스트의 길이가 k보다 같거나 크다면 내부의 인덱스를 순회하며 인덱스 간의 길이가 가장 길고 짧은 것을 판단하는 로직을 생각하였습니다.

 

가장 최악의 케이스를 고려해보면 다음과 같습니다.

 

T: 100 (케이스 개수)

W: 10000 (문자열 길이)

C: 26개의 알파벳이 모두 10000 / 26 개를 나눠가짐 

K: 1 (k = 1이므로 idx에 저장된 값을 모두 탐색)

A: 알파벳 26개 

 

따라서, 계산은 O(T * W + T * C * A) = O(100 * 10000 + 100 * 10000 / 26 * 26 * 2(add 후 clear)) = O(약 300만)

1초당 1억번이 가능하다고 가정하면 대략적으로 300만이면 충분히 가능한 시간이라고 판단하였습니다.

 

Map 사용

Map<Character, List<Integer>> map = new HashMap<>(); // 해시맵

--- 중략 ----

void init() {
        for (char c = 'a'; c <= 'z'; c++) map.put(c, new ArrayList<>());
    } // 미리 각 문자에 대한 어레이 리스트 생성하여 맵에 저장
    
---- 중략 ----

Set<Character> set = map.keySet();

 

HashMap에 먼저 알파벳과 arrayList<>를 저장하였습니다. 

그리고 각 문자열을 순회하며 각 문자가 나온 인덱스를 map에 저장하였습니다.

map.keySet()으로 알파벳을 모두 가져왔습니다.

 

 

idx.size() >= k개 활용

 

for (Character ch : set) { // 키셋을 순회하며 ch 가져옴
    List<Integer> idxs = map.get(ch); // 각 맵에 저장되어 있는 인덱스를 저장한 리스트 가져오기

    if (idxs.size() >= k) { // k보다 같거나 크다면, k개 이상이 알파벳이 존재한다는 의미
        for (int idx = 0; idx + (k - 1) < idxs.size(); idx++) { // 현재 인덱스부터 k - 1 큰 인덱스까지
            min = Math.min(min, idxs.get(idx + k - 1) - idxs.get(idx) + 1); // 1 ~ 3까지의 길이는 3이므로 + 1
            max = Math.max(max, idxs.get(idx + k - 1) - idxs.get(idx) + 1);
        }
    } 
    idxs.clear(); // 저장된 인덱스 모두 제거하기
}

idxs.size()로 k와 비교를 한 후, idx + (k - 1)가 idx.size()보다 작을 때까지 순회하며 값을 업데이트하였습니다.

 

이상으로 문자열 게임2 자바 풀이를 마치도록 하겠습니다.

감사합니다.!

 

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 문자열 문제 회문 자바 풀이를 진행하도록 하겠습니다.

문제 출처: https://www.acmicpc.net/problem/17609

 

17609번: 회문

각 문자열이 회문인지, 유사 회문인지, 둘 모두 해당되지 않는지를 판단하여 회문이면 0, 유사 회문이면 1, 둘 모두 아니면 2를 순서대로 한 줄에 하나씩 출력한다.

www.acmicpc.net

 

1. 풀이 소스

 

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        List<String> strs = new ArrayList<>(); // 문자열 담을 리스트

        int t = Integer.parseInt(br.readLine());
        for (int i = 0; i < t; i++) strs.add(br.readLine()); // 문자열 리스트에 추가

        for (String str : strs) {
            int cnt = 0; // 회문, 유사회문, 그 외 판단
            int left = 0; // 포인터 왼쪽
            int right = str.length() - 1; // 포인터 오른쪽

            while (left < right) { // left 포인터가 right 포인터보다 작을 때 까지 , 만약 홀수길이이의 문자열이라면 중앙을 제외한 나머지가 모두 맞으면 가능 abkba

            if (str.charAt(left) != str.charAt(right)) { // 만약 양쪽 포인터가 가리키는 문자열이 같지 않다면
                    if (str.charAt(left + 1) == str.charAt(right)) { // 만약 두 포인터가 같지 않다면 유사회문 판단을 위해 left를 한칸 옮겨서 판단 
                        cnt = move(str, left + 1, right);
                        if (cnt == 1) break; // 만약 왼쪽 포인터를 한번 더 옮긴 것이 유사회문이라면 종료 
                    }
                    if (str.charAt(left) == str.charAt(right - 1)) { // 유사회문이 아닌 경우 right를 한칸 옮기고 진행 
                        cnt = move(str, left, right - 1);
                        if (cnt == 1) break; // 유사회문 종료 
                    }
                    cnt = 2; // 둘 다 아니거나, cnt = 2라면, 
                    break;
                } else { // 유사회문 없이 돌아간다면, 하나씩 포인터 양쪽에서 옮겨주기 
                    left++;
                    right--;
                }
            }

            if (cnt == 0) sb.append(0).append("\n");
            else if (cnt == 1) sb.append(1).append("\n");
            else sb.append(2).append("\n");
        }

        System.out.print(sb);
    }

    static int move(String str, int left, int right) { // 유사회문 판단을 위한 포인터 이동 
        while (left < right) {
            if (str.charAt(left) == str.charAt(right)) {
                left++;
                right--;
            } else return 2; // 유사회문도 아니므로 2 리턴 
        }
        return 1; // 유사회문 
    }
}

 

2. 풀이 중점 사항 

 

이번 풀이는 포인터를 이동하는 방식으로 문제를 해결할 수 있었습니다. 

 

    a      b       c         b        a  

   p1 ->                          <- p2

 

포인터를 하나씩 옮겨가며 값을 찾되, 두 포인터가 가리키는 값이 다를 수 있습니다. 이 경우 유사회문일 가능성이 있으므로, left를 옮기고 검사하고, 아니라면, 오른쪽을 한번 더 옮겨서 유사회문을 검사하는 로직을 작성하였습니다.

 

 

if (str.charAt(left + 1) == str.charAt(right)) { // 만약 두 포인터가 같지 않다면 유사회문 판단을 위해 left를 한칸 옮겨서 판단
    cnt = move(str, left + 1, right);
    if (cnt == 1) break; // 만약 왼쪽 포인터를 한번 더 옮긴 것이 유사회문이라면 종료
}
if (str.charAt(left) == str.charAt(right - 1)) { // 유사회문이 아닌 경우 right를 한칸 옮기고 진행
    cnt = move(str, left, right - 1);
    if (cnt == 1) break; // 유사회문 종료
}
cnt = 2; // 둘 다 아니거나, cnt = 2라면,
break;

 

다음과 같이, if - else if 가 아니라 왼쪽 유사회문 검사 후, 오른쪽 유사회문 검사하는 이유는 반례에서 찾을 수 있습니다.

만약 테스트 케이스가 'abbab'가 주어졌다고 가정하겠습니다. 

 

// 잘못된 코드

if (str.charAt(left + 1) == str.charAt(right)) { // 만약 두 포인터가 같지 않다면 유사회문 판단을 위해 left를 한칸 옮겨서 판단
    cnt = move(str, left + 1, right);
    if (cnt == 1) break; // 만약 왼쪽 포인터를 한번 더 옮긴 것이 유사회문이라면 종료
}
else if (str.charAt(left) == str.charAt(right - 1)) { // 유사회문이 아닌 경우 right를 한칸 옮기고 진행
    cnt = move(str, left, right - 1);
    if (cnt == 1) break; // 유사회문 종료
}
cnt = 2; // 둘 다 아니거나, cnt = 2라면,
break;

 

만약 이처럼 if else if를 적용하게 된다면, 먼저 기술된 left + 1만 실행됩니다.

 

a            b                 b             a             b

p1                                                          p2       -------> arr[p1] != arr[p2]

             p1                                             p2       -------> arr[p1] = arr[p2]

                              p1           p2                        -------> arr[1]  != arr[p2]

 

abbab 는 유사회문일지라도 left + 1를 하면 일반 문자열이 됩니다.

따라서, if문으로 left + 1을 먼저 거치고, 오른쪽도 right - 1로 검사할 수 있도록 하였습니다.

 

a            b                 b             a             b

p1                                                          p2       -------> arr[p1] != arr[p2]

p1                                           p2                      -------> arr[p1] = arr[p2]

              p1               p2                                    -------> arr[1]  = arr[p2] 

 

 

right - 1도 적용하면 다음처럼 유사회문을 찾을 수 있습니다.

이상으로 회문 자바 풀이를 마치도록 하겠습니다.! 감사합니다.!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 문자열 문제 - 단어 뒤집기 2 자바 풀이를 진행하도록 하겠습니다.

 

문제 출처: https://www.acmicpc.net/submit/17413

 

로그인

 

www.acmicpc.net

 

1. 풀이 소스

 

import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str = br.readLine();
        
        StringBuilder sb = new StringBuilder();

        boolean flag = false; // '<' 의 시작여부

        int start = -1; // 단어를 뒤집어야할 때 역순으로 순회하기 위한 인덱스 (초기화  -1)
        int end = -1;

        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);

            if (c == '<') {
                if (start != -1) { // 만약 '<' 이전에 'apple<' 처럼 뒤집어야 하는 문자열이 있음
                    for (int j = end; j >= start; j--) sb.append(str.charAt(j));
                    start = -1; // 초기화
                    end = -1;
                }
                flag = true; // flag가 true라면 '>'로 닫히지전까지 유효
            }

            if (flag) sb.append(str.charAt(i)); // 현재 꺽쇠 안이므로 flag
            else {
                if (start == -1) start = i; // 시작 인덱스 설정
                end = i; // 끝 인덱스는 계속 업데이트 (단어 길이가 1일 수 있음)

                if (c == ' ') {
                    for (int j = end - 1; j >= start; j--) sb.append(str.charAt(j));
                    start = -1;
                    sb.append(c);
                }
            }

            if (c == '>') flag = false; // flag false로 설정하여 꺽쇠 해제
        }

        // 꺽쇠 혹은 공백이 없어서 sb에 추가되지 않은 문자열이 있을 수 있으므로
        if (start != -1) {
            for (int j = end; j >= start; j--) sb.append(str.charAt(j));
        }

        System.out.print(sb);
    }
}

 

2. 풀이 중점 사항

 

꺽쇠 내부가 아닌 밖에서 단어를 뒤집어야 할 때, start, end 인덱스를 활용하여 역순으로 sb에 append 하는 것이 중요하였습니다.

단어가 뒤집힐 수 있는 경우를 고려하여, 꺽쇠 밖에 위치하여 공백이 나오거나 다음 char에 '<'를 만난다면, 단어가 있다면 뒤집을 수 있도록 하였습니다. 테스트 케이스 1과 같이 'judge'는 꺽쇠를 만나서 sb에 append 되거나, 공백으로 append 되지 않습니다. 따라서 str.length()에 대한 순회를 마친 후 start 인덱스로 단어가 있는지 판단하였습니다.

이상으로 단어 뒤집기 2 자바 풀이를 마치도록 하겠습니다. 감사합니다!

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 문자열 파일 정리 자바 풀이를 작성하고자 합니다.

 

문제 출처: https://www.acmicpc.net/problem/20291

 

20291번: 파일 정리

친구로부터 노트북을 중고로 산 스브러스는 노트북을 켜자마자 경악할 수밖에 없었다. 바탕화면에 온갖 파일들이 정리도 안 된 채 가득했기 때문이다. 그리고 화면의 구석에서 친구의 메시지를

www.acmicpc.net

 

1. 풀이 소스

 

import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        
        HashMap<String, Integer> map = new HashMap<>(); // 개수를 저장할 map 선언
        HashSet<String> set = new HashSet<>(); // 셋으로 고유한 문자 저장
        
        for (int i = 0; i < n; i++) {
            String type = br.readLine().split("\\.")[1]; // 정규식 문제로 이스케이프로 . 처리

            if (!set.contains(type)) { // 만약 key가 없다면 map에 저장
                set.add(type);
                map.put(type, 1);
            } else map.put(type, map.get(type) + 1); // 이미 값이 있는 경우 + 1
        }

        List<String> sortType = new ArrayList<>(set); // set을 문자열에 addAll
        Collections.sort(sortType); // 오름차순 정렬
        
        StringBuilder sb = new StringBuilder();
        for (String key : sortType) sb.append(key).append(" ").append(map.get(key)).append("\n");
        
        System.out.print(sb);
    }
}

 

 

2. 풀이 중점 사항 

 

.으로 분리하기

split 인자로 들어가는 String 값은 regex 정규식입니다. "."로 문자열을 분리하기 위해서는 "\\."을 사용하여. 을 분할하기 위한 문자로 인식할 수 있도록 해주어야 합니다.

 

HashSet

HashSet에 현재 key가 존재하는지 파악하는 메서드는 set.contains() 입니다. contains()에 값이 있다면 true, 없다면 false가 됩니다. 이를 바탕으로 값을 추가할 수 있습니다.

 

List의 컬렉션 생성자

set에 있는 key를 모두 List에 추가하고자 할 때, "컬렉션 생성자"를 토대로 컬렉션을 생성할 수 있습니다.

new ArrayList<>(set) 으로 샐 성자에 set을 넣어서 컬렉션을 생성하는 방법입니다. 

 

이상으로 파일 정리 자바 풀이를 마치도록 하겠습니다. 감사합니다.!

 

안녕하세요. 회사와 함께 성장하고 싶은 KOSE입니다.

이번 포스팅은 백준 문자열 문제 - 염색체 자바 풀이를 진행하고자 합니다.

 

문제 출처: https://www.acmicpc.net/problem/9342

 

9342번: 염색체

상근이는 생명과학 연구소에서 염색체가 특정한 패턴인지를 확인하는 일을 하고 있다. 염색체는 알파벳 대문자 (A, B, C, ..., Z)로만 이루어진 문자열이다. 상근이는 각 염색체가 다음과 같은 규칙

www.acmicpc.net

 

1. 풀이 소스

 

import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());

        Solution solution = new Solution();
        for (int i = 0; i < t; i++) {
            solution.setStr(br.readLine());
        }

        System.out.print(solution.getStr());
    }
}

class Solution {

    List<String> strs = new ArrayList<>(); // 문자열 저장 리스트

    void setStr(String str) {
        strs.add(str);
    }

    String getStr() {
        StringBuilder sb = new StringBuilder();
        boolean isInfected; // 조건에 맞는지 판단

        for (String str : strs) {
            isInfected = true;

            if (str == null || str.length() < 3) { // 문자가 null이거나 3개보다 작다면 조건 만족 불가능
                sb.append("Good!").append("\n");
                continue;
            }

            int flag = 0; // 0: A, 1: F, 2: C 3: C가 문자 한 개
            for (int i = 1; i < str.length(); i++) {
                char c = str.charAt(i);

                if (flag == 3) { // 만약 flag == 3인 조건이 true라면 끝에 문자가 하나가 아니라는 것
                    isInfected = false; // 조건 만족 불가
                    break;
                }

                else if (flag == 0) { // A 문자가 유지되는 중
                    if (c == 'F') { // F가 나왔다면 flag++
                        flag++;
                    }
                    else if (c != 'A') { // F와 A가 아니라면 조건 불만족
                        isInfected = false;
                        break;
                    }
                } else if (flag == 1) { // F 문자 유지되는 중
                    if (c == 'C') { // C가 나왔다면 flag++
                        flag++;
                    }
                    else if (c != 'F') { // C와 F가 아니라면 조건 불만족
                        isInfected = false;
                        break;
                    }
                } else if (flag == 2) {  // C 문자 유지 중
                    if (c != 'C') {
                        if (c == 'A' || c == 'B' || c == 'D' || c == 'E' || c =='F') flag++; // 마지막 문자 한 개가 되어야 함
                        else {
                            isInfected = false;
                            break;
                        }
                    }
                }
            }
            if (isInfected && (flag == 2 || flag == 3)) {
                sb.append("Infected!").append("\n");
            } else {
                sb.append("Good").append("\n");
            }

        }

        return sb.toString();
    }
}

 

2. 풀이 중점 사항

 

각 조건은

'첫 번째 문자 -> A -> F -> C -> 끝 문자 혹은 종료'의 순서로 순차적으로 진행되게 됩니다.

따라서, O(n)로 문제를 해결할 수 있었습니다.

flag를 int 타입으로 선언하여, 각 문자를 한 번 순회하면서 같은 문자라면 flag 유지하고,
조건에 맞도록 다음 문자가 이어지면 flag가 변경될 수 있도록 하였습니다.

 

다소 헷갈릴 수 있는 부분은 규칙을 지키는 경우에 Infected!를 출력해야 한다는 점입니다.!

이상으로 백준 염색체 자바 풀이를 마치도록 하겠습니다. 감사합니다!!

 

 

+ Recent posts