backtracking Leetcode 回溯算法题

devtools/2024/9/24 12:23:56/

77.组合

第一个位置选择有 n 种,接下来每个位置只能在前面选择数字的后面选,所以有了 beg 参数,才能保持不重复

剪枝:res.size + (n - beg + 1) < k , 已有答案的长度 + 剩余所有未选择的个数 都小于最终答案长度了 就没有必要尝试下去

class Solution {List<List<Integer>> ans = new ArrayList<List<Integer>>();public List<List<Integer>> combine(int n, int k) {List<Integer> res = new ArrayList<Integer>();dfs(res, 1, n, k);return ans;}public void dfs(List<Integer> res, int beg, int n, int k){if(k == 0){// 这里一定要 new 一个新的 ArrayList 啊,否则最后加进去的 res 都是 nullans.add(new ArrayList<Integer>(res));return;}if(res.size() + n - beg + 1 < k) return;for(int i = beg; i <= n; ++ i){res.add(i);dfs(res, i + 1, n, k - 1);res.remove(res.size() - 1);}}
}

216.组合总和III

class Solution {List<Integer> res = new ArrayList<>();List<List<Integer>> ans = new ArrayList<List<Integer>>();public List<List<Integer>> combinationSum3(int k, int n) {dfs(1, k, n);return ans;}public void dfs(int beg, int k, int n){if(k < 0 || n < 0){return;}// 剩下最大的 k 个数相加都小于 n || 最小的 k 个数相加都大于 n : 也就没有必要继续遍历了if(k*(19-k)/2 < n || k*(2*beg+k-1)/2 > n){return;}if(k == 0 && n == 0){ans.add(new ArrayList(res));return;}for(int i = beg; i <= 9; ++ i){res.add(i);dfs(i + 1, k - 1, n - i);res.remove(res.size() - 1);}}
}

随想录代码:

class Solution {List<List<Integer>> result = new ArrayList<>();LinkedList<Integer> path = new LinkedList<>();public List<List<Integer>> combinationSum3(int k, int n) {backTracking(n, k, 1, 0);return result;}private void backTracking(int targetSum, int k, int startIndex, int sum) {// 减枝if (sum > targetSum) {return;}if (path.size() == k) {if (sum == targetSum) result.add(new ArrayList<>(path));return;}// 减枝 9 - (k - path.size()) + 1for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {path.add(i);sum += i;backTracking(targetSum, k, i + 1, sum);//回溯path.removeLast();//回溯sum -= i;}}
}

17.电话号码的字母组合

第一次遍历判断第一个位置的 beg 所对应的字符 idx,遍历所有字符的可能

第二次遍历判断第二个位置的 beg + 1 所对应的字符 idx,遍历所有字符的可能

一直到所有位置都被遍历完,也就是 digits 所有位置都被遍历完,那么 beg 就等于 digits.length() 了,此时记录答案

注意,digits 的第 beg 位置,对应的数字是 idx,该 idx 对应的字符才是要遍历的字符

  • 字符串中提取对应位置的字符:digits.chatAt(beg)
  • 字符 char 转为 int 类型:
    • 首先 char 转为 String :String.valueOf(x)
    • String 转为 int : Integer.parseInt(xx)
class Solution {List<String> table = Arrays.asList("", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz");List<String> ans = new ArrayList<>();public List<String> letterCombinations(String digits) {if(digits.equals("")) return ans;dfs(digits, new StringBuilder(), 0);return ans;}public void dfs(String digits, StringBuilder res, int beg){if(beg == digits.length()){ans.add(res.toString());return;}int idx = Integer.parseInt(String.valueOf(digits.charAt(beg)));for(int j = 0; j < table.get(idx).length(); ++j){res.append(table.get(idx).charAt(j));dfs(digits, res, beg + 1);res.delete(beg, beg + 1);}}
}

39.组合总和

不限制元素使用次数,使其达到目标数,的所有不同组合

