39. 组合总和
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backTracking(candidates, target , 0, 0);
return res;
}
private void backTracking(int[] candidates,int target, int sum, int Index){
if(sum > target) return;
if(sum == target){
res.add(new ArrayList(path));
return;
}
for(int i = Index; i < candidates.length; i++){
path.add(candidates[i]);
sum += candidates[i];
backTracking(candidates, target, sum, i);
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
自己A了,有几点java获取长度的知识做个总结。
Java 里的 length 和 length() 和 size() 的区别与理解
首先来区分一下这三种:
length
– 数组 (int[]
, double[]
, String[]
) – 用来获得数组长度
length()
– 和字符串相关的对象 (String
, StringBuilder
, etc) – 获得数组的长度
size()
– Collection 对象 (ArrayList
, Set
, etc) – 获得 Collection 对象的大小
40. 组合总和 II
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
used = new boolean[candidates.length];
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return res;
}
public void backTracking(int[] candidates, int target,int startIndex){
if(sum == target){
res.add(new ArrayList(path));
return;
}
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);
sum -= candidates[i];
path.remove(path.size() - 1);
used[i] = false;
}
}
}
卡哥 的思路和代码都写出来了,时间问题优化版的还没看,明天 看一下优化版liweiwei的 题解,同是看一下 path的回溯,卡哥代码中
LinkedList<Integer> path = new LinkedList<>();
path.removeLast();
的区别,Deque中也是removeLast()
liweiwei的优化版本
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
Arrays.sort(candidates);
dfs(candidates, target, res, path, 0);
return res;
}
public void dfs(int[] candidates, int target, List<List<Integer>> res, List<Integer> path, int index) {
if(target == 0) {
res.add(new ArrayList<>(path));
return;
}
Set<Integer> set = new HashSet<>();
for(int i = index; i < candidates.length; i++) {
//小于0,则该数字不可能取到,直接越过
if(target - candidates[i] < 0) continue;
//如果在set集合中已经有,也越过。同层剪枝 考虑[1,2,2,2,3] target=5的例子
if(!set.add(candidates[i])) continue;
path.add(candidates[i]);
dfs(candidates, target - candidates[i], res, path, i + 1);
path.remove(path.size() - 1);
}
}
}
将维护是否使用的布尔数组改成 if cur > begin,
解释语句: if cur > begin and candidates[cur-1] == candidates[cur] 是如何避免重复的。
这个避免重复当思想是在是太重要了。
这个方法最重要的作用是,可以让同一层级,不出现相同的元素。即
1
/ \
2 2 这种情况不会发生 但是却允许了不同层级之间的重复即:
/ \
5 5
例2
1
/
2 这种情况确是允许的
/
2
为何会有这种神奇的效果呢?
首先 cur-1 == cur 是用于判定当前元素是否和之前元素相同的语句。这个语句就能砍掉例1。
可是问题来了,如果把所有当前与之前一个元素相同的都砍掉,那么例二的情况也会消失。
因为当第二个2出现的时候,他就和前一个2相同了。
那么如何保留例2呢?
那么就用cur > begin 来避免这种情况,你发现例1中的两个2是处在同一个层级上的,
例2的两个2是处在不同层级上的。
在一个for循环中,所有被遍历到的数都是属于一个层级的。我们要让一个层级中,
必须出现且只出现一个2,那么就放过第一个出现重复的2,但不放过后面出现的2。
第一个出现的2的特点就是 cur == begin. 第二个出现的2 特点是cur > begin.
131. 分割回文串
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
String s;
public List<List<String>> partition(String s)
{
this.s = s; //将题目输入的字符串变为全局变量
dfs(0);
return res;
}
public void dfs(int startIndex)
{
//当子串的起始点超出范围时,说明分割结束。记录此次分割方案
if(startIndex == s.length())
{
res.add(new ArrayList<>(path));
return;
}
//递归更新起始点,for循环延伸子串
//起始点的更新优先于子串的延伸
for (int i = startIndex; i < s.length(); i++)
{
if (isPalindrome(startIndex, i))
{
//记录一个回文子串。从startIndex(包括)提取子字符串,直到i + 1(不包括)
path.add(s.substring(startIndex, i + 1));
//以下一个字符为新的起始点
dfs(i +1);
//恢复现场,接下来以startIndex为起始,延长一下子串再试试
path.remove(path.size() - 1);
}
}
}
/**
* @param left 子串的起始索引
* @param right 子串的末尾索引
*/
public boolean isPalindrome(int left, int right)
{
while (left < right)
{
if (s.charAt(left) != s.charAt(right))
return false;
left++;
right--;
}
return true;
}
这个比卡哥的好懂,不明白卡哥Java版本代码中sb为什么没回溯,明天探究一下再补充。
应为
- 不同实例:由于每次递归创建新的
StringBuilder
,每个实例的状态是独立的。 - 无需回溯:不需要手动撤销对
sb
的修改,因为每次递归都是基于一个新的StringBuilder
。