Leetcode 刷题笔记(二十) ——回溯算法篇之分割、子集、全排列问题

系列文章目录

一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的层序遍历及相关题目
十 四、二叉树篇之二叉树的属性相关题目
十 五、 二叉树篇之二叉树的修改与构造
十 六、 二叉树篇之二叉搜索树的属性
十 七、二叉树篇之公共祖先问题
十 八、二叉树篇之二叉搜索树的修改与构造
十 九、回溯算法篇之组合问题
更新中 …


前言

刷题路线来自 :代码随想录
切割问题: 一个字符串按一定规则有几种切割方式
子集问题: 一个N个数的集合里有多少符合条件的子集
排列问题: N个数按一定规则全排列,有几种排列方式

题录

131. 分割回文串

Leetcode 链接
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
在这里插入图片描述
题解:

  1. 递归参数:可以传入 s,然后传入下层递归开始的下标,然后在下层分割字符串,也可以将字符串分割好作为参数,这里选择后者
  2. 收集结果:s 字符串无法再分割,即长度为 0 时(同结束条件)
  3. 剪枝:分割的部分子串不是回文串,continue 进行下次分割
class Solution {
    List<List<String>> res = new ArrayList<>();
    List<String> list = new ArrayList<>();
    public List<List<String>> partition(String s) {
        backtracking(s);
        return res;
    }

    public void backtracking(String s) {
        if (s.length() == 0) {
            res.add(new ArrayList<>(list));
            return;
        }
        // s 为没有分割过的子串,从头分割
        for (int i = 1; i <= s.length(); i++) {
        	// 分割部分不是回文串
            if (!isE(s, 0, i)) continue;
            list.add(s.substring(0, i));
            // s.substring(i, s.length()):下层递归分割
            backtracking(s.substring(i, s.length()));
            // 回溯
            list.remove(list.size() - 1);
        }
    }

	// 判断回文串
    public boolean isE(String s, int l, int r) {
        r--;
        while (l < r) {
            if (s.charAt(l) != s.charAt(r)) {
                return false;
            }
            l++;
            r--;
        }
        return true;
    }
}

93. 复原 IP 地址

Leetcode 链接
在这里插入图片描述
题解: 相比上一题,细节繁多

  1. 回溯对象:StringBuilder 拼接 IP ,拼接前记录下长度,回溯时作为删除末尾子串的下标起始位置
  2. 收集结果:刚好分割为 4 个整数,并且字符串刚好分割完
  3. 返回列表:接下来要分割是子串 s,conut 记录分割的整数个数
  4. 结束条件/剪枝:分割的整数大于 255、前导有 0、分割的整数个数大于 4
class Solution {
    List<String> res = new ArrayList<>();
    StringBuilder sb = new StringBuilder();
    public List<String> restoreIpAddresses(String s) {
        backtracking(s, 0);
        return res;
    }
    
    public void backtracking(String s, int count) {
        if (s.length() == 0 && count == 4) {
        	// 刚好分割为 4 个整数,删除拼接时的'.',添加到返回列表
            sb.deleteCharAt(sb.length() - 1);
            res.add(new String(sb));
            return;
        }

        for (int i = 1; i <= s.length() && (count < 4); i++) {
            int num = Integer.valueOf(s.substring(0, i));
            // 结束条件,还有一个放在了for 循环的判断中
            if ((i > 1 && s.charAt(0) == '0') || num > 255) {
                return;
            }
            
            // 记录拼接前下标用来回溯
            int idx = sb.length();
            // 拼接
            sb.append(num).append('.');
            backtracking(s.substring(i, s.length()), count + 1);
            // 回溯
            sb.delete(idx, sb.length());
        }
    }
}

78. 子集

Leetcode 链接
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

在这里插入图片描述
题解:

  1. 回溯对象:路径
  2. 收集结果:收集路径上的所有结果,注意还有一个额外的空集
  3. 结束条件:自然结束,因为套搜索到所有结果
  4. 参数列表:每层递归数组的起始位置
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        res.add(path);
        backtracking(nums, 0);
        return res;
    }

    public void backtracking(int[] nums, int start) {
        for (int i = start; i < nums.length; i++) {
        	// 收集路径上的所有结果
            path.add(nums[i]);
            res.add(new ArrayList<>(path));
            // 是 i + 1, 不是 start + 1
            backtracking(nums, i + 1);
            // 回溯
            path.remove(path.size() - 1);
        }
    }
}

90. 子集 II

