1.递归
- 递归遵循的规则
- 代码:
- * @param map :代表传入的地图数组元素8*7
- * @param i
- * @param j:表示球的起始坐标(i,j)
- * @return:这里用2表示走过的路,0表示未走过的路,1表示边界或障碍物,3表示死路
- * 因为是从左上角开始要走到右下角,我们制定走路的策略为下->右->左->上
package Game;
/*
* 使用递归来实现迷宫问题,就是一个球从左上角开始走,到达右下角的路线
*地图采用二维数组实现,元素为1表示不能走的格子,0表示可以走,2表示走过的
*/
public class MiGong {
public static void main(String[] args) {
//创建地图8*7,并给好边界
int [][] Map = new int[8][7];
//将上下边封住,置为1
for(int i=0;i<7;i++ ) {
Map[0][i] = 1;
Map[7][i] = 1;
}
//将左右边封住,置为1
for(int j=0;j<8;j++ ) {
Map[j][0] = 1;
Map[j][6] = 1;
}
//设置挡板,置为1
Map[3][1] = 1;
Map[3][2] = 1;
//未走时的地图显示
System.out.println("起始地图显示:");
//打印地图
for(int i = 0;i<8;i++) {
for(int j =0;j<7;j++) {
System.out.printf("%d ",Map[i][j]);
}
System.out.println();
}
//测试游戏,传入地图,起始点的行列坐标
FindWay(Map,1 ,1);
//走过时的地图显示
System.out.println("走过地图显示:");
for(int i = 0;i<8;i++) {
for(int j =0;j<7;j++) {
System.out.printf("%d ",Map[i][j]);
}
System.out.println();
}
}
//编写小球的走路策略
/**
* @param map :代表传入的地图数组元素8*7
* @param i
* @param j:表示球的起始坐标(i,j)
* @return:这里用2表示走过的路,0表示未走过的路,1表示边界或障碍物,3表示死路
* 因为是从左上角开始要走到右下角,我们制定走路的策略为下->右->左->上
*/
public static boolean FindWay(int[][] map,int i ,int j) {
//右下角表示最后的目标点,判断到达了目标点
if(map[6][5]==2) {
return true;//表示走到最后的点
}else { //未走到终点继续走
if(map[i][j]==0) {//表示这个点还未走过
map[i][j]= 2; //表示这个点可以走通,探索下一个通路点
if(FindWay(map,i+1 ,j)) {return true; //向右可以走通
}else if(FindWay(map,i ,j+1)) {return true; //向下可以走通
}else if(FindWay(map,i ,j-1)) {return true; //向左可以走通
}else if(FindWay(map,i-1 ,j)) {return true; //向上可以走通
}else{ map[i][j]=3; //如果这个点肯定走不通
return false;
}
}else {
return false; //说明map的元素非0,就是走过的和走不通的
}
}
}
}
回溯算法
-
回溯算法其实就是把所有的结果进行枚举,通过设定条件函数来搜索目标解、做搜索、回溯问题的套路是画图,代码其实 就是根据画出的树形图写出来的
- 剪枝函数:描述合法解的一般特征,避免搜索不合法解
- 条件:每一步搜索的解必须符合条件,其所有可能的解可以由空间状态树进行描述
- 记录:当每次达到结束条件时对结果进行保存,通常是全局变量并作为传递参数
- 这里以力扣39:组合总和,liweiwei1419 的解答过程进行描述:
-
要求找数组中元素和满足目标值的元素排列,元素可以重复;它这里采用减的方式,将解空间进行表述。
示例:输入: candidates = [2, 3, 6, 7],target = 7,所求解集为: [[7], [2, 2, 3]]
这里,只有最后的条件满足为0的数组元素记录返回就行,通过递归调用的方式,当然我这用了累减和累加的两种形式,
class Solution {
//采用数组元素累加和和满足目标值的解答过程,
/*List <List<Integer>> list = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List <Integer> ls = new ArrayList<>();
int[] arr =candidates;
search(arr,0,ls,0,target); //目标数组,当前索引,子列表集合,当前和,目标和
return list;
}
public void search(int[] arr,int index,List <Integer> ls,int sum,int target){
if(sum> target){
return;
}else if(sum==target){
List<Integer> e = new ArrayList<Integer>();
e.addAll(ls); //记录所有满足条件的,
list.add(e);
return;
}
for(int i=index;i<arr.length;i++){
if(arr[i]>target){continue;} //跳过不符合的值
ls.add(arr[i]);
search(arr,i,ls,sum+arr[i],target); //执行完后返回
ls.remove(ls.size()-1);//根据下标去掉最后一个元素,最后一个(当前元素不满足时跳过)
}
}*/
//利用减法来做,只有当最后的值为0时作为回溯条件
List <List<Integer>> list = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List <Integer> ls = new ArrayList<>(); //每次获得子集合的目标元素会进行再次清空处理
int[] arr =candidates;
search(arr,0,ls,target,target); //目标数组,当前索引,子列表集合,当前减值,目标和
return list;
}
public void search(int[] arr,int index,List <Integer> ls,int delete,int target){
if(delete < 0){
return;
}else if(delete==0){
List<Integer> e = new ArrayList<Integer>();
e.addAll(ls); //记录所有满足条件的,
list.add(e);
return;
}
for(int i=index;i<arr.length;i++){
if(arr[i]>target){continue;} //跳过不符合的值
ls.add(arr[i]);
search(arr,i,ls,delete-arr[i],target);
//执行完后返回,索引还是当前的索引,可能满足同一个元素的倍数情形
ls.remove(ls.size()-1);//根据下标去掉最后一个元素,当最后一个(当前元素不满足时跳过)
}
}
}
八黄后问题
- 思路分析:
- 源代码编写: 若行的差值与列的差值相同,则两位置必定是同一斜线上
package Game;
//八皇后问题,利用回溯算法实现,结果用一维数组进行存储
//其数组的下标+1代表第几个皇后,放在第几行,数组元素是第几列
public class EightQenue {
int MaxSize = 8;
int []arr = new int[MaxSize]; // 因为要共用的,所以定义在前面
public static void main(String[] args) {
EightQenue qt = new EightQenue();
qt.check(0);
}
//编写放置第n个皇后的方法
public void check(int n) {
if(n>= 8) {
//前面8个位置已经摆放好
show();
return;
}
//下面进行位置摆放,不停地在每一列试放
for (int i = 0; i < MaxSize; i++) {
arr[n] = i; //摆放位置
if(Judge(n)) { //摆放第n个不冲突
check(n+1);//摆放下一个,直到摆放完
}
//若冲突的话就会进行回溯,i++,放下一列继续判断。
}
}
//判断皇后冲突的方法,使得同列同斜线的位置不摆放,同行的话肯定是不存在,由数组的下标决定了
public boolean Judge(int n) { // n表示第n个皇后
for(int i=0;i<n;i++) { //判断与前面摆法是否有冲突
//arr[i]==arr[n]判断是否在同一列,列数存放在数组的元素值
//Math.abs(n-i)==Math.abs(arr[n]-arr[i])
//n-i代表行的差值 与arr[n]-arr[i]代表列的差值,
//若行的差值与列的差值相同,则两位置必定是同一斜线上
if(arr[i]==arr[n] || Math.abs(n-i)==Math.abs(arr[n]-arr[i])) {
return false;
}
}
return true;
}
//将皇后排放的位置进行打印
public void show() {
for (int i = 0; i < MaxSize; i++) {
System.out.print(arr[i]+1+" ");
}//这是一种解法
System.out.println();
}
}
-
结果:采用一个一维数组进行存储,数组的下标代表位置的行数-1,数组元素的值为位置的列数-1 一共是72种,
很明显,当找完第一个解法时,会继续进行遍历,光是冲突判断就15720次,其效率不高:
力扣40题:组合总和II,这里要求元素不能重复使用,且不能有相同的排列出现。
-
与39不同的是,这里每次返回的索引一定是指向下一个,使得每个元素不被重复使用, 这里没有TreeSet排序,利用数组排序来避免出现重复的排列, 当然也可以一开始就进行排序,后面的结果肯定没有重复:这里采用和的形式判断
class Solution {
List<List<Integer>> list = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<Integer> lst = new ArrayList<>();
int[] arr = candidates;
search(arr,0,lst,0,target);//目标数组,当前索引,子列表集合,当前和,目标和
return list;
}
public void search(int[] arr,int index,List<Integer> lst,int sum,int target){
if(sum>target){
return;
}
if(sum == target){
List<Integer> temp = new ArrayList<>();
//HashSet<Integer> hs = new HashSet<>();
//集合转数组,数组转集合
Integer[] brr = new Integer[lst.size()];
lst.toArray(brr);
Arrays.sort(brr);
temp = Arrays.asList(brr);
if(list.contains(temp)){ //判断大集合是否有重复,重复跳过
return;
}
list.add(temp);
return;
}
for(int i=index;i<arr.length;i++){
if(arr[i]>target){continue;}
//if(lst.contains(arr[i])){ continue;}//判断是否元素重复
lst.add(arr[i]);
search(arr,i+1,lst,sum+arr[i],target); //避免原来用过的元素重复使用
lst.remove(lst.size()-1);//根据下标去掉最后一个元素,最后一个(当前元素不满足时跳过)
}
}
}