  • 由于可以重复选取,所以 i 没有+1
  • 由于结果要求组合不同,所以不能选 i 之前的数,这里用for循环让 i 只能往后选择剩余的数
class Solution {List<Integer> res = new ArrayList<>();List<List<Integer>> ans = new ArrayList<List<Integer>>();public List<List<Integer>> combinationSum(int[] candidates, int target) {dfs(res, candidates, target, 0);return ans;}public void dfs(List<Integer> res, int[] candidates, int target, int beg){if(target < 0){return;}if(target == 0){ans.add(new ArrayList(res));return;}for(int i = beg; i < candidates.length; ++ i){res.add(candidates[i]);dfs(res, candidates, target - candidates[i], i);res.remove(res.size() - 1);}}
}

40.组合总和II

元素有重复,选出总和达到目标数的不同组合

由于元素有重复,所以一个数只能连续的被使用,那么就需要对原数组排序

我这里使用了一个freq记录每个数的可使用次数,当这个数的使用次数被用完,或者以后都不打算使用这个数的时候,才可以用下一个数

class Solution {List<int[]> freq = new ArrayList<>();List<Integer> res = new ArrayList<>();List<List<Integer>> ans = new ArrayList<List<Integer>>();public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates);int num = 1, size = candidates.length;for(int i = 0; i < size; ++ i){if(i < size -1 && candidates[i] == candidates[i+1]){++num;}else{freq.add(new int[]{candidates[i], num});num = 1;}}dfs(target, 0);return ans;}public void dfs(int target, int beg){if(target < 0){return;}if(target == 0){ans.add(new ArrayList<>(res));}for(int i = beg; i < freq.size(); ++i){res.add(freq.get(i)[0]);--freq.get(i)[1];dfs(target-freq.get(i)[0], freq.get(i)[1] == 0 ? i+1 : i);res.remove(res.size() - 1);++freq.get(i)[1];}}
}

更优雅

另一个方法是让当前搜索层级里不出现相同的元素,要想同一个值使用多次,则必须在dfs的下一个层级

class Solution {List<Integer> res = new ArrayList<>();List<List<Integer>> ans = new ArrayList<List<Integer>>();public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates);dfs(candidates, target, 0);return ans;}public void dfs(int[] candidates, int target, int beg){if(target < 0){return;}if(target == 0){ans.add(new ArrayList<>(res));}for(int i = beg; i < candidates.length; ++i){if(i > beg && candidates[i] == candidates[i-1]){continue;}res.add(candidates[i]);dfs(candidates, target-candidates[i], i+1);res.remove(res.size() - 1);}}
}

131.分割回文串

动态规划

动态规划判断一个字串是否回文:

f[i][j] = f[i+1][j-1] && (s.charAt(i) == s.charAt(j));

注意,先定后边界 j,再遍历所有前边界 i

dfs 遍历所有可能划分

class Solution {boolean[][] f;List<String> seq = new ArrayList<>();List<List<String>> res = new ArrayList<List<String>>();public List<List<String>> partition(String s) {int n = s.length();f = new boolean[n][n];for(int i = 0; i < n; ++ i){for(int j = 0; j <= i; ++ j){f[i][j] = true;}}for(int j = 1; j < n; ++ j){for(int i = 0; i < j; ++ i){f[i][j] = f[i+1][j-1] && (s.charAt(i) == s.charAt(j));}}dfs(s, 0);return res;}public void dfs(String s, int beg){if(beg == s.length()){res.add(new ArrayList<String>(seq));return;}for(int j = beg; j < s.length(); ++ j){if(f[beg][j]){seq.add(s.substring(beg, j + 1));dfs(s, j + 1);seq.remove(seq.size() - 1);}}}
}

记忆化搜索

将回文串的判断用dfs实现:

class Solution {int[][] f;List<String> seq = new ArrayList<>();List<List<String>> res = new ArrayList<List<String>>();public List<List<String>> partition(String s) {int n = s.length();f = new int[n][n];dfs(s, 0);return res;}public void dfs(String s, int beg){if(beg == s.length()){res.add(new ArrayList<String>(seq));return;}for(int j = beg; j < s.length(); ++ j){if(isHuiwen(s, beg, j) == 1){seq.add(s.substring(beg, j + 1));dfs(s, j + 1);seq.remove(seq.size() - 1);}}}public int isHuiwen(String s, int i, int j){if(f[i][j] != 0){return f[i][j];}if(i >= j){f[i][j] = 1;}else if(s.charAt(i) == s.charAt(j)){f[i][j] = isHuiwen(s, i+1, j-1);}else{f[i][j] = -1;}return f[i][j];}
}

78.子集

  • 迭代法:可以枚举二进制
  • 回溯法:
class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();List<Integer> ans = new ArrayList<>();public List<List<Integer>> subsets(int[] nums) {dfs(nums, 0);return res;}public void dfs(int[] nums, int beg){res.add(new ArrayList(ans));if(beg == nums.length){return;}for(int i = beg; i < nums.length; ++ i){ans.add(nums[i]);dfs(nums, i + 1);ans.remove(ans.size()-1);}}
}

90.子集II

与之前的 40.组合总和II 解法相同,但没有达到目标数的要求,更简单。

class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();List<Integer> ans = new ArrayList<>();public List<List<Integer>> subsetsWithDup(int[] nums) {Arrays.sort(nums);dfs(nums, 0);return res;}public void dfs(int[] nums, int beg){res.add(new ArrayList(ans));for(int i = beg; i < nums.length; ++ i){if(i > beg && (nums[i-1] == nums[i])){continue;}ans.add(nums[i]);dfs(nums, i + 1);ans.remove(ans.size() - 1);}}
}

491.递增子序列

由于元素有重复且需要维护相对顺序,所以无法通过排序去重

使用 HashSet 对同一层的数据去重

class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();List<Integer> ans = new ArrayList<>();public List<List<Integer>> findSubsequences(int[] nums) {dfs(nums, 0, -101);return res;}public void dfs(int[] nums, int beg, int pre){if(ans.size() > 1){res.add(new ArrayList(ans));}Set<Integer> used = new HashSet<>();for(int i = beg; i < nums.length; ++ i){if(nums[i] < pre || used.contains(nums[i])) {continue;}used.add(nums[i]);ans.add(nums[i]);dfs(nums, i + 1, nums[i]);ans.remove(ans.size() - 1);}}
}

