문자열 폭발

구해야 하는 것

  • 주어진 문자열에서 폭발 문자열을 전부 제거하라

주어진 자료

  • 문자열이 폭발 문자열을 포함하고 있으면 모든 폭발 문자열이 폭발. 남은 문자열을 순서대로 이어 붙여 새로운 문자열 만듦
  • 새로 생긴 문자열에 폭발 문자열 포함 가능
  • 폭발은 폭발 문자열이 문자열에 없을 때까지 계속됨

조건

  • 1 <= 문자열 길이 <= 1,000,000
  • 1 <= 폭발 문자열 길이 <= 36
  • 모든 폭발이 끝난 후 남아있는 문자가 없으면 "FRULA" 출력

계획 1

  1. 문자열에 폭발 문자열이 포함되어 있으면 모두 없앤다.
  2. 폭발 문자열이 더이상 존재하지 않을 때까지 계속 없앤다.
  3. 폭발 문자열 제거가 끝났을 때 문자열 길이가 0이면 FRULA를 출력한다.
  4. 그렇지 않으면 문자열을 출력한다.

실행 1 → 메모리 초과

public class Main {
    public void solution() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = br.readLine();
        String explodeStr = br.readLine();
        boolean hasExplodeStr = true;
        
        while(hasExplodeStr) {
            hasExplodeStr = input.contains(explodeStr); 
            if(hasExplodeStr) { // 폭발 문자열이 포함 되어 있으면 모두 제거
                input = input.replaceAll(explodeStr, "");
            }
        }
        
        if(input.length() == 0) {
            System.out.println("FRULA");
        } else {
            System.out.println(input);
        }
    }
    
    public static void main(String[] args) throws IOException {
        new Main().solution();
    }
}

계획 2

  1. 입력받은 문자열을 맨 뒤부터 스택에 쌓는다.
  2. 하나씩 꺼내보면서 폭발 문자열의 첫 번째 문자와 일치하면, 그때부터 폭발 문자열 길이만큼 돌면서 폭발 문자열과 계속 일치하는지 확인한다. 그리고 tmp에 저장해놓는다. 중간에 일치하지 않으면 isExplodeStr를 false로 바꾼 후 반복문 탈출한다.
  3. isExplodeStr가 false면 tmp를 newStr에 붙인다.
  4. 그렇게 만든 새로운 문자열이 폭발 문자열 제거 전의 문자열(input)과 같으면 더이상 폭발 문자열이 없는 것이므로 input을 출력한다.
  5. input의 길이가 0이면 "FRULA"를 출력한다.

실행 2 → 여전히 메모리 초과

public class Main {
    // 입력 받은 문자열을 맨 뒤부터 스택에 쌓기
    public Stack<Character> addStack(String input) {
        Stack<Character> stack = new Stack<>();

        for (int i = input.length() - 1; i >= 0; i--) {
            stack.push(input.charAt(i));
        }

        return stack;
    }
    
    // 새로운 문자열 만들기
    public String makeNewStr(Stack<Character> stack, String explodeStr) {
        String newStr = "";
        
        while(!stack.isEmpty()) {
            char cur = stack.pop();

            if (cur != explodeStr.charAt(0)) { // 폭발 문자열이 아니면
                newStr += cur; // 새로운 문자열에 더함
            } else { // 폭발 문자열의 첫 번째 문자와 일치하면
                String tmp = "";
                tmp += cur;
                Boolean isExplodeStr = true;
                
                // 폭발 문자열 길이만큼 돌면서 일치하는지 확인
                for (int i = 1; i < explodeStr.length(); i++) {
                    if (stack.peek() != explodeStr.charAt(i)) { // 돌다 보니 폭발 문자열이 아닌 경우
                        isExplodeStr = false;
                        break;
                    } else {
                        cur = stack.pop();
                        tmp += cur;
                    }
                }
                if (!isExplodeStr) { // 폭발 문자열이 아니었으면 tmp에 저장된 것을 newStr에 붙여줌
                    newStr += tmp;
                }
            }
        }
        
        return newStr;
    }
    
