回溯算法思想
- 回溯思想的总概
- leetcode22括号生成
- leetcode46全排列
- leetcode47全排序2🔐
- leetcode39组合总和🔐
- leetcode40组合总和 II🔐
- leetcode216. 组合总和 III
- leetcode78子集🔐
- leetcode90子集2🔐
- leetcode79单词搜索
- leetcode212单词搜索2
- leetcode93复原 IP 地址🔐
- leetcode89格雷编码🔐
- leecode357计算各个位数不同的数字个数
- leetcode131分割回文串
- leetcode140单词差分2
- leetcode77组合
- leeetcode10正则表达式匹配
- leetcode95. 不同的二叉搜索树 II🔐
- leetcode113. 路径总和 II
回溯思想的总概
- 另外在回溯算法思想过程中,要着重注意优化减枝,这是提高回溯算法效率的主要方式。
- 常见的剪枝方法:改变树形结构、限制长度、限制数量、有时剪枝前需要进行排序
三种容易混淆的去重方式:
去重方式:牢记递归树的同一层上不能存在相同节点分支.
- 全排序2:排序+isvisited数组—>从0开始for循环(i>0因为是for起始位置)(i>0&&nums[i]==nums[i-1]&&isvisited[i-1]==false)isvisited[i-1]==false是用来去掉同层相同节点分支点条件
- 子集2:排序—>for从start开始for循环(i>start&&nums[i]==nums[i-1]) i>start是用来去掉同层相同分支,因为可以start时定可以取到,取到start后进行下一次for循环就会判断是否相同
- 组合总和2:(存在重复元素):排序–>,for从start开始,(i>start&&nums[i]==nums[i-1]))i>start是用来去掉同层相同分支
leetcode22括号生成
- 时间效率都差不多,不用咋剪枝。
class Solution {
List<String> list = new ArrayList<String>();
int N;
StringBuffer s = new StringBuffer("");
public List<String> generateParenthesis(int n) {
N = n;
process(0, 0);
return list;
}
private void process(int i, int j) {
if (j == N) {
list.add(s.toString());
return;
}
if (i < N) {
s.append("(");
process(i + 1, j);
s.deleteCharAt(i + j);
}
if (j < i) {
s.append(")");
process(i, j + 1);
s.deleteCharAt(i + j);
}
}
}
leetcode46全排列
- 注意这是不含重复的
class Solution {
List<List<Integer>> res;
LinkedList<Integer> output;
int n;
boolean[] visited;
public List<List<Integer>> permute(int[] nums) {
res = new ArrayList<>();
output = new LinkedList<>();
n = nums.length;
visited = new boolean[n];
backtrack(nums, 0);
return res;
}
public void backtrack(int[] nums, int first) {
// 所有数都填完了
if (first == n) {
res.add(new ArrayList<>(output));
}
for (int i = 0; i < n; i++) {
if (!visited[i]){
visited[i]=true;
// 动态维护数组
output.addLast(nums[i]);
// 继续递归填下一个数
backtrack(nums, first + 1);
// 撤销操作
output.removeLast();
visited[i]=false;
}
}
}
}
leetcode47全排序2🔐
class Solution {
//存放结果
List<List<Integer>> result = new ArrayList<>();
//暂存结果
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.sort(nums);
backTrack(nums, used);
return result;
}
private void backTrack(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
// 注意是从0开始的
for (int i = 0; i < nums.length; i++) {
// used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
// used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
// 如果同⼀树层nums[i - 1]使⽤过则直接跳过,满足跳过前定已经深搜过nums[i]了,只不过已经撤销了置为false了
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
//如果同⼀树⽀nums[i]没使⽤过开始处理
if (used[i] == false) {
used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用
path.addLast(nums[i]);
backTrack(nums, used);
path.removeLast();//回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
used[i] = false;//回溯
}
}
}
}
leetcode39组合总和🔐
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
int N;
int[] arr;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
N = candidates.length;
arr = candidates;
process(0, target);
return res;
}
private void process(int i, int target) {
if (target == 0) {
res.add(new ArrayList<>(list));
return;
}
if (i == N || target < 0) return;
int num = 0;
for (; num * arr[i] <= target; ++num) {
process(i + 1, target - num * arr[i]);
list.add(arr[i]);
}
for (; num > 0; num--) {
list.remove(list.size() - 1);
}
}
}
- 宽度优先搜索,一次剪枝(必要),一次优化(提高效率)
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates); // 先进行排序,优化:若当前+sum超过了target,后面的必然不可能
backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
return res;
}
public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {
// 找到了数字和为 target 的组合
if (sum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = idx; i < candidates.length; i++) {
// 如果 sum + candidates[i] > target 就终止遍历
if (sum + candidates[i] > target) break;
path.add(candidates[i]);
backtracking(res, path, candidates, target, sum + candidates[i], i);// i传值进行了剪枝,避免重复值出现
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
}
}
}
leetcode40组合总和 II🔐
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtracking(candidates, target, 0, 0);
return res;
}
public void backtracking(int[] candidates, int target, int sum, int idx) {
if (sum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = idx; i < candidates.length; i++) {
if (i > idx && candidates[i] == candidates[i - 1]) continue;
if (sum + candidates[i] > target) break;
path.addLast(candidates[i]);
backtracking(candidates, target, sum + candidates[i], i + 1);
path.removeLast();
}
}
}
leetcode216. 组合总和 III
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int target, tarNum;
public List<List<Integer>> combinationSum3(int k, int n) {
if (n <= 0) return res;
target = n;
tarNum = k;
backtrack(1, 0, 0);
return res;
}
private void backtrack(int start, int num, int cur) {
if (num == tarNum && cur == target) {
res.add(new ArrayList<>(path));
return;
}
if (num >= 3 || cur > target) return;
for (int i = start; i <= 9; i++) {
if (i * (tarNum - num) > target - cur) return;// 可能性剪枝
path.addLast(i);
backtrack(i + 1, num + 1, cur + i);
path.removeLast();
}
}
}
leetcode78子集🔐
- 最优深搜方式
- 总节点数是2^N
思路讲解,戳这里
class Solution {
List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
public List<List<Integer>> subsets(int[] nums) {
subsetsHelper(nums, 0);
return result;
}
private void subsetsHelper(int[] nums, int startIndex){
result.add(new ArrayList<>(path));//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
for (int i = startIndex; i < nums.length; i++){
path.add(nums[i]);
subsetsHelper(nums, i + 1);
path.removeLast();
}
}
}
- 一般深搜
- 这种方法叶子节点为2^N
class Solution {
List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> list=new LinkedList<>();
public List<List<Integer>> subsets(int[] nums) {
process(nums, 0);
return res;
}
private void process(int[] nums, int i) {
if (i == nums.length) {
res.add(new ArrayList<>(list));
return;
}
process(nums, i + 1);
list.addLast(nums[i]);
process(nums, i + 1);
list.removeLast();
}
}
leetcode90子集2🔐
- 相对于1,这个要进行排序去重
class Solution {
List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);// 排序,准备去重
subsetsHelper(nums, 0);
return result;
}
private void subsetsHelper(int[] nums, int startIndex) {
result.add(new ArrayList<>(path));//「遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合」。
for (int i = startIndex; i < nums.length; i++) {
if (i > startIndex && nums[i] == nums[i - 1]) continue;// 去重
path.add(nums[i]);
subsetsHelper(nums, i + 1);
path.removeLast();
}
}
}
leetcode79单词搜索
class Solution {
int M, N;
public boolean exist(char[][] board, String word) {
M = board.length;
N = board[0].length;
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
if (dfs(board, i, j, word, 0)) return true;
}
}
return false;
}
private boolean dfs(char[][] board, int i, int j, String word, int index) {
if (index == word.length()) return true;
if (i < 0 || i >= M || j < 0 || j >= N || board[i][j] != word.charAt(index) || board[i][j] == ' ') return false;
char temp = board[i][j];
board[i][j] = ' ';// 防止回头
boolean flag = false;
flag |= dfs(board, i + 1, j, word, index + 1);
flag |= dfs(board, i - 1, j, word, index + 1);
flag |= dfs(board, i, j + 1, word, index + 1);
flag |= dfs(board, i, j - 1, word, index + 1);
board[i][j] = temp;
return flag;
}
}
leetcode212单词搜索2
class Solution {
Set<String> set = new HashSet<>();
List<String> ans = new ArrayList<>();
char[][] board;
int[][] dirs = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int n, m;
boolean[][] vis = new boolean[15][15];
public List<String> findWords(char[][] _board, String[] words) {
board = _board;
m = board.length;
n = board[0].length;
set.addAll(Arrays.asList(words));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
vis[i][j] = true;
sb.append(board[i][j]);
dfs(i, j, sb);
vis[i][j] = false;
sb.deleteCharAt(sb.length() - 1);
}
}
return ans;
}
void dfs(int i, int j, StringBuilder sb) {
if (sb.length() > 10) return;
if (set.contains(sb.toString())) {
ans.add(sb.toString());
set.remove(sb.toString());
}
for (int[] d : dirs) {
int dx = i + d[0], dy = j + d[1];
if (dx < 0 || dx >= m || dy < 0 || dy >= n) continue;
if (vis[dx][dy]) continue;
vis[dx][dy] = true;
sb.append(board[dx][dy]);
dfs(dx, dy, sb);
vis[dx][dy] = false;
sb.deleteCharAt(sb.length() - 1);
}
}
}
leetcode93复原 IP 地址🔐
class Solution {
List<String> result = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
if (s.length() > 12) return result; // 算是剪枝了
backTrack(s, 0, 0);
return result;
}
// startIndex: 搜索的起始位置, pointNum:添加逗点的数量
private void backTrack(String s, int startIndex, int pointNum) {
if (pointNum == 3) {// 逗点数量为3时,分隔结束
// 判断第四段⼦字符串是否合法,如果合法就放进result中
if (isValid(s, startIndex, s.length() - 1)) {
result.add(s);
}
return;
}
for (int i = startIndex; i < s.length(); i++) {
if (isValid(s, startIndex, i)) {
if (s.length() - i - 1 <= (3 - pointNum) * 3)// 后面是否太长了
backTrack(s.substring(0, i + 1) + "." + s.substring(i + 1), i + 2, pointNum + 1);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
} else {
break;
}
}
}
// 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
private Boolean isValid(String s, int start, int end) {
if (start > end || end - start > 2) {
return false;
}
if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
return false;
}
num = num * 10 + (s.charAt(i) - '0');
if (num > 255) { // 如果⼤于255了不合法
return false;
}
}
return true;
}
}
leetcode89格雷编码🔐
- 经典回溯思想
class Solution {
LinkedList<Integer> res = new LinkedList<>();
boolean[] visited;
public List<Integer> grayCode(int n) {
visited = new boolean[1 << n];
res.add(0);
visited[0] = true;
dfs(0, n);
return res;
}
boolean dfs(int cur, int n) {
if (res.size() == 1 << n) return true;
for (int i = 0; i < n; i++) {
int next = cur ^ (1 << i);
if (!visited[next]) {
res.addLast(next);
visited[next] = true;
if (dfs(next, n)) return true;
res.removeLast();
visited[next] = false;
}
}
return false;
}
}
- 根据循环码变化规律编写代码
class Solution {
List<Integer> res = new ArrayList<>();
boolean[] visited;
public List<Integer> grayCode(int n) {
visited = new boolean[1 << n];
dfs(0, n);
return res;
}
boolean dfs(int cur, int n) {
if (res.size() == (1 << n))
return true;
res.add(cur);
visited[cur] = true;
for (int i = 0; i < n; i++) {
int next = cur ^ (1 << i); //这里改变cur的某一位,用异或
if (!visited[next] && dfs(next, n))
return true;
}
visited[cur] = false;
return false;
}
}
- 格雷码生成原则
class Solution {
public List<Integer> grayCode(int n) {
/**
关键是搞清楚格雷编码的生成过程, G(i) = i ^ (i/2);
如 n = 3:
G(0) = 000,
G(1) = 1 ^ 0 = 001 ^ 000 = 001
G(2) = 2 ^ 1 = 010 ^ 001 = 011
G(3) = 3 ^ 1 = 011 ^ 001 = 010
G(4) = 4 ^ 2 = 100 ^ 010 = 110
G(5) = 5 ^ 2 = 101 ^ 010 = 111
G(6) = 6 ^ 3 = 110 ^ 011 = 101
G(7) = 7 ^ 3 = 111 ^ 011 = 100
**/
List<Integer> ret = new ArrayList<>();
for(int i = 0; i < 1<<n; ++i)
ret.add(i ^ i>>1);
return ret;
}
}
leecode357计算各个位数不同的数字个数
class Solution {
public int countNumbersWithUniqueDigits(int n) {
//各位数字都不同。
//来详解一下
//dp[i]=dp[i-1]+(dp[i-1]-dp[i-2])*(10-(i-1));
// - 加上dp[i-1]没什么可说的,加上之前的数字
// - dp[i-1]-dp[i-2]的意思是我们上一次较上上一次多出来的各位不重复的数字。
// 以n=3为例,n=2已经计算了0-99之间不重复的数字了,
// 我们需要判断的是100-999之间不重复的数字,
// 那也就只能用10-99之间的不重复的数在后面加上一位去组成三位数,
// 而不能使用0-9之间的不重复的数,因为他们加上一位也组成不了3位数。
// 而10-99之间不重复的数等于dp[2]-dp[1]。
// - (10 - (i - 1))
// 当i=2时,说明之前选取的数字只有1位,
// 那么对于每一个数均有9种数字能与之匹配,
// dp[i-1]-dp[i-2]作为高位,我们只要与这些高位不重复即可,
// 所以其实有9(10-1)种情况(比如十位是1,后面可以跟0,2,3,4,5,6,7,8,9)。
// 当i=3时,说明之前选取的数字有2位,那么我们需要与2位不重复
// 所以剩余的有8(10-2)种(比如12,后面可以跟0,3,4,5,6,7,8,9)
if (n == 0)return 1;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 10;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + (dp[i - 1] - dp[i - 2]) * (10 - (i - 1));
}
return dp[n];
}
}
class Solution {
public int countNumbersWithUniqueDigits(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
int sum = 1;
for (int i = 1; i <= n; i++) {
dp[i] = 9 * sum + dp[i - 1];
sum *= (10 - i);
}
return dp[n];
}
}
leetcode131分割回文串
class Solution {
List<List<String>> res = new ArrayList<>();
LinkedList<String> path = new LinkedList<>();
public List<List<String>> partition(String s) {
dfs(s, 0);
return res;
}
private void dfs(String s, int start) {
if (start == s.length()) {
res.add(new ArrayList<>(path));
return;
}
for (int i = start; i < s.length(); i++) {
String str = s.substring(start, i + 1);
if (!isReserve(str)) continue;
path.addLast(str);
dfs(s, i + 1);
path.removeLast();
}
}
private boolean isReserve(String s) {
if (s.length() <= 1) return true;
int i = 0, j = s.length() - 1;
while (i < j) {
if (s.charAt(i++) != s.charAt(j--)) {
return false;
}
}
return true;
}
}
leetcode140单词差分2
- maxLen剪枝,在s较长的时候效果明显
class Solution {
List<String> res = new ArrayList<>();
int maxLen = 0;
public List<String> wordBreak(String s, List<String> wordDict) {
for (String str : wordDict) {
maxLen = Math.max(maxLen, str.length());
}
dfs(s, wordDict, new LinkedList<>(), 0);
return res;
}
public void dfs(String s, List<String> wordDict, LinkedList<String> path, int index) {
if (index == s.length()) {
res.add(String.join(" ", path));
return;
}
for (int i = index; i < s.length(); i++) {
if (wordDict.contains(s.substring(index, i + 1))) {
path.addLast(s.substring(index, i + 1));
dfs(s, wordDict, path, i + 1);
path.removeLast();
} else if (i - index + 1 >= maxLen) {
break;
}
}
}
}
leetcode77组合
- 进行了数量限制的剪枝方式
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
dfs(1, 0, n, k);
return res;
}
private void dfs(int start, int num, int n, int k) {
if (num == k) {
res.add(new ArrayList<>(path));
return;
} else if (start > n) return;
for (int i = start; i <= n; i++) {
path.addLast(i);
dfs(i + 1, num + 1, n, k);
path.removeLast();
if (k - num > n - i) {// 数量剪枝
return;
}
}
}
}
leeetcode10正则表达式匹配
class Solution {
int M, N;
String s, p;
public boolean isMatch(String s, String p) {
M = s.length();
N = p.length();
if (N == 0) return false;
this.s = s;
this.p = p;
return process(0, 0);
}
private boolean process(int i, int j) {
if (j == N) return i == M;
boolean is = !(i == M) && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
if (j < N - 1 && p.charAt(j + 1) == '*') {
return process(i, j + 2) || (is && process(i + 1, j));
} else {
return is && process(i + 1, j + 2);
}
}
}
leetcode95. 不同的二叉搜索树 II🔐
class Solution {
public List<TreeNode> generateTrees(int n) {
if (n == 0) {
return new LinkedList<>();
}
return generateTrees(1, n);
}
public List<TreeNode> generateTrees(int start, int end) {
List<TreeNode> allTrees = new LinkedList<>();
if (start > end) {
allTrees.add(null);
return allTrees;
}
// 枚举可行根节点,根据i的取值作为根节点不同,树的形状也随之改变,从而获得所有答案
for (int i = start; i <= end; i++) {
// 获得所有可行的左子树集合
List<TreeNode> leftTrees = generateTrees(start, i - 1);
// 获得所有可行的右子树集合
List<TreeNode> rightTrees = generateTrees(i + 1, end);
// 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
// 让i作为根节点,左右两个集合可以自由任意组合形成新的组合树。i的取值范围[start,end]
for (TreeNode left : leftTrees) {
for (TreeNode right : rightTrees) {
TreeNode currTree = new TreeNode(i);
currTree.left = left;
currTree.right = right;
allTrees.add(currTree);
}
}
}
return allTrees;//根节点集合
}
}
leetcode113. 路径总和 II
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if (root == null) return res;
path.addLast(root.val);
backtrack(root, root.val, targetSum);
return res;
}
private void backtrack(TreeNode root, int cur, int targetSum) {
if (root.left == null && root.right == null) {// 叶子节点的标志
if (cur == targetSum) res.add(new ArrayList<>(path));
return;
}
if (root.left != null) {// 不是叶子节点,存在左子节点
path.addLast(root.left.val);
backtrack(root.left, cur + root.left.val, targetSum);
path.removeLast();
}
if (root.right != null) {// 不是叶子节点,存在右子节点
path.addLast(root.right.val);
backtrack(root.right, cur + root.right.val, targetSum);
path.removeLast();
}
}
}