解决一个回溯问题,实际上就是一个决策树的遍历过程
遇到回溯问题先画出决策树
需要思考三个问题:
- 路径:也就是当前已经做出的选择
- 选择列表:也就是当前可以做的选择
- 结束条件,也就是到达决策树底层,无法再做出选择的条件
回溯算法框架:
result=[]
def backtrack(路径,选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径,选择列表)
撤销选择
核心就是在递归调用之前「做选择 」,在递归调用后「撤销选择」。
做选择包含两个步骤:
- 对路径更新
- 对选择列表更新
问题的难点在于选择列表的更新和结束条件的确定
选择列表的更新
子集问题
根据决策树我们可以看出需要进行剪枝,剪枝的过程即为对选择列表更新的过程。
观察可以发现,在当前节点只能选择数组中当前节点之后的节点
结束条件
注意回溯问题分为两种,一种需要搜索到所有正确的问题,一种只需要判断是否存在正确解。
对于第一种情况,backtrack可以没有返回值,设置一个res变量,将所有正确解添加到res中。
对于第二种情况,backtrack要设置返回布尔值,在某一搜索节点,找到一条正确路径后,就返回,详见单词搜索
例题
求根到叶子节点数字之和
class Solution {
List<Integer> ls=new ArrayList<> ();
public int sumNumbers(TreeNode root) {
dfs(root,0);
int res=0;
for (int x:ls) {
res+=x;
}
return res;
}
private void dfs(TreeNode root,int path) {
if (root==null)
return;
path+=root.val;
if (root.left==null && root.right==null) {//满足结束条件
ls.add(path);
return;
}
dfs(root.left,path*10);
path-=root.val;
path+=root.val;
if (root.left==null && root.right==null) {
ls.add(path);
return;
}
dfs(root.right,path*10);
path-=root.val;
}
}
叶子相似的树
class Solution {
public boolean leafSimilar(TreeNode root1, TreeNode root2) {
List<Integer> ls1=new ArrayList<> ();
List<Integer> ls2=new ArrayList<> ();
dfs(root1,ls1);
dfs(root2,ls2);
return ls1.equals(ls2);
}
private void dfs(TreeNode root,List<Integer> ls) {
if (root==null)
return;
if (root.left==null && root.right==null) {
ls.add(root.val);
return;
}
dfs(root.left,ls);
dfs(root.right,ls);
}
}
路径总和ⅱ
class Solution {
List<List<Integer>> res=new ArrayList<> ();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<Integer> path=new ArrayList<> ();
dfs(root,path,0,sum);
return res;
}
private void dfs(TreeNode root,List<Integer> path,int curSum,int sum){
if (root==null) {
return;
}
if (root.left==null && root.right==null) {
if (curSum+root.val==sum) {
curSum+=root.val;
path.add(root.val);
List<Integer> copy=new ArrayList<> ();
for (int i=0;i<path.size();i++) {
copy.add(path.get(i));
}
res.add(copy);
path.remove(path.size()-1);
}
return;
}
path.add(root.val);
curSum+=root.val;
dfs(root.left,path,curSum,sum);
curSum-=root.val;
path.remove(path.size()-1);
path.add(root.val);
curSum+=root.val;
dfs(root.right,path,curSum,sum);
curSum-=root.val;
path.remove(path.size()-1);
}
}
路径总和Ⅲ
class Solution {
int count=0;
List<TreeNode> ls=new ArrayList<> ();
public int pathSum(TreeNode root, int sum) {
dfs(root);
for (int i=0;i<ls.size();i++) {
TreeNode start=ls.get(i);
backtrack(start, 0, sum);
}
return count;
}
private void dfs(TreeNode root) {
if (root==null)
return;
ls.add(root);
dfs(root.left);
dfs(root.right);
}
private void backtrack(TreeNode root,int cur,int sum) {
if (root==null)
return;
if (root.val+cur==sum) {
count++;//注意这里并没有return,因为子树可能还存在答案
}
backtrack(root.left, cur+root.val, sum);
backtrack(root.right, cur+root.val, sum);
}
}
不同路径
class Solution {
int r;
int c;
int[][] grid;
int[][] dirs= {{1,0},{0,1},{-1,0},{0,-1}};
public int uniquePathsIII(int[][] grid) {
this.grid=grid;
r=grid.length;
c=grid[0].length;
int res=0;
for (int i=0;i<r;i++) {
for (int j=0;j<c;j++) {
if (grid[i][j]==1)
res=dfs(i,j);
}
}
return res;
}
private int dfs(int x,int y) {
if (!inArea(x, y) || grid[x][y]==-1) {//不合法的
return 0;
}
if (grid[x][y]==-1) {//搜索过的
return 0;
}
if (grid[x][y]==2) {
return full()?1:0;
}
int oldval=grid[x][y];
grid[x][y]=-1;//标记为已搜索
int count=0;
for (int[] dir:dirs) {
int newX=x+dir[0];
int newY=y+dir[1];
count+=dfs(newX,newY);
}
grid[x][y]=oldval;//注意这个节点搜索完成后恢复为原来的值
return count;
}
private boolean full() {//是否通过所有无障碍方格
for (int i=0;i<r;i++) {
for (int j=0;j<c;j++) {
if (grid[i][j]==0) {
return false;
}
}
}
return true;
}
private boolean inArea(int x,int y) {
return x>=0 && x<r && y>=0 && y<c;
}
}
二叉树中和为某一值的路径
回溯:
终止条件:root为空
路径记录:
- root为叶节点
- 路径和等于目标值
class Solution {
int sum;
List<List<Integer>> res=new ArrayList<> ();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
this.sum=sum;
backtrack(new ArrayList<> (),root,0);
return res;
}
private void backtrack(List<Integer> path,TreeNode root,int add) {
if (root==null) {
return;
}
path.add(root.val);
add+=root.val;
if (add==sum && root.left==null && root.right==null) {
res.add(new ArrayList<> (path));
}
backtrack(path,root.left,add);
backtrack(path, root.right, add);
path.remove(path.size()-1);
}
}