赞
踩
题目链接:39. 组合总和
代码随想录题解:39. 组合总和
视频讲解:带你学透回溯算法-组合总和(对应「leetcode」力扣题目:39.组合总和)| 回溯法精讲!_哔哩哔哩_bilibili
组合总和这一类题的基本思路是一样的,这道题是在组合总和III这题的基础上,增加了一个元素可重复选取的需求,实际上正如提示所说,只要修改startIndex的入参即可。
同样还是用回溯法做,三部曲如下:
参数有返回的结果result,中间记录结果的变量list,用于计算当前数字之和的变量sum,递归函数的入参是输入的数组candidates,目标和target,以及每一次递归的起始值startIndex;
终止条件仍然为sum等于target,将list插入result,并返回;
单层遍历条件也是类似的,不同的地方在于startIndex不是i+1,而是i(因为当前元素可以重复选取)。
考虑到输入数组的元素都是正数,所以当sum>target时,可以剪枝返回。
- class Solution {
- List<List<Integer>> result = new ArrayList<>();
- LinkedList<Integer> list = new LinkedList<>();
- int sum;
- public List<List<Integer>> combinationSum(int[] candidates, int target) {
- combinationSum(candidates, target, 0);
- return result;
- }
-
- public void combinationSum(int[] candidates, int target, int startIndex) {
- if (sum > target) return;
- if (sum == target) {
- result.add(new ArrayList<>(list));
- return;
- }
- for (int i = startIndex; i < candidates.length; i++) {
- sum += candidates[i];
- list.add(candidates[i]);
- combinationSum(candidates, target, i);
- list.pollLast();
- sum -= candidates[i];
- }
- }
- }
有些可以优化的细节:
sum参数可以不需要,递归时每次入参用target-candidates代替target即可,如果某次递归时target直接为0,说明当前list已经符合要求,可以直接返回。
剪枝可以再优化:如果先对数组进行排序,取值就会从小到大取得,这样一旦前面某个序列已经大于target了,同一层后面的序列就不用再计算了。
- // 剪枝优化
- class Solution {
- public List<List<Integer>> combinationSum(int[] candidates, int target) {
- List<List<Integer>> res = new ArrayList<>();
- Arrays.sort(candidates); // 先进行排序
- 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);
- path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
- }
- }
- }
无
题目链接:40.组合总和II
代码随想录题解:40.组合总和II
视频讲解:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II_哔哩哔哩_bilibili
总体套路仍旧和之前做过的组合总和题目类似,不同的地方在于,candidates中的元素会有重复,但每个元素不可以重复选取,所以做的时候需要去重。
入参,终止条件和剪枝条件不作赘述,关键在于单层遍历的逻辑。因为考虑到有重复元素,首先在candidates入参之前要进行排序,这样方便后序遇到重复元素的时候跳过处理。跳过的条件为:如果当前元素不是startIndex,且它和它的前一个元素相同,就直接跳过本次循环。
- class Solution {
- List<List<Integer>> result = new ArrayList<>();
- LinkedList<Integer> list = new LinkedList<>();
- int sum = 0;
- public List<List<Integer>> combinationSum2(int[] candidates, int target) {
- Arrays.sort(candidates);
- combinationSum2(candidates, target, 0);
- return result;
- }
-
- public void combinationSum2(int[] candidates, int target, int startIndex) {
- if (sum > target) return;
- if (sum == target) {
- result.add(new ArrayList<>(list));
- }
- for (int i = startIndex; i < candidates.length; i++) {
- if (i > startIndex && candidates[i] == candidates[i-1]) continue;
- sum += candidates[i];
- list.add(candidates[i]);
- combinationSum2(candidates, target, i + 1);
- list.pollLast();
- sum -= candidates[i];
- }
- }
- }
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
随想录这里讲的非常清楚,用一个标记去重数组或者startIndex去重都可以,剪枝也是跟前一题相同。这里学习一下用标记去重数组。
- class Solution {
- LinkedList<Integer> path = new LinkedList<>();
- List<List<Integer>> ans = new ArrayList<>();
- boolean[] used;
- int sum = 0;
-
- public List<List<Integer>> combinationSum2(int[] candidates, int target) {
- used = new boolean[candidates.length];
- // 加标志数组,用来辅助判断同层节点是否已经遍历
- Arrays.fill(used, false);
- // 为了将重复的数字都放到一起,所以先进行排序
- Arrays.sort(candidates);
- backTracking(candidates, target, 0);
- return ans;
- }
-
- private void backTracking(int[] candidates, int target, int startIndex) {
- if (sum == target) {
- ans.add(new ArrayList(path));
- }
- for (int i = startIndex; i < candidates.length; i++) {
- if (sum + candidates[i] > target) {
- break;
- }
- // 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
- if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
- continue;
- }
- used[i] = true;
- sum += candidates[i];
- path.add(candidates[i]);
- // 每个节点仅能选择一次,所以从下一位开始
- backTracking(candidates, target, i + 1);
- used[i] = false;
- sum -= candidates[i];
- path.removeLast();
- }
- }
- }
去重方法一开始不好想,本来想用hashset试图去重,但是不确定顺序不同但元素相同的中间list在hashset算不算同一种序列,所以最后还是根据测试用例的结果似乎是排过序的,想到可以排序后比较前后元素去重。
题目链接:131.分割回文串
代码随想录题解:131.分割回文串
关键点在于分割线如何划分,划分后后续的字符串又可以通过回溯函数递归的再次划分。但是不知道怎么写就直接看答案了。
这里同样需要取startindex作为每次递归的起点,以及一个判断是否是回文的函数
递归函数参数:全局变量数组path存放切割后回文的子串,二维数组result存放结果集,以及输入参数s和startIndex。
终止条件:在处理组合问题的时候,递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。
单层搜索逻辑:不断取startIndex到i之间的字符串,如果取到回文,就用剩下的字符串调用回溯函数,否则continue。
- class Solution {
- List<List<String>> lists = new ArrayList<>();
- Deque<String> deque = new LinkedList<>();
-
- public List<List<String>> partition(String s) {
- backTracking(s, 0);
- return lists;
- }
-
- private void backTracking(String s, int startIndex) {
- //如果起始位置大于s的大小,说明找到了一组分割方案
- if (startIndex >= s.length()) {
- lists.add(new ArrayList(deque));
- return;
- }
- for (int i = startIndex; i < s.length(); i++) {
- //如果是回文子串,则记录
- if (isPalindrome(s, startIndex, i)) {
- String str = s.substring(startIndex, i + 1);
- deque.addLast(str);
- } else {
- continue;
- }
- //起始位置后移,保证不重复
- backTracking(s, i + 1);
- deque.removeLast();
- }
- }
- //判断是否是回文串
- private boolean isPalindrome(String s, int startIndex, int end) {
- for (int i = startIndex, j = end; i < j; i++, j--) {
- if (s.charAt(i) != s.charAt(j)) {
- return false;
- }
- }
- return true;
- }
- }
还有判断回文有剪枝方法,后面再补
思路不是很清晰,无从下手
基本回溯套路掌握,但稍微变化一点就不太会做,还是没有掌握回溯的真正技巧,需要练习。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。