|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: (Leetcode) 647 - Decode Ways 풀이 |
| 4 | +categories: [스터디-알고리즘] |
| 5 | +tags: |
| 6 | + [자바, java, 리트코드, Leetcode, 알고리즘, string, dp, dynamic programming] |
| 7 | +date: 2024-07-08 17:50:00 +0900 |
| 8 | +toc: true |
| 9 | +--- |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +기회가 되어 [달레님의 스터디](https://github.com/DaleStudy/leetcode-study)에 참여하여 시간이 될 때마다 한문제씩 풀어보고 있다. |
| 14 | + |
| 15 | +[https://neetcode.io/practice](https://neetcode.io/practice) |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +[https://leetcode.com/problems/decode-ways/description/](https://leetcode.com/problems/decode-ways/description/) |
| 20 | + |
| 21 | +## dfs를 통한 모든 경우의 수 탐색하기 |
| 22 | + |
| 23 | +일단 가장 단순하게 접근해보았다. 해보면 시간 초과로 실패한다. |
| 24 | + |
| 25 | +```java |
| 26 | +class Solution { |
| 27 | + public int numDecodings(String s) { |
| 28 | + Counter counter = new Counter(); |
| 29 | + dfs(counter, s, 0, 1); |
| 30 | + dfs(counter, s, 0, 2); |
| 31 | + return counter.counter; |
| 32 | + } |
| 33 | + |
| 34 | + public void dfs(Counter counter, String s, int start, int end) { |
| 35 | + if(end > s.length()) { |
| 36 | + return; |
| 37 | + } |
| 38 | + |
| 39 | + String substring = s.substring(start, end); |
| 40 | + int value = Integer.parseInt(substring); |
| 41 | + if (value <= 0 || value > 26 || substring.startsWith("0")) { |
| 42 | + return; |
| 43 | + } |
| 44 | + |
| 45 | + if (end == s.length()) { |
| 46 | + counter.increment(); |
| 47 | + } |
| 48 | + |
| 49 | + dfs(counter, s, end, end + 1); |
| 50 | + dfs(counter, s, end, end + 2); |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +class Counter { |
| 55 | + int counter = 0; |
| 56 | + |
| 57 | + void increment() { |
| 58 | + counter++; |
| 59 | + } |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +## 규칙성으로 풀기 (DP) |
| 64 | + |
| 65 | +우리가 각 자리에서 고려할 수 있는 케이스는 2가지이다. |
| 66 | +한 자리를 선택할지, 두 자리를 선택할지이다. |
| 67 | + |
| 68 | +각 자리까지의 경우의 수를 dp 배열에 저장한다. |
| 69 | + |
| 70 | +첫번쨰 자리는 하나밖에 선택하지 못한다. |
| 71 | +맨 앞자리가 0인 경우는 불가능이다. 따라서 0을 반환해주고 그 외의 경우에는 1을 넣어준다. (`dp[0] = 1`) |
| 72 | + |
| 73 | +그 이후부터가 조금 복잡하다. |
| 74 | + |
| 75 | +우선 해당 수가 0보다 큰 자리인지를 확인한다. |
| 76 | +0보다 크다면 혼자서 선택이 가능하다. 이 경우 앞 자리(n-1)의 경우의 수를 물려받는다. (`dp[i] = dp[i - 1]`) |
| 77 | +앞의 수가 무엇이 되었든 혼자서 독립적으로 선택이 가능하다. |
| 78 | + |
| 79 | +이제 두 숫자를 묶어서 선택하는 케이스를 고려해보자. |
| 80 | +이 경우에는 앞자리가 0이면 불가능 하다. (`prevDigit == 0`) |
| 81 | +문제에 있는 `06` 이 이 케이스에 속한다. |
| 82 | +그리고 두 자리를 선택했을 때 26보다 작거나 같아야 한다. (`twoDigit <= 2`) |
| 83 | +27보다 큰 경우는 decode 될 수 없다. |
| 84 | +만약 두 자리로 선택이 가능할 경우 n-2의 경우의 수를 물려받는다. |
| 85 | +다만 앞에서 한자리 수를 선택했을 경우도 있기 때문에 기존의 dp[i] 에 더하기를 해준다. (`dp[i] = dp[i] + dp[i - 2]`) |
| 86 | +예외적으로 만약 i가 1인 상태라면 이전의 케이스가 없다. 따라서 1을 더해준다. (`dp[i] = dp[i] + 1`) |
| 87 | + |
| 88 | +```java |
| 89 | +class Solution { |
| 90 | + public int numDecodings(String s) { |
| 91 | + int[] dp = new int[s.length()]; |
| 92 | + |
| 93 | + if (s.charAt(0) == '0') { |
| 94 | + return 0; |
| 95 | + } |
| 96 | + dp[0] = 1; |
| 97 | + |
| 98 | + for (int i = 1; i < s.length(); i++) { |
| 99 | + int oneDigit = Integer.parseInt(String.valueOf(s.charAt(i))); |
| 100 | + if (oneDigit > 0) { |
| 101 | + dp[i] = dp[i - 1]; |
| 102 | + } |
| 103 | + |
| 104 | + int prevDigit = Integer.parseInt(String.valueOf(s.charAt(i - 1))); |
| 105 | + if (prevDigit == 0) { |
| 106 | + continue; |
| 107 | + } |
| 108 | + |
| 109 | + int twoDigit = prevDigit * 10 + oneDigit; |
| 110 | + if (twoDigit <= 26) { |
| 111 | + if (i > 2) { |
| 112 | + dp[i] = dp[i] + dp[i - 2]; |
| 113 | + } else { |
| 114 | + dp[i] = dp[i] + 1; |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + return dp[s.length() - 1]; |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +### TC, SC |
| 125 | + |
| 126 | +시간 복잡도는 O(n), 공간 복잡도는 O(n)이다. |
| 127 | +이런식으로 최근 데이터만 재사용 하는 경우에는 공간복잡도를 O(1) 으로도 줄일 수 있을 것이다. |
| 128 | +최근의 데이터가 아닌 이전 데이터들은 더 이상 참조되지 않기 때문에 필요한 공간만 만들어서 보관하면 된다. |
0 commit comments