【前端er每日算法】回溯2- 组合与分割回文串

93 阅读3分钟

题目1 39. 组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

思路

利用回溯法找出所有组合,判断当前路径的组合的和是否等于target,等于就加入,小于就返回,这里要注意的是同一个数字可以被选择多次,所以再向下遍历的时候,每次都从当前元素开始,要包含当前元素。

var combinationSum = function(candidates, target) {
    let result = [];
    let path = [];
    candidates.sort((a, b) => a - b)
    const combine = (candidates, target, startIndex) => {
        if (target < 0) {
            return;
        }
        if (target === 0) {
            result.push(path.slice());
            return;
        }
        for (let i = startIndex; i < candidates.length; i++) {
            path.push(candidates[i]);
            target -= candidates[i];
            combine(candidates, target, i);
            path.pop();
            target += candidates[i];
        }
    }
    combine(candidates, target, 0);
    return result
};

这里有可以剪枝的地方,就是在同层遍历的时候,判断target是否小于0了,如果小于0,则直接退出,因为我们排过序了,当减去当前值小于0不符合了,那再去遍历后面比它大的数字,明显也是不符合条件的,所以可以提前跳出。

var combinationSum = function(candidates, target) {
    candidates.sort((a, b) => a - b);
    const len = candidates.length;
    const result = [];
    const path = [];
    const backtracking = (index, sum) => {
        if (sum > target) {
            return;
        }
        if (sum === target) {
            result.push(path.slice());
            return;
        }
        for (let i = index; i < len; i++) {
            path.push(candidates[i]);
            sum += candidates[i];
            if (sum > target) {
                path.pop();
                sum -= candidates[i];
                return;
            }
            backtracking(i, sum);
            path.pop();
            sum -= candidates[i];
        }
    };
    backtracking(0, 0);
    return result;
};

题目2 40. 组合总和 II

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

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

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

思路

找出所有的组合,和上一个题目不同的是每次递归时从下一个元素开始,这样一个元素只会出现一次。然后不能包含重复的组合,如何判断,需要将数组进行排序,然后判断,如果当前层的元素和上一个元素相同,并且不是一条路径上相同的元素,则可以继续搜索,如果不符合,就跳过。同时剪枝操作,遍历时需要target减去当前元素大于等于0,否则无需遍历。

/**
 * @param {number[]} candidates
 * @param {number} target
 * @return {number[][]}
 */
var combinationSum2 = function(candidates, target) {
    let result = [];
    let path = [];
    let used = new Array(candidates.length).fill(0);
    candidates.sort((a, b) => a - b)
    const combine = (candidates, target, startIndex, used) => {
        if (target === 0) {
            result.push(path.slice());
            return;
        }
        for (let i = startIndex; i < candidates.length && target - candidates[i] >= 0; i++) {
            if (i > 0 && candidates[i] === candidates[i - 1] && !used[i-1]) {
                continue;
            }
            if (target < 0) {
                return;
            }
            path.push(candidates[i]);
            target -= candidates[i];
            used[i] = 1;
            combine(candidates, target, i + 1, used);
            path.pop();
            target += candidates[i];
            used[i] = 0;
        }
    }
    combine(candidates, target, 0, used);
    return result;
};

如果不用used数组,直接用startIndex来判断:

var combinationSum2 = function(candidates, target) {
    candidates.sort((a, b) => a - b);
    let result = [];
    let path = [];
    const backtracking = (startIndex, sum) => {
        if (sum === target) {
            result.push(path.slice());
            return;
        }
        for (let i = startIndex; i < candidates.length; i++) {
            if (i > startIndex && candidates[i] === candidates[i - 1]) {
                continue;
            }
            path.push(candidates[i]);
            sum += candidates[i];
            if (sum > target) {
                sum -= candidates[i];
                path.pop();
                return;
            }
            backtracking(i + 1, sum);
            path.pop();
            sum -= candidates[i];
        }
    };
    backtracking(0, 0);
    return result;
};

题目3 131. 分割回文串

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

回文串 是正着读和反着读都一样的字符串。

思路

分割获取子串切割方案和求组合一样,startIndex作为切割点,子串就是startIndex到i的子串,回溯过程中判断子串是否是回文串,是的话,加入path,否则跳过这个子集。如果startIndex的值大于了s的长度,则返回。


function isPalindrome(s, startIndex, endIndex) {
    while (startIndex < endIndex) {
        if (s[startIndex] !== s[endIndex]) {
            return false;
        }
        startIndex++;
        endIndex--;
    }
    return true;
}
/**
 * @param {string} s
 * @return {string[][]}
 */
var partition = function(s) {
    let result = []
    let path = []
    const len = s.length;
    const getPartition = (startIndex) => {
        if (startIndex >= len) {
            result.push(path.slice());
            return;
        }
        for (let i = startIndex; i < len; i++) {
            let subStr = s.slice(startIndex, i+1);
            if (isPalindrome(s, startIndex, i)) {
                path.push(subStr);
            } else {
                continue;
            }
            getPartition(i + 1);
            path.pop();
        }
    }
    getPartition(0);
    return result;
};

ps:脑容量不够了,这个递归还是有点抽象,有问题,还没彻底掌握。