3.28 图论+回溯

This commit is contained in:
zhangsan 2025-03-28 17:37:12 +08:00
parent 5dfb8008da
commit c85a5023f1
14 changed files with 751 additions and 0 deletions

View File

@ -0,0 +1,49 @@
package backtrack;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 39. 组合总和 (combinationSum)
* 描述给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target 找出 candidates 中可以使数字和为目标数 target 所有 不同组合 并以列表形式返回你可以按 任意顺序 返回这些组合
* candidates 中的 同一个 数字可以 无限制重复被选取 如果至少一个数字的被选数量不同则两种组合是不同的
* 对于给定的输入保证和为 target 的不同组合数少于 150
示例 1
输入candidates = [2,3,6,7], target = 7
输出[[2,2,3],[7]]
解释
2 3 可以形成一组候选2 + 2 + 3 = 7 注意 2 可以使用多次
7 也是一个候选 7 = 7
仅有这两种组合
示例 2
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
* 链接https://leetcode.cn/problems/combination-sum/
*/
public class CombinationSum {
void dfs(int[] candidates,List<Integer>path,List<List<Integer>>res,int start,int cur,int target){
if(cur==target) {
res.add(new ArrayList<>(path));
}
else if(cur>target)
return;
else {
for (int i = start; i < candidates.length; i++) {
path.add(candidates[i]);
cur+=candidates[i];
dfs(candidates,path,res,i,cur,target);
cur-=candidates[i];
path.remove(path.size()-1);
}
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>>res=new ArrayList<>();
List<Integer>path=new ArrayList<>();
dfs(candidates,path,res,0,0,target);
return res;
}
}

View File

@ -0,0 +1,59 @@
package backtrack;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 题目 40. 组合总和 II (combinationSum2)
* 描述给定一个候选人编号的集合 candidates 和一个目标数 target 找出 candidates 中所有可以使数字和为 target 的组合
* candidates 中的每个数字在每个组合中只能使用 一次
* 注意解集不能包含重复的组合
示例 1
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[[1,1,6],
[1,2,5],
[1,7],
[2,6]]
示例 2
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[[1,2,2],
[5]]
* 链接https://leetcode.cn/problems/combination-sum-ii/
*/
public class CombinationSum2 {
void backtrack(List<List<Integer>>res,List<Integer>path,int[] candidates,int[]visited,int start,int cur,int target){
if(cur==target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
if(i>0 &&candidates[i]==candidates[i-1]&&visited[i-1]==0)
continue;
if(visited[i]==0) {
cur += candidates[i];
if(cur>target)
return;
path.add(candidates[i]);
visited[i] = 1;
backtrack(res, path, candidates, visited, i + 1, cur, target);
path.remove(path.size() - 1);
visited[i] = 0;
cur -= candidates[i];
}
}
}
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>>res=new ArrayList<>();
List<Integer>path=new ArrayList<>();
int[] visited=new int[candidates.length];
Arrays.sort(candidates);
backtrack(res,path,candidates,visited,0,0,target);
return res;
}
}

View File

@ -0,0 +1,43 @@
package backtrack;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 77. 组合 (combine)
* 描述给定两个整数 n k返回范围 [1, n] 中所有可能的 k 个数的组合
* 你可以按 任何顺序 返回答案
示例 1
输入n = 4, k = 2
输出
[[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],]
* 链接https://leetcode.cn/problems/combinations/
*/
public class Combine {
void backtrack(List<List<Integer>> res,List<Integer>path,int n,int start,int cur,int k){
if(cur==k){
res.add(new ArrayList<>(path));
return;
}
for (int i = start; i <= n; i++) {
path.add(i);
cur++;
backtrack(res,path,n,i+1,cur,k);
cur--;
path.remove(path.size()-1);
}
}
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res=new ArrayList<>();
List<Integer>path=new ArrayList<>();
backtrack(res,path,n,1,0,k);
return res;
}
}

View File

