X is a good number if after rotating each digit individually by 180 degrees, we get a valid number that is different from X. Each digit must be rotated - we cannot choose to leave it alone.
A number is valid if each digit remains a digit after rotation. 0, 1, and 8 rotate to themselves; 2 and 5 rotate to each other; 6 and 9 rotate to each other, and the rest of the numbers do not rotate to any other number and become invalid.
Now given a positive number N
, how many numbers X from 1
to N
are good?
Example: Input: 10 Output: 4 Explanation: There are four good numbers in the range [1, 10] : 2, 5, 6, 9. Note that 1 and 10 are not good numbers, since they remain unchanged after rotating.
Note:
- N will be in range
[1, 10000]
.
這道題定義了一種好數字,就是把每位上的數字自身翻轉一下,能得到一個不同的數字。翻轉規則定義為,0,1,和8翻轉后還為其本身,2和5,6和9可以互相翻轉。然后給了一個數字N,問 [1, N] 區間內共有多少個這樣的好數字。這道題大家踩👎的個數遠超頂👍的個數,貌似很多人不太喜歡這道給數字發好人卡的題,博主倒覺得這道題還不錯,感覺沒有太多的技巧,就是一個數字一個數字的驗證唄,對於每個數字,再一位一位的驗證唄。將驗證某個數字是否 Good 的操作寫到一個子函數中,遍歷數字的每一位的方法,可以通過不停的除以 10 來獲得,也可以像博主這樣通過轉變為字符串,再遍歷字符即可。翻轉規則中沒有提到的數字有三個,3,4,和7,說明這三個數字無法翻轉,若一旦被翻轉,則無法形成 valid 的數字,所以只要一旦遇到這三個數字中的一個,直接返回 false 即可。還有要注意的是,0,1,和8這三個數字翻轉后還是其本身,由於好數字的定義是需要翻轉一個不同的數字,所以若都是由這三個數字組成,翻轉后不會產生不同的數字,也不符合題意。所以需要2,5,6,和9這四個數字中至少出現一個,用一個 flag 來標記出現過這些數字,最后只要 check 這個 flag 變量即可,參見代碼如下:
解法一:
class Solution { public: int rotatedDigits(int N) { int res = 0; for (int i = 1; i <= N; ++i) { if (check(i)) ++res; } return res; } bool check(int k) { string str = to_string(k); bool flag = false; for (char c : str) { if (c == '3' || c == '4' || c == '7') return false; if (c == '2' || c == '5' || c == '6' || c == '9') flag = true;; } return flag; } };
其實這道題還有更好的解法呢,使用動態規划 Dynamic Programming 來做的,思路非常巧妙,博主深為嘆服。定義了一個長度為 N+1 的一維布爾型 DP 數組,其中 dp[i] 表示數字i的三種狀態,0表示數字i翻轉后不合法,1表示數字i翻轉后和原數相同,2表示數字i翻轉后形成一個不同的數字。那么根據題目中的定義可知,只有當 dp[i]=2 的時候才是好數字。那么下面來看狀態轉移方程吧,如果數字只有1位的話,那么判斷起來很簡單,如果是 0,1,和8中的一個,則 dp[i]=1,如果是 2,5,6,和9中的一個,則 dp[i]=2,並且結果 res 自增1。如果是剩下的三個數字 3,4,7中的一個不用更新,因為dp數組初始化就為0。下面來看數字i大於 10 的情況,非常的經典,dp[i] 的值其實可以從 dp[i/10] 和 dp[i%10] 這兩個狀態值轉移而來,由於更新的順序是從小到大,所以當要更新 dp[i] 的時候,dp[i/10] 和 dp[i%10] 這兩個狀態值已經算過了。為啥 dp[i] 的值是由這兩個狀態值決定的呢?因為每個數字都是相互獨立的翻轉,比如四位數字 abcd,可以拆分為三位數 abc,和個位數d,如果 abc 翻轉后仍是 abc,d翻轉后仍是d,說明 abcd 翻轉后仍是 abcd,所以 dp[i]=1,只要其中有一個大於1了,另外一個至少是1的話,那么說明可以翻轉成不同的數字,dp[i]=2,並且結果 res 自增1,參見代碼如下:
解法二:
class Solution { public: int rotatedDigits(int N) { int res = 0; vector<int> dp(N + 1); for (int i = 0; i <= N; ++i) { if (i < 10) { if (i == 0 || i == 1 || i == 8) dp[i] = 1; else if (i == 2 || i == 5 || i == 6 || i == 9) { dp[i] = 2; ++res; } } else { int a = dp[i / 10], b = dp[i % 10]; if (a == 1 && b == 1) dp[i] = 1; else if (a >= 1 && b >= 1) { dp[i] = 2; ++res; } } } return res; } };
討論:這題貌似還有 O(lgN) 的解法,比如這個帖子,但感覺比較 tricky,博主不是很理解,哪位看官大神們可以給博主講講~
Github 同步地址:
https://github.com/grandyang/leetcode/issues/788
參考資料:
https://leetcode.com/problems/rotated-digits/
https://leetcode.com/problems/rotated-digits/discuss/117975/Java-dp-solution-9ms
https://leetcode.com/problems/rotated-digits/discuss/264282/Java-O(logN)-0ms-100
https://leetcode.com/problems/rotated-digits/discuss/116530/O(logN)-Time-O(1)-Space