随想录日记part22
t i m e : time: time: 2024.03.17
主要内容:今天主要是结合类型的题目加深对回溯算法的理解:1.组合总和;2.组合总和
;3.分割回文串。
- 39. 组合总和
- 40.组合总和II
- 131.分割回文串
Topic1组合总和
题目:
给你一个无重复元素的整数数组 c a n d i d a t e s candidates candidates 和一个目标整数 t a r g e t target target ,找出 c a n d i d a t e s candidates candidates 中可以使数字和为目标数 t a r g e t target target 的所有不同组合 ,并以列表形式返回。你可以按任意顺序返回这些组合。
c a n d i d a t e s candidates candidates 中的同一个数字可以无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 t a r g e t target target 的不同组合数少于 150 个。
输入: c a n d i d a t e s = [ 2 , 3 , 6 , 7 ] , t a r g e t = 7 candidates = [2,3,6,7], target = 7 candidates=[2,3,6,7],target=7
输出: [ [ 2 , 2 , 3 ] , [ 7 ] ] [[2,2,3],[7]] [[2,2,3],[7]]
思路: 按照回溯模板我们进行回溯三部曲:
递归三部曲:
1.回溯函数模板返回值以及参数
在这里要定义两个全局变量, p a t h path path用来存放符合条件单一结果, r e s u l t result result用来存放符合条件结果的集合。回溯函数里一定有一个参数记录当前 p a t h path path里面值的和 n o w s u m nowsum nowsum;还需要一个参数为 i n t int int 型变量 s t a r t I n d e x startIndex startIndex。
所以整体代码如下:
List<List<Integer>> result=new ArrayList<>();
LinkedList<Integer> path=new LinkedList<>();
void backtracking(int target, int start, int nowsum, int[] candidates)
2.回溯函数终止条件
回溯出口,如果 t a r g e t target target 里面的数量等于 n o w s u m nowsum nowsum,说明其到达叶子节点则将其加入到 r e s u l t result result,否则直接返回 r e t u r n return return
代码如下:
if (nowsum > target)return;if (target == nowsum) {result.add(new ArrayList<>(path));return;}
3.回溯搜索的遍历过程
f o r for for 循环每次从 s t a r t I n d e x startIndex startIndex 开始遍历,然后用 p a t h path path 保存取到的节点i搜索的过程如下图:
实现代码如下:
for (int i = start; i < candidates.length; i++) {path.add(candidates[i]);nowsum += candidates[i];backtracking(target, i, nowsum, candidates);// 不用i+1了,表示可以重复读取当前的数nowsum -= candidates[i];path.removeLast();}
完整的代码如下:
class Solution {List<List<Integer>> result = new ArrayList<>();List<Integer> path = new LinkedList<>();public List<List<Integer>> combinationSum(int[] candidates, int target) {result.clear();path.clear();backtracking(target, 0, 0, candidates);return result;}private void backtracking(int target, int start, int nowsum, int[] candidates) {if (nowsum > target)return;if (target == nowsum) {result.add(new ArrayList<>(path));return;}for (int i = start; i < candidates.length; i++) {path.add(candidates[i]);nowsum += candidates[i];backtracking(target, i, nowsum, candidates);// 不用i+1了,表示可以重复读取当前的数nowsum -= candidates[i];path.removeLast();}}
}
Topic2组合总和||
题目:
给定一个候选人编号的集合 c a n d i d a t e s candidates candidates 和一个目标数 t a r g e t target target ,找出 c a n d i d a t e s candidates candidates 中所有可以使数字和为 t a r g e t target target 的组合。 c a n d i d a t e s candidates candidates 中的每个数字在每个组合中只能使用 一次 。注意:解集不能包含重复的组合。
输入: c a n d i d a t e s = [ 10 , 1 , 2 , 7 , 6 , 1 , 5 ] , t a r g e t = 8 candidates = [10,1,2,7,6,1,5], target = 8 candidates=[10,1,2,7,6,1,5],target=8
输出: [ [ 1 , 1 , 6 ] , [ 1 , 2 , 5 ] , [ 1 , 7 ] , [ 2 , 6 ] ] [ [1,1,6], [1,2,5], [1,7], [2,6] ] [[1,1,6],[1,2,5],[1,7],[2,6]]
思路: 按照回溯模板我们进行回溯三部曲:
递归三部曲:
1.回溯函数模板返回值以及参数
在这里要定义两个全局变量, p a t h path path用来存放符合条件单一结果, r e s u l t result result用来存放符合条件结果的集合。回溯函数里一定有一个参数记录当前 p a t h path path里面值的和 n o w s u m nowsum nowsum;还需要一个参数为 i n t int int 型变量 s t a r t I n d e x startIndex startIndex,还有一个用于记录是否被使用过的数组 u s e d used used。
所以整体代码如下:
List<List<Integer>> result=new ArrayList<>();
LinkedList<Integer> path=new LinkedList<>();
void reback(int[] candidates,int target,int nowsum,int startindex)
boolean[] used;//记录元素是否被用过
2.回溯函数终止条件
回溯出口,如果索引值 s t a r t i n d e x startindex startindex 里面的数量等于 d i g i t s . l e n g t h ( ) digits.length() digits.length(),说明其到达叶子节点,则将 t e m p temp temp其加入到 l i s t list list,否则直接返回 r e t u r n return return
代码如下:
if(target<nowsum)return;if(target==nowsum){result.add(new ArrayList<>(path));}
3.回溯搜索的遍历过程
首先将原始数据进行排序,进行排序后相同的数字就会相邻。如果 c a n d i d a t e s [ i ] = = c a n d i d a t e s [ i − 1 ] 并且 u s e d [ i − 1 ] = = f a l s e candidates[i] == candidates[i - 1] 并且 used[i - 1] == false candidates[i]==candidates[i−1]并且used[i−1]==false,就说明:前一个树枝,使用了 c a n d i d a t e s [ i − 1 ] candidates[i - 1] candidates[i−1],也就是说同一树层使用过 c a n d i d a t e s [ i − 1 ] candidates[i - 1] candidates[i−1]。此时 f o r for for 循环里就应该做 c o n t i n u e continue continue 的操作。
实现代码如下:
for(int i=startindex;i<candidates.length;i++){if(i>startindex && candidates[i]==candidates[i-1])continue;nowsum+=candidates[i];path.add(candidates[i]);used[i]=true;reback(candidates,target,nowsum,i+1);nowsum-=candidates[i];path.removeLast();used[i]=false;}
完整的代码如下:
class Solution {List<List<Integer>> result=new ArrayList<>();//用于记录最后的结果List<Integer> path=new LinkedList<>();//用于记录临时结果boolean[] used;//记录元素是否被用过public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates);result.clear();path.clear();used=new boolean[candidates.length];Arrays.fill(used, false);reback(candidates,target,0,0);return result;}private void reback(int[] candidates,int target,int nowsum,int startindex){if(target<nowsum)return;if(target==nowsum){result.add(new ArrayList<>(path));}for(int i=startindex;i<candidates.length;i++){if(i>startindex && candidates[i]==candidates[i-1])continue;nowsum+=candidates[i];path.add(candidates[i]);used[i]=true;reback(candidates,target,nowsum,i+1);nowsum-=candidates[i];path.removeLast();used[i]=false;}}private void reback1(int[] candidates,int target,int nowsum,int startindex){if(target<nowsum)return;if(target==nowsum){result.add(new ArrayList<>(path));}for(int i=startindex;i<candidates.length;i++){if(i>startindex && candidates[i]==candidates[i-1]){continue;}nowsum+=candidates[i];path.add(candidates[i]);reback(candidates,target,nowsum,i+1);nowsum-=candidates[i];path.removeLast();}}
}
时间复杂度: O ( n ∗ 2 n ) O(n * 2^n) O(n∗2n)
空间复杂度: O ( n ) O(n) O(n)
Topic3分割回文串
题目:
给你一个字符串 s s s,请你将 s s s 分割成一些子串,使每个子串都是回文串。返回 s s s 所有可能的分割方案。
输入: s = " a a b " s = "aab" s="aab"
输出: [ [ " a " , " a " , " b " ] , [ " a a " , " b " ] ] [["a","a","b"],["aa","b"]] [["a","a","b"],["aa","b"]]
思路:
解决该问题需要解决下面几个问题:
- 切割问题可以抽象为组合问题
- 如何模拟那些切割线
- 切割问题中递归如何终止
- 在递归循环中如何截取子串
如何判断回文按照回溯模板我们进行回溯三部曲:
递归三部曲:
1.回溯函数模板返回值以及参数
在这里要定义两个全局变量, p a t h path path用来存放符合条件单一结果, r e s u l t result result用来存放符合条件结果的集合。回溯函数里一定有一个参数记录当前 p a t h path path里面值;还需要一个参数为 i n t int int 型变量 i n d e x index index。
所以整体代码如下:
List<List<String>> result = new ArrayList<>();// 存最后的结果
Deque<String> path = new LinkedList<>();// 存中间的结果
void reback(String s, int index)
2.回溯函数终止条件
回溯出口,从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。
代码如下:
if (index >= s.length()) {result.add(new ArrayList(path));return;}
3.回溯搜索的遍历过程
首先判断这个子串是不是回文,如果是回文,就加入在 p a t h path path中, p a t h path path 用来记录切割过的回文子串。
实现代码如下:
for (int i = index; i < s.length(); i++) {if (isHuiwen(s, index, i)) {String str = s.substring(index, i + 1);path.addLast(str);} else {continue;}reback(s, i + 1);path.removeLast();}
完整的代码如下:
class Solution {List<List<String>> result = new ArrayList<>();// 存最后的结果Deque<String> path = new LinkedList<>();// 存中间的结果public List<List<String>> partition(String s) {reback(s, 0);return result;}private void reback(String s, int index) {if (index >= s.length()) {result.add(new ArrayList(path));return;}for (int i = index; i < s.length(); i++) {if (isHuiwen(s, index, i)) {String str = s.substring(index, i + 1);path.addLast(str);} else {continue;}reback(s, i + 1);path.removeLast();}}private boolean isHuiwen(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;}
}