回溯算法的核心思想:回溯法是深度优先遍历中的一种特有现象,主要用于在一个较大的数据集中寻找满足特定条件的解。回溯法就是当前状态不满足条件时,就回到上一个状态,即回到过去,然后再次向下搜索。在此过程中,需要用到决策树(因为在每一个节点上,都要做出决策要走哪一条路径),在该树上完成向上回溯,其实就是多叉树的遍历问题。
导例: 最多可达成的换楼请求数目
// 我去这道题简直将我对回溯的理解拔升了一个高度,之前都是半知半解,套模版
// 现在有种突然理解的感觉
// 首先回到定义什么是回溯?当搜素到某个状态不合理时,回到上一状态,继续搜索
// 希望你们也能真的理解这句话
// 然后时这道题的理解
// 我们可以通过回溯的方式枚举每一个请求是否被选择。
class Solution {
// 首先创建一个变量来记录能够满足的员工的个数
int satinums=0;
// 创建一个数组来记录每一层楼的饱和情况,因为0<=n<=20,所以直接创建大小为21的数组
// 其中数组的值为0时表示楼层刚好住满
int[] condi=new int[21];
// 记录用户的个数
int n;
int size;
public int maximumRequests(int n, int[][] requests) {
this.n=n;
this.size=requests.length;
dfs( requests,0,0);
return satinums;
}
// 创建一个回溯函数
// 其中pos是目前搜索到的请求的位置,cursat,是当前情况下所满足的员工的数量
public void dfs(int[][] requests,int pos,int cursat){
// 首先是回溯的出口:如果搜索到了最后一个请求就得回退到上一个状态
if(pos==size){
// 创建一个变量记录楼是否否符合条件
boolean pd=true;
// 先判断目前的楼层是否都满足条件
for(int i=0;i<21;i++){
if(condi[i]!=0){
pd=false;
}
}
// 在楼层都满足条件的情况下,比较满足的最多员工数
if(pd){
satinums=Math.max(satinums,cursat);
}
return;
}
// 首先是不选择当前的员工
dfs(requests,pos+1,cursat);
// 回退后选择当前员工
condi[requests[pos][0]]--;
condi[requests[pos][1]]++;
// 继续向下搜索
dfs(requests,pos+1,cursat+1);
// 回退到上一个层次之前将状态复原
condi[requests[pos][0]]++;
condi[requests[pos][1]]--;
}
}
例题1: 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
有效括号组合需满足:左括号必须以正确的顺序闭合。(力扣:22)
// 又是一种新的题型:根据官方的解释:回溯+减枝
// 但就我理解回溯不应该是单独存在的:就例如dfs,它里面也有回溯,dfs=递推+回溯
// 其实这里也是使用dfs的思路,只不过出口变了而已
//不过这道题更加强调dfs中回溯的阶段,所以将这道题归类为回溯:
// 大致思路:利用dfs将所有的组合都找出来然后再去除不合适的组合
class Solution {
List<String> list=new ArrayList<String>();
int n;
public List<String> generateParenthesis(int n) {
this.n=n;
dfs(0,0,"");
return list;
}
public void dfs(int left,int right,String str){
// 首先出口1:当左右()都使用完了
if(left>n||right>n){
return ;
}
// 剪纸将不符合条件的组合去掉,因为一个合格的字符串是不能出现)(这种情况的所以(使用的数量一定要比)的多
if(left<right){
return;
}
// 如果是符合条件的组合加入到list中去
if(left==n&&right==n){
list.add(str);
return;
}
// 深搜
dfs(left+1,right,str+"(");
dfs(left,right+1,str+")");
}
}
例题2: 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。(力扣:17)
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
// 本来是一点思路都没有的,看了看题解,想到了暴搜的思路,也就是深度优先遍历
// 怎么理解把它想象成为二叉树的深搜即可
//你要说回溯好像也没错,因为dfs中本来就有回溯的思想
// 这道题的主要思路阶级知识点讲解:1.使用hashmap将数字所对应的字符串建立联系方便后来的深度优先遍历
// 虽然String是引用数据类型,但它的传递方式,是值传递所以不需要,在会退到上一层的时候三处加入的字符
class Solution {
// 创建一个全局list来保存字符串
List<String> list=new ArrayList<String>();
public List<String> letterCombinations(String digits) {
// 避免异常的产生
if(digits.length()==0){
return list;
}
// 首先使用hashmap与其对应的字符串建立联系
Map<Character,String> map=new HashMap();
// 手动为他们建立联系
map.put('2',"abc");
map.put('3',"def");
map.put('4',"ghi");
map.put('5',"jkl");
map.put('6',"mno");
map.put('7',"pqrs");
map.put('8',"tuv");
map.put('9',"wxyz");
dfs(map,digits,0,"");
return list;
}
// 创建一个dfs函数
public void dfs(Map<Character,String> map,String digits,int deep,String zhstr){
if(zhstr.length()==digits.length()){
list.add(zhstr);
// 返回上一层
return;
}
// 将当前index所对应的字符串提取出来
String temp=map.get(digits.charAt(deep));
for(int i=0;i<temp.length();i++){
// 进行深搜
// zhstr=zhstr+temp.charAt(i)错误代码引以为戒,如果这样写就导致该层的zhstr值被改变,会影响该层的回溯
dfs(map,digits,deep+1, zhstr+temp.charAt(i));
}
}
}
例题3: 给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。(力扣:39)
/*
// 最为纯粹的深搜,不做任何的剪纸,会出现重复的组合,也就是会出现全排列的情况
// 改进的方法,就是不再与在其之前的数组元素进行组合,就是改变每层for的起点,改进后的代码在上面
// 第一眼思路同样dfs,也可以称为回溯
// 整理思路的时候发现这道题十分有意义,建议好好的跟着代码走一趟,好好的理解整个回溯的过程,尤其
// 是for循环结束后的回退
class Solution {
// 将targrt转化为全局变量
int target;
// 创建一个集合来保存符合的数组
List<List<Integer>> list=new ArrayList<List<Integer>>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
this.target=target;
ArrayList<Integer> temp=new ArrayList<Integer>();
dfs(candidates,temp,0,0);
return list;
}
public void dfs(int[] can,ArrayList temp,int index,int sum){
if(index>=can.length){
return;
}
if(sum>target){
return;
}
if(sum==target){
// 因为List是引用数据类型,从是之中temp都指向同一个数组,所以不能直接list.add(temp);
// 应该创建一个新的List将temp中的元素添加进去后,再将该集合加入list中
ArrayList<Integer> temp1=new ArrayList<Integer>();
temp1.addAll(temp);
list.add(temp1);
return;
}
// 这里的for循环为什么可以直接从index开始,数组前面的元素在进行全排列的时候肯定已经和后面的
// 元素组合过了,所以后面的元素没必要再与前面的元素组合,因为这里的组合是不论循序的
for(int i=0;i<can.length;i++){
int a=can[i];
temp.add(a);
dfs(can,temp,index+1,sum+a);
// 搜索同一层的下一个元素前,把当前的最后一个元素去掉
temp.remove(temp.size()-1);
}
}
}
*/
//根据上面dfs思路进行改进
class Solution {
// 将targrt转化为全局变量
int target;
// 创建一个集合来保存符合的数组
List<List<Integer>> list=new ArrayList<List<Integer>>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
this.target=target;
ArrayList<Integer> temp=new ArrayList<Integer>();
dfs(candidates,temp,0,0);
return list;
}
public void dfs(int[] can,ArrayList temp,int index,int sum){
// 递归的出口
if(index>can.length){
return;
}
if(sum>target){
return;
}
if(sum==target){
// 因为List是引用数据类型,从是之中temp都指向同一个数组,所以不能直接list.add(temp);
// 应该创建一个新的List将temp中的元素添加进去后,再将该集合加入list中
ArrayList<Integer> temp1=new ArrayList<Integer>();
temp1.addAll(temp);
list.add(temp1);
// 为什么可以直接回退,因为所有的数据都是正整数,所以不用考虑0的情况
return;
}
// 这里的for循环为什么可以直接从index开始,数组前面的元素在进行全排列的时候肯定已经和后面的
// 元素组合过了,所以后面的元素没必要再与前面的元素组合,因为这里的组合是不论循序的
for(int i=index;i<can.length;i++){
int a=can[i];
temp.add(a);
dfs(can,temp,i,sum+a);
// 搜索同一层的下一个元素前,把当前的最后一个元素去掉
// 这个地方也是非常的巧妙,直接在这里就可以在下一次的,dfs前删除所 加入的值
temp.remove(temp.size()-1);
}
}
}
例题4 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。 (力扣:40)
// 一眼看过去:回溯
// 无非就是在递推过程中不再添加自身,改变一下循环i的范围即可
// 我太单纯了这又是一道大哥级别的题目
//再通俗点:就是有重复元素的话,比如测试用例中的第一个1和第二个1,都会有1,7组成8,这样就产生了重复的list。因为都是从当前数开始遍历,所以加这一层的意思就是过滤掉重复的数,但是第一个1依然能使用第二个1,而第二个1是失去了作用的。
// 做完全排列我对于去重也有了更深刻的理解所以打算完善一下这道题
class Solution {
// 将target转化为全局变量
int target;
// 创建一个全局集合变量来保存符合条件的集合
List<List<Integer>> alllist=new ArrayList<List<Integer>>();
// 创建一个数组保存已经使用过的数字
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if(candidates.length==0){
return alllist;
}
this.target=target;
ArrayList<Integer> list=new ArrayList <Integer>();
// 先将数组排序
Arrays.sort(candidates);
dfs(candidates,0,list,0);
return alllist;
}
public void dfs(int[] cand,int sum,ArrayList <Integer> list,int index){
// 这个地方为什么是>而不是>=
// 因为我该层的sum值是在下一层开始前才判断的
if(index>cand.length){
return;
}
if(sum>target){
return;
}
if(sum==target){
ArrayList<Integer> temp1=new ArrayList<Integer>(list);
alllist.add(temp1);
return;
}
// 为什么i要从index开始,应为该层前面的数字已经图该层的数字组合过来,我相同数字的不同序列
// 是同一个序列所以不可以从0开始
for(int i=index;i<cand.length;i++){
// 结合例子1 2 2 2 5 理解
// 关键就是要理解这个地方,横向遍历去重,就是避免同一层中出现重复元素的情况
// 每次出现重复就是只使用该层重复元素的第一个元素
// i>index,就是为了被上一层的相同元素影响
// cand[i]==cand[i-1],其后的重复元素不再使用
// 但前提是数组已经排好序了,这样重复的元素才会相邻
// 确实感觉i>index是为了避免越界
// 这道题为什么不需要使用一个数组来记录使用过的元素,因为这道题考虑的是组合
// 也就是所{1,2,3}和 {1,3 ,2}是同一个组合,所以我们可以直接让后面的元素不再添加在
// 其前面的元素,所以压根就不用考虑重复使用的问题
if(i>index&&cand[i]==cand[i-1]){
continue;
}
int temp=cand[i];
list.add(temp);
// 这里为什么要i+1
// 这里是为了不重复使用自己
dfs(cand,sum+temp,list,i+1);
list.remove(list.size()-1);
}
}
}
例题5 幂集。编写一种方法,返回某集合的所有子集。集合中不包含重复的元素。
说明:解集不能包含重复的子集。(力扣:面试题 08.04. 幂集)
// 一看到组合问题,肯定是dfs,我们只需将遍历过程中的list加入集合即可
// 无非就是将添加集合的操作放进循环里面
// 如何避免引用自己我也很困惑,不管了,先将所有的组合弄出来再说
// 写着写着我想到了,只要在一每个不同数组元素为起点的搜索过程中不加入在它前面的元素即可
// 因为集合中不含有重复的元素所以不用考虑去重的操作
class Solution {
// 创建一个全局变量List来保存结果
List<List<Integer>> alist=new ArrayList<List<Integer>> ();
// 将集合的长度也变成全局变量
int len;
public List<List<Integer>> subsets(int[] nums) {
this.len=nums.length;
ArrayList<Integer> temp1=new ArrayList<Integer>();
// 因为空集也算是集合的一个子集所以想将一个空集加入全局alist中
alist.add(temp1);
// 进行深搜
dfs(nums,temp1,0);
return alist;
}
// 创建一个深搜函数,讲讲每个参数的作用吧!nums不用讲这是肯定要的
// list是为了贯穿整个搜索过程,简单的来说,就是记录整个回溯过程
// index是进行深搜的层次,其实这类题本质就是二维数组的深搜,如何讲一维数组变化
// 为二维数组,靠的就是这个index
public void dfs(int[] nums,ArrayList<Integer> list,int index){
// 如果层次大于数组的长度也返回
if(index>=len){
return;
}
// 我想了想出口应该就是当集合中的元素的长度,集合长度大于数组的长度
if(list.size()>len){
return;
}
// 以该层的每个节点为起点进行深搜
// 如何避免出现重复子集得情况,靠的就是这里i=index,后面元素得深搜不再添加在
// 其前面的元素
for(int i=index;i<len;i++){
list.add(nums[i]);
List<Integer> temp=new ArrayList<Integer>(list);
// 因为求得是子集,所以,每加入一个元素都将目前得list将入到alist中去
alist.add(temp);
// 这里的i+1又是一个重点
// 这里的i加一是为了避免它自己调用自己
dfs(nums,list,i+1);
// 回溯到上一层之前先将本层所添加的元素去掉
// 这里的步骤很关键
list.remove(list.size()-1);
}
}
}
例题6: 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。(力扣:46)
// // 这题我一看典型的dfs(回溯),想都不想直接开搞
// // 发现解决出现从重复元素是个难题,解决方法:
// // 先将获取所有的子集的方法写出来,然后将不符合的子集去掉
// class Solution {
// // 创建一个全局list保存结果
// List<List<Integer>> alist=new ArrayList<List<Integer>>();
// int len;
// public List<List<Integer>> permute(int[] nums) {
// this.len=nums.length;
// ArrayList<Integer> temp1=new ArrayList<Integer>();
// dfs(nums,temp1,0);
// return alist;
// }
// // 创建一个深度优先遍历函数
// public void dfs(int[] nums,ArrayList<Integer> list,int index){
// // 出口:如果index层次大于len,直接返回
// if(index>=len){
// return;
// }
// for(int i=0;i<len;i++){
// // 创建一个boolean值记录本层是否有元素加入集合
// boolean enter=false;
// // 如果集合中已经含有重复的元素就不再将重复的元素加入集合
// if(!list.contains(nums[i])){
// enter=true;
// list.add(nums[i]);
// }
// if(list.size()==len){
// ArrayList<Integer> temp=new ArrayList<Integer>(list);
// alist.add(temp);
// }
// // 进入下一层次
// dfs(nums,list,index+1);
// // 最关键的一步回溯前将本层次加入的元素去掉
// if(enter){
// list.remove(list.size()-1);
// }
// }
// }
// }
// 这题我一看典型的dfs(回溯),想都不想直接开搞
// 发现解决出现从重复元素是个难题,解决方法:
// 先将获取所有的全排列的方法写出来,然后将不符合的遍历思路剪枝掉
// 做了全排列2后,有了更合理的方法,添加一个visited[]数组来记录已经访问过的节点
// 建议以后还是先使用这个思路
class Solution {
// 创建一个全局list保存结果
List<List<Integer>> alist=new ArrayList<List<Integer>>();
int len;
public List<List<Integer>> permute(int[] nums) {
this.len=nums.length;
ArrayList<Integer> temp1=new ArrayList<Integer>();
int[] visited=new int[len];
dfs(nums,visited,temp1,0);
return alist;
}
// 创建一个深度优先遍历函数
public void dfs(int[] nums,int[] visited,ArrayList<Integer> list,int index){
// 出口:如果index层次大于len,直接返回
if(list.size()==len){
ArrayList<Integer> temp=new ArrayList<Integer>(list);
alist.add(temp);
return;
}
for(int i=0;i<len;i++){
if(visited[i]==1){
continue;
}
list.add(nums[i]);
// 将访问过的数组标记为1
visited[i]=1;
// 进入下一层次
dfs(nums,visited,list,index+1);
// 并将visited数组还原
visited[i]=0;
// 最关键的一步回溯前将本层次加入的元素去掉
list.remove(list.size()-1);
}
}
}
例题7: 二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。(力扣:401)
理解 :这道题与其他全排列最大的不同,就在与它的dfs里面嵌套了两个ds
// 一开始是一点思路都没有的,只能去看看别人的思路
// 思路大致是这样的:有n个灯亮了,首先假设所有都是小时,也就是先令所有的小时都亮起来,然后多余的给分
// 然后时亮的灯数-1,分亮的灯+1,以此类推直到亮的灯都是分
// 也许有的同学会问你怎么实现时灯的数量减一?这就为什么这道题使用回溯
// 思路大致如上开搞:
class Solution {
// 创建一个全局变量数组
int[] time={1,2,4,8,1,2,4,8,16,32};
// 创建一个集合来保存字符串
List<String> list=new ArrayList<String>();
public List<String> readBinaryWatch(int turnedOn) {
dfs(turnedOn,0,0,0);
return list;
}
// 创建一个dfs函数
public void dfs(int sum,int hour,int minute,int index){
// 递归出口
if(index>time.length){
return;
}
if(hour>11||minute>59){
return ;
}
if(sum==0){
String str =hour+":"+(minute>9?minute:"0"+minute);
list.add(str);
}
// 为什么要从index开始,因为这道题时类似与组合问题,[1,2] [2,1]都是一样的
// 所以后面的元素没必要与前面的元素进行组合
for(int i=index;i<time.length;i++){
// 首先先将所有的时灯都点亮,然后将剩余的灯分配给分灯
if(i<4){
// 为什么要进行i+1因为你一盏灯总不可能亮两次吧
dfs(sum-1,hour+time[i],minute,i+1);
}
if(i>=4){
dfs(sum-1,hour,minute+time[i],i+1);
}
}
}
}
例题8: 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。(力扣:47)
理解:这道题真就是一道大哥级别的题目,理解了这道题你对回溯的理解可以说是蹭蹭往上涨,其实代码中已经讲的很详细了,我这里就不再讲述了:我将要重点理解的几个点讲一讲:
1.首先这是一道全排列,{1,2,3}与{1,3,2}是两个不同的组合,所以第一个要解决的问题的是元素调用自己的问题,这个通过设置一个visited数组来解决
2.就是去重的问题,具体解决方法代码中有:如果a[i-i]==a[i]&&a[i-1]==0则continue,为什么?具体看代码
// 不管了,直接来个dfs算法先
// 终于将所有的细节都嚼碎了
// 这是一道大哥级别的题目,理解后会大大加深你对dfs还有回溯的理解
// class Solution {
// // 创建一个全局变量集合,保存结果
// List<List<Integer>> alist=new ArrayList<List<Integer>>();
// // 讲数组的长度变为全局变量
// int len;
// public List<List<Integer>> permuteUnique(int[] nums) {
// this.len=nums.length;
// // 创建一个贯穿整个递归过程的集合
// ArrayList<Integer> all=new ArrayList<Integer>();
// /*
// 下面这个想法打错特错这样会导致最后一层的元素肯定都是被标记过的导致无元素可加
// 好像也不是完全错只是我忽略了数组也是引用类型,返回上一层时,应撤销在本层的操作
// // 想到一个解决方法:参数中加上一个visited数组,将该层已经访问过的数子标记
// */
// int[] visited=new int[len];
// dfs(nums,visited,all,0);
// return alist;
// }
// public void dfs(int[] nums,int[] visited,ArrayList list,int index){
// // 出口
// if(list.size()==len){
// // 因为list是引用变量所以需要创建一个新的集合来保存list中的数据
// ArrayList<Integer> temp=new ArrayList<Integer>(list);
// alist.add(temp);
// return ;
// }
// // 将符合条件的集合加入到alist中
// for(int i=0;i<len;i++){
// if(visited[i]==1){
// continue;
// }
// // 如果是没有访问过的就将它加入集合
// list.add(nums[i]);
// visited[i]=1;
// dfs(nums,visited,list,index+1);
// // 回退到上一层之前将本层加入的数据给去掉
// list.remove(list.size()-1);
// visited[i]=0;
// }
// }
// }
//上面的代码解决了自己引用自己的问题,可是在同一层有重复元素怎么办
// 例如 [1,1,2] 上面的代码可以得出
//[[1,1,2],[1,2,1],[1,1,2],[1,2,1],[2,1,1],[2,1,1]]
// 可它有两个重复的1,就那第1层说是,他们两肯定可以获得重复的结果,怎么解决
// 大佬给出了解决方法:先对数组进行排序,就是如果该元素的前面有和它一样的元素,那这个元素就别用了
class Solution {
// 创建一个全局变量集合,保存结果
List<List<Integer>> alist=new ArrayList<List<Integer>>();
// 讲数组的长度变为全局变量
int len;
public List<List<Integer>> permuteUnique(int[] nums) {
this.len=nums.length;
// 创建一个贯穿整个递归过程的集合
ArrayList<Integer> all=new ArrayList<Integer>();
/*
下面这个想法打错特错这样会导致最后一层的元素肯定都是被标记过的导致无元素可加
好像也不是完全错只是我忽略了数组也是引用类型,返回上一层时,应撤销在本层的操作
// 想到一个解决方法:参数中加上一个visited数组,将该层已经访问过的数子标记
*/
int[] visited=new int[len];
// 对数组进行排序
Arrays.sort(nums);
dfs(nums,visited,all,0);
return alist;
}
public void dfs(int[] nums,int[] visited,ArrayList list,int index){
// 出口
if(list.size()==len){
// 因为list是引用变量所以需要创建一个新的集合来保存list中的数据
ArrayList<Integer> temp=new ArrayList<Integer>(list);
alist.add(temp);
return ;
}
// 将符合条件的集合加入到alist中
for(int i=0;i<len;i++){
// visit解决了自己访问自己的问题,并且
if(visited[i]==1){
continue;
}
// 现在解决同一层出现重复元素的问题
// 为什么要加上这个?visited[i-1]==0
// 解释在下面
if(i>0&&nums[i]==nums[i-1] && visited[i-1]==0){
continue;
}
// 如果是没有访问过的就将它加入集合
list.add(nums[i]);
visited[i]=1;
dfs(nums,visited,list,index+1);
// 回退到上一层之前将本层加入的数据给去掉
list.remove(list.size()-1);
visited[i]=0;
}
}
}
/*
大神的理解:for循环保证了从数组中从前往后一个一个取值,再用if判断条件。所以nums[i - 1]一定比nums[i]先被取值和判断。如果nums[i - 1]被取值了,那vis[i - 1]会被置1,只有当递归再回退到这一层时再将它置0。每递归一层都是在寻找数组对应于递归深度位置的值,每一层里用for循环来寻找。所以当vis[i - 1] == 1时,说明nums[i - 1]和nums[i]分别属于两层递归中,也就是我们要用这两个数分别放在数组的两个位置,这时不需要去重。但是当vis[i - 1] == 0时,说明nums[i - 1]和nums[i]属于同一层递归中(只是for循环进入下一层循环),也就是我们要用这两个数放在数组中的同一个位置上,这就是我们要去重的情况。
可以好好的遍历一次就会理解该思路了,遍历时主要是要理解,第2层的元素,在第三层所有的元素被访问后,就会回溯
到第一层,回到第一层后所有的元素是没有标记的,所以去重一定要加上visited[i-1]==0这样才说明他们才是在同一层
[1,1,2]
[1,1,2]
[1,1,2]
*/
例题9: 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。(力扣:77)
理解:这道题就不多说了,组合问题
//同样dfs,只不过这道题更简单一些k就是我们所需要遍历的层数
class Solution {
int len;
int k;
// 创建一个全局list来保存结果
List<List<Integer>> alist=new ArrayList<List<Integer>>();
public List<List<Integer>> combine(int n, int k) {
// 将1-n仿到数组里面
int[] nums=new int[n];
for(int i=0;i<n;i++){
nums[i]=i+1;
}
this.len=n;
this.k=k;
ArrayList<Integer> temp=new ArrayList<Integer>();
// 避免异常的产生
if(k==0){
return alist;
}
dfs(nums,temp,0);
return alist;
}
public void dfs(int[] nums,ArrayList<Integer> list,int index){
if(list.size()==k){
ArrayList<Integer> temp=new ArrayList<Integer>(list);
alist.add(temp);
return;
}
for(int i=index;i<len;i++){
list.add(nums[i]);
dfs(nums,list,i+1);
// 关键的一步,回退到上一步之前要将加入的元素去掉
list.remove(list.size()-1);
}
}
}
例题10 :给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。(力扣:78)
// 子集问题感觉应该和组合总和问题差不多,无非就是把添加集合的操作放到循环里面
// 需要注意的问题:1.包含相同数字的序列属于同一个序列,所以后面的元素不要在和前面的元素组合
// 2.它是所有的子集都要添加所以要将添加的操作放到循环里面
class Solution {
int len;
// 创建一个全局变量来保存结果
List<List<Integer>> alist=new ArrayList<List<Integer>>();
public List<List<Integer>> subsets(int[] nums) {
this.len=nums.length;
ArrayList<Integer> temp=new ArrayList<Integer>();
alist.add(temp);
dfs(nums,temp,0);
return alist;
}
public void dfs(int[] nums,ArrayList<Integer> list,int index){
// 这里递归的出口只能是靠层数来作为出口
if(index>=len){
return;
}
for(int i=index;i<len;i++){
list.add(nums[i]);
ArrayList<Integer> temp=new ArrayList<Integer>(list);
alist.add(temp);
dfs(nums,list,i+1);
// 关键步骤回退到上一层之前,去掉本层添加的元素
list.remove(list.size()-1);
}
}
}
例题11: 给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序)
二维数组的第 i 个数组中的单元都表示有向图中 i 号节点所能到达的下一些节点,空就是没有下一个结点了。
译者注:有向图是有方向的,即规定了 a→b 你就不能从 b→a 。(力扣:797)
理解:简单的回溯思路:难点在于1.理解回溯的过程 2.每次递归到某节点时以该节点为起点再次深搜
// 第一思路:回溯
class Solution {
int[][] graph;
// 创建一个全局集合来保存结果
int zd;
List<List<Integer>> alllist=new ArrayList<List<Integer>>();
public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
this.graph=graph;
this.zd=graph.length-1;
List<Integer> temp=new ArrayList<Integer>();
temp.add(0);
dfs(temp,0);
return alllist;
}
public void dfs(List<Integer> list,int next){
// 深搜的出口
// 其次如果该层没有指向任何的元素,也直接返回
if(graph[next].length==0){
return;
}
for(int i=0;i<graph[next].length;i++){
// 使用一个变量来保存好下一各要搜索的节点
int a=graph[next][i];
list.add(a);
// 判断是否到达了终点
if(a==zd){
// 如果到达了终点。使用一个新的集合来保存当前集合中的数据
List<Integer> temp=new ArrayList(list);
alllist.add(temp);
// 到达终点后直接返回上一层,记住返回上一层之前一定要将本层加入的元素去掉,避免重复调用
// list.remove(list.size()-1);
// return;
}
// 向下一层进行搜索
dfs(list,a);
//在返回上一层的时候将本层添加的数据给去掉
list.remove(list.size()-1);
}
}
}
例题12:找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。(力扣:216)
// 一眼看过去直接dfs,想都不带想的
class Solution {
// 创建一个集合来保存每种组合结果
List<List<Integer>> list =new ArrayList<List<Integer>>();
int k;
int n;
public List<List<Integer>> combinationSum3(int k, int n) {
this.k=k;
this.n=n;
// 首先将数据1-9传入数组中,方便我们后面的深搜
int[] nums={1,2,3,4,5,6,7,8,9};
ArrayList<Integer> temp=new ArrayList<>();
dfs(nums,0,temp,0);
return list;
}
// 创建一个深搜函数dfs
public void dfs(int[] nums,int index,ArrayList<Integer> list1,int sum){
// 递归的出口
if(list1.size()>k){
return;
}
if(list1.size()==k&&sum==n){
// 这里也很关键,因为list1是贯穿整个递归过程的所以也不能直接将其加入list中
ArrayList<Integer> temp1=new ArrayList<Integer>(list1);
list.add(temp1);
return;
}
// 进行深搜,因为着也属于组合问题,也就是所123,321是同一种组合,所以后面的元素没必要与前面的元素组合
for(int i=index;i<9;i++){
list1.add(nums[i]);
// 因为不能含有重复的元素所以不能引用自己i+1
dfs(nums,i+1,list1,sum+nums[i]);
// 最重要的一步返回上一层之前一定要将本层加入的元素给去掉
list1.remove(list1.size()-1);
}
}
}
例题13:给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。(力扣:46)
/*
// 这题我一看典型的dfs(回溯),想都不想直接开搞
// 发现解决出现从重复元素是个难题,解决方法:
// 先将获取所有的全排列的方法写出来,然后将不符合的遍历思路剪枝掉
// 做了全排列2后,有了更合理的方法,添加一个visited[]数组来记录已经访问过的节点
// 建议以后还是先使用这个思路
class Solution {
// 创建一个全局list保存结果
List<List<Integer>> alist=new ArrayList<List<Integer>>();
int len;
public List<List<Integer>> permute(int[] nums) {
this.len=nums.length;
ArrayList<Integer> temp1=new ArrayList<Integer>();
int[] visited=new int[len];
dfs(nums,visited,temp1,0);
return alist;
}
// 创建一个深度优先遍历函数
public void dfs(int[] nums,int[] visited,ArrayList<Integer> list,int index){
// 出口:如果index层次大于len,直接返回
if(list.size()==len){
ArrayList<Integer> temp=new ArrayList<Integer>(list);
alist.add(temp);
return;
}
for(int i=0;i<len;i++){
if(visited[i]==1){
continue;
}
list.add(nums[i]);
// 将访问过的数组标记为1
visited[i]=1;
// 进入下一层次
dfs(nums,visited,list,index+1);
// 并将visited数组还原
visited[i]=0;
// 最关键的一步回溯前将本层次加入的元素去掉
list.remove(list.size()-1);
}
}
}
*/
// 手痒了再做一次
// 可能是最近的理解深刻了一些第一时间竟感觉简单
class Solution {
// 创建一个全局list保存结果
List<List<Integer>> alist=new ArrayList<List<Integer>>();
int len;
// 创建一个变量来记录已经访问过的数字
int[] visited;
public List<List<Integer>> permute(int[] nums) {
this.len=nums.length;
this.visited=new int[len];
List<Integer> list=new ArrayList<Integer>();
dfs(nums,list);
return alist;
}
// 创建一个函数来进行深搜
public void dfs(int[] nums,List<Integer> list){
// 将符合条件的list加入到alist中去
if(list.size()==len){
List<Integer> temp=new ArrayList<Integer>(list);
alist.add(temp);
return;
}
for(int i=0;i<len;i++){
// 如果是已经访问过的节点直接跳过
if(visited[i]==1){
continue;
}
list.add(nums[i]);
visited[i]=1;
dfs(nums,list);
// 回退到上一层之前将本层的痕迹清理掉
visited[i]=0;
list.remove(list.size()-1);
}
}
}
例题14:给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。(力扣:131)
这道题十分有难度:建议好好理解
// 这道题我一点思路都没有,看了半小时题解还是有点蒙懂
// 分别截取如果该截取的字符串是回文再对剩余的字符串进行递归判断
// 简单来讲讲:大概就是:每一层递归要做的事就是不断截取长度为1 2 3 4 5的字符串,进行判断,利用for循环
// 而不同层次之间的区别就是截取的字符串不同,下一层中截取的字符串为上一层,所留下的字符串,如何改变截取的字符串不同?
// 通过改变start的位置
// 我发现将思路写出后好像清晰了很多
class Solution {
int len;
// 创建一个全局变量来保存结果
List<List<String>> alist=new ArrayList<List<String>>();
public List<List<String>> partition(String s) {
this.len=s.length();
ArrayList<String> list =new ArrayList<String>();
dfs(s,list,0);
return alist;
}
// 编写一个判断该字符串是否为回文序列
public boolean pd(String str){
boolean pd=true;
int l=0;
int r=str.length()-1;
while(l<=r){
if(str.charAt(l)!=str.charAt(r)){
pd=false;
return pd;
}
l++;
r--;
}
return pd;
}
public void dfs(String str,ArrayList<String> list,int start){
// 如果start的位置大于字符串的长度,说明在上一层已经将字符串截完了,直接返回
if(start>len){
return ;
}
// 递归的出口如果,截取的字符串的长度为1,说明前面的都满足回文序列,
// 什么时候会得出截取的长度为1,当start的位置位于最后字符上面时
if(start==len){
ArrayList<String> temp =new ArrayList<String>(list);
alist.add(temp);
}
// 每一层都截取长度为1到该层最大长度的字符串
for(int i=start;i<len;i++){
// 判断该层所截取的字符串是否为序列,如果不是直接进入下一次循环,截取长度加一
String str1=str.substring(start,i+1);
if(!pd(str1)){
continue;
}
// 如果是回文序列,将该序列加入list中
list.add(str1);
// 进入下一层循环,进入执行记得调整该层所能截取的字符串序咧,也就是调整好start的位置
dfs(str,list,i+1);
// 同样关键的一步返回上一层之间将该层加入的字符串去掉
list.remove(list.size()-1);
}
}
}
例题 15: 2044. 统计按位或能得到最大值的子集数目
//回溯在求子集的过程中,比较最大的异或值,并记录个数
class Solution {
// 创建一个变量来记录最大的异或值
int max=Integer.MIN_VALUE;
// 创建一个变量记录符合条件的子集的个数
int count=0;
public int countMaxOrSubsets(int[] nums) {
dfs(nums,0,0);
return count;
}
// 创建一个深度优先搜索函数来获取子集
public void dfs(int[] nums,int index,int sum){
if(index>nums.length){
return;
}
if(sum>max){
count=1;
max=sum;
// 直接使用if会导致重复判断,真傻逼了
} else if(sum==max){
count++;
}
for(int i=index;i<nums.length;i++){
int temp=sum|nums[i];
dfs(nums,i+1,temp);
}
}
}
例题16: 三羊献瑞
祥 瑞 生 辉
+ 三 羊 献 瑞
————————————-----------------------------------
三 羊 生 瑞 气
这道题是典型的会所算法题:主要就是从0-9中去7个数据出来进行全排列即可,就使用一个集合保存结果,然后每个汉字对应一个位置
package one;
import java.util.ArrayList;
import java.util.List;
class solution_6{
int[] arr= {0,1,2,3,4,5,6,7,8,9};
// 创建一个数组记录已经倍访问过的数字,避免一个数字被重复使用
int[] visited=new int[10];
// 创建一个集合保存结果
List<Integer> result;
public List<Integer> solution() {
List<Integer> list=new ArrayList<Integer>();
dfs(list);
return result;
}
public void dfs(List<Integer> list) {
if(list.size()!=0&&list.get(0)!=1) {
return;
}
if(list.size()==8) {
int sum1=list.get(0)*1000+list.get(1)*100+list.get(2)*10+list.get(3);
int sum2=list.get(4)*1000+list.get(3)*100+list.get(5)*10+list.get(6);
int sum=1*10000+list.get(1)*1000+list.get(5)*100+list.get(3)*10+list.get(7);
if(sum1+sum2==sum) {
result=new ArrayList<Integer>(list);
}
return;
}
for(int i=0;i<arr.length;i++) {
if(visited[i]==1) {
continue;
}
list.add(arr[i]);
// 经访问过的节点表姐
visited[i]=1;
dfs(list);
// 回到上一层之前将本层加入的元素去掉
list.remove(list.size()-1);
visited[i]=0;
}
}
}
//排列组合问题,固定每个数字在集合中的位置
//然后找出符合条件的排列即可
public class None_6 {
public static void main(String[] args) {
//还是用自己熟悉的思路来进行排列
//即对0-9进行排列,其中使用一个集合来贯穿全程,其中0-7个位置分别对应“三羊献瑞生辉”
solution_6 a=new solution_6();
List<Integer> list1=a.solution();
System.out.print(list1.get(0)+"-"+list1.get(1)+"-"+list1.get(2)+"-"+list1.get(3));
//System.out.print(list1.size());
}
}
例题17: 牌型种数
小明被劫持到X赌城,被迫与其他3人玩牌。
一副扑克牌(去掉大小王,共52张),均匀发给4个人,每个人13张
这时,小明脑子里突然想到一个问题:
如果不考虑花色,只考虑点数,也不考虑自己得到的牌的先后顺序,自己手里能拿到的初始牌型组合一共有多少种呢?
package two;
import java.util.ArrayList;
import java.util.List;
class Solution_Two_2{
// 创建一个数组保存52张牌
int[] nums=new int[52];
// 创建一个集合保存所有的组合数
int count=0;
public void solution() {
// 为nums数组赋值
int j=0;
for(int i=1;i<=13;i++) {
nums[j++]=i;
nums[j++]=i;
nums[j++]=i;
nums[j++]=i;
}
List<Integer> list=new ArrayList<Integer>();
dfs(list,0);
System.out.print(count);
}
// 创建一个回溯函数
public void dfs(List<Integer> list,int index) {
if(list.size()==13) {
count++;
return;
}
// 进行深搜
for(int i=index;i<nums.length;i++) {
// 需要注意的是如果之前已经由了相同的牌,需要对器进行跳过,避免出现重复的组合
// 现在才体会刀了i>index再组合问题中的重要性,以前总以为是为了解决i-1越界问题
// 现在才体会刀了他允许了连续往集合中添加元素,由不会出现重复的组合
if(i>index&&nums[i]==nums[i-1]) {
continue;
}
list.add(nums[i]);
dfs(list,i+1);
// 回到上一层之前先将本层添加的元素去掉
list.remove(list.size()-1);
}
}
}
public class Two_2 {
public static void main(String[] args) {
Solution_Two_2 a=new Solution_Two_2();
a.solution();
}
}
例题18: 凑算式(全排列)
package two;
import java.util.ArrayList;
import java.util.List;
class Solution_Two_5 {
// 创建一个变量保存符合条件的排列
int count=0;
// 创建一个数组记录我们访问国的数字,避免出现重复添加同一个位置上的数字的情况
int[] visited=new int[9];
// 将1-9保存到数组中方便我们进行回溯
int[] nums= {1,2,3,4,5,6,7,8,9};
public void solution() {
List<Integer> list=new ArrayList<Integer>();
dfs(list);
System.out.println(count);
}
// 创建一个函数进行回溯
public void dfs(List<Integer> list) {
// 对符合条件的情况进行判断
if(list.size()==9) {
int a1 = list.get(0) * list.get(2) * (list.get(6) * 100 + list.get(7) * 10 + list.get(8));
int a2 = list.get(1) * (list.get(6) * 100 + list.get(7) * 10 + list.get(8));
int a3 = (list.get(3) * 100 + list.get(4) * 10 + list.get(5)) * list.get(2);
if (a1 + a2 + a3 == 10 * list.get(2) * (list.get(6) * 100 + list.get(7) * 10 + list.get(8))) {
count++;
}
// 无论找到与否都得返回上一层
return;
}
for(int i=0;i<nums.length;i++) {
// 如果是已经访问过的直接跳过
if(visited[i]==1) {
continue;
}
list.add(nums[i]);
visited[i]=1;
dfs(list);
// 返回上一层之前将本层的痕迹清理带哦
list.remove(list.size()-1);
visited[i]=0;
}
}
}
public class Two_5 {
// 同样先固定每个字母再集合中的位置,然后队每种排列都进行判断,找出符合条件的排列
public static void main(String[] args) {
Solution_Two_5 a=new Solution_Two_5();
a.solution();
}
}
例题19 纸牌三角形
package two;
import java.util.ArrayList;
import java.util.List;
//又是全排列不过这次不打算再创建一个新的class了
public class Two_6 {
// 创建一个包含1-9的数组
static int[] nums= {1,2,3,4,5,6,7,8,9};
// 创建一个变量记录符合条件的排法
static int count;
// 创建一个数组记录访问过的数
static int[] visited=new int[9];
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
dfs(list);
// 着道题确实是典型的全排列,但是需要考虑镜像,和旋转后为同一排列的情况,所以需要将得到
// 的结果/6
System.out.print(count/6);
}
// 创建一个深度优先搜索函数
public static void dfs(List<Integer> list) {
// 同样是深搜的出口
if(list.size()==9) {
int a=list.get(0)+list.get(1)+list.get(2)+list.get(3);
int a1=list.get(3)+list.get(4)+list.get(5)+list.get(6);
int a2=list.get(6)+list.get(7)+list.get(8)+list.get(0);
if(a==a1&&a==a2&&a1==a2) {
count++;
}
}
for(int i=0;i<nums.length;i++) {
if(visited[i]==1) {
continue;
}
list.add(nums[i]);
visited[i]=1;
dfs(list);
list.remove(list.size()-1);
visited[i]=0;
}
}
}
例题20: 算式问题
看这个算式:
☆☆☆ + ☆☆☆ = ☆☆☆
如果每个五角星代表 1 ~ 9 的不同的数字。
这个算式有多少种可能的正确填写方法?
package three;
import java.util.ArrayList;
import java.util.List;
//右下hi一道纯正的全排列
public class Three_9 {
// 创建一个变量保存符合条件的结果
static int result=0;
// 创建一个数组用来进行遍历
static int[] nums= {1,2,3,4,5,6,7,8,9};
// 创建一个数组保存使用过的数据
static int[] visited=new int[9];
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
dfs(list);
System.out.print(result);
}
// 创建一一个函数进行深搜
public static void dfs(List<Integer> list) {
// 函数的出口
if(list.size()==9) {
int a=list.get(0)*100+list.get(1)*10+list.get(2);
int b=list.get(3)*100+list.get(4)*10+list.get(5);
int c=list.get(6)*100+list.get(7)*10+list.get(8);
if(a+b==c) {
result++;
}
return;
}
// 进行函数深搜
for(int i=0;i<nums.length;i++) {
if(visited[i]==1) {
continue;
}
list.add(nums[i]);
visited[i]=1;
dfs(list);
list.remove(list.size()-1);
visited[i]=0;
}
}
}
例题21:
package four;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class Four_7 {
static List<List<Integer>> result=new ArrayList<List<Integer>>();
static int findsum=0;
static int[] nums;
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int totalmoney=scan.nextInt();
int count=scan.nextInt();
// 创建一个数组保存明细
nums=new int[count];
int sum=0;
for(int i=0;i<count;i++) {
nums[i]=scan.nextInt();
sum=sum+nums[i];
}
Arrays.sort(nums);
findsum=sum-totalmoney;
List<Integer> list=new ArrayList<Integer>();
dfs(0,list,0);
// 输出结果
for(List<Integer> temp:result) {
for(int a:temp) {
System.out.print(a+" ");
}
System.out.println();
}
}
// 创建一个深搜函数保存符合情况的组合
public static void dfs(int index,List<Integer> list,int sum) {
if(sum>findsum) {
return;
}
if(sum==findsum) {
List<Integer> temp=new ArrayList<Integer>(list);
result.add(temp);
return;
}
for(int i=index;i<nums.length;i++) {
// 首先进行去重处理避免出现重复的组合
if(i>index&&nums[i]==nums[i-1]) {
continue;
}
list.add(nums[i]);
dfs(i+1,list,sum+nums[i]);
// 回退到上一层之前先将本层的操作撤销
list.remove(list.size()-1);
}
}
}
例题22: N皇后
解析
首先是要解决不再同一行的问题:这个其实可以利用我们回溯的特点进行解决,就是如果我们在该层已经放好了皇后就直接进入下一层
其次就是解决不在同一列的问题:这个问题我们可以使用一个数组将访问过的列记录好
最后就是解决对角线的问题:这里我利用了斜率,所以要经之前皇后的坐标保存好,也就是每一行中皇后所在的列记录好
方便求斜率
其他的就是简单的回溯思想,以及格式转换
class Solution {
int n;
// 创建数组保存已经放过棋子的行中的第几列
int[] pdcol;
// 创建一个数组保存该列是否已经放过
int[] visited;
// 创建一个集合保存每次成功的皇后在棋盘中的摆放位置
List<List<Integer>> result=new ArrayList<List<Integer>> ();
public List<List<String>> solveNQueens(int n) {
this.n=n;
// 将pdcol初始为-1,将访问过的列保存为该列的列数
this.pdcol=new int[n];
this.visited=new int[n];
Arrays.fill(pdcol,-1);
Arrays.fill(visited,-1);
List<Integer> list=new ArrayList<Integer>();
dfs(list,0);
// 创建一个集合保存我们需要转化成为的集合形式
List<List<String>> result1=new ArrayList<List<String>>();
// 将result转化格式并保存在result1中
for(List<Integer> temp:result){
List<String> list1=new ArrayList<String>();
for(int i=0;i<n;i++) {
String str="";
int a=temp.get(i);
for(int j=0;j<n;j++) {
if(j!=a) {
str=str+".";
}else {
str=str+"Q";
}
}
list1.add(str);
}
result1.add(list1);
}
return result1;
}
// 创建一个深搜函数
public void dfs(List<Integer> list,int index){
// 深搜的出口,首先如果访问到:n行则说明0-n-1行已经全部填完
if(index==n){
List<Integer> temp=new ArrayList<Integer>(list);
result.add(temp);
return;
}
// 其次就是要对该行中的每一列都进行搜索
for(int i=0;i<n;i++){
//如果该列已经摆放了皇后这跳过
if(visited[i]!=-1){
continue;
}
int j;
// 对角线上已经摆放了皇后这跳过
for(j=0;j<index;j++){
if(Math.abs(index-j)==Math.abs(i-pdcol[j])){
break;
}
}
if(j!=index){
continue;
}
// 如果该列可以摆放,这将该列的值保存到集合中去
list.add(i);
// 并将该列标记为访问过
pdcol[index]=i;
visited[i]=0;
dfs(list,index+1);
// 回到上一层的时候,将本层的操作撤销掉
list.remove(list.size()-1);
pdcol[index]=-1;
visited[i]=-1;
}
}
}
例题23: 全球变暖
问题描述
你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......
其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
解释: 要求的是淹没的岛屿的数量,并且只会淹没岛屿的最外层,所以这里就涉及了连通性的问题:我习惯使用深搜,并且我们要在遍历的过程中将会淹没的陆地标记好,但不能标记为".",因为这里会导致所有的陆地都被淹没
package five;
//只能拿到37分就先这样把,也学到不少东西了
import java.util.Scanner;
public class Six {
static int[][] visited;
// 创建一个变量记录岛屿的数量
static int countbefore=0;
static int countafter=0;
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int N=scan.nextInt();
// 创建一个数组记录访问过的陆地节点
visited=new int[N][N];
// 创建一个字符数组保存结果
char[][] nums=new char[N][N];
for(int i=0;i<N;i++) {
nums[i]=scan.next().toCharArray();
}
// 先从数组中找出一块陆地并且是没有访问过的陆地
for(int i=0;i<N;i++) {
for(int j=0;j<N;j++) {
if(nums[i][j]=='#'&&visited[i][j]==0) {
// 使用深搜将岛屿进行处理
countbefore++;
dfs(nums,i,j);
}
}
}
// 将vidited重置
visited=new int[N][N];
// 再次使用深搜,计算淹没后剩下多少岛屿
for(int i=0;i<N;i++) {
for(int j=0;j<N;j++) {
if(nums[i][j]=='#'&&visited[i][j]==0) {
// 使用深搜将岛屿进行处理
countafter++;
dfs(nums,i,j);
}
}
}
System.out.print(countbefore-countafter);
}
// 创建一个深搜函数:遍历每一块岛屿的每一个方格
public static void dfs(char[][] nums,int row,int col) {
// 深搜的出口:超出边界,或者是遇到陆地或者是已经访问过的陆地
if(row<0||row>=nums.length||col<0||col>=nums.length) {
return;
}
if(visited[row][col]==1||nums[row][col]=='.'||nums[row][col]=='*') {
return;
}
// 在搜索的过程中将会被淹没的岛屿变为'.'
if(row>0&&row<nums.length-1&&col>0&&col<nums.length-1) {
if(nums[row][col]=='#') {
// 只要是陆地就要标记为已经访问
visited[row][col]=1;
if(nums[row-1][col]=='.'||nums[row+1][col]=='.'||nums[row][col+1]=='.'||nums[row][col-1]=='.') {
// 如果是靠近海的就会被淹没,并将器标记为*
nums[row][col]='*';
}
}
}
//向四个方向进行搜索
dfs(nums,row-1,col);
dfs(nums,row+1,col);
dfs(nums,row,col-1);
dfs(nums,row,col+1);
}
}
例题24:
解析: 无重复元素的全排列,我们只需要对每种排列结果进行分割,然后找出最大的排列结果即可
import java.util.Arrays;
import java.util.Scanner;
//超出时间限制了,但结果是正确的
public class Main{
// 创建一个变量保存最大的符合条件的结果
static int max=0;
// 创建一个数组保存访问过的位置
static int[] visited=new int[9];
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int[] nums= {1,2,3,4,5,6,7,8,9};
String str="";
dfs(nums,str);
System.out.print(max);
}
// 创建一个回溯函数找出1-9中的排列组合
public static void dfs(int[] nums,String str) {
if(str.length()==9) {
for(int i=1;i<=8;i++) {
int a=Integer.parseInt(str.substring(0,i));
int b=Integer.parseInt(str.substring(i,9));
int c=a*b;
String str1=String.valueOf(c);
if(pd(str1)) {
// 将这个字符串转化为整数
int temp=Integer.parseInt(str1);
max=Math.max(max, temp);
}
}
return;
}
// 进行全排列
for(int i=0;i<nums.length;i++) {
if(visited[i]==1) {
continue;
}
visited[i]=1;
dfs(nums,str+nums[i]);
// 回退到上一层撤销本层的操作
visited[i]=0;
}
}
// 创建一个函数判断这个字符串是否值包含了1-9个数字
public static boolean pd(String str) {
// 先将字符串转化为数组,并进行排序
char[] temp=str.toCharArray();
Arrays.sort(temp);
String str1=new String(temp);
if(str1.equals("123456789")) {
return true;
}
return false;
}
}
例题24: 洛谷P1910
思路:其实就是简单的组合问题,找到所有的组合然后找出满足条件的组合下的获取资料数最多的组合
package 每天打卡;
import java.util.Scanner;
public class P1901 {
static int max=0;
static int N;
static int M;
static int X;
static int nums[][];
public static void main(String[] args) {
// 思路1:将问题转化为组合问题,找出所有符合条件的可能,然后从其中找出最大的资料数
Scanner sc=new Scanner(System.in);
N=sc.nextInt();
M=sc.nextInt();
X=sc.nextInt();
// 创建一个数组保存每个间谍的信息
nums=new int[N][3];
for(int i=0;i<N;i++) {
for(int j=0;j<3;j++) {
nums[i][j]=sc.nextInt();
}
}
dfs(0,0,0,0);
System.out.print(max);
}
// 创建一个回溯函数,所有组合能获取的资料数
public static void dfs(int all,int money,int sum,int index) {
// 函数的出口
if(all>M||money>X) {
return;
}
if(all<=M&&money<=X) {
if(sum>max) {
max=sum;
}
}
for(int i=index;i<N;i++) {
dfs(all+nums[i][1],money+nums[i][2],sum+nums[i][0],i+1);
}
}
}