@ -0,0 +1,56 @@
package backtrack;
/**
* 题目 79. 单词搜索 (Exist)
* 描述给定一个 m x n 二维字符网格 board 和一个字符串单词 word 如果 word 存在于网格中返回 true 否则返回 false
* 单词必须按照字母顺序通过相邻的单元格内的字母构成其中相邻单元格是那些水平相邻或垂直相邻的单元格同一个单元格内的字母不允许被重复使用
示例 1
输入board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出true
示例 2
输入board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出true
* 链接https://leetcode.cn/problems/word-search/
*/
public class Exist {
boolean flag = false;
void dfs(char[][] board, String word, int i, int j, int index) {
if (index == word.length()) {
flag = true;
return;
}
int r = board.length, c = board[0].length;
// 检查边界和是否匹配字符
if (i < 0 || i >= r || j < 0 || j >= c || board[i][j] != word.charAt(index)) {
return;
}
// 记录当前字符并标记为已访问这里用 '#' 标记已访问可以用其他不可能出现在 board 中的字符
char temp = board[i][j];
board[i][j] = '#';
// 向四个方向递归
dfs(board, word, i - 1, j, index + 1);
dfs(board, word, i + 1, j, index + 1);
dfs(board, word, i, j - 1, index + 1);
dfs(board, word, i, j + 1, index + 1);
// 回溯还原
board[i][j] = temp;
}
public boolean exist(char[][] board, String word) {
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] == word.charAt(0)) {
dfs(board, word, i, j, 0);
if (flag) return true;
}
}
}
return flag;
}
}

View File

@ -0,0 +1,50 @@
package backtrack;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 22. 括号生成 (generateParenthesis)
* 描述数字 n 代表生成括号的对数请你设计一个函数用于能够生成所有可能的并且 有效的 括号组合
示例 1
输入n = 3
输出["((()))","(()())","(())()","()(())","()()()"]
示例 2
输入n = 1
输出["()"]
* 链接https://leetcode.cn/problems/generate-parentheses/
*/
//做出来了但我的写法很丑改为标准答案
public class GenerateParenthesis {
// 回溯函数
void dfs(List<String> res, StringBuilder path, int left, int right, int n) {
// 如果左右括号都用完了加入结果
if (left == n && right == n) {
res.add(path.toString());
return;
}
// 尝试加左括号
if (left < n) {
path.append('(');
dfs(res, path, left + 1, right, n);
path.deleteCharAt(path.length() - 1); // 回溯
}
// 尝试加右括号右括号不能多于左括号
if (right < left) {
path.append(')');
dfs(res, path, left, right + 1, n);
path.deleteCharAt(path.length() - 1); // 回溯
}
}
public List<String> generateParenthesis(int n) {
List<String> res = new ArrayList<>();
dfs(res, new StringBuilder(), 0, 0, n);
return res;
}
}

View File

