백준 알고리즘 정리 (백트래킹 - 골드 2문제)

황제연·2024년 3월 22일
0

알고리즘

목록 보기
12/169
post-thumbnail
문제번호제목난이도
1759암호 만들기골드 5
16987계란으로 계란치기골드 5

1759번 암호 만들기

해결 코드:

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


/**
 * 풀이 과정:
 * - 고민과 풀이:
 * 1-1. 일단 모음과 자음을 분리해서 리스트에 저장하자 -> 그냥 하나에 넣자
 * 1-2. 위 방법 말고 그냥 하나의 리스트에 넣고 오름차순 정렬을 해주자
 *
 * 2. 재귀
 * 2-1. 함수식 security(주어진 문자열 리스트, depth, 만들어진 문자열 + 현재 문자열)
 * 2-2. base condition depth == L -> 현재 문자열에 모음이 한개이상 있는지, 자음이 두개이상 있는지 함수를 통해 체크해주자
 * -> 만약 이 조건을 만족하면 StringBuilder에 넣어주고 종료하고, 아니면 그냥 조료한다
 * 2-3. 재귀식 security(depth+1, 만들어진 문자열 + 현재문자열)
 *
 * - 문제 해결:
 *
 *
 *
 * 시간복잡도: O()
 * 공간복잡도: O()
 *
 */


public class Main {

    static StringBuilder sb = new StringBuilder();
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        StringTokenizer st = new StringTokenizer(br.readLine());
        int L = Integer.parseInt(st.nextToken());
        int C = Integer.parseInt(st.nextToken());
        st = new StringTokenizer(br.readLine());
        List<String> code = new ArrayList<>();
        for (int i = 0; i < C; i++) {
            String input = st.nextToken();
            code.add(input);
        }
        Collections.sort(code);
        security(code, L, 0, "", 0);

        bw.write(sb.toString());
        br.close();
        bw.close();
    }

    private static void security(List<String> code, int size, int depth, String total, int start) {
        if(depth == size){
            if(moOk(total, size) && zaOk(total, size)){
                sb.append(total).append("\n");
            }
            return;
        }

        for (int i = start; i < code.size(); i++) {
               security(code, size, depth+1, total + code.get(i), i+1);
        }


    }

    private static boolean zaOk(String total, int size) {
        int tmp = 0;
        for (int i = 0; i < size; i++) {
            if(total.charAt(i) != 'a' && total.charAt(i) != 'e' && total.charAt(i) != 'i' && total.charAt(i) != 'o' && total.charAt(i) != 'u'){
                tmp++;
            }
        }

        return tmp >= 2;
    }

    private static boolean moOk(String total, int size) {
        for (int i = 0; i < size; i++) {
            if(total.charAt(i) == 'a' || total.charAt(i) == 'e' || total.charAt(i) == 'i' || total.charAt(i) == 'o' || total.charAt(i) == 'u'){
                return true;
            }
        }

        return false;
    }

}

학습 정리:

  • 지금까지 N과 M 시리즈를 하면서 나온 부분을 모두 활용하면 쉽게 풀 수 있는 문제다.

고민의 시간:

  1. 처음에는 본능적으로 모음과 자음을 분리해서 따로 처리해야겠다고 생각했으나, 재귀식을 생각하다보니 분리하는 것보다는 그냥 하나로 통합해서 구분하는 것이 낫겠다고 풀이 방식을 바꾸게 되었다.
  2. 입력되는 문자들을 모두 리스트에 넣었고, 오름차순 출력을 위해 리스트를 정렬하였다.
  3. 깊이와 만들려는 문자열 크기인 L 을 이용하여 base condition을 만족시켰다.
  • 추가로 total에다가는 지금까지 만들어진 문자열을 넣는 방식을 택했다.
  • 이후 다시 푼 후에 정리할 Moo 게임에서는 이 방식을 사용할 수 없는 것과 대조대는 방식이다.
  • 이 문제는 만들어지는 문자열의 크기가 한도없이 크게 늘어나지 않고, 1씩 증가하며 L의 크기에서 멈추기 때문에 메모리초과는 걱정하지 않아도 된다
  1. 하나의 수열에서 리스트의 이전 위치에 있는 값을 다시 탐색하는 암호는 만들면 안된다. 따라서 start라는 파라미터와 i+1라는 인수를 넘겨서 시작 위치를 새롭게 갱신하였다
  2. 종료조건에서 정답 문자열에 추가하는 조건을 두가지 추가한다. 문제에서 주어진 최소 모음 한개최소 자음 두개 조건이다.
  3. 해당 조건은 따로 메소드로 빼서 처리하였다. 최소 모음 한개 조건은 문자열을 순회하면서 모음이 있으면 true,없으면 false를 리턴한다
  4. 최소 자음 두개 조건은 tmp라는 임시 변수를 선언하여 모음이 아닌 경우 자음이므로, 자음인 경우가 발견되면 tmp++를 해준다.
  5. 순회 종료 후에는 tmp값이 2이상이냐 아니냐에 따라 true와 false를 리턴하도록 한다
  6. 이러한 방식으로 진행된다면 수학적 귀납법에 의해 원하는 암호가 출력됨을 확인할 수 있다.

