回溯法练习
全排列
给定一个不含重复数字的整数数组 nums ,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
- 采用回溯法求解,采用深度遍历,递归方式
- 当递归的深度到达nums数组的长度时,说明所有的元素都被选择了一遍了,此时生成了一个排列。递归退出
- 遍历过程中需要记录哪些元素被访问过,使用 boolean[] used 数组记录。刚好 used 数组 能和 nums 数组一一对应。
- 遍历过程中需要记录遍历的路径,path
- 回溯,在进入时添加元素,在退出时(回溯)删除元素,采用:ArrayDeque 能方便地在末尾删除元素。
代码如下:
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> resultList = new LinkedList<>();
if (null == nums || nums.length == 0) {
//边界条件
return resultList;
}
//default false
boolean[] used = new boolean[nums.length];
//想象成一棵树,记录树的遍历路径,也即记录将nums中的哪些元素加入到了path
List<Integer> path = new ArrayList<>(nums.length);
dfs(nums, resultList, 0, new ArrayDeque<>(nums.length), used);
return resultList;
}
private void dfs(int[] nums, List<List<Integer>> resultList, int depth, ArrayDeque<Integer> path, boolean[] used) {
//递归退出条件,数组中所有的元素都选取了一遍之后,depth就等于nums.length
if (depth == nums.length) {
resultList.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!used[i]) {
path.addLast(nums[i]);
used[i] = true;
//递归遍历,深度加1
dfs(nums, resultList, depth + 1, path, used);
used[i] = false;
path.removeLast();
}
}
}
}
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
https://leetcode.cn/problems/binary-tree-paths/description/
做这类题目的共同点:
- 递归函数怎么写,思考递归退出条件
- 是否需要记录某个节点已经被访问了
- 如何构造路径List,记录遍历的每个节点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> resultList = new LinkedList<>();
if (null == root) {
//考虑边界条件
return resultList;
}
dfs(root, resultList, new ArrayDeque<>());
return resultList;
}
private void dfs(TreeNode root, List<String> resultList, ArrayDeque<Integer> pathList) {
//递归退出条件
if (root == null) {
//当某个节点,它只有一个孩子时,比如 root.left为null, root.right不为null,但是root.right为叶子结点了。
// 那么 dfs(root.left,resultList,pathList) 就会从这里返回,由于 root.right不为null, 因此这里不添加 root.val,而是直接 return
//只有遍历到 dfs(root.right,resultList,pathList)时,此时 root.right 为叶子结点,在下面if中就会生成路径。
return;
}
//递归退出条件,只有当左右孩子都为空时,该节点才能加入到路径。
if (root.left == null && root.right == null) {
//----------入-------------//
//将叶子结点加入path
pathList.addLast(root.val);
StringBuilder sb = new StringBuilder();
for (Integer value : pathList) {
sb.append(value);
sb.append("->");
}
//生成一条路径:比如:"1->3"
resultList.add(sb.substring(0, sb.length() - 2));
//----------出------------//
pathList.removeLast();
return;
}
//----------入-------------//
pathList.addLast(root.val);//非叶子结点加入路径
//递归遍历
dfs(root.left, resultList, pathList);
dfs(root.right, resultList, pathList);
//----------出------------//
pathList.removeLast();//非叶子结点出路径,回溯时避免有重复的元素。
}
}