@ -0,0 +1,57 @@
package backtrack;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 17. 电话号码的字母组合 (letterCombinations)
* 描述给定一个仅包含数字 2-9 的字符串返回所有它能表示的字母组合答案可以按 任意顺序 返回
* 给出数字到字母的映射如下与电话按键相同注意 1 不对应任何字母
*
示例 1
输入digits = "23"
输出["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2
输入digits = ""
输出[]
* 链接https://leetcode.cn/problems/letter-combinations-of-a-phone-number/
*/
//重做一遍
public class LetterCombinations {
public List<String> letterCombinations(String digits) {
List<String> res = new ArrayList<>();
// 如果输入为空直接返回空结果
if (digits == null || digits.length() == 0) {
return res;
}
// 电话号码对应的字母映射索引 0 对应数字 2依此类推
String[] mapping = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
// 从索引 0 开始回溯
backtrack(digits, mapping, 0, new StringBuilder(), res);
return res;
}
private void backtrack(String digits, String[] mapping, int index, StringBuilder combination, List<String> res) {
// 当组合长度等于输入数字长度时说明构造出一个完整组合
if (index == digits.length()) {
res.add(combination.toString());
return;
}
// 当前数字
char digitChar = digits.charAt(index);
// 计算 mapping 数组中的索引数字 '2' 对应索引 0
int mappingIndex = digitChar - '2';
String letters = mapping[mappingIndex];
// 遍历当前数字对应的每个字母
for (int i = 0; i < letters.length(); i++) {
// 选择当前字母加入组合
combination.append(letters.charAt(i));
// 递归构造下一个数字的字母组合
backtrack(digits, mapping, index + 1, combination, res);
// 回溯移除最后加入的字母尝试其他可能性
combination.deleteCharAt(combination.length() - 1);
}
}
}

View File

@ -0,0 +1,61 @@
package backtrack;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 131. 分割回文串 (partition)
* 描述给你一个字符串 s请你将 s 分割成一些 子串使每个子串都是 回文串 返回 s 所有可能的分割方案
示例 1
输入s = "aab"
输出[["a","a","b"],["aa","b"]]
示例 2
输入s = "a"
输出[["a"]]
* 链接https://leetcode.cn/problems/palindrome-partitioning/
*/
//不会
public class Partition {
/**
* 递归与回溯
* 从字符串的起始位置开始遍历所有可能的分割点对于每个可能的子串先判断它是否为回文串
*
* 如果是回文串则将其加入当前的分割方案然后递归处理剩下的部分
* 当走到字符串末尾时说明当前方案已经完成将该方案加入最终结果
* 回溯时需要将上一步加入的子串移除以便尝试其他分割方案
*/
public List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
backtrack(s, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(String s, int start, List<String> current, List<List<String>> result) {
if (start == s.length()) {
result.add(new ArrayList<>(current));
return;
}
for (int i = start; i < s.length(); i++) {
// 提取子串并判断是否为回文
String sub = s.substring(start, i + 1);
if (isPalindrome(sub)) {
current.add(sub);
backtrack(s, i + 1, current, result);
current.remove(current.size() - 1);
}
}
}
// 修改后的回文判断方法只接收一个字符串
private boolean isPalindrome(String s) {
for (int i = 0; i < s.length() / 2; i++) {
if (s.charAt(i) != s.charAt(s.length() - i - 1)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,56 @@
package backtrack;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 46. 全排列 (permute)
* 描述给定一个不含重复数字的数组 nums 返回其 所有可能的全排列 你可以 按任意顺序 返回答案
*示例 1
* 输入nums = [1,2,3]
* 输出[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
*
* 示例 2
* 输入nums = [0,1]
* 输出[[0,1],[1,0]]
*
* 示例 3
* 输入nums = [1]
* 输出[[1]]
*
* 链接https://leetcode.cn/problems/permutations/
*/
public class Permute {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
// 用来标记数组中数字是否被使用
boolean[] used = new boolean[nums.length];
List<Integer> path = new ArrayList<>();
backtrack(nums, used, path, res);
return res;
}
private void backtrack(int[] nums, boolean[] used, List<Integer> path, List<List<Integer>> res) {
// 当path中元素个数等于nums数组的长度时说明已构造出一个排列
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
// 遍历数组中的每个数字
for (int i = 0; i < nums.length; i++) {
// 如果该数字已经在当前排列中使用过则跳过
if (used[i]) {
continue;
}
// 选择数字nums[i]
used[i] = true;
path.add(nums[i]);
// 递归构造剩余的排列
backtrack(nums, used, path, res);
// 回溯撤销选择尝试其他数字
path.remove(path.size() - 1);
used[i] = false;
}
}
}

View File

@ -0,0 +1,51 @@
package backtrack;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 题目 47. 全排列 II (permuteUnique)
* 描述给定一个可包含重复数字的序列 nums 按任意顺序 返回所有不重复的全排列
示例 1
输入nums = [1,1,2]
输出
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2
输入nums = [1,2,3]
输出[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
* 链接https://leetcode.cn/problems/permutations-ii/
*/
public class PermuteUnique {
void backtrack(List<List<Integer>>res,List<Integer>path,int[]nums,int[] visited){
if(path.size()==nums.length){
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if(i > 0 && nums[i] == nums[i - 1] && visited[i - 1] == 0)
continue;
if(visited[i]==0){
visited[i]=1;
path.add(nums[i]);
backtrack(res,path,nums,visited);
path.remove(path.size()-1);
visited[i]=0;
}
}
}
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>>res=new ArrayList<>();
List<Integer>path=new ArrayList<>();
int[] visited=new int[nums.length];
Arrays.sort(nums);
backtrack(res,path,nums,visited);
return res;
}
}

View File

@ -0,0 +1,43 @@
package backtrack;
import java.util.ArrayList;
import java.util.List;
/**
* 题目 78. 子集 (subsets)
* 描述给你一个整数数组 nums 数组中的元素 互不相同 返回该数组所有可能的子集幂集
* 解集 不能 包含重复的子集你可以按 任意顺序 返回解集
*
示例 1
输入nums = [1,2,3]
输出[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2
输入nums = [0]
输出[[],[0]]
* 链接https://leetcode.cn/problems/subsets/
*/
public class Subsets {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
// 从索引0开始递归
backtrack(nums, 0, path, res);
return res;
}
private void backtrack(int[] nums, int start, List<Integer> path, List<List<Integer>> res) {
// 每个递归节点的当前path都构成了一个子集将其复制加入结果集合中
res.add(new ArrayList<>(path));
// 从start开始遍历所有可能的元素
for (int i = start; i < nums.length; i++) {
// 做选择将当前数字加入path
path.add(nums[i]);
// 递归从下一个位置继续选取避免重复选择
backtrack(nums, i + 1, path, res);
// 回溯撤销选择继续尝试下一个数字
path.remove(path.size() - 1);
}
}
}

View File

@ -0,0 +1,51 @@
package backtrack;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 题目 90. 子集 II (subsetsWithDup)
* 描述给你一个整数数组 nums 其中可能包含重复元素请你返回该数组所有可能的 子集幂集
* 解集 不能 包含重复的子集返回的解集中子集可以按 任意顺序 排列
示例 1
输入nums = [1,2,2]
输出[[],[1],[1,2],[1,2,2],[2],[2,2]]
* 链接https://leetcode.cn/problems/subsets-ii/
*/
//不会有点难
public class SubsetsWithDup {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
// 排序数组使重复元素相邻便于剪枝
Arrays.sort(nums);
backtrack(res, path, nums, 0);
return res;
}
void backtrack(List<List<Integer>> res, List<Integer> path, int[] nums, int start) {
// 每次递归都将当前路径加入结果注意空集也会被加入
res.add(new ArrayList<>(path));
for (int i = start; i < nums.length; i++) {
// 如果当前数字和前一个数字相同并且前一个数字没有被选取在本层中出现过则跳过
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
path.add(nums[i]);
backtrack(res, path, nums, i + 1);
path.remove(path.size() - 1);
}
}
// 测试方法
public static void main(String[] args) {
SubsetsWithDup solution = new SubsetsWithDup();
int[] nums = {1, 2, 2};
List<List<Integer>> subsets = solution.subsetsWithDup(nums);
System.out.println(subsets);
// 输出[[], [1], [1, 2], [1, 2, 2], [2], [2, 2]]
}
}

View File

@ -0,0 +1,41 @@
package graph;
/**
* 题目 733. 图像渲染 (floodFill)
* 描述有一幅以 m x n 的二维整数数组表示的图画 image 其中 image[i][j] 表示该图画的像素值大小你也被给予三个整数 sr , sc color 你应该从像素 image[sr][sc] 开始对图像进行上色 填充
* 为了完成 上色工作
* 从初始像素开始将其颜色改为 color
* 对初始坐标的 上下左右四个方向上 相邻且与初始像素的原始颜色同色的像素点执行相同操作
* 通过检查与初始像素的原始颜色相同的相邻像素并修改其颜色来继续 重复 此过程
* 没有 其它原始颜色的相邻像素时 停止 操作
* 最后返回经过上色渲染 修改 后的图像
*
* 示例 1
输入image = [[1,1,1],[1,1,0],[1,0,1]]sr = 1, sc = 1, color = 2
输出[[2,2,2],[2,2,0],[2,0,1]]
* 链接https://leetcode.cn/problems/flood-fill/
*/
public class FloodFill {
void dfs(int[][] image,int[][] visited,int origin,int i,int j,int color){
int r=image.length;
int c=image[0].length;
if(i<0||i>=r||j<0||j>=c||visited[i][j]==1||image[i][j]!=origin)
return;
image[i][j]=color;
visited[i][j]=1;
dfs(image,visited,origin,i-1,j,color);
dfs(image,visited,origin,i+1,j,color);
dfs(image,visited,origin,i,j-1,color);
dfs(image,visited,origin,i,j+1,color);
}
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
int r=image.length;
int c=image[0].length;
int origin=image[sr][sc];
int[][] visited=new int[r][c];
dfs(image,visited,origin,sr,sc,color);
return image;
}
}

View File

@ -0,0 +1,116 @@
package graph;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* 题目 130. 被围绕的区域 (solve)
* 描述给你一个 m x n 的矩阵 board 由若干字符 'X' 'O' 组成捕获 所有 被围绕的区域
* 连接一个单元格与水平或垂直方向上相邻的单元格连接
* 区域连接所有 'O' 的单元格来形成一个区域
* 围绕如果您可以用 'X' 单元格 连接这个区域并且区域中没有任何单元格位于 board 边缘则该区域被 'X' 单元格围绕
* 通过 原地 将输入矩阵中的所有 'O' 替换为 'X' 捕获被围绕的区域你不需要返回任何值
* <p>
* 示例 1
* 输入board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
* 输出[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
* <p>
* 链接https://leetcode.cn/problems/surrounded-regions/
*/
//做出来了但是很繁琐1
public class Solve {
// void dfs(char[][] board, Queue<int[]> queue, int i, int j) {
// if (!judgeValid(board, i, j) || board[i][j] == 'X')
// return;
// queue.offer(new int[]{i, j});
// board[i][j] = 'X';
// dfs(board, queue, i - 1, j);
// dfs(board, queue, i + 1, j);
// dfs(board, queue, i, j - 1);
// dfs(board, queue, i, j + 1);
//// board[i][j]='0';
// }
//
// public void solve(char[][] board) {
// Queue<int[]> queue = new LinkedList<>();
// List<Queue<int[]>> list = new ArrayList<>();
// for (int i = 0; i < board.length; i++) {
// for (int j = 0; j < board[0].length; j++) {
// if (board[i][j] == 'O') {
// dfs(board, queue, i, j);
// list.add(new LinkedList<>(queue));
// queue.clear();
// }
// }
// }
// for (Queue<int[]> queue1 : list) {
// int size = queue1.size();
// boolean flag = false;
// Queue<int[]> temp = new LinkedList<>(queue1);
// for (int i = 0; i < size; i++) {
// int[] tp = queue1.poll();
// if (!judgeValid(board, tp[0]-1, tp[1])||!judgeValid(board, tp[0]+1, tp[1])||!judgeValid(board, tp[0], tp[1]-1)||!judgeValid(board, tp[0], tp[1]+1)) {
// flag = true;
// break;
// }
// }
// if (flag) {
// while (!temp.isEmpty()) {
// int[] tp = temp.poll();
// board[tp[0]][tp[1]] = 'O';
// }
// }
// temp.clear();
// }
// }
//
// boolean judgeValid(char[][] board, int i, int j) {
// int r = board.length;
// int c = board[0].length;
// return i >= 0 && i < r && j >= 0 && j < c;
// }
public void solve(char[][] board) {
if (board == null || board.length == 0) {
return;
}
int rows = board.length, cols = board[0].length;
// 从边界开始将所有与边界相连的 'O' 标记为 '#'
for (int i = 0; i < rows; i++) {
dfs(board, i, 0);
dfs(board, i, cols - 1);
}
for (int j = 0; j < cols; j++) {
dfs(board, 0, j);
dfs(board, rows - 1, j);
}
// 遍历整个矩阵翻转剩余的 'O' 'X'同时将标记 '#' 恢复为 'O'
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (board[i][j] == 'O') {
board[i][j] = 'X';
} else if (board[i][j] == '#') {
board[i][j] = 'O';
}
}
}
}
private void dfs(char[][] board, int i, int j) {
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != 'O') {
return;
}
board[i][j] = '#';
dfs(board, i - 1, j);
dfs(board, i + 1, j);
dfs(board, i, j - 1);
dfs(board, i, j + 1);
}
}

View File

@ -0,0 +1,18 @@
package backtrack;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.*;
public class SubsetsWithDupTest {
@Test
public void subsetsWithDup() {
int[]nums = {1,2,2};
SubsetsWithDup solution = new SubsetsWithDup();
List<List<Integer>>res=solution.subsetsWithDup(nums);
System.out.println(res);
}
}