LeetCode-Backtracking-Easy 回溯算法
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); //撤销选择 } } }
}