硅基计划4.0 算法 递归回溯 - 实践

news/2026/1/20 10:39:08/文章来源:https://www.cnblogs.com/ljbguanli/p/19505287

硅基计划4.0 算法 递归&回溯 - 实践

123773799_p2


文章目录

  • 一、电话号码数字组合
  • 二、括号生成
  • 三、组合
  • 四、二叉树的最大宽度
    • 1. 广度优先搜索(BFS)解法
    • 2. 深度优先搜索(DFS)解法
  • 五、目标和
  • 六、组合总和
    • 1. 数字选择法
    • 2. 数字选择次数法
  • 七、字母大小写全排列
  • 八、优美的排列
  • 九、N皇后——Hard
  • 十、有效的数独
  • 十一、解数独
    • 1. 函数有返回值的写法
    • 2. 函数无返回值写法
  • 十二、单词搜索
  • 十三、黄金矿工
  • 十四、不同路径III
  • 十五、找出所有子集异或总和再求和
  • 十六、全排列II


一、电话号码数字组合

题目链接
这一题我们说白了就是对题目中给的数字进行解析,我们画一个决策树就好

image-20251202092804639

有了这个决策树,我们就知道函数怎么设计了

class Solution {
String [] letter = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
List<String> list;StringBuilder path;public List<String> letterCombinations(String digits) {list = new ArrayList<>();path = new StringBuilder();if(digits.equals("")){return list;}dfs(digits,0);return list;}private void dfs(String digits,int pos){if(pos == digits.length()){//说明到头了,进行添加结果list.add(path.toString());return;}//获取当前数字映射的字母String current = letter[digits.charAt(pos)-'0'];//开始递归for(int i = 0;i < current.length();i++){path.append(current.charAt(i));dfs(digits,pos+1);//回溯现场path.deleteCharAt(path.length()-1);}}}

二、括号生成

题目链接
首先我们来明确一下什么是有效的括号,那么整体上左括号数量=右括号数量,这不是废话吗!!
还有,我们要保证从头开始的子串中左括号数量必须 >= 右括号数量
比如(()))这个就是一个非法括号,而((())这个是合法的括号(前提是没有到最后位置)

因此,我们还是通过画决策树明确剪枝关系

image-20251202094246338

因此,我们也需要一个path变量记录路径,并且在回溯的时候要恢复现场,还原,并且根据决策树我们还涉及到一些剪枝操作

class Solution {
StringBuilder path = new StringBuilder();
int left;
int right;
int count;
List<String> list = new ArrayList<>();public List<String> generateParenthesis(int n) {count = n;dfs();return list;}private void dfs(){if(right == count){list.add(path.toString());return;}if(left < count){path.append("(");left++;dfs();path.deleteCharAt(path.length()-1);left--;}if(right < left){path.append(")");right++;dfs();path.deleteCharAt(path.length()-1);right--;}}}

三、组合

题目链接
这一题不能枚举重复结果,即2,44,2是同一种情况
我们同样画一个决策树

image-20251202095619089

那要怎么保证不能重复呢,诶,我们可以每次枚举的时候,从上一次递归的下一个位置开始枚举

class Solution {
List<Integer> path = new ArrayList<>();List<List<Integer>> list = new ArrayList<>();int num;int knum;public List<List<Integer>> combine(int n, int k) {if(n <= 0){return list;}num = n;knum = k;dfs(1);return list;}private void dfs(int pos){if(path.size() == knum){list.add(new ArrayList<>(path));return;}for(int i = pos;i <= num;i++){path.add(i);dfs(i+1);path.remove(path.size()-1);}}}

四、二叉树的最大宽度

题目链接
这道题如果你直接硬来的话,它可能会给你1500个单分支树的节点,因此我们可以利用的思想,给每个节点编号,这样我们求宽度的时候,就可以拿当前层最右侧的节点编号 - 当前层最左侧的节点编号
如果我们根节点编号从0开始,左孩子编号就是2x+1,右孩子编号就是2x+2
如果我们根节点编号从1开始,左孩子编号就是2x,右孩子就是2x+1
虽然我们编号下标可能会溢出,但是我们做的差是不可能溢出的
同时,我们使用数组去模拟队列,避免头部删除的时候增加时间复杂度

1. 广度优先搜索(BFS)解法

/**
* Definition for a binary tree node.
* public class TreeNode {
*     int val;
*     TreeNode left;
*     TreeNode right;
*     TreeNode() {}
*     TreeNode(int val) { this.val = val; }
*     TreeNode(int val, TreeNode left, TreeNode right) {
*         this.val = val;
*         this.left = left;
*         this.right = right;
*     }
* }
*/
class Solution {
public int widthOfBinaryTree(TreeNode root) {
//数组模拟队列
List<Pair<TreeNode,Integer>> queue = new ArrayList<>();//根节点从1开始queue.add(new Pair<TreeNode,Integer>(root,1));//记录结果int ret = 0;while(!queue.isEmpty()){Pair<TreeNode,Integer> cur1 = queue.get(0);Pair<TreeNode,Integer> cur2 = queue.get(queue.size()-1);ret = Math.max(ret,cur2.getValue()-cur1.getValue()+1);//开始为下一层进队//使用临时队列存储,后续直接赋值,避免头部删除增加时间复杂度List<Pair<TreeNode,Integer>> tmps = new ArrayList<>();for(Pair<TreeNode,Integer> tmp : queue){TreeNode node = tmp.getKey();int index = tmp.getValue();if(node.left != null){tmps.add(new Pair<TreeNode,Integer>(node.left,index*2));}if(node.right != null){tmps.add(new Pair<TreeNode,Integer>(node.right,index*2+1));}}//更新到下一层queue = tmps;}return ret;}}

2. 深度优先搜索(DFS)解法

/**
* Definition for a binary tree node.
* public class TreeNode {
*     int val;
*     TreeNode left;
*     TreeNode right;
*     TreeNode() {}
*     TreeNode(int val) { this.val = val; }
*     TreeNode(int val, TreeNode left, TreeNode right) {
*         this.val = val;
*         this.left = left;
*         this.right = right;
*     }
* }
*/
class Solution {
//哈希表记录每一层最左侧节点的下标
HashMap<Integer,Integer> hash = new HashMap<>();int maxWide;public int widthOfBinaryTree(TreeNode root) {dfs(root,0,1);return maxWide;}private void dfs(TreeNode root,int depth,int index){if(root == null){return;}hash.putIfAbsent(depth,(int)index);//计算宽度maxWide = Math.max(maxWide,(int)(index-hash.get(depth)+1));//递归左右子树dfs(root.left,depth+1,index*2);dfs(root.right,depth+1,index*2+1);}}

五、目标和

题目链接
这一题我们采用暴力搜索策略


这里顺便提一嘴,如果我们的path变量是诸如int类型,就使用参数传递
如果我们的path变量是诸如List类型,就使用全局变量传递
当然具体哪种还是看个人习惯


我们还是一样画一个决策树

image-20251202101438047

class Solution {
int path;
int targets;
int count;
public int findTargetSumWays(int[] nums, int target) {
//path作为全局变量传递
targets = target;
dfs(nums,0);
return count;
}
private void dfs(int [] array,int pos){
if(pos == array.length){
if(path == targets){
count++;
}
return;
}
//加法
path += array[pos];
dfs(array,pos+1);
path -= array[pos];//恢复现场
//减法
path -= array[pos];
dfs(array,pos+1);
path += array[pos];//恢复现场
}
}

六、组合总和

题目链接

1. 数字选择法

这道题关键就是在于可以选择重复元素,并且2 2 33 2 2视为同一种情况
我们还是来画决策树

image-20251202103019168

class Solution {
List<Integer> path = new ArrayList<>();List<List<Integer>> list = new ArrayList<>();int targets;public List<List<Integer>> combinationSum(int[] candidates, int target) {targets = target;dfs(candidates,0,0);return list;}private void dfs(int [] array,int pathSum,int pos){//递归出口一:到达数组最后一个元素if(pathSum == targets){list.add(new ArrayList<>(path));return;}//递归出口二:路径和提前超过了目标值if(pathSum > targets || pos == array.length){return;}for(int i = pos;i < array.length;i++){path.add(array[i]);dfs(array,pathSum+array[i],i);path.remove(path.size()-1);}}}

2. 数字选择次数法

其实我们还有另外一种解法,就是考虑每个数字选择几次
我们再来画一个决策树

image-20251202103623137

请注意,每次我们回溯现场的时候,一定要等当前数字使用次数全部枚举完毕后,再进行恢复现场
具体怎么恢复,我们递归的时候是怎么加的数字,我们回溯的时候就相反地怎么减去数字

class Solution {
List<Integer> path = new ArrayList<>();List<List<Integer>> list = new ArrayList<>();int targets;public List<List<Integer>> combinationSum(int[] candidates, int target) {targets = target;dfs(candidates,0,0);return list;}private void dfs(int [] array,int pathSum,int pos){if(pathSum == targets){list.add(new ArrayList<>(path));return;}if(pathSum > targets || pos == array.length){return;}//枚举几个当前pos位置的数字for(int i = 0;i*array[pos] <= targets;i++){if(i != 0){//只有i不为0才去添加值//因为i为0添加值也没用path.add(array[pos]);}dfs(array,pathSum+i*array[pos],pos+1);}//恢复现场,要把我们当前这一层添加的所有元素都删除,好让其他层保持上一层递归状态for(int i = 1;i*array[pos] <= targets;i++){path.remove(path.size()-1);}}}

七、字母大小写全排列

题目链接
这题我们直接画一个决策树

image-20251202104240936

class Solution {
List<String> list = new ArrayList<>();StringBuilder path = new StringBuilder();public List<String> letterCasePermutation(String s) {char [] ss = s.toCharArray();dfs(ss,0);return list;}private void dfs(char [] ss,int pos){if(pos == ss.length){list.add(path.toString());return;}//不改变大小写path.append(ss[pos]);dfs(ss,pos+1);//恢复现场path.deleteCharAt(path.length()-1);//改变大小写,并且是字母才改变if(Character.isLetter(ss[pos])){//判断此时字母是大写还是小写if(Character.isLowerCase(ss[pos])){path.append(Character.toUpperCase(ss[pos]));}else{path.append(Character.toLowerCase(ss[pos]));}dfs(ss,pos+1);//恢复现场path.deleteCharAt(path.length()-1);}}}

八、优美的排列

题目链接
这题就在于不能选择重复元素,因此我们可以使用一个boolean类型数组,标记当前数字有没有被使用过
直接看代码吧

class Solution {
boolean[] isUse;
int count;
public int countArrangement(int n) {
isUse = new boolean[n+1];
dfs(1,n);
return count;
}
private void dfs(int pos,int num){
if(pos > num){
//说明之前都是优美排列
count++;
return;
}
for(int i = 1;i <= num;i++){
if(!isUse[i] && (i % pos == 0 || pos % i == 0)){
isUse[i] = true;
dfs(pos+1,num);
//恢复现场
isUse[i] = false;
}
}
}
}

九、N皇后——Hard

题目链接

我们首先来明确皇后攻击其他皇后的规则
首先,当前行和列不能有其他皇后,并且主对角线和副对角线也不能有皇后
我们先来画决策树

image-20251202111628611

因此棋盘大小是3是不可以的,其实我们决策树里边就可以看出来我们的具体函数设计
即我们每行每行的去考虑皇后放在哪个位置,检查行列对角线,一直枚举到超出棋盘范围终止
但是如果我们直接循环四次查看行列对角线符合要求,这世界复杂度也太高了,因此我们可以类似哈希表,搞三个boolean数组
一个数组表示当前列是否存在皇后(我们是逐行递归的,行不用看)
另一个数组是主对角线数组,这个利用到我们数学原理

image-20251202112413243

因此只要我们y-x=b,这个b值相同,就说明我们在同一条线上
但是,如果y-x < 0怎么办呢,我们可以加上偏移量,让其结果永远是正数
y - x + 棋盘大小 = b


同理副对角线也是这样搞法,只不过不用担心负值

image-20251202112725007

y + x = b


同时,我们对角线数组大小必须是2倍棋盘大小,不然的话可能会越界

class Solution {
//列数组,标识每一列的皇后出现情况
boolean [] cols;
//主对角线数组,表示每一个主对角线皇后出现情况
boolean [] dig1;
//副对角线,表示每一个副对角线皇后出现情况
boolean [] dig2;
//统计结果
List<List<String>> list = new ArrayList<>();//表示当前棋盘大小int size;//需要一个全局变量路径去记录结果char [][] path;public List<List<String>> solveNQueens(int n) {cols = new boolean[n];//使用y-x=b这一特性判断,因此需要两倍n的大小dig1 = new boolean[2*n];//使用y+x=b这一特性判断,因此需要两倍n的大小dig2 = new boolean[2*n];//初始化路径path = new char[n][n];//初始化棋盘大小size = n;//初始棋盘for(int i = 0;i < n;i++){for(int j = 0;j < n;j++){path[i][j] = '.';}}//因此需要一个坐标dfs(0);return list;}private void dfs(int row){//当我们枚举到最后一行的下一行(即越界时候)停止枚举if(row == size){//能到达这里,说明是一种合法情况//遍历path结果集List<String> tmp = new ArrayList<>();for(int i = 0;i < size;i++){tmp.add(new String(path[i]));}list.add(new ArrayList<>(tmp));return;}//开始逐行枚举,现在针对当前行的每一列进行枚举for(int i = 0;i < size;i++){if(!cols[i] && !dig1[row-i+size] && !dig2[row+i]){//说明是一个合法位置path[row][i] = 'Q';//设置数组cols[i] = dig1[row-i+size] = dig2[row+i] = true;dfs(row+1);//恢复现场path[row][i] = '.';cols[i] = dig1[row-i+size] = dig2[row+i] = false;}}}}

十、有效的数独

题目链接
我们可以定义一个row的二维数组,row[i][j]表示第i行是否出现j这个数字
同时我们可以定义一个col的二维数组,col[i][j]表示第i列是否出现j这个数字
再者我们可以定义一个grid的三维数组,grid[i][j][num]表示第i行且第j列个九宫格内是否出现num这个数字
那我们要如何确定自己在哪个九宫格之中呢,我们仅需把当前坐标比如是(4,2)除以3,即第1行第0列的九宫格,即可找到位置

image-20251202113946273

class Solution {
public boolean isValidSudoku(char[][] board) {
int size = 9;
// 记录每行数字出现情况
boolean[][] row = new boolean[size][size + 1];
// 记录每列数字出现情况  
boolean[][] col = new boolean[size][size + 1];
// 记录每个3x3宫格数字出现情况
boolean[][][] grid = new boolean[3][3][size + 1];
// 遍历整个数独板
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
char c = board[i][j];
// 跳过空白格
if (c == '.') {
continue;
}
// 字符数字转整型数字
int num = c - '0'; // 或者使用 Character.getNumericValue(c)
// 检查当前数字是否在行、列、宫格中重复出现
if (row[i][num] || col[j][num] || grid[i / 3][j / 3][num]) {
return false; // 发现重复,立即返回false
}
// 标记数字已出现
row[i][num] = true;
col[j][num] = true;
grid[i / 3][j / 3][num] = true;
}
}
return true; // 所有检查通过
}
}

十一、解数独

题目链接
这题难点就在于我们要对每行每列(即每个单元格都要判断),并且跟上一题一样,也是使用三个数组标记
但是如果我们填入某个合法数字,后续也可能会导致数字无法填入,就要进行回溯操作
那我们要怎么知道我们填的是否正确呢,我们可以使用一个返回值,如果到了最后整个棋盘都填成功了,我们就一直向上返回true,反之就返回false,以便进行恢复现场操作

image-20251202114746171

1. 函数有返回值的写法

class Solution {
boolean [][] row;
boolean [][] col;
boolean [][][] grid;
public void solveSudoku(char[][] board) {
row = new boolean[9][10];
col = new boolean[9][10];
grid = new boolean[3][3][10];
//初始化原本就存在数独表中的数字
for(int i = 0;i < 9;i++){
for(int j = 0;j < 9;j++){
if(board[i][j] != '.'){
int num = board[i][j] - '0';
row[i][num] = true;
col[j][num] = true;
grid[i/3][j/3][num] = true;
}
}
}
dfs(board);
}
private boolean dfs(char[][] board){
//遍历每一个单元格
for(int rows = 0;rows < 9;rows++){
for(int cols = 0;cols < 9;cols++){
if(board[rows][cols] == '.'){
//不是数字才开始填入
for(int num = 1;num <= 9;num++){
//开始尝试1-9数字
if(!row[rows][num] && !col[cols][num] && !grid[rows/3][cols/3][num]){
//先尝试放入数字
board[rows][cols] = (char)('0' + num);
//设置状态
row[rows][num] = true;
col[cols][num] = true;
grid[rows/3][cols/3][num] = true;
//递归,自动会找到后续的单元格,尝试填入数字
//如果我们最后发现整个数独表填满了,直接向上返回
if(dfs(board)){
return true;
}
//此时如果我们填入的数字不合法,我们就进行恢复现场操作
board[rows][cols] = '.';
//同时不要忘了去设置状态
row[rows][num] = false;
col[cols][num] = false;
grid[rows/3][cols/3][num] = false;
}
}
//说明我们填入的1-9数字不合法,就要给上一层返回false
return false;
}
}
}
//说明整个棋盘每个空格我们都填完了,因此直接返回true
return true;
}
}

2. 函数无返回值写法

class Solution {
//表示棋盘大小
int size;
//全局变量棋盘
char[][] boards;
//检查行是否存在相同的数字
boolean[][] row;
//检查列是否有存在相同的数字
boolean[][] col;
//检查九宫格内是否存在相同的数字
boolean[][][] grid;
//标记是否找到解
boolean isFound = false;
public void solveSudoku(char[][] board) {
boards = board;
size = 9; // 数独固定为9×9
row = new boolean[size][10]; //数字1-9,所以需要10个位置
col = new boolean[size][10];
grid = new boolean[3][3][10]; //九宫格是3×3的
//初始化已有数字
for(int i = 0; i < size; i++){
for(int j = 0; j < size; j++){
char ch = board[i][j];
if(ch != '.'){
int num = ch - '0';
row[i][num] = true;
col[j][num] = true;
grid[i/3][j/3][num] = true;
}
}
}
dfs(0,0);
}
private void dfs(int posx, int posy){
if(isFound){
//整个表求解完毕
return;
}
if(posx == size){
//找到了解
isFound = true;
return;
}
if(posy == size){
//说明当前行找完了
dfs(posx+1,0);
return;
}
if(boards[posx][posy] != '.'){
//当前位置已经有数字了
dfs(posx,posy+1);
return;
}
//每个单元格看,前提要是非数字才去判断
for(int i = 1; i <= 9; i++){
//遍历1-9数字
if(!row[posx][i] && !col[posy][i] && !grid[posx/3][posy/3][i]){
boards[posx][posy] = (char)('0' + i);
row[posx][i] = true;
col[posy][i] = true;
grid[posx/3][posy/3][i] = true;
//去当前行的下一列看看
dfs(posx,posy+1);
if(isFound){
//找到了解直接向上返回
return;
}
/*
1.递归到达某个单元格,所有1-9的数字都违反规则
2.循环结束,该层递归返回false
3.返回到上一层递归,执行回溯代码
4.我们有理由认为这个数字的填入导致后续单元格数字无法填入
*/
//回溯现场
boards[posx][posy] = '.';
row[posx][i] = false;
col[posy][i] = false;
grid[posx/3][posy/3][i] = false;
}
}
}
}

十二、单词搜索

题目
这题我们可以针对性的一行行扫描,具体看图

image-20251202125006208

因此我们函数设计就可以变成每一次递归的时候,尝试去上下左右四个方位寻找目标字符,找到了就递归,找不到就回溯
并且我们搜索也是不能走重复路径的,因此同样使用一个boolean标记当前位置是否走过


但是我们要进行四个位置递归,开销太大,我们可以搞两个向量数组,表示四个方位,循环就循环四次就好
向量数组x方向:{0,0,1,-1} y方向:{1,-1,0,0},注意x和y方向数组要一一对应,能够表示当前位置的上下左右四个位置


你肯定想说,我不一定要用到向量数组啊,但是如果题目要求不能修改原数组呢


class Solution {
boolean [][] isUse;
char [][] boards;
int wordLength;
char[] words;
int wide;
int height;
public boolean exist(char[][] board, String word){
wide = board[0].length;
height = board.length;
if(height == 0){
return false;
}
wordLength = word.length();
words = word.toCharArray();
isUse = new boolean[height][wide];
//使用全局棋盘,为了在参数传递的时候节省递归开销
boards = board;
for(int i = 0;i < height;i++){
for(int j = 0;j < wide;j++){
//寻找字符串中第一个字符
if(board[i][j] == word.charAt(0)){
//就从此位置开始找,从字符串的第二个字符开始找
//先标记这个位置使用过
isUse[i][j] = true;
if(dfs(i,j,1)){
//如果从此位置开始后续的字符都可以被找到
//我们就直接返回true
return true;
}
//说明上面判断不合法,继续往后寻找
isUse[i][j] = false;
}
}
}
//到了这里说你整个表中并不存在和字符串中第一个字符匹配的字符
//直接返回false表示结果
return false;
}
//为避免多次递归,对于矩阵,我们使用一个向量数组表示各个位置
//上下两组一一对应
int [] x = {0,0,1,-1};
int [] y = {1,-1,0,0};
//posX表示第几行,poxY表示第几列
private boolean dfs(int posx,int posy,int strPos){
if(strPos == wordLength){
//说明此时到了字符串的最后一个字符,返回true
return true;
}
//使用循环直接去枚举四个方向
for(int i = 0;i < 4;i++){
//此时的位置,我们使用向量作为偏移量
int curX = posx+x[i];
int curY = posy+y[i];
//判断当前位置的合法性
if(curX >= 0 && curX < height && curY >= 0 && curY < wide && !isUse[curX][curY] && boards[curX][curY] == words[strPos]){
//标记当前位置被使用过了
isUse[curX][curY] = true;
//进行递归
if(dfs(curX,curY,strPos+1)){
//说明后续的都是正确的,我们直接向上返回
return true;
}
//说明此时是一种错误的走法,恢复现场
isUse[curX][curY] = false;
}
}
//到了这里说明四个方向都不符合要求,再一次回退
return false;
}
}

十三、黄金矿工

题目链接
这题和单词搜索简直是一模一样,因此就不啰嗦了

class Solution {
//同理使用一个bool数组表示当前位置是否被使用过
boolean[][] isUse;
int wide;
int height;
int ret;
public int getMaximumGold(int[][] grid) {
wide = grid[0].length;
height = grid.length;
isUse = new boolean[height][wide];
//选取一个不为0的地点开始挖矿
for (int i = 0; i < height; i++) {
for (int j = 0; j < wide; j++) {
if (grid[i][j] != 0) {
//从这个地方开始挖,先标记当前位置状态
isUse[i][j] = true;
//先把当前位置黄金加上
dfs(grid, i, j, grid[i][j]);
//恢复现场,从其他位置开始挖掘
isUse[i][j] = false;
}
}
}
return ret;
}
//同理为避免多次递归,我们使用向量数组
int[] x = { 0, 0, -1, 1 };
int[] y = { 1, -1, 0, 0 };
//posX表示第几行,posYB表示第几列
private void dfs(int[][] grid, int posx, int posy, int path) {
ret = Math.max(ret, path);
//同理使用循环枚举四个方向
for (int i = 0; i < 4; i++) {
int curX = posx + x[i];
int curY = posy + y[i];
if (curX >= 0 && curX < height && curY >= 0 && curY < wide && !isUse[curX][curY] && grid[curX][curY] != 0) {
//先统计结果
path += grid[curX][curY];
//再设置状态
isUse[curX][curY] = true;
//递归下一次
dfs(grid, curX, curY, path);
//恢复现场
isUse[curX][curY] = false;
//不要忘记path也要恢复
path -= grid[curX][curY];
}
}
}
}

十四、不同路径III

题目链接
这题难就难在要把所有0的位置都走一遍,并且还不能重复,并且-1的位置还不能走
这题我们采用暴力搜索方式,先扫描整个表,寻找开始位置和结束位置
再去统计0的个数,这决定了我们走到终点时,我们实际走到步数和理想步数能不能对的上

class Solution {
//标记某个位置是否已经走过
boolean [][] isUse;
//起始位置
int startX,startY;
int endX,endY;
//统计0的个数,也就是预期走多少步
int step;
//定义计数器
int count;
int wide;
int height;
public int uniquePathsIII(int[][] grid) {
//废话不多说,直接开始暴力搜索枚举
height = grid.length;
wide = grid[0].length;
isUse = new boolean[height][wide];
step = 0; // 重置step
count = 0; // 重置count
//先扫描一遍整个表,统计0的个数,并且明确起始位置
for(int i = 0;i < height;i++){
for(int j = 0;j < wide;j++){
if(grid[i][j] == 0){
step++;
}else if(grid[i][j] == 1){
startX = i;
startY = j;
}else if(grid[i][j] == 2){
endX = i;
endY = j;
}
}
}
isUse[startX][startY] = true;
//开始递归,从开始位置开始
dfs(grid,startX,startY,0);
return count;
}
//同样地使用向量数组
int [] x = {0,0,1,-1};
int [] y = {1,-1,0,0};
//同理posx表示行,posy表示列,path表示路径上0的个数
private void dfs(int [][] grid,int posx,int posy,int path){
// 先检查是否到达终点
if(posx == endX && posy == endY){
//如果遇到了数字2,就到了终点,检查结果
if(path == step){
count++;
}
return;
}
//枚举四个方向
for(int i = 0;i < 4;i++){
int curX = posx+x[i];
int curY = posy+y[i];
// 检查边界、是否访问过、是否是障碍物
if(curX >= 0 && curX < height && curY >= 0 && curY < wide &&
!isUse[curX][curY] && grid[curX][curY] != -1){
// 标记为已访问
isUse[curX][curY] = true;
// 计算新的路径长度:如果是0则加1,否则保持不变
int newPath = path;
if(grid[curX][curY] == 0) {
newPath = path + 1;
}
// 递归到下一个位置
dfs(grid, curX, curY, newPath);
//恢复现场
isUse[curX][curY] = false;
}
}
}
}

十五、找出所有子集异或总和再求和

题目链接
决策树就不用画了,这一题就是子集那道题的综合版本
找子集就是我们刚开始传入0下标,我们每一次递归这个下标++就好
然后我们在回溯的时候,要记得恢复现场,可以再异或当前位置的数字,达到消除的效果

class Solution {
int sum;
int path;
public int subsetXORSum(int[] nums) {
subsetXORSumChild(nums,0);
return sum;
}
private void subsetXORSumChild(int [] nums,int pos){
//统计每一层的异或和
sum += path;
for(int i = pos;i < nums.length;i++){
path ^= nums[i];
subsetXORSumChild(nums,i+1);
//向上回溯的时候,要把当前层多余的元素消除
//因为刚刚在sum中就已经把每一层的结果都统计到了
path ^= nums[i];
}
}
}

十六、全排列II

题目链接
这题就是全排列I的升级版本,要求同一个位置不能选择重复的数字,我们还是来换一个决策树
image-20251202162112026

代码实在看不明白可以看我画的决策树,自己找几种情况带入就好了

class Solution {
List<List<Integer>> ret;List<Integer> path;boolean [] isUse;public List<List<Integer>> permuteUnique(int[] nums) {isUse = new boolean[nums.length];ret = new ArrayList<>();path = new ArrayList<>();Arrays.sort(nums);permuteUniqueChild(nums,0);return ret;}private void permuteUniqueChild(int [] nums,int pos){if(pos == nums.length){ret.add(new ArrayList<>(path));return;}//正常遍历数组for(int i = 0;i < nums.length;i++){if((isUse[i] == true || (i != 0 && nums[i] == nums[i-1] && isUse[i-1] == false))){//剪去不合法分支,其中i != 0 是为了保证i-1不越界continue;}path.add(nums[i]);isUse[i] = true;permuteUniqueChild(nums,pos+1);//恢复现场isUse[i] = false;//剪枝path.remove(path.size()-1);}/*for循环也可以这样写,即只看合法分支for(int i = 0;i < nums.length;i++){if(!isUse[i] && *i == 0 || nums[i] != nums[i-1] && isUse[i-1]){path.add(nums[i]);isUse[i] = true;permuteUniqueChild(nums,pos+1);//恢复现场isUse[i] = false;//剪枝path.remove(path.size()-1);}*/}}

希望本篇文章对您有帮助,有错误您可以指出,我们友好交流

END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1188505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何为制造业选geo优化公司?2026年geo优化公司全面评测与推荐,直击精准询盘痛点 - 品牌推荐

基于《2026年中国企业AI搜索生态应用趋势报告》核心洞察、行业权威技术认证及第三方实测数据,甄选出2026年值得合作的GEO优化服务商榜单,覆盖高端制造、专业服务、知识内容、工业B2B等多种行业需求,逐一解答“哪家G…

钱包技术:从私钥保管到Web3入口的演进之路

在区块链世界中&#xff0c;钱包不仅仅是存储数字货币的地方&#xff0c;更是用户与整个去中心化网络交互的入口。它经历了从简单的密钥管理工具到多功能数字资产控制中心的演变&#xff0c;成为Web3生态的核心基础设施。一、钱包技术的发展历程早期单链时代&#xff08;2009-2…

EI会议推荐!2026年机器视觉、检测与三维成像技术国际学术会议(MVDIT 2026)

会议官网&#xff1a;https://www.yanfajia.com/action/p/QHT2TU33 会议日期&#xff1a; 2026年5月15-17日 会议地点&#xff1a;中国 南昌 接受或拒绝通知日期&#xff1a;提交后7个工作日 检索类型&#xff1a;EI Compendex、Scopus 会议秘书&#xff1a;Julian 联系电…

数据安全有保障的BI产品?观远数据筑牢企业核心资产防护墙 - 速递信息

在数据驱动的商业时代,数据安全已成为企业数字化转型的生命线。BI 产品作为整合、分析、呈现企业核心数据的关键工具,其数据安全保障能力直接决定企业数据资产是否面临泄露、篡改、丢失等风险。对于金融、央国企、零…

单北斗GNSS变形监测系统是什么?主要应用于水库和桥梁形变监测吗?

单北斗GNSS变形监测系统作为一种高效的监测工具&#xff0c;广泛应用于水库和桥梁等基础设施的形变监测。它利用北斗卫星信号&#xff0c;实时获取结构的微小变形数据&#xff0c;为工程安全提供可靠依据。在这些应用中&#xff0c;系统能够自动化记录和分析监测数据&#xff0…

操作系统进程间通信(IPC)的庖丁解牛

操作系统进程间通信&#xff08;IPC, Inter-Process Communication&#xff09;是 多进程系统中协调、同步与数据交换的核心机制。它解决了 进程隔离性 与 协作需求 之间的根本矛盾。理解 IPC&#xff0c;是掌握高并发、分布式系统、安全模型的基石。一、为什么需要 IPC&#x…

2026年GEO优化公司推荐:针对知识密集型行业痛点排名,涵盖法律与教育多场景应用 - 品牌推荐

由中国领先的行业监测与权威平台《广告主评论》主办、中经总网中经在线(全称中国经济报道)、世界品牌研究院(中国)集团有限公司协办支持的“全球 GEO 厂商评测”,从 GEO 理论奠基、技术实践、创始人背景、技术资质…

springboot高等数学课程教辅资源系统的设计与实现

高等数学课程教辅资源系统的背景高等数学是理工科专业的基础课程&#xff0c;内容涵盖微积分、线性代数、概率统计等&#xff0c;理论性强且抽象。传统教学模式下&#xff0c;学生常面临知识点理解困难、习题资源分散、个性化学习支持不足等问题。SpringBoot作为轻量级Java框架…

EI往届检索稳定JPCS出版| 往届检索可查 | 第四届机械工程与先进制造智能化技术研讨会(MEAMIT 2026)

高录用&#xff5c;EI 稳定检索&#xff5c; 学生投稿优惠 在线征集&#xff1a;机械工程、先进制造技术、智能制造系统与自动化 机器人及协作系统、智能检测与质量控制、先进加工与增材制造等相关主题稿件 欢迎团体投稿/参会&#xff0c;享专属优惠&#xff01;详情请咨询大…

springboot高校党员信息管理系统

高校党员信息管理系统的背景高校党员信息管理系统是针对高校党组织管理需求开发的数字化平台。高校党员群体具有流动性强、信息更新频繁的特点&#xff0c;传统纸质档案或分散的电子表格管理方式效率低下&#xff0c;难以满足党员发展、组织关系转接、党费收缴等工作的精准化需…

命名管道和匿名管道

命名管道 命名管道(Named Pipe)是一种特殊类型的文件,它以文件系统路径为标识,允许任意两个进程(无论是否有亲缘关系)通过读写该路径进行数据交换。其本质是内核维护的一个内存缓冲区,遵循“先进先出”(FIFO)…

好写作AI|回复“刁钻”审稿意见的智囊:当AI开始“阅读理解”审稿人的潜台词…

收到审稿意见&#xff0c;第一反应不是“怎么改”&#xff0c;而是“他到底想让我干嘛&#xff1f;&#xff01;”——你的AI“学术拆弹专家”已上线&#xff0c;专治各种话中有话。每位经历过同行评审的学术战士&#xff0c;都曾被那封邮件支配过恐惧&#xff1a;点开&#xf…

springboot高校督导听查课支持服务系统

高校督导听查课支持服务系统的背景 高校督导听查课支持服务系统是基于SpringBoot框架开发的数字化管理工具&#xff0c;旨在优化传统教学督导工作的流程。传统督导方式依赖纸质记录和人工统计&#xff0c;存在效率低、数据易丢失、反馈滞后等问题。该系统通过信息化手段整合听…

2026年知名的数控凸轮磨床生产商哪家靠谱?口碑排行 - 品牌宣传支持者

在精密机械加工领域,数控凸轮磨床作为关键设备,其性能直接关系到凸轮轴等核心零部件的加工精度。本文基于设备性能、技术创新能力、市场反馈及售后服务等维度,综合评估当前国内数控凸轮磨床生产商的综合实力。经过对…

知名的服装衬布公司哪家靠谱?2026年行业口碑排行 - 品牌宣传支持者

在服装辅料领域,衬布作为支撑服装廓形与功能性的关键材料,其品质直接影响成衣的耐用性与舒适度。选择靠谱的服装衬布供应商需综合考量企业历史、产能规模、技术实力及市场口碑。根据2026年行业调研数据,如皋市纪林服…

Prodigy AI标注工具v1.18更新详解

Changelog Prodigy 此页面列出了 Prodigy 的历史变更。每当有新的更新可用时&#xff0c;都会向购买时指定的邮箱地址发送邮件通知。然后您可以通过个人下载链接下载新版本。如果您的免费升级已过期&#xff0c;您现在可以通过我们的在线商店为您的许可证添加 12 个月的更新。…

Agent Skill: react-best-practices

背景 Agent Skill 是一个可复用的能力单元&#xff0c;通常包含明确的职责边界、触发条件、输入输出约定&#xff0c;且可能封装了提示词、工具调用逻辑、状态 处理和约束规则。在工程化 Agent 系统中&#xff0c;Skill 更接近“函数 / 用例 / 子代理”。相对于提示词&#xf…

深度测评研究生必用的10款AI论文写作软件

深度测评研究生必用的10款AI论文写作软件 学术写作工具测评&#xff1a;为何需要一份权威榜单&#xff1f; 在当前科研环境日益激烈的背景下&#xff0c;研究生群体面临论文写作、文献检索、格式规范等多重挑战。随着AI技术的不断进步&#xff0c;各类论文写作软件层出不穷&…

找不到上海智推时代对接方式?这份官方渠道清单收好 - 速递信息

数字化转型的浪潮早已从 “基础建设” 迈入 “深度赋能” 的新阶段,而生成式 AI 的爆发,则让这场转型迎来了最关键的 “生死竞速”—— 谁能率先让品牌与产品融入 AI 的信息生态,谁就能在下一代市场竞争中掌握主动权…

基于 SSH 反向隧道的家庭实验室接入方案

概述 我们经常面临这样的需求:如何在公司或差旅环境中,安全地访问位于家庭内网的AI 训练机? 传统的 DDNS + 路由器端口映射方案暴露面过大,极易招致全网扫描。本文将分享一种 “隐形”架构方案:利用一台公网 VPS(…