算法刷题打卡025 | 回溯5

LeetCode 491 递增子序列

题目链接:491. 递增子序列 - 力扣(Leetcode)

乍一看和子集问题很像,但自己写还真不容易写出来,没有用used记录当前一层遍历已经使用过的元素时,结果集合会出现重复,也不能直接套用子集问题的去重方式。

class Solution:
    def __init__(self):
        self.result = []
        self.path = []
    
    def backtracking(self, nums, startIndex, n):
        if len(self.path) >= 2:
            self.result.append(self.path[:])
        if startIndex == n:
            return
        # 需要一个集合记录本层已经使用过的元素
        used = set()
        for i in range(startIndex, n):
            if (self.path and nums[i] < self.path[-1]) or nums[i] in used:
                continue
            used.add(nums[i])
            self.path.append(nums[i])
            self.backtracking(nums, i+1, n)
            self.path.pop()

    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        self.backtracking(nums, 0, len(nums))
        return self.result

LeetCode 46 全排列

题目链接:

排列问题和组合问题不同,每次递归的范围是除path中元素的所有元素,之前的做法是直接传入除去当前元素的列表切片,当传入递归函数的列表为空时,path已经构成数组的一种排列:

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res, path = [], []
        def dfs(li):
            if not li:
                res.append(path.copy())  # 添加path.copy()才不会在path结果在回溯被修改时影响res
                return
            for i in range(len(li)):
                path.append(li[i])
                dfs(li[:i] + li[i+1:])  # 不包含当前li[i]的列表继续递归
                path.remove(li[i])  # 回溯
        
        dfs(nums)
        return res

讲解做法是使用used数组标记数组中哪些元素已经被使用,因为一个排列里一个元素只能使用一次(要和递增子序列问题中的used数组含义区分开来):

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res, path = [], []
        def dfs2(used):
            if len(path) == len(nums):
                res.append(path[:])
                return 
            for i in range(len(nums)):
                if used[i]:
                    continue
                used[i] = 1
                path.append(nums[i])
                dfs2(used)
                path.pop()
                used[i] = 0
        used = [0] * len(nums)
        dfs2(used)

        return res

LeetCode 47 全排列II

题目链接:47. 全排列 II - 力扣(Leetcode)

 相比于原始的全排列,含有重复元素时,同样需要像递增子序列的问题一样,对同一层遍历中已经用过的元素进行去重,为了避免和全排列中的used数组重名,这里使用levelused集合记录:

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        res, path = [], []
        def dfs(nums, used):
            if len(path) == len(nums):
                res.append(path[:])
                return
            levelused = set()
            for i in range(len(nums)):
                if used[i] or nums[i] in levelused:
                    continue
                used[i] = 1
                levelused.add(nums[i])
                path.append(nums[i])
                dfs(nums, used)
                path.pop()
                used[i] = 0
        
        used = [0] * len(nums)
        dfs(nums, used)
        return res 

 举例说明levelused的理解,比如数组[1, 1, 2, 3, 4],递归第一层中用第一个1作为排列的第一个元素,依次递归下去构成一些全排列的子集,这一层中levelused中添加1。当回溯回来,开始使用第二个1作为排列的第一个元素,递归下去构成的全排列子集会和前一组一模一样,因此需要通过去重去掉。

讲解中使用一个used数组同时完成去重的判断也很精妙!我当时就是没想清楚used[i-1]应该是true还是false,同时还想把used[i]也放到同一个if逻辑去判断,所以就放弃用used数组去重:

class Solution {
private:
    vector> result;
    vector path;
    void backtracking (vector& nums, vector& used) {
        // 此时说明找到了一组
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            // used[i - 1] == true,说明同一树枝nums[i - 1]使用过
            // used[i - 1] == false,说明同一树层nums[i - 1]使用过
            // 如果同一树层nums[i - 1]使用过则直接跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            if (used[i] == false) {
                used[i] = true;
                path.push_back(nums[i]);
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
public:
    vector> permuteUnique(vector& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // 排序
        vector used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

你可能感兴趣的:(刷题,算法,leetcode)