回溯

143 阅读2分钟

解决一个回溯问题,实际上就是一个决策树的遍历过程

遇到回溯问题先画出决策树

需要思考三个问题:

  1. 路径:也就是当前已经做出的选择
  2. 选择列表:也就是当前可以做的选择
  3. 结束条件,也就是到达决策树底层,无法再做出选择的条件

回溯算法框架:

result=[]
def backtrack(路径,选择列表):
    if 满足结束条件:
        result.add(路径)
        return 
    for 选择 in 选择列表:
        做选择
        backtrack(路径,选择列表)
        撤销选择

核心就是在递归调用之前「做选择 」,在递归调用后「撤销选择」。

做选择包含两个步骤:

  1. 对路径更新
  2. 对选择列表更新

问题的难点在于选择列表的更新结束条件的确定

选择列表的更新

子集问题

根据决策树我们可以看出需要进行剪枝,剪枝的过程即为对选择列表更新的过程。

观察可以发现,在当前节点只能选择数组中当前节点之后的节点

结束条件

注意回溯问题分为两种,一种需要搜索到所有正确的问题,一种只需要判断是否存在正确解。

对于第一种情况,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为空

路径记录:

  1. root为叶节点
  2. 路径和等于目标值
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);
    	
    }
}