[알고리즘 문제풀이] 프로그래머스 큰 수 만들기

고럭키·2021년 4월 1일
0

알고리즘 문제풀이

목록 보기
9/68

라인 코테에서 떨어지고 이런 저런 생각이 많이 들었다. 사고력이나 프로그래밍 스킬이나 해당 문제를 풀어낼 수 없는 정도의 수준은 아니라고 생각이 되는데, 문제 풀이 속도가 너무 느리다는 판단이 들었다. 어떻게 문제 풀이 속도를 늘릴 수 있을까 많은 생각이 들었다.

속상하기도 했지만 처음 취준 시작할 때 생각했듯이 코테는 다른 전형에 비해서 솔직하고, 노력에 비례한다고 생각한다. (아마도?) 내가 노력을 더 해서 더 많은 문제를 풀었다면 풀이를 떠올리는 과정이든, 그것을 코드로 표현해내는 과정에서의 스킬이나 함수 사용이든 그 어떤 부분에서라도 시간이 줄어들었을 것이다. 그렇기에 결국은 내 노력이 부족했다고 생각한다 !

또 다시 새로운 코테들이 다가오고 우선은 더 많은 문제들을 풀어보는 방법뿐이 없지 않을까 생각한다 ! 화이팅 ! 🔥🔥🔥🔥🔥🔥

그래서 오늘 푼 문제는 프로그래머스 고득점 키트 그리디 알고리즘 분류에 있는 level2의 문제이다.
https://programmers.co.kr/learn/courses/30/lessons/42883

문제 설명

어떤 숫자에서 k개의 수를 제거했을 때 얻을 수 있는 가장 큰 숫자를 구하려 합니다.

예를 들어, 숫자 1924에서 수 두 개를 제거하면 [19, 12, 14, 92, 94, 24] 를 만들 수 있습니다. 이 중 가장 큰 숫자는 94 입니다.

문자열 형식으로 숫자 number와 제거할 수의 개수 k가 solution 함수의 매개변수로 주어집니다. number에서 k 개의 수를 제거했을 때 만들 수 있는 수 중 가장 큰 숫자를 문자열 형태로 return 하도록 solution 함수를 완성하세요.

제한 조건

  • number는 1자리 이상, 1,000,000자리 이하인 숫자입니다.
  • k는 1 이상 number의 자릿수 미만인 자연수입니다.

입출력 예

numberkreturn
"1924"2"94"
"1231234"3"3234"
"4177252841"4"775841"

풀이 방법

큰 수와 관련된 알고리즘 문제는 굉장히 많다. 그러한 문제들을 해결할 때 생각해야 하는 핵심은 오직 하나다 ! 큰 수를 만들기 위해서는 자릿수가 큰, 즉 앞에 있는 숫자들이 커야한다 !

이 문제도 역시 그러한 관점에서 풀이 방법을 고민해 나갔다. 먼저 k개를 모두 제거할 때 까지 반복문을 수행해야 한다. 그러면서 k개를 제거해서 앞에 큰 숫자들이 올 수 있도록 만들어야 한다. 그렇기 때문에 나는 입력 문자열에서 k개 이하를 제거해서 맨 앞에 올 수 있는 범위까지를 탐색하며 가장 큰 수를 찾고, 그 숫자는 최종 결과를 담을 String 변수에 저장하고 그 앞의 숫자들은 제거하였다. 그리고 제거한 개수만큼 k를 감소시켜 주었다. 그리고 number는 삭제하거나 확정한 숫자를 제거한, 즉 탐색하며 찾은 가장 큰 수 뒤의 부분만으로 변경시켜주었다. 이 과정을 반복하고 k개 만큼 제거하여 반복문이 종료되면, 최종적으로 반환되는 값은 확정된 문자열 + 남은 문자열이다.

이렇게만 알고리즘을 작성했을 때, 위에 제공된 테스트 케이스는 모두 만족을 하여 제출을 하였으나, 마지막 테스트 케이스에서 실패가 떴다. 그래서 내가 생각하지 못 한 코너 테스트 케이스를 찾아서 개선을 시도했다. 우선 그 코너 케이스들은 아래와 같다.

코너 케이스

numberkreturn
"999"1"99"
"9991"2"99"
"1119"2"19"

