剑指Offer中的回溯法题

1.面试题38 字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。


代码实现:
class JianZhiOffer {
	public static void main(String[] args) {
		JianZhiOffer p = new JianZhiOffer();
		System.out.println(p.Permutation("abc").toString());
	}

	public ArrayList<String> Permutation(String str) {
		ArrayList<String> list = new ArrayList<>();
		if(str==null)
			return list;
		PermutationHelper(str.toCharArray(), 0, list);
		System.out.println(list);
		Collections.sort(list);                //字典序输出
		return list;
	}

	public void PermutationHelper(char[] cs, int i, List<String> list) {
		if(i==cs.length-1){
			String str = cs.toString();
			if(!list.contains(str))        //如果列表中没有,就添加
				list.add(str);
		}
		for(int j=i;j<cs.length;j++){
			swap(cs,i,j);	
			PermutationHelper(cs,i+1,list);  //向下递归
			swap(cs,i,j);                    //数组回到原先的排列
		}
	}

	public void swap(char[] cs, int i, int j) {
		char temp = cs[i];
		cs[i] = cs[j];
		cs[j] = temp;
	}
}

说明:

for(int j=i;j<cs.length;j++){
	swap(cs,i,j);	
	PermutationHelper(cs,i+1,list);  //向下递归
	swap(cs,i,j);                    //数组回到原先的排列
}
这里的交换是每次j从i开始i交换(比如i=0,那么j=0,i和j交换,j=1,i和j交换,j=2,i和j交换)。看纸上笔记,其实类似于先序遍历。  

2.面试题34 二叉树中和为某一值的路径

输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

代码实现:

public class Solution {
    ArrayList<ArrayList<Integer>> list1 = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root==null)
            return list1;
        SumFindPath(root,target);
        return list1;
    }
    public void SumFindPath(TreeNode root,int target){
        if(root==null)
            return;
        list2.add(root.val);
        target = target - root.val;
        if(target==0&&root.left==null&&root.right==null)
        {
            list1.add(new ArrayList(list2));
        }
        SumFindPath(root.left,target);    //target
        SumFindPath(root.right,target);   //target
        list2.remove(list2.size()-1);
    }
}

3.面试题12 矩形中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

代码实现:

class JianZhiOffer{
	public static void main(String[] args) {
		//测试用例一
		char[]ch = {'a','b','t','g','c','f','c','s','j','d','e','h'};
		char[] str = {'b','f','c','e'};
		//char[] str = {'a','b','f','b'};
		//测试用例二
		//char[]ch = {'A','B','C','E','S','F','C','S','A','D','E','E'};	
		//char[] str = {'A','B','C','C','E','D'};
		Solution s = new Solution();
		System.out.println(s.hasPath(ch,3,4,str));
	}
}
class Solution {
	public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
		int flag[] = new int[matrix.length];	//每一个位置对应一个字符判断是不是去过
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				if (helper(matrix, rows, cols, i, j, str, 0, flag))
					return true;
			}
		}
		return false;
	}
	private boolean helper(char[] matrix,int rows,int cols,int i,int j,char[] str,int k,int[] flag){
		int index = i*cols + j;		//与机器人的题目不同,这里用一位数组表示的二维数组
		if(i<0||i>=rows||j<0||j>=cols||flag[index]==1||matrix[index]!=str[k]){
			return false;
		}
		if(k==str.length-1)		//要找到的字符已经找到了,直接返回true
			return true;
		flag[index] = 1;
		boolean flag1 = helper(matrix,rows,cols,i-1,j,str,k+1,flag);
		boolean flag2 = helper(matrix,rows,cols,i+1,j,str,k+1,flag);
		boolean flag3 = helper(matrix,rows,cols,i,j+1,str,k+1,flag);
		boolean flag4 = helper(matrix,rows,cols,i,j-1,str,k+1,flag);
		if(flag1||flag2||flag3||flag4){			//四个方向,有一条路能走通就返回true
			return true;
		}
		flag[index] = 0;
		return false;
	}
}

注意点:

1)

flag[index] = 0;
return false;

2)

int index = i*cols + j;		//与机器人的题目不同,这里用一位数组表示的二维数组
3)
boolean left = helper(matrix, i-1, j, rows, cols, str, k+1, flag);
boolean right = helper(matrix,i+1,j ,rows,cols, str, k+1,flag );
boolean up = helper(matrix, i ,j+1,rows,cols,str,k+1,flag);
boolean down = helper(matrix ,i ,j-1,rows, cols,str,k+1,flag);
不能改成i--,i++,j++,j--

