代码随想录算法训练营第二十七天|39. 组合总和、40.组合总和II、131.分割回文串
39. 组合总和
问题简述:在无重复元素的candidates
中找出所有和为target的组合,candidates
中元素可重复使用。
思考:感觉好几道组合总和都很相似,但都有细节差别。
算法思路:
- 定义一个startIndex作为每次循环的起始位置,path为每个结果的路径,sum为当前路径的总和。进
- 行操作时首先判断当前的sum是否大于target,如果大于target直接返回并回溯,然后判断当path元素总和为target时,将path加入lists并返回。
- 如果都不满足,依次从startIndex开始遍历所有元素,每次遍历在path中加入startIndex位置元素,sum中加入当前元素,再进行递归,递归时保证第一次递归仍需要加入当前元素作为起始位置,即startIndex + index位置的元素,index初始值为0,每次循环依次加一,保证path中元素可以重复。递归完成后再移除上一步加入的元素,并将sum剪去当前元素值。
import java.util.ArrayList;
import java.util.List;
class Solution {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtracking(candidates, target, 0);
return lists;
}
public void backtracking(int[] candidates, int target,int startIndex){
//进行剪枝
if (sum > target) return;
//确定结束条件
if (sum == target) {
lists.add(new ArrayList<>(path));
return;
}
//用来确定下一次循环的其实位置
int index = 0;
for (int i = startIndex; i < candidates.length; i++) {
path.add(candidates[i]);
sum += candidates[i];
//将当前元素添加后,下一次仍可以添加这个元素,但再次循环就不需要再添加当前元素了
backtracking(candidates, target, startIndex + index);
index++;
//回溯
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
40.组合总和II
问题简述:在有重复元素的candidates
中找出所有和为target的组合,candidates
中的每个数字最多使用一次,结果集中不允许重复,但组合中的元素可以因candidates有重复元素而重复。
思考:这个问题的关键在于去重,需要注意的细节非常非常多,感觉非常难了。
算法思路:
- 定义一个startIndex作为每次递归中candidates的起始位置;path存储每个结果集;lists存储path的集合;sum为当前path元素和;定义一个used数组,长度为candidates长度,初始元素为0,used用来标记当前元素是否在path中,如果在的话变为1,进行回溯时变回0。
- 先对candidates进行排序,排序后有重复的元素。
- 递归函数的结束条件为sum等于target,这时将path加入lists;同时进行剪枝,如果sum大于target,则之前返回然后回溯。
- 每次递归从startIndex指向的元素加入path,先进行去重,判断当前元素是否等于上一个元素,如果等于的话,还要判断used[i - 1]是否等于0,如果不为0说明上一个元素正在当前的path路径中,而path中可以包含重复的元素,所以不去重;只有当used[i - 1] == 0时,我们已经确定包含上个元素的所有组合已经加入lists,才可以进行去重。然后遍历所有元素,每次加入当前元素,更改sum,更改used标志位,然后进行递归下一个位置元素,最后进行回溯,更改used、sum并推出path元素。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
//used用来标记当前元素是否在path中,如果在的话变为1,进行回溯时变回0。
int[] used = new int[candidates.length];
Arrays.sort(candidates);
backtracking(candidates, target, 0, used);
return lists;
}
public void backtracking(int[] candidates, int target,int startIndex, int[] used){
//进行剪枝
if (sum > target) return;
//确定结束条件
if (sum == target) {
lists.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < candidates.length; i++) {
//进行去重,判断当前元素是否等于上一个元素,如果等于的话,还要判断used[i - 1]是否等于0,如果不为0说明上一个元素正在当前的path路径中,而path中可以包含重复的元素,所以不去重;只有当used[i - 1] == 0时,我们已经确定包含上个元素的所有组合已经加入lists,才可以进行去重
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == 0) continue;
path.add(candidates[i]);
sum += candidates[i];
used[i] = 1;
//递归下一个元素
backtracking(candidates, target, i + 1, used);
//回溯
used[i] = 0;
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
131.分割回文串
问题简述:找到将字符串分割为回文串的所有可能。
思考:开始想着吧s中字符都存入新的数组中,后来发现没必要,主要是对字符串的函数不熟悉,比如函数的范围是左闭右开,错误找了半天才懂。
算法思路:
-
定义函数用双指针判断字符串的
[left,right]
范围是否为回文串。 -
定义lists存储所有组合,定义path存储当前分割的组合。startidx为当前指向的字符。
-
递归函数中如果当前的startidx出界则将path加入lists并返回,如果未出界则依次从startIdx遍历所有元素,每次判断startIndex到当前i是否为回文串,如果是回文串则将这一段加入path,然后继续递归这一段之后的字符串后,进行回溯,即将path最近的回文串移除。
import java.util.ArrayList;
import java.util.List;
class Solution {
//定义返回值
List<List<String>> lists = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0);
return lists;
}
//回溯函数
public void backtracking(String s, int startidx){
//如果当前的startidx出界则将path加入lists
if (startidx >= s.length()) {
lists.add(new ArrayList<>(path));
return;
}
//依次遍历每个元素,判断startidx到当前i元素是否为回文串
for (int i = startidx; i < s.length(); i++) {
if (isDestring(s, startidx, i)){
//如果是回文串则将串加入进path,并递归再回溯,substring方法是加入左闭右开的字符
String str = s.substring(startidx, i + 1);
path.add(new String(str));
backtracking(s, i + 1);
//回溯
path.remove(path.size() - 1);
}
}
}
//判断是否为回文串,取left到right的左闭右闭区间
public boolean isDestring(String s, int left, int right){
while (left <= right){
if (s.charAt(left) != s.charAt(right)) return false;
left++;
right--;
}
return true;
}
}
感想
创立柱间拿到了,感觉过程一直很期待,到手了也就是那回事吧。然后还买到了rex还有吉迪恩的套装,真的超级开心,明天争取完成一天算法的同时,再学习一下项目。