排列-组合-回溯总结笔记--参考labuladong

参考:https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/hui-su-suan-fa-xiang-jie-xiu-ding-ban
解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:
1、路径:也就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。

排列需要用record来记住,因为避免重复取了之前取过的;组合虽然也需要,用处不同,组合不是为了避免重复取了之前取过的,不可能取到之前的。因为其通过sign已经记录了,他的作用是避免重复的相等的数,虽然在选择列表里的不同位置,但是也被取到。 if i >= 1 and nums[i-1] == nums[i] and i-1 not in record: continue

遇到全排列\组合直接想回溯框架。不要纠结DFS/BFS
遇到重复的列表情况,再不济就先用列表,然后转tuple,放进set集合,就可以排除重复情况。如果是列表,可以先sorted
回溯算法框架

result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

在这里插入图片描述

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。
回溯本就是暴力!!!

全排列的代码

List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
    // 记录「路径」
    LinkedList<Integer> track = new LinkedList<>();
    backtrack(nums, track);
    return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
    // 触发结束条件
    if (track.size() == nums.length) {
        res.add(new LinkedList(track));
        return;
    }

    for (int i = 0; i < nums.length; i++) {
        // 排除不合法的选择
        if (track.contains(nums[i]))
            continue;
        // 做选择
        track.add(nums[i]);
        // 进入下一层决策树
        backtrack(nums, track);
        // 取消选择
        track.removeLast();
    }
}

47. 全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

切记这里函数里面如果不传参很容易出错~~~因为track一直在变化。不然就要list(track)///或者拷贝

class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:

        if not nums:
            return []
        out = []
        check = {}
        nums = sorted(nums)
        for i in range(len(nums)):
            check[i] = 0

        def backtrack(nums, track, check):   #巧用了check 其实就是DFS
            if len(track) == len(nums):
                out.append(track)
                return 

            for i in range(len(nums)):
                if check[i] == 1:
                    continue
                if i >= 1 and nums[i] == nums[i-1] and check[i-1] == 0:
                    continue  #  如果前一个是相同的,则跳过!!!!
                check[i] = 1
                backtrack(nums, track + [nums[i]], check)
                check[i] = 0
        backtrack(nums, [], check)
        return out

77. 组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

这个题 容易忽略的一个点是 里面的回溯 使用的是i,不是sign+1,这个要切记!!!理清含义。

在这里插入图片描述
注意剪枝的作用!!

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        
        nlist = []
        check = {}
        for i in range(1, n+1):
            nlist.append(i)
            check[i-1] = 0

        if n == k:
            return [nlist]

        out = []
        def backtrack(check, track, sign):

            if len(track) == k:
                out.append(track)
                return 
            
            for i in range(len(nlist)):
                if i < sign:
                    continue
                if check[i] == 1:
                    continue 
                if len(track) + n - i == k:  # 好强悍的剪枝
                    out.append(track+nlist[i:])
                    return 
                elif len(track) + n - (i + 1) < k:
                    return 
                check[i] = 1
                # track.append(nlist[i])   不可这么写,直接改变了!后面就还需要pop!
                backtrack(check, track+[nlist[i]], i)
                check[i] = 0
        backtrack(check, [], 0)
        return out

无重复元素+无限制重复–此时就是直接用了一个标记,下一次不能小于上一次的标记,也就是不会返回。只会重复当前数或者是往后走~~ 39组合总和

如果只是无重复元素,每一个只能使用一次的话,那么直接用字典记录就可以了。

有重复元素, 只能使用一次-- 先排序,多使用一个字典来标记,记住全部标记序号,如果nums[i] == nums[i-1] and i-1 not in record,则continue;

有重复元素,无限次使用,其实没有意义,重复包含在无限次里了。

39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。

说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]

有重复情况下, 没时间只能用以下的set()

class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        if not candidates:
            return []

        out = set()
        candidates = sorted(candidates)
        def backtrack(track, track_sum):

            if track_sum == target:
                out.add(tuple(sorted(track)))
                return 
            if track_sum > target:
                return 

            for i in range(len(candidates)):
                backtrack(track+[candidates[i]], track_sum+candidates[i])
        
        backtrack([], 0)
        return list(out)

优化重复情况:

用一个序号来记录,每次都只要大于等于记号的,就能保证能在同一个数不断地取,也不会返回之前的数。

class Solution: # 优化了重复
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        if not candidates:
            return []

        out = []
        candidates = sorted(candidates)
        def backtrack(track, track_sum, sign):

            if track_sum == target:
                out.append(track)
                return 
            if track_sum > target:
                return 

            for i in range(len(candidates)):
                if i < sign:
                    continue
                backtrack(track+[candidates[i]], track_sum+candidates[i], i)
        
        backtrack([], 0, 0)
        return out

