LeetCode 39. 组合总和
题目链接:39. 组合总和 - 力扣(LeetCode)
思路:
同一个数字可以无限制的重复被选取,因此可以不断地进行选取,但是要注意要求组合不同,因此写个判断函数可以判断一下新得到的组合是否已经出现在res中了。
在第一段代码中,dfs(i)的i完全没派上用场,没有限制candidates的下标,因此有大量重复元素出现。比如[2,2,4]与[2,4,2]虽然会被查重函数排除掉,但是在生成path时做了很多无用功,最后导致了代码运行超时。
在第二段代码中,每次添加删除的就不是for循环遍历的candidates元素,而是通过下标元素来固定之前选取过的元素不会再被使用。
代码:
#python 超出时间限制
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res, path = [], []
if not candidates:
return res
candidates.sort(reverse = False)
def check_is_same(a, b):
for x in b:
if Counter(a) == Counter(x):
return True
return False
def dfs(i):
if sum(path) == target and not check_is_same(path.copy(), res):
res.append(path.copy())
diff = target - sum(path)
for j in candidates:
if j <= diff:
path.append(j)
dfs(i + 1) //这样写,i没起到作用,其实可以把候选数组排序这样来确定下标,一旦遍历过的下标就过去不要再考虑了,我这样写重复考虑了所有元素,虽然写了一个check_is_same函数来去重但是仍然会超时。
path.pop()
else:
break
dfs(0)
return res
#python 正常运行,未超时
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res, path = [], []
if not candidates:
return res
candidates.sort()
def check_is_same(a, b):
for x in b:
if Counter(a) == Counter(x):
return True
return False
def dfs(i):
if sum(path) == target and not check_is_same(path, res):
res.append(path.copy())
elif sum(path) < target:
for j in range(i, len(candidates)): //在i的后面选取candidates中的元素,避免之前元素的再次出现。
if candidates[j] <= target - sum(path):
path.append(candidates[j])
dfs(j)
path.pop()
dfs(0)
return res
/java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates); // 对数组进行排序
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
traversal(candidates, target, 0, path, res);
return res;
}
private void traversal(int[] candidates, int target, int start, List<Integer> path, List<List<Integer>> res) {
if (target == 0) {
// 找到一个有效的组合,添加到结果列表中
res.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
// 跳过重复的元素
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
}
// 如果当前元素大于剩余目标,直接返回
if (candidates[i] > target) {
break;
}
// 选择当前元素
path.add(candidates[i]);
// 递归地寻找剩余目标的组合
traversal(candidates, target - candidates[i], i, path, res);
// 回溯,移除当前元素
path.remove(path.size() - 1);
}
}
}
LeetCode 40. 组合总和 II
题目链接:40. 组合总和 II - 力扣(LeetCode)
思路:
与上一题不同的是:同一个数字不能被重复选取。
代码:
#python
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
res, path = [], []
if not candidates:
return res
def dfs(i):
if sum(path) == target:
res.append(path.copy())
return
for j in range(i, len(candidates)): // 和上一题一样,也要用下标来固定元素不能重复出现。
if j > i and candidates[j] == candidates[j-1]: //相同就继续,别再选了
continue
if sum(path) + candidates[j] > target: //超过范围了,因为是有序的,后面别选了
break
path.append(candidates[j])
dfs(j+1)
path.pop()
candidates.sort()
dfs(0)
return res
/java
class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates); // 对数组进行排序
List<List<Integer>> res = new ArrayList<>();
backtrack(candidates, target, 0, new ArrayList<>(), res, new boolean[candidates.length]);
return res;
}
private void backtrack(int[] candidates, int target, int start, List<Integer> path, List<List<Integer>> res, boolean[] used) {
if (target == 0) {
res.add(new ArrayList<>(path)); // 找到一种组合
return;
}
for (int i = start; i < candidates.length; i++) {
// 跳过重复的元素
if (i > start && candidates[i] == candidates[i - 1]) {
continue;
}
// 如果当前元素大于剩余目标,直接返回
if (candidates[i] > target) {
break;
}
// 使用当前元素
if (!used[i]) {
used[i] = true; // 标记为已使用
path.add(candidates[i]); // 添加到路径
backtrack(candidates, target - candidates[i], i + 1, path, res, used); // 递归调用
path.remove(path.size() - 1); // 回溯,移除当前元素
used[i] = false; // 重置为未使用
}
}
}
}
LeetCode 131. 分割回文串
题目链接:131. 分割回文串 - 力扣(LeetCode)
思路:
与之前题目思路是相同的,只不过判断条件变为了从下标0一直到长度n结束,把中间所有的回文字符串均加入path中,最后把所有的res给return。
代码:
#python
class Solution:
def partition(self, s: str) -> List[List[str]]:
n = len(s)
res, path = [], []
if n == 0:
return res
def dfs(i):
if i == n:
res.append(path.copy())
return
for j in range(i, n + 1):
t = s[i : j + 1] //利用字符串操作来分割
if t == t[::-1]: //判断
path.append(t)
dfs(j + 1)
path.pop()
dfs(0)
return res
/java
public class Solution {
List<List<String>> result = new ArrayList<>();
public List<List<String>> partition(String s) {
List<String> path = new ArrayList<>();
backtrack(s, 0, path);
return result;
}
private void backtrack(String s, int start, List<String> path) {
if (start == s.length()) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < s.length(); i++) {
if (isPalindrome(s, start, i)) {
String palindrome = s.substring(start, i + 1);
path.add(palindrome);
// 继续递归处理剩余部分
backtrack(s, i + 1, path);
// 回溯,撤销选择
path.remove(path.size() - 1);
}
}
}
private boolean isPalindrome(String s, int left, int right) {
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
}