LeetCode体操-23 | 回溯算法理论、39. 组合总和、40. 组合总和 II、131. 分割回文串

49 阅读3分钟

39. 组合总和

题目

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

示例 3:
输入: candidates = [2], target = 1
输出: []
 
提示:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
candidates 的所有元素 互不相同
1 <= target <= 40

解题思路

  1. 从候选数组的第一个数字开始尝试
  2. 对于每个数字,我们可以选择用它或不用它
  3. 如果选择用它,我们可以继续选择它(因为可以重复使用)
  4. 如果当前和超过目标值,我们就回溯
  5. 如果当前和等于目标值,我们就找到了一个有效组合

image.png

代码实现

function combinationSum(candidates: number[], target: number): number[][] {
    const result: number[][] = [];

    function backtrack(start: number, combination: number[], sum: number): void {
        if (sum === target) {
            result.push([...combination]);
            return;
        }

        if (sum > target) {
            return;
        }

        for (let i = start; i < candidates.length; i++) {
            combination.push(candidates[i]);
            backtrack(i, combination, sum + candidates[i]);
            combination.pop();
        }
    }

    backtrack(0, [], 0);
    return result;
}

40. 组合总和 II

题目

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次 。

注意:解集不能包含重复的组合。

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
 
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30

解题思路

  1. 该题目回溯时要考虑重复元素,这是关键点
  2. 首先对数组进行排序,这样相同的元素会相邻
  3. 在每一层的循环中,跳过重复的元素,以避免生成重复的组合
  4. 在递归时,将索引加1,因为每个元素只能使用一次

image.png

代码实现

function combinationSum2(candidates: number[], target: number): number[][] {
    const result: number[][] = [];
    candidates.sort((a, b) => a - b);

    function backtrack(start: number, tempArr: number[], sum: number): void {
        if (sum === target) {
            result.push([...tempArr]);
            return;
        }

        if (sum > target) {
            return;
        }

        for (let i = start; i < candidates.length; i++) {
            // 跳过重复元素
            if (i > start && candidates[i] === candidates[i - 1]) continue;

            tempArr.push(candidates[i]);
            backtrack(i + 1, tempArr, sum + candidates[i]);
            tempArr.pop();
        }
    }

    backtrack(0, [], 0);
    return result;
}

131. 分割回文串

题目

给你一个字符串 s,请你将 **s **分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:
输入:s = "a"
输出:[["a"]]

提示:
1 <= s.length <= 16
s 仅由小写英文字母组成

解题思路

  1. 使用回溯算法解题,收集分割结果为回文串的状态
  2. 当起始点等同于字符串长度时终止递归,每次分割都应该确保加入已分割的部分为回文串,这才有继续分割的必要性

image.png

代码实现

function partition(s: string): string[][] {
    const result: string[][] = [];

    function isPalindrome(start: number, end: number): boolean {
        while (start < end) {
            if (s[start] !== s[end]) return false;
            start++;
            end--;
        }
        return true;
    }

    function backtrack(start: number, tempArr: string[]) {
        if (start === s.length) {
            result.push([...tempArr]);
            return
        }

        for (let i = start; i < s.length; i++) {
            if (isPalindrome(start, i)) {
                tempArr.push(s.substring(start, i + 1));
                backtrack(i + 1, tempArr);
                tempArr.pop()
            }
        }
    }
    backtrack(0, [])
    return result
};