这里的将标识位设置为0,这是因为从某个字母开始找不到了,这时候要将标识位清理,让其他字母还可以经过这个字母

关于回溯的理解:其实回溯的套路差不过,在类似于后序遍历的时候,都要做相关的清理工作。

4.走迷宫问题问题描述:

    以一个M×N的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。设计程序,对任意设定的迷宫,求出从入口到出口的所有通路。 
  下面我们来详细讲一下迷宫问题的回溯算法。 
(入口) 0 0 1 0 0 0 1 0 
   0 0 1 0 0 0 1 0 
   0 0 1 0 1 1 0 1 
   0 1 1 1 0 0 1 0 
   0 0 0 1 0 0 0 0 
   0 1 0 0 0 1 0 1 
   0 1 1 1 1 0 0 1 
   1 1 0 0 0 1 0 1 
   1 1 0 0 0 0 0 0(出口) 
  该图是一个迷宫的图。1代表是墙不能走,0是可以走的路线。只能往上下左右走,直到从左上角到右下角出口。 

  做法是用一个二维数组来定义迷宫的初始状态,然后从左上角开始,不停的去试探所有可行的路线,碰到1就结束本次路径,然后探索其他的方向,当然我们要标记一下已经走的路线,不能反复的在两个可行的格子之间来回走。直到走到出口为止,算找到了一个正确路径。 (题目描述参考博客https://blog.csdn.net/sunhuaqiang1/article/details/52599850)

代码实现:

public class HuiSu {  
	static int count = 0;
    /** 
     * 定义迷宫数组 
     */  
    private static int[][] array = {  
            {0, 0, 1, 0, 0, 0, 1, 0},  
            {0, 0, 1, 0, 0, 0, 1, 0},  
            {0, 0, 1, 0, 1, 1, 0, 1},  
            {0, 1, 1, 1, 0, 0, 1, 0},  
            {0, 0, 0, 1, 0, 0, 0, 0},  
            {0, 1, 0, 0, 0, 1, 0, 1},  
            {0, 1, 1, 1, 1, 0, 0, 1},  
            {1, 1, 0, 0, 0, 1, 0, 1},  
            {1, 1, 0, 0, 0, 0, 0, 0}  
  
    };    
  
    public static void main(String[] args) {  
    	int maxLine = 8;	//列
    	int maxRow = 9;		//行
        System.out.println("开始时间: "+System.currentTimeMillis()); 
        int[][] flag = new int[maxRow][maxLine];
       helper(0,0,maxRow,maxLine,flag);;  
        System.out.println("结束时间: "+System.currentTimeMillis());  
    }  
  
    public static void helper(int i,int j,int maxRow,int maxLine,int[][] flag){
    	//如果没有找到就继续
    	if(i<0||i>=maxRow||j<0||j>=maxLine||array[i][j]==1||array[i][j]==5){
    		return;
    	}
    	
    	if(i==maxRow-1&&j==maxLine-1){
    		print(maxRow,maxLine);	//将能到达目的的路径打印出来
    		return;
    	}
    	array[i][j] = 5;	//已经走过的路径设置为5
    	helper(i-1,j,maxRow,maxLine,flag);
    	helper(i+1,j,maxRow,maxLine,flag);
    	helper(i,j+1,maxRow,maxLine,flag);
    	helper(i,j-1,maxRow,maxLine,flag);
    	array[i][j] = 0;	//从坐标[i,j]开始,都不行,就清除标志
    }
    
    //打印
    private static void print(int maxRow,int maxLine) {  
        System.out.println("得到第"+(++count)+"解");  
        for (int i = 0; i < maxRow; i++) {  
            for (int j = 0; j < maxLine; j++) {  
                System.out.print(array[i][j] + " ");  
            }  
            System.out.println();  
        }  
    }  
} 
说明:走迷宫问题相比于矩阵中找字符串有几点不同,1)找字符串要从矩阵中的每个字母出现,向其上下左右遍历,而走迷宫问题只从(0,0)坐标点出发,向其上下左右遍历。2) 找字符串问题在某条路径找到字符串后就停止,所以多了这行语句。
if(flag1||flag2||flag3||flag4){			//四个方向,有一条路能走通就返回true
    return true;		//这里的return true一定要有
}

