Abbreviation은 주어진 A 문자열에서 B문자열을 만들어 낼 수 있는지 판단하는 문제이다.

규칙은 다음과 같다.

  • 규칙 1. A 문자열 중 소문자는 지울 수 있다.
  • 규칙 2. A 문자열 중 소문자는 대문자로 변환 시킬 수 있다.

소문자는 없는 문자로 취급해도 되니, 순서에 맞게 B문자열이 나올 수만 있으면 정답이 될 것이다.

위의 규칙을 소스코드로 만들어 보면 다음과 같을 것이다.

    public static void abbreviation(String a, String b) {
    	if (b.length() == 0) { #3
            if (hasUpperCase(a) == false) {
                result = true;
            }
            return;
        }
        
        char aLetter = a.charAt(0);

        if (Character.isLowerCase(aLetter) == true) { #1
            abbreviation(a.substring(1), b);
        }
        if (Character.toUpperCase(aLetter) == b.charAt(0)) { #2
            abbreviation(a.substring(1), b.substring(1));
        }
        return;
    }
    
    static boolean hasUpperCase(String a) {
        for (int i = 0; i < a.length(); i++) {
            if (Character.isUpperCase(a.charAt(i))) {
                return true;
            }
        }
        return false;
    }

#1 if 문 : 소문자는 지울 수 있다.

#2 if문 : 소문자를 대문자로 바꿀 수 있다.

재귀를 통해 쉽게 구현을 할 수 있다. 항상 재귀는 탈출 조건을 가지고 있어야하는데 위의 문제 탈출 조건은 변환이 가능할 때와 그렇지 않을 때이다. #3에서 B 문자열이 존재하지 않을 때(#2에 의해서 지워진다.), A 문자열의 남은 문자열에 대문자가 있으면 답이 아닌 것으로 간주(#1의 조건 문에 의하여)한다.

예를 들어 A 문자열이 ABCdfgh이고 B 문자열이 ABC이면 ABC가 #2에 의해 맞아 떨어지고 A문자열이 dfgh가 남았을 경우 dfgh는 지울 수 있는 문자이므로 답이라고 본다.

 

하지만 위의 코드를 정답에 넣어보면 타임아웃이 나올 것이다. 

한가지 예시가 있다. dd라는 문자열이 A 문자열로 들어 왔을 때이다. 왼쪽으로 가면 규칙 1에 의해 소문자를 지우고 오른쪽으로 가면 규칙 2에 의해 대문자로 만든다. 만약 dd뒤에 더 많은 문자열이 있다면 리프 노드의 D는 중복이다. 문자열이 길어질 수록 이런 중복된 계산은 더 비효율적인 계산이 될 것이다. (마치 피보나치에서 중복 계산을 하는 것 처럼)

이러한 중복된 계산을 지우기 위해 미리 계산해 놓을 필요가 있다.

    public static void abbreviation(String a, String b) {
        if (b.length() == 0) {
            if (hasUpperCase(a) == false) {
                result = true;
            }
            return;
        }

        char aLetter = a.charAt(0);

        if (memo.containsKey(a) == true) {
            String memoB = memo.get(a);
            if (memoB.equals(b) == true) {
                return;
            }
        }
        memo.put(a, b);
        if (Character.isLowerCase(aLetter) == true) {
            abbreviation(a.substring(1), b);
        }
        if (Character.toUpperCase(aLetter) == b.charAt(0)) {
            abbreviation(a.substring(1), b.substring(1));
        }

        return;
    }

동적계획법을 활용하여 미리 계산된 값이 있는지 확인하는 코드 이다.

A 문자열이 ddA이고 B문자열이 DC라고 가정하면 경우의 수는 비교 가능한 경우의 수는 

["ddA", "DC"], ["dA", "DC"], ["A", "C"], ["DdA", "DC"], ["dA", "C"], ["A", "C"] 이고 ["A", "C"]가 2번 나오는 계산을 한다. 만약 A뒤의 문자열이 더 길어지면 어떨까? 문자열 A와 문자열 B의 키, 밸류 값으로 중복 계산을 막는 코드를 넣으므로 시간 복잡도는 훨씬 나아질 것으로 기대된다. 

public class Main {
    public static void abbreviation(String a, String b) {
        if (result == true) return;

        if (a.length() < b.length()) return;

        if (b.length() == 0) {
            if (hasUpperCase(a) == false) {
                result = true;
            }
            return;
        }

        char aLetter = a.charAt(0);

        if (memo.containsKey(a) == true) {
            String memoB = memo.get(a);
            if (memoB.equals(b) == true) {
                return;
            }
        }
        memo.put(a, b);
        if (Character.isLowerCase(aLetter) == true) {
            abbreviation(a.substring(1), b);
        }
        if (Character.toUpperCase(aLetter) == b.charAt(0)) {
            abbreviation(a.substring(1), b.substring(1));
        }

        return;
    }

    static boolean hasUpperCase(String a) {
        for (int i = 0; i < a.length(); i++) {
            if (Character.isUpperCase(a.charAt(i))) {
                return true;
            }
        }
        return false;
    }

    private static final Scanner scanner = new Scanner(System.in);
    private static boolean result = false;
    private static Map<String, String> memo = new HashMap<>();

    public static void main(String[] args) throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));

        int q = scanner.nextInt();
        scanner.skip("(\r\n|[\n\r\u2028\u2029\u0085])?");

        for (int qItr = 0; qItr < q; qItr++) {
            String a = scanner.nextLine();

            String b = scanner.nextLine();

            abbreviation(a, b);
            bufferedWriter.write(result == true ? "YES" : "NO");
            result = false;
            memo = new HashMap<>();
            bufferedWriter.newLine();
        }

        bufferedWriter.close();

        scanner.close();
    }
}

 

'알고리즘 풀이' 카테고리의 다른 글

[백준] 6996 애너그램  (0) 2020.04.08
[백준] 2579번 계단 오르기  (0) 2020.04.01
[HackerRank] Max Array Sum  (0) 2020.04.01
[백준] 2960번 에라토스테네스의 체  (0) 2020.03.27

+ Recent posts