回溯法小结,简单一招鲜

回溯法小结

  • 1. 前言
  • 2. 思想
  • 3. 一招鲜框架
  • 4. 一个有意思的手表问题


1. 前言

Leetcode上也就做了小10道回溯法题目,都是挑简单/中等的题目做的。谈不上帮大家深入此类题目解答,只是分享自己做题心得。

2. 思想

回溯法,我把它称为“聪明穷举法”,它的思想可以总结为:“递归+剪枝+取消已做的选择”1。一般递归是穷举所有可能的情况,最后判断是否符合题目要求。一般递归缺点是会出现明显不符合题意的结果(如Leetcode22题会出现"((((((“的情况)。而回溯法也有用到递归,但它的"聪明"之处在于——1.递归过程中如果发现已经不符合题目要求了,则不继续进行。2.同时,A选择递归完成之后,它还会取消递归前做的A选择,而改用B,C…选择继续递归。
如Leetcode22,当输入参数为3时,表示需要3对括号,也就是3个左括号,3个右括号。那么递归过程中,剪枝条件为:1.左括号大于等于3 2.右括号大于等于左括号时,就触发剪枝条件,直接return,不再往下递归,也就不会生成如”(((((("这样的结果。

3. 一招鲜框架

 回溯法框架2

1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
代码方面,回溯算法的框架:
result = []
void backtrack(路径, 选择列表):
    if 满足结束条件{
        result.add(路径)
        return
    }

for 选择 in 选择列表{
    做选择
    backtrack(路径, 选择列表)
    撤销选择
}

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。

4. 一个有意思的手表问题

回溯法小结,简单一招鲜_第1张图片

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);
    }

完~
回溯法告一段落,接下来尝试动态规划。


  1. Leetcode22——括号生成(回溯法) ↩︎

  2. Leetcode刷题java之39. 组合总和(回溯法以及回溯法框架). ↩︎

  3. 二进制手表. ↩︎

你可能感兴趣的:(LeetCode)