    public void solution() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = br.readLine();
        String explodeStr = br.readLine();

        Boolean hasExplodeStr = true;
        while(hasExplodeStr) {
            // 입력받은 문자열을 맨 뒤부터 스택에 쌓음
            Stack<Character> stack = new Stack<>();
            stack = addStack(input);

            // 폭발 문자열을 제거해서 새로운 문자열을 만듦
            String newStr = makeNewStr(stack, explodeStr);

            // 폭발 문자열 제거 전과 제거 후가 같으면 더이상 폭발 문자열이 없는 것이므로 결과 출력
            if(input.equals(newStr)) {
                hasExplodeStr = false;
            } else {
                input = newStr;
            }
        }
        
        // 문자열의 길이가 0이 되면 "FRULA"를 출력
        if(input.length() == 0) {
            System.out.println("FRULA");
        } else {
            System.out.println(input);
        }
    }
    
    public static void main(String[] args) throws IOException {
        new Main().solution();
    }
}

계획 3

  1. 입력받은 문자열을 스택에 쌓는다.
  2. 쌓다가 스택 크기가 폭발 문자열 크기와 같아지면 폭발 문자열인지 검사한다.
  3. 폭발 문자열이면 폭발 문자열 길이만큼 pop() 한다.
  4. 문자열 길이만큼 다 돌았으면 스택에서 꺼내서 문자열을 만든다.
  5. 문자열 길이가 0이면 "FAULA" 출력하고, 그렇지 않으면 문자열을 출력한다.

실행 3

public class Main {
    public void solution() throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String input = br.readLine();
        String explodeStr = br.readLine();
        Stack<Character> stack = new Stack<>();
        
        for(int i = 0; i < input.length(); i++) {
            stack.push(input.charAt(i)); // 입력받은 문자열을 스택에 쌓다가
            
            if(stack.size() >= explodeStr.length()) { // 스택 크기가 폭발 문자열 크기 이상이 되면 검사 시작
                Boolean isExplodeStr = true;
                for(int j = 0; j < explodeStr.length(); j++) {
                    char cur = stack.get(stack.size() - explodeStr.length() + j);
                    if(cur != explodeStr.charAt(j)) {
                        isExplodeStr = false;
                        break;
                    }
                }
                
                if(isExplodeStr) { // 폭발 문자열이면 그 길이만큼 pop()
                    for(int j = 0; j < explodeStr.length(); j++) {
                        stack.pop();
                    }
                }
            }
        }

        // 결과 출력
        StringBuilder sb = new StringBuilder();
        for(Character c : stack) {
            sb.append(c);
        }
        if(sb.length() == 0) {
            System.out.println("FRULA");
        } else {
            System.out.println(sb.toString());
        }
    }
    
    public static void main(String[] args) throws IOException {
        new Main().solution();
    }
}

반성

  • 예제 입력에 대한 결과는 맞게 잘 나오는데, 메모리 초과 때문에 삽질을 좀 했다. 우선 처음에는 '그냥 스택 안 써도 폭발 문자열을 replaceAll()로 다 없애버리면 되는 간단한 문제 아닌가?' 라고 생각했었는데, replaceAll()은 내부적으로 많은 Garbage Collection 객체가 생성되어 메모리를 효율적으로 사용할 수 없으므로 스택으로 풀어야 한다는 것을 알게 됐다.
    그래서 스택으로 바꿔서 풀었는데도 여전히 메모리 초과가 발생해서 좀 혼란스러웠다. 지금 보니까 스택을 사용하긴 했으나, 문자열을 '끝까지' 다 스택에 넣었다가 빼는 방식이기 때문에 메모리 초과가 난 것 같다.
    결국 다른 사람의 풀이를 좀 참고해서 스택 관리하는 로직은 잘 짰는데, 이번에는 시간 초과가 났다. String 대신 StringBuilder를 쓰니까 통과 되었는데, 찾아보니까 속도 차이가 엄청 나더라.. 앞으로는 그냥 StringBuilder를 쓰도록 해야겠다.