39. 组合总和
思路:这题的思路其实和上一题的组合总和|||思路差不多,只是这里的元素是可以重复使用的,所以这里的剪枝操作也和上一题组合总和|||有点不一样,以及传入参数中startIndex(idx)的值变化了之外,其他的代码是差不多的。首先先排序,是为了后面剪枝方便,然后res存储结果集,path存储组合,candidates是排序后的数组,sum是总和,startIndex(idx)就是当前要遍历的数组的位置
代码:
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates); // 先进行排序,这里排序后,是为了方便后面剪枝操作,这里进行了排序,后面进行+运算时,可以判断当前和是否大于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,上一题组合总和问题中传的是i+1,这里传入i是因为这里的元素是可以重复使用的,所以递归到下一层的时候,还是从当前位置开始
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
}
}
}
40.组合总和II
思路:
这道题和之前做的总和题都有点不一样,这题里面数组中有重复元素,而且每个元素也是只能使用一次,这里去重以及遇到重复元素时的判断都是至关重要的。
(重点理解)这里重复元素中,在层遍历中,我们用start进行判断是否在同一层,然后因为包含重复元素,例如都出现 1 1 这种情况,在同一层上,第一个1和后面的元素进行组合,与第二个1与后面的元素进行组合,得到的组合情况是一样的,所以会造成组合重复的情况,所以当重复元素出现,而且在同一层的情况下,就直接跳过。这里同一层的意思就在同一级上,按树状图的形式,每往下就是一层。
其他的方式就是和之前组合题目的方式一样的
代码:
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int sum = 0;
public List<List<Integer>> combinationSum2( int[] candidates, int target ) {
//为了将重复的数字都放到一起,所以先进行排序
Arrays.sort( candidates );
backTracking( candidates, target, 0 );
return res;
}
private void backTracking( int[] candidates, int target, int start ) {
if ( sum == target ) {
res.add( new ArrayList<>( path ) );
return;
}
for ( int i = start; i < candidates.length && sum + candidates[i] <= target; i++ ) {
//正确剔除重复解的办法
//跳过同一树层使用过的元素
//因为前面对数组进行了排序,所以当前一个元素和后一个元素相等时,而且是在同一层的情况下,就直接跳过,判断同一层的就是i>start,这个比较就是判断是否在同一层上
if ( i > start && candidates[i] == candidates[i - 1] ) {
continue;
}
sum += candidates[i];
path.add( candidates[i] );
// i+1 代表当前组内元素只选取一次
backTracking( candidates, target, i + 1 );
int temp = path.getLast();
sum -= temp;
path.removeLast();
}
}
}
131.分割回文串
思路:这题其实和之前题目思路差不多,只是这里是分割字符串,把分割好的字符串存储到一个StringBuilder中,判断其是否是回文子串,如果是回文串才进行下一步的递归
代码:
class Solution {
//保持前几题一贯的格式, initialization
List<List<String>> res = new ArrayList<>();
List<String> cur = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0, new StringBuilder());
return res;
}
private void backtracking(String s, int start, StringBuilder sb){
//因为是起始位置一个一个加的,所以结束时start一定等于s.length,因为进入backtracking时一定末尾也是回文,所以cur是满足条件的
if (start == s.length()){
//注意创建一个新的copy
res.add(new ArrayList<>(cur));
return;
}
//像前两题一样从前往后搜索,如果发现回文,进入backtracking,起始位置后移一位,循环结束照例移除cur的末位
for (int i = start; i < s.length(); i++){
sb.append(s.charAt(i));
if (check(sb)){//判断回文,这里判断回文,每往下递归都是只有一个元素,只有在同一层的情况下才会出现两个元素
cur.add(sb.toString());
backtracking(s, i + 1, new StringBuilder());
cur.remove(cur.size() -1 );
}
}
}
//helper method, 检查是否是回文
private boolean check(StringBuilder sb){
for (int i = 0; i < sb.length()/ 2; i++){
if (sb.charAt(i) != sb.charAt(sb.length() - 1 - i)){return false;}
}
return true;
}
}