LeetCode-Backtracking-Easy
阿新 • • 發佈:2020-07-13
1. 二進位制手錶(leetcode-401)
二進位制手錶頂部有 4 個 LED 代表 小時(0-11),底部的 6 個 LED 代表 分鐘(0-59)。 每個 LED 代表一個 0 或 1,最低位在右側。 給定一個非負整數 n 代表當前 LED 亮著的數量,返回所有可能的時間。 示例: 輸入: n = 1 返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"] 提示: 輸出的順序沒有要求。 小時不會以零開頭,比如 “01:00” 是不允許的,應為 “1:00”。 分鐘必須由兩位陣列成,可能會以零開頭,比如 “10:2” 是無效的,應為 “10:02”。 超過表示範圍(小時 0-11,分鐘 0-59)的資料將會被捨棄,也就是說不會出現 "13:00", "0:61" 等時間。
1)暴力窮舉(推薦)
思路:
- 由題目要求:小時(0-11)、分鐘(0-59)可知,表示小時的燈最多亮3個,表示分鐘的燈最多亮5個。
- 窮舉表示小時的燈不亮、亮一盞、亮兩盞、亮三盞的情況;窮舉表示分鐘的燈亮0~5盞的情況。
- 根據指定的num,列出所有情況。
暴力窮舉
class Solution {
public List<String> readBinaryWatch(int num) { List<String> res = new ArrayList<>(); //if(num<0 || num>8) // return res; String[][] hstrs = {{"0"}, {"1","2","4","8"}, {"3","5","6","9","10"}, {"7","11"}}; // 沒亮燈、亮一個燈、亮兩個燈、亮三個燈。 String[][] mstrs = {{"00"}, {"01","02","04","08","16","32"}, {"03","05","06","09","10","12","17","18","20","24","33","34","36","40","48"}, {"07","11","13","14","19","21","22","25","26","28","35","37","38","41","42","44","49","50","52","56"}, {"15","23","27","29","30","39","43","45","46","51","53","54","57","58"}, {"31","47","55","59"}}; // 亮0~5個燈的各情況 for(int i=0; i<=Math.min(3,num); i++){ // i:表示小時的燈的數量 if(num-i > 5) continue; String[] hstr = hstrs[i]; String[] mstr = mstrs[num - i]; for(int j=0; j<hstr.length; j++){ for(int k=0; k<mstr.length; k++){ res.add(hstr[j]+":"+mstr[k]); } } } return res; }
}
自動生成列舉
public List readBinaryWatch(int num) {
int[] nums = new int[]{8,4,2,1};
List<List<Integer>> h = new ArrayList<>();
help(nums, 0, 0, 1, 12, h);
h.get(0).add(0);
nums = new int[]{32, 16, 8, 4, 2, 1};
List<List<Integer>> m = new ArrayList<>();
help(nums, 0, 0, 1, 60, m);
m.get(0).add(0);
List<String> rs = new ArrayList<>();
for(int i = 0; i <= 3 && i <= num; i++){
if(num - i > 5) continue;
for (int j : h.get(i)) {
for (int k : m.get(num - i)) {
if (k >= 10) rs.add(j + ":" + k);
else rs.add(j + ":0" + k);
}
}
}
return rs;
}
public void help(int[] nums, int v, int start, int level, int max, List<List<Integer>> rs){
for(int i = start; i < nums.length; i++){
int value = v + nums[i];
if(value < max) {
while(rs.size() <= level) rs.add(new ArrayList<>());
rs.get(level).add(value);
help(nums, value, i + 1, level + 1, max, rs);
}
}
}
2)Integer.bitCount()法
bitCount實現的功能是計算一個(byte,short,char,int統一按照int方法計算)int,long型別的數值在二進位制下“1”的數量。
Integer.bitCount()法
class Solution {
public List<String> readBinaryWatch(int num) { List<String> res = new ArrayList<>(); for(int h=0; h<12; h++){ for(int m=0; m<60; m++){ if(Integer.bitCount(h) + Integer.bitCount(m) == num){ res.add(String.format("%d:%02d",h,m)); } } } return res; }
}
原始碼
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
3)回溯方法
DFS法
class Solution {
public List<String> readBinaryWatch(int num) { // 可選擇的內容陣列 int[] times = new int[]{8,4,2,1,32,16,8,4,2,1}; List<String> res = new ArrayList<>(); int hours = 0; int minutes = 0; dfs(times,num,0,hours,minutes,res); return res; } public void dfs(int[] times, int num,int start,int hours,int minutes, List<String> res){ if (0 == num){ if (hours < 12 && minutes < 60){ // 合理的值 StringBuilder sb = new StringBuilder(); sb.append(hours).append(':').append(minutes < 10 ? "0" + minutes: minutes); res.add(sb.toString()); // String result = hours + ":" + (minutes < 10 ? "0" + minutes: minutes); // String result = hours + ":" + (minutes < 10 ? "0" + minutes: minutes); // res.add(result); } } else { for (int i = start; i < times.length; i++) { if (i < 4){ // hours hours += times[i]; dfs(times,num-1,i+1,hours,minutes,res); hours -= times[i]; } else { minutes += times[i]; dfs(times,num-1,i+1,hours,minutes,res); minutes -= times[i]; } } } }
}
2. 字母大小寫全排列(leetcode-784)
給定一個字串S,通過將字串S中的每個字母轉變大小寫,我們可以獲得一個新的字串。返回所有可能得到的字串集合。
示例:
輸入: S = "a1b2"
輸出: ["a1b2", "a1B2", "A1b2", "A1B2"]
輸入: S = "3z4"
輸出: ["3z4", "3Z4"]
輸入: S = "12345"
輸出: ["12345"]
注意:
S 的長度不超過12。
S 僅由數字和字母組成。
1)佇列
從左往右依次遍歷字元,過程中保持 ans 為已遍歷過字元的字母大小全排列。
如果下一個字元 c 是字母,將當前已遍歷過的字串全排列複製兩份,在第一份的每個字串末尾新增 lowercase(c),在第二份的每個字串末尾新增 uppercase(c)。
如果下一個字元 c 是數字,將 c 直接新增到每個字串的末尾。
佇列
class Solution {
public List<String> letterCasePermutation(String S) { List<StringBuffer> ans = new ArrayList<>(); ans.add(new StringBuffer()); for(char ch : S.toCharArray()){ int n = ans.size(); if(Character.isLetter(ch)){ for(int i=0; i<n;i++){ ans.add(new StringBuffer(ans.get(i))); //ans.add(ans.get(i)); 演算法結果不對! ans.get(i).append(Character.toLowerCase(ch)); ans.get(n+i).append(Character.toUpperCase(ch)); } }else{ for(int i=0; i<n;i++){ ans.get(i).append(ch); } } } List<String> res = new ArrayList<>(); for(StringBuffer sb: ans){ res.add(sb.toString()); } return res; }
}
複雜度分析
時間複雜度:O(2^N * N),其中 N 是 S 的長度。
空間複雜度:O(2^N * N)。
2)二分掩碼
假設字串 S 有 B 個字母,那麼全排列就有 2^B 個字串,且可以用位掩碼 bits 唯一地表示。
例如,可以用 00 表示 a7b, 01 表示 a7B, 10 表示 A7b, 11 表示 A7B。注意數字不是掩碼的一部分。
根據位掩碼,構造正確的全排列結果。如果下一個字元是字母,則根據位掩碼新增小寫或大寫字母。 否則新增對應的數字。
二分掩碼
class Solution {
public List<String> letterCasePermutation(String S) { int B = 0; // 統計字元的個數 for(char ch: S.toCharArray()){ if(Character.isLetter(ch)) B++; } List<String> ans = new ArrayList(); for(int bits = 0; bits< (1<<B); bits++){ // 2^b種情況 int b = 0; StringBuilder word = new StringBuilder(); for(char ch: S.toCharArray()){ if (Character.isLetter(ch)){ if(((bits>> b++) & 1) ==1){ //遍歷bits的每一位(位元位) word.append(Character.toLowerCase(ch)); }else{ word.append(Character.toUpperCase(ch)); } }else{ word.append(ch); } } ans.add(word.toString()); } return ans; }
}
時間和空間複雜度:O(2^N∗N),與方法一分析相同。
3)深度優先遍歷
小技巧
大小寫的轉換:直接異或32。
大小寫之間差了32,是2的5次方,異或是不進位的加法。大寫的二進位制碼第5位是0,小寫的二進位制碼是1,所以異或就實現了大小寫的轉換。
深度優先遍歷實現一
class Solution {
public List<String> letterCasePermutation(String S) { List<String> ans = new ArrayList<String >(); dfs(S.toCharArray(), ans, 0); return ans; } public void dfs(char[] arr, List<String > e, int index){ if(index == arr.length) { e.add(String.valueOf(arr)); return; } dfs(arr, e, index + 1); //不處理數字與字母 if(Character.isLetter(arr[index])) { arr[index] ^= 32; //轉換字母大小寫 dfs(arr, e, index + 1); } }
}
深度優先遍歷實現二
class Solution {
public List<String> letterCasePermutation(String S) { List<String> res = new ArrayList<>(); dfs( S, res, 0, new StringBuffer(), S.length()); return res; } public void dfs(String S, List<String> res, int start, StringBuffer tmp,int len){ if(tmp.length() == len){ res.add(tmp.toString()); return; } if(start<len){ char ch = S.charAt(start); if(Character.isDigit(ch)){ tmp.append(ch); dfs( S, res, start+1, tmp, len); tmp.deleteCharAt(tmp.length()-1); //撤銷選擇 }else{ tmp.append(Character.toUpperCase(ch)); dfs( S, res, start+1, tmp, len); tmp.deleteCharAt(tmp.length()-1); //撤銷選擇 tmp.append(Character.toLowerCase(ch)); dfs( S, res, start+1, tmp, len); tmp.deleteCharAt(tmp.length()-1); //撤銷選擇 } } }
}