Leetcode 链接
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
在这里插入图片描述
题解: 相比上题,需要增加去重操作

  1. 回溯对象:路径
  2. 收集结果:同上题收集路径上所有结果
  3. 结束条件/剪枝:自然结束
  4. 去重:排序后,同层第一次出现的数不用去重,连续出现的数除第一个外全部 continue 跳过
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        res.add(path);
        Arrays.sort(nums);
        backtracking(nums, 0);
        return res;
    }

    public void backtracking(int[] nums, int start) {
        for(int i = start; i < nums.length; i++) {
            if (i > start && nums[i] == nums[i - 1]) {
            	// 同层不是第一次出现
                continue;
            }
            path.add(nums[i]);
            res.add(new ArrayList<>(path));
            backtracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
    }
}

46. 全排列

Leetcode 链接
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
在这里插入图片描述
题解:

  1. 回溯对象:路径,boolean[ ] used
  2. 收集结果:路径长度等于 数组长度 (同结束条件)
  3. 递归参数:因为全排列每层递归从头开始遍历,只需跳过已经过的数,无需 startIndex
  4. 去重操作:每一层递归可用的数都在减少,上题去重只需一个判断,而这里去重的数太多,所以要使用 set 来记录,当然我们也可以使用数组 boolean[ ] used 当遇到已经使用过的数,continue 跳过
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        backtracking(nums);
        return res;
    }

    public void backtracking(int[] nums) {
        if (path.size() == nums.length) {
        	// 长度判断,收集结果
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (used[i] == true) {
            	// 使用过
                continue;
            }
            // 记录新的路径,并标记已经使用过
            path.add(nums[i]);
            used[i] = true;
            backtracking(nums);
            // 回溯
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }

}

47. 全排列 II

Leetcode 链接
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列
在这里插入图片描述
题解:

  1. 回溯对象:路径,boolean[ ] used
  2. 收集结果:路径长度等于 数组长度 (同结束条件)
  3. 递归参数:因为全排列每层递归从头开始遍历,只需跳过已经过的数,无需 startIndex
  4. 去重操作:
    1.每一层递归可用的数都在减少,上题去重只需一个判断,而这里去重的数太多,所以要使用 set 来记录,当然我们也可以使用数组 boolean[ ] used 当遇到已经使用过的数,continue 跳过
    2.层去重,同子集二,先排序再判断是否当前层第一次出现

哈希去重:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {
        used = new boolean[nums.length];
        Arrays.sort(nums);
        backtracking(nums);
        return res;
    }

    public void backtracking(int[] nums) {
        if (path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            if (used[i] == true) {
                continue;
            }
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }

            path.add(nums[i]);
            used[i] = true;
            backtracking(nums);
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }
}

排序去重:

class Solution {
    private List<List<Integer>> res = new ArrayList<>();
    private List<Integer> path = new ArrayList<>();
    private boolean[] used = null;

    public List<List<Integer>> permuteUnique(int[] nums) {
        used = new boolean[nums.length];
        //Arrays.sort(nums);
        backtracking(nums);
        return res;
    }

    public void backtracking(int[] nums) {
        if (path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }
        HashSet<Integer> hashSet = new HashSet<>();//层去重
        for (int i = 0; i < nums.length; i++) {
            if (hashSet.contains(nums[i]))
                continue;
            if (used[i] == true)//枝去重
                continue;
            hashSet.add(nums[i]);//记录元素
            used[i] = true;
            path.add(nums[i]);
            backtracking(nums);
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }
}

491. 递增子序列

Leetcode 链接
给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
在这里插入图片描述
题解:

  1. 回溯对象:路径
  2. 收集结果:路径长度 大于等于 2,收集结果完不可以 return
  3. 递归参数:因为全排列每层递归从头开始遍历,只需跳过已经过的数,无需 startIndex
  4. 结束条件/剪枝:不递增时 continue,注意不递增的判断 是 path 的最后一个数和当前数对比
  5. 去重操作:因为这里的数组无序切不能排序,使用 set 进行层去重,发现同层不是第一次出现 continue。
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        backtracking(nums, 0);
        return res;
    }

    public void backtracking(int[] nums, int startIndex) {
        if (path.size() >= 2) {
            res.add(new ArrayList<>(path));
        }

        Set<Integer> set = new HashSet<>();
        for (int i = startIndex; i < nums.length; i++) {
//            if (i > 0 && nums[i - 1] > nums[i]) {   错误示范
//                continue;
//            }

			// 递增判断
            if (!path.isEmpty() && path.get(path.size() - 1) > nums[i]) {
                continue;
            }
            // 去重
            if (set.contains(nums[i])) {
                continue;
            }
            set.add(nums[i]);
            path.add(nums[i]);
            backtracking(nums, i + 1);
            // 回溯
            path.remove(path.size() - 1);
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值