여기서의 문제는 입력 문자열에서 k개 이하를 제거해서 맨 앞에 올 수 있는 범위 내에서 가장 큰 수가 맨 앞인 경우이다. 이 경우에는 해당 loop에서는 제거할 문자가 없어 k가 감소되지 않는다. 그래야 남은 문자열에서 k개를 제거하는 경우까지 계산할 수 있기 때문이다. 하지만 남은 문자열의 길이가 k인 경우, 즉 이제 남은 문자열들을 제거하면 되는 경우에 index out of range exception이 발생하는 것이다.

그래서 만약 남은 문자열의 길이가 k와 같게 된다면, 이미 큰 숫자들부터 앞에 채택한 후이고, 남은 문자열은 모두 제거해야 하는 상황이기 때문에 남은 문자열을 모두 지워주고 반복문을 종료하는 조건 하나를 추가해주었다.

코드

public class Solution {
    public static String solution(String number, int k) {
        StringBuilder answer = new StringBuilder();
        int max, index;
        while (k > 0){
            if(k >= number.length()){
                number = "";
                break;
            }
            max = 0;
            index = 0;
            for(int i=0; i<=k; i++){
                if((number.charAt(i)-'0') > max){
                    max = number.charAt(i)-'0';
                    index = i;
                }
            }
            answer.append(number.charAt(index));
            number = number.substring(index+1);
            k-=index;
        }
        return answer + number;
    }
}

개선된 풀이

문제 풀이를 완료한 후 다른 사람의 코드를 살펴보았다. 그러면서 이 문제의 분류가 왜 greedy인지 그리고 더 간단하게 풀이할 수 있는 방법을 알 수 있었다. 이 문제는 단순히 문자열을 순차적으로 탐색하며 현재의 숫자가 앞의 숫자보다 크다면, 앞의 숫자들을 제거하기만 하면 되는 것이었다. 내가 처음 언급했던 큰 수 만들기의 핵심이 전부였던 것이다. 왜 문자를 꼬아 생각했는지 모르겠다. 알고리즘이 직관적이지 않기도 하지만 시간 효율성 측면에서도 number = 1929, k = 2인 경우에 내 알고리즘은 이미 192까지 탐색하여 9가 제일 큰 수라는 것을 확인한 후 1을 제거하고 9를 확보한 후에 29에 대해서 같은 과정을 반복하게 되어있기 때문에 비효율 적이다 !

스택을 이용하여 처음이거나, 앞의 문자보다 작다면 스택에 넣어준다. 하지만 앞의 문자보다 크고, k가 아직 0이 아니라면 반복하여 앞의 문자를 제거해준다. 그리고 현재 문자를 넣어준다. 결과적으로 k개가 제거되거나, 앞에가 이미 큰 숫자들이었다면 뒤의 문자들은 들어가지 않게 되면서 최종결과대로 스택에 문자가 쌓이게 된다.

개선된 풀이 코드

import java.util.Stack;
class Solution {
    public String solution(String number, int k) {
        char[] result = new char[number.length() - k];
        Stack<Character> stack = new Stack<>();

        for (int i=0; i<number.length(); i++) {
            char c = number.charAt(i);
            while (!stack.isEmpty() && stack.peek() < c && k-- > 0) {
                stack.pop();
            }
            stack.push(c);
        }
        for (int i=0; i<result.length; i++) {
            result[i] = stack.get(i);
        }
        return new String(result);
    }
}

위에 언급한 방법으로 풀이된 코드를 가져왔다. ( 이거 문제 풀어야만 볼 수 있는데 포스팅 해도 되겠쥬 ? 혹시 문제된다면 댓글.. 달아주세오.. ) 같은 방식으로 내가 코드를 새로 짜보면서 느꼈는데 한 줄 한 줄 이유가 있는, 필요 없는 줄이 하나도 없는 코드다 요곤.. 내가 짜도 비효율적이거나 예외 잡고나면 이거랑 똑같이 나온당.. 정말 최적화 되어있는 코드 ! 괜히 좋아요 많이 받은 게 아니다 !

추가 정보

+) 원래 내 풀이대로 문제를 풀 때, 문자열에서 특정 index의 문자를 제거하는 방법을 찾아보았다. 나는 따로 사용하지 않아서 기록만 해두려고 한다. String builder의 deleteCharAt()함수를 이용하면 된다. 또한 여러개의 문자를 삭제할 경우에는 delete()라는 함수도 존재한다.

0개의 댓글