5.面试题13 机器人的运动路径

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

分析:递归从一点触发,向四周的四个点递归搜索,其实这个根二叉树的深度和二叉树的后序遍历都是一个模子里出来的。

代码实现:

class JianZhiOffer{
	public static void main(String[] args) {
		Solution s = new Solution();
		int threshold = 3;
		int rows = 5;
		int cols = 5;
		System.out.println(s.movingCount(threshold, rows, cols));
	}
}
class Solution {
    public int movingCount(int threshold, int rows, int cols)
    {
        int[][] flag = new int[rows][cols];
        return helper(threshold,0,0,rows,cols,flag);
    }
    private int helper(int threshold,int i,int j,int rows,int cols,int[][] flag){
        if(i<0||i>=rows||j<0||j>=cols||flag[i][j]==1||(change(i)+change(j)>threshold)){
            return 0;
        }
        flag[i][j] = 1;            //已经走过的设置标识位
        int left = helper(threshold,i+1,j,rows,cols,flag);
        int right = helper(threshold,i-1,j,rows,cols,flag);
        int up = helper(threshold,i,j-1,rows,cols,flag);
        int down = helper(threshold,i,j+1,rows,cols,flag);
        return left + right + up + down + 1;    //求各个方向能走过的所有的点
    }
    public int change(int i){		//求数的各位相加的结果
    	int sum = 0;
    	while(i>0){
    		int temp = i%10;
    		sum += temp;
    		i = i/10;
    	}
    	return sum;
    }
}

说明:1)从某坐标开始,将走过的坐标的标志位都置为1,到达边界(行坐标和列坐标的数位和等于k的格子)此时计数为0,从该坐标返回走,每走一步就加一

2)这边为了方便用了一个二维数组,其实也可以像上面一样使用一维的数组,使用index = i*cols + j来计算数组的下标值。

6.找和为某值的数字组合题目描述

有数组{64,50,32,16,8,2,1,96},找和为100的数字的组合。

class HuiSu {
	static ArrayList<Integer> temp = new ArrayList<>();
	static ArrayList<Integer> save = new ArrayList<>();
	static ArrayList<ArrayList<Integer>> result = new ArrayList<>();
	static int[] visit;
	public static void main(String[] args) {
		int[] nums = {64,50,32,16,8,4,2,1,96};
		int[] flag = new int[nums.length];	//用来标记已经访问过的坐标
		dfs(nums,100,0,flag);
		System.out.println(result);
	}
	private static void dfs(int[] nums,int target,int index,int[] flag){
		if(target==0){			
			result.add(new ArrayList<Integer>(temp));	//将相加等于100的路径添加到result列表中
		}
		if(target<0||index>=nums.length||flag[index]==1){	//如果target小于0或者已经访问过了
			return;
		}
		for(int i=index;i<nums.length;i++){
			flag[i] = 1;
			temp.add(nums[i]);
			dfs(nums,target-nums[i],i+1,flag);
			flag[i] = 0;		//从小标为i处遍历完后,要清除标志位
			temp.remove(temp.size()-1);
		}
	}
}
代码输出:
[[64, 32, 4], [50, 32, 16, 2], [4, 96]]

说明:该题的难点在于:前面的几道题,都只考虑相邻的组合,而该题要考虑非相邻的组合

注意:这两行代码的顺序不能交换

if(target==0){			
	result.add(new ArrayList<Integer>(temp));	//将相加等于100的路径添加到result列表中
}
if(target<0||index>=nums.length||flag[index]==1){	//如果target小于0或者已经访问过了
	return;
}

例如这题最后一个数是96,如果nums[i]=96,此时target-nums[i] =0,但是此时i+1等于nums.length所以这两个判断交换机会漏解

temp.add(nums[i]);
dfs(nums,target-nums[i],i+1,flag);

参考学习牛客网通过的代码:

https://www.nowcoder.com/questionTerminal/6e5207314b5241fb83f2329e89fdecc8

https://www.nowcoder.com/questionTerminal/c61c6999eecb4b8f88a98f66b273a3cc

https://www.nowcoder.com/questionTerminal/fe6b651b66ae47d7acce78ffdd9a96c7

https://blog.csdn.net/sunhuaqiang1/article/details/52599850



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值