Leetcode上也就做了小10道回溯法题目,都是挑简单/中等的题目做的。谈不上帮大家深入此类题目解答,只是分享自己做题心得。
回溯法,我把它称为“聪明穷举法”,它的思想可以总结为:“递归+剪枝+取消已做的选择”1。一般递归是穷举所有可能的情况,最后判断是否符合题目要求。一般递归缺点是会出现明显不符合题意的结果(如Leetcode22题会出现"((((((“的情况)。而回溯法也有用到递归,但它的"聪明"之处在于——1.递归过程中如果发现已经不符合题目要求了,则不继续进行。2.同时,A选择递归完成之后,它还会取消递归前做的A选择,而改用B,C…选择继续递归。
如Leetcode22,当输入参数为3时,表示需要3对括号,也就是3个左括号,3个右括号。那么递归过程中,剪枝条件为:1.左括号大于等于3 2.右括号大于等于左括号时,就触发剪枝条件,直接return,不再往下递归,也就不会生成如”(((((("这样的结果。
回溯法框架2
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
代码方面,回溯算法的框架:
result = []
void backtrack(路径, 选择列表):
if 满足结束条件{
result.add(路径)
return
}
for 选择 in 选择列表{
做选择
backtrack(路径, 选择列表)
撤销选择
}
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。
401. 二进制手表
二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59)。
每个 LED 代表一个 0 或 1,最低位在右侧。
例如,上面的二进制手表读取 “3:25”。
给定一个非负整数 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”。
题目来源Leetcode 401题3。
咱直接套一招鲜框架:
1、路径:也就是已经做出的选择。 -> 咱用一个boolean[] selected数据表示10个LED灯哪些亮了;变量hour,minute表示已做的选择已经有多少小时和分钟。
2、选择列表:也就是你当前可以做的选择。 ->从start下标开始做选择(防止重复选择),且需要selected[i]为false。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。 ->已亮灯的数量大于等于传入的参数数量。
3、剪枝条件:与题意明显不符的情况。 -> hour取值范围[0,11],minute取值范围[0,59]。
以下是代码:
List<String> results = new ArrayList<>();
public List<String> readBinaryWatch(int num) {
boolean[] isSelected = new boolean[10];
backTrack(num, isSelected, 0, 0, 0, 0);
return results;
}
/**
* @param num 亮灯的数量
* @param isSelected 是否亮灯数组
* @param selectedNum 已亮灯的数量
* @param hour 小时
* @param minute 分钟
* @param start 起始下标
*/
private void backTrack(int num, boolean[] isSelected, int selectedNum, int hour, int minute, int start) {
if (num == selectedNum) {
results.add(hour + ":" + String.format("%02d", minute));
return;
}
for (int i = start; i < isSelected.length; i++) {
if (isSelected[i]) {
continue;
}
//剪枝 小时[0,11],分钟[0,59]
if (i <= 3) {
//小时
if (hour + Math.pow(2, i) <= 11) {
isSelected[i] = true;
backTrack(num, isSelected, selectedNum + 1, (int) (hour + Math.pow(2, i)), minute, i);
isSelected[i] = false;
}
} else {
//分钟
if (minute + Math.pow(2, i - 4) <= 59) {
isSelected[i] = true;
backTrack(num, isSelected, selectedNum + 1, hour, (int) (minute + Math.pow(2, i - 4)), i);
isSelected[i] = false;
}
}
}
}
@Test
public void test() {
List<String> strings = this.readBinaryWatch(2);
System.out.println(strings.size());
System.out.println(strings);
}
完~
回溯法告一段落,接下来尝试动态规划。
Leetcode22——括号生成(回溯法) ↩︎
Leetcode刷题java之39. 组合总和(回溯法以及回溯法框架). ↩︎
二进制手表. ↩︎