文章:代码随想录
组合总和
思路:
这题是在同一个集合里面连续选取,所以需要startIndex来指出开始方式。 但是这题的难点在于一个数可以无限次重复选取,那证明往下传的i不能加一,因为要选取自己本身。 那一直选取本身,不是会进入死循环吗?那就得加一个判断条件了,当sum>target的时候也返回,等于target的时候才记录结果。
代码:
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if (candidates == null || candidates.length == 0) return result;
Arrays.sort(candidates);
backTracking(candidates, target, 0, 0);
return result;
}
private void backTracking(int[] candidates, int target, int startIndex, int sum) {
if (sum > target) return;
if (sum == target) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < candidates.length; i++) {
int ele=candidates[i];
path.add(ele);
sum += ele;
backTracking(candidates, target, i, sum);
sum -= ele;
path.remove(path.size() - 1);
}
}
//剪枝优化
//可以先对数组进行排序,如果遍历到当前元素的和已经大于target了,可以直接中止循环不用往下递归。
private void backTracking2(int[] candidates, int target, int startIndex, int sum) {
if (sum == target) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < candidates.length; i++) {
int ele=candidates[i];
if(sum+candidates[i]>target) return;
path.add(ele);
sum += ele;
backTracking(candidates, target, i, sum);
sum -= ele;
path.remove(path.size() - 1);
}
}
组合总和II
思路:
这道题有重复元素并且要求结果组合不能相同,这就是难点,因为结果组合内的元素是可以重复的,但是最后结果不能重复.
//所以本题怎么理解去重很重要。这里要理解,相同的值是没关系的,但是不能以相同的值开头收集集合,相同的值开头收集到的数值一定是会存在重复的,因为后面相同的值为开头收集到的结果集一定是前面的子集.直接这么说很抽象,来看代码,会有详细注释。
代码:
List<List<Integer>> result=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracing(candidates,target,0,0);
return result;
}
private void backTracing(int[] candidates, int target, int startIndex, int sum) {
if(target==sum){
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i <candidates.length ; i++) {
int ele=candidates[i];
// if(i!=candidates.length-1 && candidates[i+1]==ele) continue;
//这里为什么判断后一个相同continue不行呢?因为我们要收集最大的集合
//如果是判断后一个就continue,那么排序后,如果有一连串相同的数字,那么就会从这一连串相同的数字的最后一个数字开始计算收集集合对吧?
//那你思考一下,从这个数开始,那前面的数字存在的组合情况是不是没考虑?例如前两个11就可以组合成2,那如果最后这个1在第三位,就会忽略前面的情况.
//所以我们要取每一个相同元素的第一个数字为开始的最大集合,为什么第一个就为最大呢?因为从第一个起搜索的数是最全的,包括相同的值.从后面相同的值开始收集的集合全部是第一个数值收集集合的子集。
//比如 [1,1,2,3] target=4,那么从第一个1收集到的就是{[1,1,2],[1,3]},第二个1收集的就是{[1,3]},第二个的结果集合很明显是第一个的子集。
//也很明显,后面相同的值作为起始位置来遍历无法考虑到前面的元素,固然是子集.
//所以这里要判断前一个不相同。
if(i>startIndex && ele==candidates[i-1] ){continue;}
//剪枝优化
if(sum+ele>target) return;
sum+=ele;
path.add(ele);
backTracing(candidates,target,i+1,sum);
sum-=ele;
path.remove(path.size()-1);
}
分割回文子串
思路:
其实主要是要理解切割回文子串一样是在做组合的过程,最外层的for循环其实还是寻找以哪一个元素为开头。
// 这里最外层的for就是找以不同i结尾的切割规则作为结果集的第一个元素,从前往后寻找以不同切割规则为结果集开头的不同切割规则的组合. //比如以头一个元素为切割线,后面的不同切割线的组合情况.下一层就是以头两个元素为切割线,后面不同切割线的组合情况。以此递归。 // 每一次切割都保证子串为回文串,所以大体逻辑还是一样的。
同样的,这是在同一个集合做组合/切割,用startIndex来记录切割的开始位置,i为切割的结束位置。
代码:
List<List<String>> result=new ArrayList<>();
List<String> path=new ArrayList<>();
public List<List<String>> partition(String s) {
backTracking(s,0);
return result;
}
private void backTracking(String s, int startIndex) {
//如果切割位置超过了最后一个索引,那么证明现在的这个切割情况已经组合完,可以记录了。
if(startIndex>s.length()-1){
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < s.length(); i++) {
//判断是否为回文串,是则第一个切割规则确定了,往下递归寻找下一个切割规则,进行组合.
//如果不是,证明当前切割规则不成立,需要继续往后找,找到第一个合格的切割规则为止。
//i这里代表结束位置
//第一层全部递归完之后(回弹到最外层的for循环),第一个以i结尾的切割规则,作为第一个的组合就找完了。
// i+1,找以i+1个为结尾的切割规则为开头的切割规则组合.
if(!isPalindrome(s,startIndex,i)){
//如果不是回文子串,继续往后寻找
continue;
}
//如果是回文子串,这个位置的切割规则符合,加入path,找下一层的切割规则
//这个函数左闭右开
String subStr=s.substring(startIndex,i+1);
path.add(subStr);
backTracking(s,i+1);
path.remove(path.size()-1);
}
}
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;
}