翻轉數字題目總結
246. Strobogrammatic Number
題目鏈接
A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).
Write a function to determine if a number is strobogrammatic. The number is represented as a string.
Example 1:
Input: “69”
Output: true
Example 2:
Input: “88”
Output: true
Example 3:
Input: “962”
Output: false
Abstract
- 如果一個數字反轉180後得到的數跟它本身相同,那麼可以說這個數字是對稱數。比如, 69 反轉180後還是69
- 據此推斷一個數字是否是對稱數
Idea
從數組兩端開始依次檢查每個字母對是否是對稱的字母對
Question to ask
什麼樣的字母對是180度對稱的?
Solution
- 把已知的對稱字符對放進map中,他們包括
map.put('1', '1');
map.put('8', '8');
map.put('6', '9');
map.put('9', '6');
- 維護兩個指針分別從兩端開始向中心,檢查當前指針所在的字母對是否是之前map所包含的對稱字母對
Code
public boolean isStrobogrammatic(String num) {
Map<Character, Character> map = new HashMap<>();
map.put('0', '0');
map.put('1', '1');
map.put('8', '8');
map.put('6', '9');
map.put('9', '6');
int left = 0;
int right = num.length() - 1;
while(left <= right){
char l = num.charAt(left);
char r = num.charAt(right);
// m1
if (!map.containsKey(l) || map.get(l) != r){
return false;
}
left++;
right--;
}
return true;
}
Time Complexity
O(n)
Space Complexity
O(1)
只存儲對稱的字母對,常數空間
Mistake
- 注意如果給的字符串長度是奇數時,中間的字母也需要進行檢查
Summary
- 如果題目比較抽象,可以舉幾個例子幫助理解
- 注意處理一些邊界的情況
247. Strobogrammatic Number II
題目鏈接
A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).
Find all strobogrammatic numbers that are of length = n.
Example:
Input: n = 2
Output: [“11”,“69”,“88”,“96”]
Abstract
- 找出長度爲n的對稱數字字符串
Idea
- 已知那些字符對是對稱的,根據這些字符對可以拼出符合要求長度的字符串
- 可能要用到遞歸
Question to ask
- 拼湊長度爲奇數與長度爲偶數的字符串過程中有什麼不同?如何處理邊界情況?
- 如果使用遞歸,退出條件是什麼?
Solution
- 用遞歸解題,準備一個長度爲n的字符數組,分別從兩端開始填充,有左右兩個指針記錄當前要填充的字母位置
- 退出條件:
- 如果n爲奇數,那麼左右指針相等時,就是要填充中間字符的時候,這時候的選擇有{0, 1, 8}
- 如果n爲偶數,那麼左右指針重疊(left > right)時
Code
Map<Character, Character> map = new HashMap<>();
char[] middle = {'0', '1', '8'};
public List<String> findStrobogrammatic(int n) {
List<String> res = new ArrayList<>();
if (n == 0) return res;
map.put('0', '0');
map.put('1', '1');
map.put('8', '8');
map.put('6', '9');
map.put('9', '6');
helper(res, n, new char[n], 0, n - 1);
return res;
}
private void helper(List<String> res, int n, char[] cs, int left, int right){
if (left == right){
for (char c: middle){
cs[left] = c;
res.add(new String(cs));
}
return;
}
// m1
if (left > right){
res.add(new String(cs));
return;
}
for (char k: map.keySet()){
// m2
if (k == '0' && left == 0 && left < right) continue;
cs[left] = k;
cs[right] = map.get(k);
helper(res, n, cs, left + 1, right - 1);
}
}
Time Complexity
The recursive depth is indeed O(N), but during each recursive call, we iterate through the list, and the list size increases exponentially with a factor of 5. Therefore, the time complexity should be 5 + 5^2 + 5^3 + … + 5^(N/2) ~= 5^(N/2)
遞歸的深度爲N,也就是要求字符串的長度。但是每次填充一個字符時都有5個選擇(5個對稱的字符對),所以總的複雜度近似於 5^(N/2)
Space Complexity
O(n)
Mistake
- 字符串長度是偶數的情況下,退出條件應該是left > right
- 需要考慮前綴零情況
Summary
- 需要總結熟悉遞歸回溯的套路
- 注意退出條件和邊界情況的處理
- 設計組成數字的問題,一定要注意前綴零的情況
248. Strobogrammatic Number III
題目鏈接
A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside down).
Write a function to count the total strobogrammatic numbers that exist in the range of low <= num <= high.
Example:
Input: low = “50”, high = “100”
Output: 3
Explanation: 69, 88, and 96 are three strobogrammatic numbers.
Note:
Because the range might be a large number, the low and high numbers are represented as string.
Abstract
- 題目給兩個數字,要求求出這個個數字間的所有對稱數
- 結果可包含這兩個數字
Idea
- 顯然還是需要用遞歸求解
- 從之前的題目我們已經知道如何求出特定長度的對稱數,那麼這個題目唯一的變量就是確保求出的對稱數是在給定的範圍內的
Question to ask
- 怎麼確認一個候選的對稱數是在給定範圍內的?
Solution
- 候選詞爲長度不小於下邊界的長度(low.length())而又不大於上邊界的長度(high.length())的詞
- 利用第二問中拼出長度爲len的對稱數的方法得到這些候選詞
- 把這些候選詞與上下邊界對比,最終篩選出要求的字符串
Code
int cnt = 0;
Map<Character, Character> map = new HashMap<>();
public int strobogrammaticInRange(String low, String high) {
map.put('0', '0');
map.put('1', '1');
map.put('8', '8');
map.put('6', '9');
map.put('9', '6');
for (int len = low.length(); len <= high.length(); len++){
helper(low, high, new char[len], 0, len - 1);
}
return cnt;
}
private void helper(String low, String high, char[] cs, int left, int right){
if (left > right){
String cand = new String(cs);
if ((cand.length() == low.length() && cand.compareTo(low) < 0) ||
(cand.length() == high.length() && cand.compareTo(high) > 0)){
return;
}
cnt++;
return;
}
for (char cl: map.keySet()){
char cr = map.get(cl);
// m1
if (cl == '0' && left == 0 && left != right) continue;
if (left < right || (left == right && cl == cr)){
//char ol = cs[left];
//char or = cs[right];
cs[left] = cl;
cs[right] = cr;
helper(low, high, cs, left + 1, right - 1);
//cs[left] = ol;
//cs[right] = or;
}
}
}
Time Complexity
根據第二問,複雜度在不考慮剪枝的情況下應該是O(5^n*(n-m)), n, m分別爲上下邊界字符串的長度
Space Complexity
O(n)
Mistake
- 注意無效前綴零的情況是,left指針爲0並且left < right
Summary
- 訓練如何把複雜問題轉換成熟悉的簡單問題
1056. Confusing Number
題目鏈接
Given a number N, return true if and only if it is a confusing number, which satisfies the following condition:
We can rotate digits by 180 degrees to form new digits. When 0, 1, 6, 8, 9 are rotated 180 degrees, they become 0, 1, 9, 8, 6 respectively. When 2, 3, 4, 5 and 7 are rotated 180 degrees, they become invalid. A confusing number is a number that when rotated 180 degrees becomes a different number with each digit valid.
Example 1:
Input: 6
Output: true
Explanation:
We get 9 after rotating 6, 9 is a valid number and 9!=6.
Example 2:
Input: 89
Output: true
Explanation:
We get 68 after rotating 89, 86 is a valid number and 86!=89.
Example 3:
Input: 11
Output: false
Explanation:
We get 11 after rotating 11, 11 is a valid number but the value remains the same, thus 11 is not a confusing number.
Example 4:
Input: 25
Output: false
Explanation:
We get an invalid number after rotating 25.
Note:
0 <= N <= 10^9
After the rotation we can ignore leading zeros, for example if after rotation we have 0008 then this number is considered as just 8.
Abstract
- 如果一個數字反轉180後得到的數還是一個有效的數字,那麼可以說這個數字是迷惑數。比如, 699 反轉180後還是669
- 據此推斷一個數字是否是一個迷惑數
- 與對稱數不同的一點是不再要求經過翻轉操作後的數字與原來的數字相同弄了,事實上難度降低了
Idea
從數組兩端開始依次檢查每個字母對是否是對稱的字母對, 對於奇數長度的字符串,中間的字符還可以是{6, 9}
Question to ask
中間字符的判定有什麼變化?
Solution
- 因爲這次問題的輸入是一個整型數字,所以可以用數學的方法翻轉
- 把一個數字左右位交換的套路:
int conf = 0;
while(src > 0){
conf = conf * 10 + (src % 10);
src /= 10;
}
- 交換的時候不僅需要左右位上的數字交換,另外還需要把他們鏡面交換,也就需要用到對稱的字符對
Code
public boolean confusingNumber(int N) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 0);
map.put(1, 1);
map.put(8, 8);
map.put(6, 9);
map.put(9, 6);
int src = N;
int conf = 0;
while(src > 0){
//m1
if (!map.containsKey(src % 10)){
return false;
}
conf = conf * 10 + (map.get(src % 10));
src /= 10;
}
return conf != N;
}
Time Complexity
O(len/2)
Space Complexity
O(1)
Mistake
- 注意檢查不是對稱字符對的情況,可以shortcut
Summary
通過一個熟悉的問題解決類似的問題
1088. Confusing Number II
We can rotate digits by 180 degrees to form new digits. When 0, 1, 6, 8, 9 are rotated 180 degrees, they become 0, 1, 9, 8, 6 respectively. When 2, 3, 4, 5 and 7 are rotated 180 degrees, they become invalid.
A confusing number is a number that when rotated 180 degrees becomes a different number with each digit valid.(Note that the rotated number can be greater than the original number.)
Given a positive integer N, return the number of confusing numbers between 1 and N inclusive.
Example 1:
Input: 20
Output: 6
Explanation:
The confusing numbers are [6,9,10,16,18,19].
6 converts to 9.
9 converts to 6.
10 converts to 01 which is just 1.
16 converts to 91.
18 converts to 81.
19 converts to 61.
Example 2:
Input: 100
Output: 19
Explanation:
The confusing numbers are [6,9,10,16,18,19,60,61,66,68,80,81,86,89,90,91,98,99,100].
Note:
1 <= N <= 10^9
Abstract
- 輸入爲N,求1到N之間的迷惑數
Idea
- 依次檢查1到N的數字是否是迷惑數
- 從已知的迷惑數構造出更多的迷惑數
Question to ask
- 已知迷惑字符對,怎麼利用它們構造迷惑數?
Solution
- 用遞歸的方法構造迷惑數,從迷惑字符對開始不斷在最高位和最低位添加新的迷惑字符對
Code
Map<Integer, Integer> map = new HashMap<>();
int res = 0;
public int confusingNumberII(int N) {
map.put(0, 0);
map.put(1, 1);
map.put(8, 8);
map.put(6, 9);
map.put(9, 6);
helper(N, 0);
return res;
}
private void helper(int N, long cur){
if (isConfusing(cur)){
res++;
}
for (int k:map.keySet()){
// m1
if (cur * 10 + k <= N && cur * 10 + k != 0){
helper(N, cur * 10 + k);
}
}
}
private boolean isConfusing(long cur){
long src = cur;
long conf = 0;
while(src > 0){
conf = conf * 10 + (map.get((int)src % 10));
src /= 10;
}
return conf != cur;
}
Time Complexity
O(5^m)
Space Complexity
O(1)
Mistake
- 需要檢查新建的迷惑數是否在範圍內,是否爲零
Summary
翻轉數字類型題目常常涉及對回溯法的考查