对于回溯出所有可行解的一个方法
在一些回溯问题中,我们有时候需要找出所有的解,而不是一个解,这样的问题往往还需要结合动态规划来解决
比如问题
* 题目:
* 在二维网格 grid 上,有 4 种类型的方格:
*
* 1 表示起始方格。且只有一个起始方格。
* 2 表示结束方格,且只有一个结束方格。
* 0 表示我们可以走过的空方格。
* -1 表示我们无法跨越的障碍。
* 返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目,每一个无障碍方格都要通过一次。
首先我们可以解决它的一个子问题,就是找出一条满足条件的路径来,当然我们是使用深度优先的策略
我们使用递归回溯来解决这个问题,主要是要注意退出方法体的条件和先后顺序
static boolean find(int[][] grid,int i,int j){
//目标矩阵,位置坐标
if (i > grid.length - 1 || i < 0 || j > grid[i].length - 1 || j < 0) {
return false;
}
if(grid[i][j]!=0&&grid[i][j]!=1&&grid[i][j]!=2){
return false;
}
//到达终点
//System.out.println(grid[i][j]);
// if(grid[i][j] == 2){
// num++;
// return false;
// }
if(grid[i][j]==2){
if(judge(grid)) {
//该处是重点!!!!!!!!!!!!!!!!!!!!!!!!!
return true;
}
else
return false;
}
grid[i][j] = -1;
if(find(grid,i+1,j)||find(grid,i,j+1)||find(grid,i-1,j)||find(grid,i,j-1)){
return true;
}
//回溯
grid[i][j] = 0;
return false;
}
重点来了,我们如何使它可以搜寻到所有的可行解呢?注意,我们这里之所以只能搜索到一个解,原因是我们在搜索到一个解之后返回了true,所以直接全部退出循环体了,如果我们在最后表示搜索到一个结果的代码处返回false是不是它还会继续搜寻下去呢?而且我们只需要在该处使用一些方法来记录它就可以知道它几次成功获得了解
按照上面的思路,我们只需要修改一些代表寻找到一个解的代码就可以了
static boolean find(int[][] grid,int i,int j){
//目标矩阵,位置坐标
if (i > grid.length - 1 || i < 0 || j > grid[i].length - 1 || j < 0) {
return false;
}
if(grid[i][j]!=0&&grid[i][j]!=1&&grid[i][j]!=2){
return false;
}
//到达终点
//System.out.println(grid[i][j]);
// if(grid[i][j] == 2){
// num++;
// return false;
// }
if(grid[i][j]==2){
if(judge(grid)) {
num++;
return false;
}
else
return false;
}
grid[i][j] = -1;
if(find(grid,i+1,j)||find(grid,i,j+1)||find(grid,i-1,j)||find(grid,i,j-1)){
return true;
}
//回溯
grid[i][j] = 0;
return false;
}
我们将寻找到一个解处的返回值从true修改为false,并且使用了一个静态变量num来记录有几个解
现在让我们来尝试一下另外一个问题
* 问题:
* 现在有一个字符串 s 和一个字符串集合List<String> wordDict
* 试着找出所有 s 可以分割为字符串集合中的字符串的集合
按照上面那个问题的思路,我们先写出寻找出一个解的算法
当然,我们使用动态规划这个问题我们十分容易解决,但是我们需要写出他的递归写法,但是我们依旧可以先写出动态规划的方法,然后将它转化为递归的方法
static boolean wordBreak(String s, List<String> wordDict){
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for(int i=1;i<dp.length;i++){
for(int j=0;j<=i;j++){
if(dp[j]&&wordDict.contains(s.substring(j,i))){
dp[i]=true;
break;
}
}
}
return dp[s.length()];
}
它的递归形式
static boolean solution2(String s, List<String> wordDict){
if(s.length()<1) {
return true;
}
for(int i=0;i<s.length();i++){
if(wordDict.contains(s.substring(0,i+1))&&solution2(s.substring(i+1),wordDict)){
mylist1.add(s.substring(0, i + 1));
return true;
}
}
return false;
}
所以我们将它转化为寻找所有的可行解,首先我们需要像前面一样将代表寻找到一个解的地方的返回值修改为假装没寻找到解的返回值,也就是false,但是我们假装没有寻找到解,其实如果代码运行到那里说明已经找到了一个解,所以我们偷偷的在那里记录一下。。
static boolean solution2(String s, List<String> wordDict){
if(s.length()<1) {
num++;
for(int i : integerList){
System.out.print(i + " ");
}
return false;
}
for(int i=0;i<s.length();i++){
if(wordDict.contains(s.substring(0,i+1))){
integerList.add(i);
numFlag++;
if(solution2(s.substring(i+1),wordDict)){
return true;
} else{
numFlag--;
integerList.remove(numFlag);
}
}
}
return false;
}
这题有些不同,应为我们在最终的代表找到一个解的代码处无法直接记录表示字符串分割的坐标,所以我们需要在之前使用一个变量来记录它,如果失败了,那么就回溯把变量的值退回去