摘要
本文主要介绍了LeetCode回溯算法的几个题目,包括39. 组合总和、40.组合总和II、131.分割回文串。
1、39.组合总和
1.1 思路
- 首先检查是否达到了目标值
target,如果达到目标值,就将当前路径path加入结果列表list中,并返回。 - 然后,使用一个循环遍历候选数组
candidates,从start位置开始。对于每个候选数candidates[i],它检查是否加入这个候选数会导致目标值target超过,如果超过了,就跳过这个候选数,继续下一个。 - 如果没有超过目标值,就将这个候选数加入当前路径
path中,然后递归调用doCombinationSum方法,继续寻找下一个候选数,但是起始位置从i开始,因为可以重复使用相同的数字。 - 在递归返回后,需要撤销对当前候选数的选择,即从路径
path中移除,以便尝试其他的组合方式。
1.2 代码
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> list = new ArrayList<>();
LinkedList path = new LinkedList<>();
doCombinationSum(list, path, candidates, target, 0);
return list;
}
public void doCombinationSum(List<List<Integer>> list, LinkedList path, int[] candidates, int target, int start) {
if(target == 0) {
list.add(new ArrayList<>(path));
return;
}
for(int i=start; i<candidates.length; i++) {
if(target-candidates[i] < 0) {
continue;
}
path.add(candidates[i]);
doCombinationSum(list, path, candidates, target-candidates[i], i);
path.removeLast();
}
}
2、40.组合总和II
2.1 思路
- 初始化了结果列表
list和路径path,并将候选数组candidates进行排序,以确保相同的数字都放在一起。 - 首先检查是否达到了目标值
target,如果达到目标值,就将当前路径path加入结果列表list中,并返回。 - 接下来,使用一个循环遍历候选数组
candidates,从start位置开始。对于每个候选数candidates[i],它检查是否加入这个候选数会导致目标值target超过,如果超过了,就跳出循环,因为数组已经排序,后面的数字都会更大。 - 如果没有超过目标值,就将这个候选数加入当前路径
path中,然后递归调用doCombinationSum2方法,继续寻找下一个候选数,但是起始位置从i+1开始,因为不能重复使用相同的数字。 - 在递归返回后,需要撤销对当前候选数的选择,即从路径
path中移除,以便尝试其他的组合方式。 - 注意这里的剪枝操作,通过
if (i > start && candidates[i] == candidates[i - 1])来跳过相同层级使用过的相同数字,以避免重复解的产生。
2.2 代码
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> list = new ArrayList<>();
LinkedList path = new LinkedList<>();
//为了将重复的数字都放到一起,所以先进行排序
Arrays.sort(candidates);
doCombinationSum2(list, path, candidates, target, 0);
return list;
}
public void doCombinationSum2(List<List<Integer>> list, LinkedList path, int[] candidates, int target, int start) {
if(target == 0) {
list.add(new ArrayList<>(path));
return;
}
for(int i=start; i<candidates.length; i++) {
if(target-candidates[i] < 0) {
break;
}
//正确剔除重复解的办法
//跳过同一树层使用过的元素
if ( i > start && candidates[i]==candidates[i - 1] ) {
continue;
}
path.add(candidates[i]);
doCombinationSum2(list, path, candidates, target-candidates[i], i+1);
path.removeLast();
}
}
3、131.分割回文串
3.1 思路
- 首先检查是否已经遍历完了字符串
s,如果是的话,就将当前路径path加入结果列表list中,并返回。 - 接下来,使用一个循环遍历字符串
s,从start位置开始。对于每个i,它调用isPalindrome方法来检查从start到i的子串是否是回文串。如果是回文串,就将这个子串加入当前路径path中,然后递归调用doPartition方法,继续寻找下一个回文子串。 - 在递归返回后,需要撤销对当前回文子串的选择,即从路径
path中移除,以便尝试其他的回文子串。 isPalindrome方法用于判断一个字符串是否是回文串,它使用双指针的方式,从字符串的两端开始比较字符,如果字符不相等,就返回false,否则继续比较直到两指针相遇。
3.2 代码
// 回溯+判断是否是回文串
public List<List<String>> partition(String s) {
List<List<String>> list = new ArrayList<>();
LinkedList<String> path = new LinkedList<>();
doPartition(list, path, s, 0);
return list;
}
public void doPartition(List<List<String>> list, LinkedList<String> path, String s, int start) {
if(start >= s.length()) {
list.add(new ArrayList<>(path));
return;
}
for(int i=start; i<s.length(); i++) {
if(!isPalindrome(s, start, i)) {
continue;
}
path.add(s.substring(start, i + 1));
doPartition(list, path, s, i + 1);
path.removeLast();
}
}
// 是否是回文串
private boolean isPalindrome(String s, int left, int right) {
int start = left;
int end = right;
while(start < end) {
if(s.charAt(start) != s.charAt(end)) {
return false;
}
start++;
end--;
}
return true;
}