-
[프로그래머스] 큰 수 만들기 / Swift프로그래밍/코딩테스트 2021. 5. 25. 00:46
[문제 보기]
더보기어떤 숫자에서 k개의 수를 제거했을 때 얻을 수 있는 가장 큰 숫자를 구하려 합니다.
예를 들어, 숫자 1924에서 수 두 개를 제거하면 [19, 12, 14, 92, 94, 24] 를 만들 수 있습니다. 이 중 가장 큰 숫자는 94 입니다.
문자열 형식으로 숫자 number와 제거할 수의 개수 k가 solution 함수의 매개변수로 주어집니다. number에서 k 개의 수를 제거했을 때 만들 수 있는 수 중 가장 큰 숫자를 문자열 형태로 return 하도록 solution 함수를 완성하세요.
제한 조건
- number는 1자리 이상, 1,000,000자리 이하인 숫자입니다.
- k는 1 이상 number의 자릿수 미만인 자연수입니다.
입출력 예
number, k, return"1924" 2 "94" "1231234" 3 "3234" "4177252841" 4 "775841" https://programmers.co.kr/learn/courses/30/lessons/42883
코딩테스트 연습 - 큰 수 만들기
programmers.co.kr
풀이 과정
프로그래머스에서의 문제조건에는 number와 k는 모두 1이상이라고 적혀있는데, 같은 경우는 어떤건지 싶어 실제 문제 출처에서 확인해보니 바로 이해가 갔다.
https://hsin.hr/coci/archive/2011_2012/contest4_tasks.pdf 문제를 여러번 다른 방법으로 시도했다.
처음에 풀었을 때는 조건에 맞는 가능한 모든 조합을 전부 구해서 그중 가장 큰 값을 반환하는 방법으로 구현했다.
1. DFS 재귀로 조합을 구함 -> 시간 초과 + 실패(signal: illegal instruction (core dumped))
(signal: illegal instruction (core dumped))라는 오류를 처음 겪었는데, 찾아보니 인덱스 범위 오류 또는 옵셔널 오류 등이라고 해서 다시 코드를 살펴 봤는데 긴 String이 주어졌을 때 Int로 바꾸는 과정에서 오류인가 싶어 그 부분을 고쳐보니 테스트 2번은 해결되었다. 그 전에 재귀 깊이 제한 때문인건가 싶어서 DFS 스택으로 바꿔서 풀어보기도 했다. (2번째 시도)
첫번째 시도 결과 [1번 시도 코드 보기]
더보기// 1. DFS 재귀 조합 버전 import Foundation var combinationResult = [Int]() var numbers = [Int]() var needToRemove = [[Int]]() func combinationDFS(phase: Int, startIndex: Int, k: Int) { if phase == k { needToRemove.append(combinationResult) return } else { for i in (startIndex..<numbers.count) { combinationResult[phase] = i combinationDFS(phase: phase + 1, startIndex: i + 1, k: k) } } } func combination(_ number: String, _ k: Int) { numbers = number.map { Int(String($0))! } combinationResult = [Int](repeating: 0, count: k) combinationDFS(phase: 0, startIndex: 0, k: k) } func solution(_ number:String, _ k:Int) -> String { combination(number, k) // needToRemove에 있는 원소들 값에 해당하는 위치를 지워준다. var result = [Int]() needToRemove.forEach { (indices) in let remainder = numbers.enumerated() .filter { !indices.contains($0.offset) } .reduce(0) { $0 * 10 + $1.element } result.append(remainder) } return "\(result.max()!)" }
2. DFS 스택으로 조합을 구함 -> 시간초과
(signal: illegal instruction (core dumped))오류 때 재귀 깊이 때문인건가 싶어 스택을 이용한 방법으로 바꾸었다.
실제 오류 원인은 String->Int 변환 부분이었다.
// 2. DFS 스택 조합 버전 import Foundation var needToRemove = [[Int]]() func combinationWithStack(length: Int, k: Int) { var stack = [Int]() var temp = 0 stack.append(0) while !stack.isEmpty { if stack.count == k { needToRemove.append(stack) temp = stack.removeLast() continue } if temp + 1 == length { temp = stack.removeLast() if temp + 1 == length { continue } } temp += 1 stack.append(temp) } } func solution(_ number:String, _ k:Int) -> String { let numbers = number.map { Int(String($0))! } combinationWithStack(length: numbers.count, k: k) // needToRemove에 있는 원소들 값에 해당하는 위치를 지워준다. var result = [Int]() needToRemove.forEach { (indices) in let remainder = numbers.enumerated() .filter { !indices.contains($0.offset) } .reduce(0) { $0 * 10 + $1.element } result.append(remainder) } return "\(result.sorted(by: >)[0])" }
3. 2번 코드를 C++로 바꿈 -> 시간초과
C++은 좀 다른가 싶어 바꿔봤는데 역시나 시간초과가 나옴. 이때부터 새로 다시풀어야겠다고 생각함.
// 3. 2번에서 구현(DFS 스택 조합)한 것을 C++로 컨버팅한 버전 #include <iostream> #include <vector> #include <string> using namespace std; vector<int> numbers; vector<int> needToRemove; string maxValue = ""; void combinationWithStack(int length, int k) { vector<int> stack; int temp = 0; string remainder = ""; stack.push_back(temp); remainder.push_back('0' + numbers[temp]); while (stack.size() != 0) { if (stack.size() == k) { if (maxValue < remainder) { maxValue = remainder; } remainder.pop_back(); temp = stack.back(); stack.pop_back(); continue; } if (temp + 1 == length) { temp = stack.back(); stack.pop_back(); remainder.pop_back(); if (temp + 1 == length) { continue; } } temp += 1; stack.push_back(temp); remainder.push_back('0' + numbers[temp]); } } string solution(string number, int k) { for (int i = 0; i < number.size(); i++) { int tmp = number.at(i) - '0'; numbers.push_back(tmp); } combinationWithStack(number.size(), number.size() - k); string answer = maxValue; return answer; }
4. 최종코드 (그리디)
그리디의 기본 개념인 각 단계에서 최선의 선택을 하는 방법으로 해결했다.
여기서 각단계는 무엇을 이야기하는 걸까?
나는 아래와 같이 풀었다.
예를들어 입력값이 각각 [1,2,3,1,2,3,4], 제거할 횟수k = 3으로 주어졌을 때,
k는 전체 빼야 할 횟수(단계)라고 보고 1개씩 뺄때마다 가장 큰 값이 되도록 구했다.
어차피 빼야할 횟수(모든 뺀 다음의 수의 자릿수)는 정해져있으므로 가장 앞자리가 제일 크게 유지하도록 (각 단계에서 가장 최선의 선택을 유지) 했다.
스택을 이용해서 앞자리가 가장 큰 상태를 유지하도록 했다.
(코드에서 phase 1 부분)
[1,2,3,1,2,3,4] 에서 스택에 맨 위의 원소와 현재 비교하는 원소를 비교한다. 빈 스택일 경우에는 단순히 append해주기만 하고, 만약에 스택의 맨 위 원소가 비교원소보다 작다면, 그리고 아직 제거횟수(count == k)가 남아있다면 (앞자리가 클수록 큰 수가 되므로 ) pop해준다. 이 비교하는 과정을 스택의 맨 밑바닥까지 반복하거나 아니면 비교원소보다 큰 수가 나올때까지 반복한다. 이 과정의 결과로 스택에는 가장 큰 앞자리수가 차곡차곡 쌓이게 된다.
가장 바깥 반복문은 입력받은 [1,2,3,1,2,3,4]를 계속순회를 하는데, 반복문이 끝날 때까지 count가 0이 아니게 될 경우가 발생할 때가 있다. ( [1,0,1,0,1,0], k = 4 ) 이 경우는 실제로 문제에서 요구하는 k개의 숫자를 제거하지 못한 경우다. ( 스택에는 1110이 남아있어 4자리수지만 실제로 문제조건에 맞추려면 2자리수를 반환해야함 )
스택에는 아래서부터 위쪽 방향으로 내림차순으로 정리가 되어있는 상태이므로 아래보다는 위쪽에 상대적으로 작으므로 스택의 위쪽에서 부터 남은 갯수만큼 지워나간다. (코드에서 phase 2, 3 부분)
미처 지우지 못한 남은 갯수만큼 지운 결과로 문제 조건에 맞는 자릿수의 수가 확실하게 나오게 된다.
// 4. 최종 버전 import Foundation func solution(_ number:String, _ k:Int) -> String { let numbers = number.map { Int(String($0))! } var count = k var stack = [Int]() var lastIndex = numbers.count - 1 // phase 1 for (i, element) in numbers.enumerated() { while true { if let last = stack.last, last < element, count > 0 { stack.removeLast() count -= 1 } else { stack.append(element) break } } if count == 0 { lastIndex = i break } } // phase 2 let start = number.index(number.startIndex, offsetBy: lastIndex + 1) let range = start..<number.endIndex // phase 3 for _ in (0..<count) { stack.removeLast() } return stack.reduce("") { $0 + String($1) } + number[range] }
생각 정리
계속 시간초과가 떠서 최댓값을 구하는 과정같은 부분에서 자잘한 부분에서 최적화를 시도해보았는데 그다지 소용이 없었다.
결국에는 방법 자체를 바꾸어서 해결했다.
알고리즘 자체를 다른 방법으로 접근하려고 했으면 좀 더 빨리 풀었을 것 같다.
반응형'프로그래밍 > 코딩테스트' 카테고리의 다른 글
[프로그래머스] H-Index / Swift (0) 2021.05.25 [프로그래머스] 올바른 괄호 / Swift (0) 2021.05.25 [프로그래머스] 위장 / Swift (0) 2021.05.22 [프로그래머스] 카펫 / Swift (0) 2021.05.22 [프로그래머스] 가장 큰 수 / Swift (0) 2021.05.22