16987번 계란으로 계란치기

해결 코드:

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


/**
 * 고민과 풀이:
 *
 * 문제 해결:
 *
 *
 * 시간복잡도: O()
 * 공간복잡도: O()
 *
 *
 */
class Egg{
    int weight;
    int power;

    public Egg(int weight, int power) {
        this.weight = weight;
        this.power = power;
    }
}


public class Main {
    static Egg[] eggs;
    static int count = 0;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(br.readLine());
        eggs = new Egg[n];
        for (int i = 0; i < n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            eggs[i] = new Egg(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()));
        }

        eggToEgg(0, n, 0);

        bw.write(count+"");
        br.close();
        bw.close();
    }

    private static void eggToEgg(int now, int n, int total) {
        if(now == n || total == n-1){
            count = Math.max(count, total);
            return;
        }

        if(eggs[now].weight <= 0){
            eggToEgg(now+1, n, total);
        }else{
            for (int i = 0; i < n; i++) {
                if(now != i && eggs[i].weight > 0){
                    eggs[now].weight -= eggs[i].power;
                    eggs[i].weight -= eggs[now].power;
                    eggToEgg(now+1, n, total + eggResult(now, i));
                    eggs[now].weight += eggs[i].power;
                    eggs[i].weight += eggs[now].power;
                }
            }
        }


    }

    private static int eggResult(int now, int i) {
        int tmp = 0;
        if(eggs[now].weight <=0 ){
            tmp++;
        }

        if(eggs[i].weight <= 0){
            tmp++;
        }
        return tmp;
    }


}

학습 정리:

고민의 시간:

  1. C++에는 Pair라는 좋은 라이브러리가 있지만 자바에는 없다. 하지만 자바는 더 확장할 수 있는 클래스가 있다
  2. 계란에는 속성이 존재한다. 내구도와 무게이다. 이를 속성으로 각각 weight, power로 지정했다
  3. 그리고 클래스를 담을 배열을 선언해서 입력값들을 담아주었다
  4. 이제 재귀식을 생각해야한다. 가장 왼쪽부터 시작해서 마지막 오른쪽을 집으면 끝나므로, 위치를 나타낼 now와 끝을 나타낼 n을 파라미터로 선언하였다
  5. 그다음 각 계란치기마다 깨진 개수를 체크할 것이다. 재귀로 쭉 그 개수가 이어지면서 내려오는 백트래킹 방법을 사용할 것이기 때문에 total을 파라미터로 지정하였다
  6. 종료조건은 현재위치인 now와 끝을 나타내는 n이랑 같을 경우거나 다른 계란이 다 깨지고 딱 한개만 남은 경우이다. 전자는 3번 조건에서 명시해줬으나 후자는 2번 조건에서 유추해서 종료조건에 추가해야한다
  7. 종료조건에서는 가장 많이 깨진 계란의 개수를 count에 넣도록 Math.max를 활용하였다. 여러 백트래킹으로 내려온 total중 가장 많은 개수가 count에 저장될 것이다.
  8. 이어서 재귀함수를 실행시킬 조건을 생각했다. 먼저 현재 계란이 깨져있으면 그대로 스킵을 해야하는데, 이때 재귀함수를 실행시키지 않고 그냥 return;해서 종료한다면 백트래킹이 깨지게된다
  9. 따라서 total에 추가적인 값을 추가하지 않도록 그냥 total을 인수로 넘겨주는 재귀함수를 실행한다
  10. 만약 깨져있지 않다면, 이제 n만큼 순회를 시작한다. 만약 순회하는 현재의 께란이 깨져있지 않고 now가 i랑 같은 경우(자기 자신을 가리키는 경우)에 다음을 수행한다
  11. 먼저 서로의 내구도를 power만큼 깎는다.
  12. 그리고 이제 깨진 계란의 개수를 확인하는 메소드를 실행시켜서 그 값만큼 total에 추가해서 백트래킹을 해준다.
  13. 이렇게 하면 여러 가지가 나올 것이고, 수학적 귀납법에 의해 원하는 결과를 얻을 수 있을 것이다.
  14. 이후에, 다시 내구도를 power만큼 복원시켜서 다음 백트래킹에서 해당 계란을 계란치기 할 수 있도록 한다
  15. 마치 11 ~ 14번은 N과 M시리즈의 visited 배열을 사용해서 방문 배열을 true로 한 뒤, 재귀함수 실행하고 다시 방문 체크를 false로 하는 방식과 유사하다
  16. 완성된 count를 출력하면 원하는 결과를 얻을 수 있다.
profile
Software Developer

0개의 댓글