diff --git a/_posts/2024-07-08-leetcode-91.md b/_posts/2024-07-08-leetcode-91.md new file mode 100644 index 0000000..5067fd6 --- /dev/null +++ b/_posts/2024-07-08-leetcode-91.md @@ -0,0 +1,128 @@ +--- +layout: post +title: (Leetcode) 647 - Decode Ways 풀이 +categories: [스터디-알고리즘] +tags: + [자바, java, 리트코드, Leetcode, 알고리즘, string, dp, dynamic programming] +date: 2024-07-08 17:50:00 +0900 +toc: true +--- + +--- + +기회가 되어 [달레님의 스터디](https://github.com/DaleStudy/leetcode-study)에 참여하여 시간이 될 때마다 한문제씩 풀어보고 있다. + +[https://neetcode.io/practice](https://neetcode.io/practice) + +--- + +[https://leetcode.com/problems/decode-ways/description/](https://leetcode.com/problems/decode-ways/description/) + +## dfs를 통한 모든 경우의 수 탐색하기 + +일단 가장 단순하게 접근해보았다. 해보면 시간 초과로 실패한다. + +```java +class Solution { + public int numDecodings(String s) { + Counter counter = new Counter(); + dfs(counter, s, 0, 1); + dfs(counter, s, 0, 2); + return counter.counter; + } + + public void dfs(Counter counter, String s, int start, int end) { + if(end > s.length()) { + return; + } + + String substring = s.substring(start, end); + int value = Integer.parseInt(substring); + if (value <= 0 || value > 26 || substring.startsWith("0")) { + return; + } + + if (end == s.length()) { + counter.increment(); + } + + dfs(counter, s, end, end + 1); + dfs(counter, s, end, end + 2); + } +} + +class Counter { + int counter = 0; + + void increment() { + counter++; + } +} +``` + +## 규칙성으로 풀기 (DP) + +우리가 각 자리에서 고려할 수 있는 케이스는 2가지이다. +한 자리를 선택할지, 두 자리를 선택할지이다. + +각 자리까지의 경우의 수를 dp 배열에 저장한다. + +첫번쨰 자리는 하나밖에 선택하지 못한다. +맨 앞자리가 0인 경우는 불가능이다. 따라서 0을 반환해주고 그 외의 경우에는 1을 넣어준다. (`dp[0] = 1`) + +그 이후부터가 조금 복잡하다. + +우선 해당 수가 0보다 큰 자리인지를 확인한다. +0보다 크다면 혼자서 선택이 가능하다. 이 경우 앞 자리(n-1)의 경우의 수를 물려받는다. (`dp[i] = dp[i - 1]`) +앞의 수가 무엇이 되었든 혼자서 독립적으로 선택이 가능하다. + +이제 두 숫자를 묶어서 선택하는 케이스를 고려해보자. +이 경우에는 앞자리가 0이면 불가능 하다. (`prevDigit == 0`) +문제에 있는 `06` 이 이 케이스에 속한다. +그리고 두 자리를 선택했을 때 26보다 작거나 같아야 한다. (`twoDigit <= 2`) +27보다 큰 경우는 decode 될 수 없다. +만약 두 자리로 선택이 가능할 경우 n-2의 경우의 수를 물려받는다. +다만 앞에서 한자리 수를 선택했을 경우도 있기 때문에 기존의 dp[i] 에 더하기를 해준다. (`dp[i] = dp[i] + dp[i - 2]`) +예외적으로 만약 i가 1인 상태라면 이전의 케이스가 없다. 따라서 1을 더해준다. (`dp[i] = dp[i] + 1`) + +```java +class Solution { + public int numDecodings(String s) { + int[] dp = new int[s.length()]; + + if (s.charAt(0) == '0') { + return 0; + } + dp[0] = 1; + + for (int i = 1; i < s.length(); i++) { + int oneDigit = Integer.parseInt(String.valueOf(s.charAt(i))); + if (oneDigit > 0) { + dp[i] = dp[i - 1]; + } + + int prevDigit = Integer.parseInt(String.valueOf(s.charAt(i - 1))); + if (prevDigit == 0) { + continue; + } + + int twoDigit = prevDigit * 10 + oneDigit; + if (twoDigit <= 26) { + if (i > 2) { + dp[i] = dp[i] + dp[i - 2]; + } else { + dp[i] = dp[i] + 1; + } + } + } + + return dp[s.length() - 1]; + } +} +``` + +### TC, SC + +시간 복잡도는 O(n), 공간 복잡도는 O(n)이다. +이런식으로 최근 데이터만 재사용 하는 경우에는 공간복잡도를 O(1) 으로도 줄일 수 있을 것이다. +최근의 데이터가 아닌 이전 데이터들은 더 이상 참조되지 않기 때문에 필요한 공간만 만들어서 보관하면 된다.