46.全排列

将一个无重复元素的数组全排列

used 记录已经使用过的元素

每次都遍历所有,只往下 dfs 未使用的元素

class Solution {boolean[] used;int n;List<Integer> ans = new ArrayList<>();List<List<Integer>> res = new ArrayList<List<Integer>>();public List<List<Integer>> permute(int[] nums) {n = nums.length;used = new boolean[n];dfs(nums);return res;}public void dfs(int[] nums){if(ans.size() == n){res.add(new ArrayList(ans));return;}for(int i = 0; i < n; ++ i){if(used[i] == false){ans.add(nums[i]);used[i] = true;dfs(nums);used[i] = false;ans.removeLast();}}}
}

47.全排列II

有重复元素的全排列

求全排列的方法加上去重算法

由于顺序无所谓,所以可以先排序,

对于同一层的搜索:让上一次搜索的元素与下一次搜索的元素不同

记录上一次用到的元素,判断当前元素是否与前一个元素相同

class Solution {boolean[] used;int n;List<Integer> ans = new ArrayList<>();List<List<Integer>> res = new ArrayList<List<Integer>>();public List<List<Integer>> permuteUnique(int[] nums) {n = nums.length;used = new boolean[n];Arrays.sort(nums);dfs(nums);return res;}public void dfs(int[] nums){if(ans.size() == n){res.add(new ArrayList(ans));return;}int pre = 100;for(int i = 0; i < n; ++ i){if(used[i] == false && nums[i] != pre){ans.add(nums[i]);used[i] = true;pre = nums[i];dfs(nums);used[i] = false;ans.removeLast();}}}
}

http://www.ppmy.cn/devtools/2610.html

相关文章

Hadoop中YARN的部署

一.什么是YARN 当你在Hadoop集群上运行大型数据处理作业时&#xff0c;YARN就像一个管理者&#xff0c;负责协调整个过程。想象一下你是一位活动组织者&#xff0c;需要协调一场盛大的活动。你需要考虑参与者之间的资源分配&#xff0c;确保每个人都能得到他们所需的空间和设备…

Python爬虫入门

Python爬虫入门&#xff1a;探索网络数据的宝藏 爬虫&#xff0c;也被称为网络爬虫或网页爬虫&#xff0c;是一种自动化的网络信息检索程序。它们被广泛用于从互联网上抓取信息&#xff0c;这些信息可以用于数据分析、数据挖掘、内容摘要、搜索引擎构建等多种场景。Python作为…

vi编辑器的用法linux中的vim编辑器大全

vim的介绍 vi 和 vim 命令是linux中强⼤的⽂本编辑器, 由于Linux系统⼀切皆⽂件&#xff0c;⽽配置⼀个服务就是在修改其配置⽂件的参数。 vim 编辑器是运维⼯程师必须掌握的⼀个⼯具, 没有它很多⼯作都⽆法完成。 其中有vi和vim两种 vi和vim的区别 Vim是Vi的升级版本&#…

【HCIP学习】OSPF协议基础

一、OSPF基础 1、技术背景&#xff08;RIP中存在的问题&#xff09; RIP中存在最大跳数为15的限制&#xff0c;不能适应大规模组网 周期性发送全部路由信息&#xff0c;占用大量的带宽资源 路由收敛速度慢 以跳数作为度量值 存在路由环路可能性 每隔30秒更新 2、OSPF协议…

100个Go语言典型错误

1 Go:入门容易&#xff0c;掌握难 1.1 Go 大纲 1.2 简单并不意味着容易 1.3 100 个 Go 错误 1.4 本章总结 2 代码和项目组织 2.1 意外的阴影变量 2.2 不必要的嵌套代码 2.3 滥用的 init 函数 2.4 过度使用 getters 和 setters 2.5 接口污染 2.6 在生产者端的接口 2.7 返回接…

云LIS系统源码,ASP.NET区域LIS系统源码,实验室信息系统

云LIS系统源码&#xff0c;ASP.NET区域LIS系统源码&#xff0c;实验室信息系统 LIS技术架构&#xff1a;ASP.NET CORE 3.1 MVC SQLserver Redis等 开发语言&#xff1a;C# 6.0、JavaScript 前端框架&#xff1a;JQuery、EasyUI、Bootstrap 后端框架&#xff1a;MVC、S…

Android零基础入门(二)gradle的安装和详解

如果你使用的SpringBoot项目&#xff0c;建议使用6.8及以上版本的Gradle。 下载 官网&#xff1a;https://gradle.org/releases/ 下滑选择一个版本&#xff0c;我选择了8.2 下载后解压到非C盘的自定义目录 插件版本 所需的Gradle版本 插件版本所需的Gradle版本centered 文…

nginx 前后的分离 负载均衡

首先前端随便访问后端的一个端口&#xff0c;后端监听这个端口进行服务转发。 比如&#xff1a;8888 VITE_APP_API_BASEURL http://192.168.10.111:8888 然后nginx在我们的服务器上部署两个后端 这里我用docker部署了两个 当然你也可以在两个服务器上面部署两个后端&#…