40. 组合总和 II

与上一题的主要区别是包含重复数字,所以在解决不重复抽取同一个数组中的数并无法避免因为数组中有重复的数字而导致的重复的组合的问题。

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。

说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
下面是用了check来剪掉上述所说的重复情况。

# 注意,即使避免了在candidates中抽取重复的数,但是也有可能其之中有相同的数,所以还是会产生重复的组合。
class Solution: 
    def combinationSum2(self, candidates, target):
        if not candidates:
            return []
        candidates = sorted(candidates)
        out = []
        check = {}
        for i in range(len(candidates)):
            check[i] = 0
        def backtrack(track, sum_track, sign):
            if sum_track == target:
                out.append(track)
                return 
            elif sum_track > target:
                return 
            for i in range(len(candidates)):
                if i < sign:
                    continue
                if i > 0 and candidates[i] == candidates[i-1] and check[i-1] == 0:
                    continue
                check[i] = 1
                backtrack(track+[candidates[i]], sum_track+candidates[i], i+1)
                check[i] = 0

        backtrack([], 0, 0)
        return out

老套路了,有重复数字则用check来判断,用sign来避免重复抽取

90. 子集 II----使用check注意排序

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。
示例:

输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        if not nums:
            return [[]]
        out = []
        check = {}
        nums = sorted(nums) # 注意排序!!!
        for i in range(len(nums)):
            check[i] = 0
        def backtrack(track, sign):

            out.append(track)

            for i in range(len(nums)):
                if i < sign:
                    continue
                if i > 0 and nums[i] == nums[i-1] and check[i-1] == 0: #说明第二个重复的数开始当家作主
                    continue
                check[i] = 1
                backtrack(track+[nums[i]], i+1)
                check[i] = 0
        backtrack([], 0)
        return out

集合的所有子集

在这里插入图片描述

# @param A int整型一维数组 
# @return int整型二维数组
#
class Solution:
    def subsets(self, A):
        if not A:
            return [[]]
        A = sorted(A)
        result = [[]]
        for i in range(len(A)):
            res = []
            for j in range(len(result)):
                res.append(result[j]+[A[i]])
            result += res
        return result
    
class Solution:
    def subsets(self, A):
        if not A:
            return [[]]
        A = sorted(A)
        result = []
        def backtrack(sub, sign):
            result.append(sub)
            for i in range(sign, len(A)):
                backtrack(sub+[A[i]], i+1)
        backtrack([], 0)
        return result

剑指offer200页

##立方体,三组面的顶点分别相等。
# a1+a2+a3+a4 = a5+a6+a7+a8; a1+a3+a5+a7 = a2+a4+a6+a8; a1+a2+a5+a6=a3+a4+a7+a8

def find_track(nums):##这是排列题,考虑重复
    result = []
    nums = sorted(nums)
    record = {}
    def backtrack(track):
        if len(track) == len(nums):
            if (track[0]+track[1]+track[2]+track[3]) == (track[4]+track[5]+track[6]+track[7]) and (track[0]+track[2]+track[4]+track[6]) == (track[1]+track[3]+track[5]+track[7]) and (track[0]+track[1]+track[4]+track[5]) == (track[2]+track[3]+track[6]+track[7]):
                result.append(track)
            return
        for i in range(len(nums)):
            if i >= 1 and nums[i] == nums[i-1] and i-1 not in record:
                continue
            if i in record:
                continue
            record[i] = 1
            backtrack(track+[nums[i]])
            record.pop(i)
    backtrack([])
    return result
print(find_track([1,1,1,1,1,1,1,1]))

其实排列组合问题,比较难的还是N皇后问题。

N皇后问题–easy

考虑三个方面:# 从上往下,同行不需要考虑;考虑同列-以及对角线:对角线的规律是y-x相同或x+y相同

import copy
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        # 从上往下,同行不需要考虑;考虑同列-以及对角线:对角线的规律是y-x相同或x+y相同
        if n <= 0:
            return []
        record = {}
        res1 = {}
        res2 = {}
        result = []
        dp = []

        for i in range(n):
            dp.append(["."]*n)

        def backtrack(n, row):
            if row == n:
                sub = []
                for i in range(len(dp)):
                    sub.append("".join(dp[i]))
                result.append(sub)
                return 
            for i in range(n):
                if i in record:
                    continue
                if i + row in res2:
                    continue
                if i - row in res1:
                    continue
                record[i] = 1
                res1[i-row] = 1
                res2[i+row] = 1
                dp[row][i] = "Q"
                backtrack(n, row+1)
                record.pop(i)
                res1.pop(i-row)
                res2.pop(i+row)
                dp[row][i] = "."
        backtrack(n, 0)
        return result```

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值