代码随想录算法训练营第二十五天| 回溯4— 491. 非递减子序列,47. 全排列 II,51. N 皇后

回溯第四天,491. 非递减子序列46. 全排列47. 全排列 II51. N 皇后。感觉就是模板人,有原先做过的模板的题目做起来就很顺利。刚进入全排列的题目,简单题做起来也很头疼,还是感觉思维能力差了很多,思维转变起来很慢。后来有时间又补了一道N皇后。

491. 非递减子序列

491. 非递减子序列 - 力扣(LeetCode)

子集类型题目,但稍微有些变化,整体思路不变。

参数:经典的index

停止条件:类似子集Ⅱ,无需return,因为每个节点都是需要添加的,不像之前组合题目达到一定条件这个叶子节点就不再递归下去了。然后是这个题的特殊判断,就是len>1

单层循环:设置used这个set,来判断每个树层内,元素是否有重复的,即nums[i] in used, 注意这个要在for循环前,是每层一个used,为了解释生成一下结构。然后另一个判断就是path and nums[i] < path[-1],即需要添加的元素和path内最后一个元素比较。后续就是常规的增加,递归,回溯,注意这里多一个在set中添加的操作。

Level 0: back(0), path=[], used={}
 ├─ i=0, nums[i]=4, used={}, 选 → path=[4]
 │
 │ Level 1: back(1), path=[4], used={}
 │  ├─ i=1, nums[i]=6, used={}, 选 → path=[4,6]
 │  │
 │  │ Level 2: back(2), path=[4,6], used={}
 │  │  ├─ i=2, nums[i]=7, used={}, 选 → path=[4,6,7]
 │  │  │
 │  │  │ Level 3: back(3), path=[4,6,7], used={}
 │  │  │  ├─ i=3, nums[i]=7, used={}, 选 → path=[4,6,7,7] ✅
 │  │  │  └─ 回溯 path=[4,6,7]
 │  │  └─ i=3, nums[i]=7, used={7},🚫 跳过
 │  ├─ i=2, nums[i]=7, used={6}, 选 → path=[4,7]
 │  │
 │  │ Level 2: back(3), path=[4,7], used={}
 │  │  ├─ i=3, nums[i]=7, used={}, 选 → path=[4,7,7] ✅
 │  │  └─ 回溯 path=[4,7]
 │  └─ i=3, nums[i]=7, used={6,7},🚫 跳过
 ├─ i=1, nums[i]=6, used={4}, 选 → path=[6]
 │
 │ Level 1: back(2), path=[6], used={}
 │  ├─ i=2, nums[i]=7, used={}, 选 → path=[6,7]
 │  │
 │  │ Level 2: back(3), path=[6,7], used={}
 │  │  ├─ i=3, nums[i]=7, used={}, 选 → path=[6,7,7] ✅
 │  │  └─ 回溯 path=[6,7]
 │  └─ i=3, nums[i]=7, used={7},🚫 跳过
 ├─ i=2, nums[i]=7, used={4,6}, 选 → path=[7]
 │
 │ Level 1: back(3), path=[7], used={}
 │  ├─ i=3, nums[i]=7, used={}, 选 → path=[7,7] ✅
 │  └─ 回溯 path=[7]
 └─ i=3, nums[i]=7, used={4,6,7},🚫 跳过
class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        def back(index):
            if len(path) > 1:
                res.append(path.copy())
            
            used = set()
            for i in range(index, len(nums)):
                if (path and nums[i] < path[-1]) or nums[i] in used:
                    continue
                    
                used.add(nums[i])
                path.append(nums[i])
                back(i+1)
                path.pop()

        res = []
        path = []
        back(0)
        return res

47. 全排列 II

47. 全排列 II - 力扣(LeetCode)

回溯新的类型,全排列。和46. 全排列相比,依旧是多了个去重的操作,但是这里的去重比之前的要麻烦也要难理解一下。


参数:因为是全排列,不像切割那样index往前移动,所以参数不用index了。但是需要一个新的used的数组,这个数组主要功能是判断同一树层的元素是否使用过。

停止条件:因为是全排列,所以就是当path的长度等于原数组长度就停止

单层递归:两个去重判断,第一个判断是路径内是否重复使用,及纵向是否重复使用,原因是我们这里没有递归i+1而是used,所以这里要去重,不然就会出现重复使用某个单个元素的情况。第二个是判断树层内是否重复使用,及横向是否重复。如看到下面代码中level = 0, i 为1.2.3的情况,其中i = 1的时候,used[i-1]是false,这就证明了是树层上的去重,如果是true,就是树枝去重,因为证明了前一个1选了,在判断第二个1选不选。如果表明前一个1没选,就开始判断第二个1选不选,就可以知道是开始在层中判断,也就是level0, i = 1的时候。判断完之后,进入常规增加,递归,回溯。然后注意即时维护used这个set里面的T/F以保证判断正确,也就是说,你在path中append了,就把当前i在used里改为True,你在path中pop了,就把i在used里改为False。

Level 0: path=[], used=[F, F, F]
 ├─ i=0, 选 1 → path=[1], used=[T, F, F]
 │
 │ Level 1: path=[1], used=[T, F, F]
 │  ├─ i=0, used[0]=T → 跳过
 │  ├─ i=1, nums[1]==nums[0] && used[0]=T ✅允许 → 选 1 → path=[1,1], used=[T, T, F]
 │  │
 │  │ Level 2: path=[1,1], used=[T, T, F]
 │  │  ├─ i=2, 选 2 → path=[1,1,2] ✅
 │  │  └─ 回溯 path=[1,1]
 │  ├─ i=2, 选 2 → path=[1,2], used=[T, F, T]
 │  │
 │  │ Level 2: path=[1,2], used=[T, F, T]
 │  │  ├─ i=1, 选 1 → path=[1,2,1] ✅
 │  │  └─ 回溯 path=[1,2]
 │  └─ 回溯 path=[1]

 ├─ i=1, nums[1]==nums[0] && used[0]=F ❌剪枝 → 跳过

 ├─ i=2, 选 2 → path=[2], used=[F, F, T]
 │
 │ Level 1: path=[2], used=[F, F, T]
 │  ├─ i=0, 选 1 → path=[2,1], used=[T, F, T]
 │  │
 │  │ Level 2: path=[2,1], used=[T, F, T]
 │  │  ├─ i=1, nums[1]==nums[0] && used[0]=T ✅允许 → 选 1 → path=[2,1,1] ✅
 │  │  └─ 回溯 path=[2,1]
 │  └─ i=1, nums[1]==nums[0] && used[0]=F ❌剪枝 → 跳过
class Solution:
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        def subtrack(used):
            if len(path) == len(nums):
                res.append(path.copy())
                return 

            for i in range(len(nums)):
                if used[i]:
                    continue
                if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]):
                    continue
                used[i] = True
                path.append(nums[i])
                subtrack(used)
                path.pop()
                used[i] = False
        
        nums.sort()
        used = [False] * len(nums)
        res = []
        path = []
        subtrack(used)
        return res
 

51. N 皇后

51. N 皇后 - 力扣(LeetCode)

难,但没有想象中那么难,不是那种毫无头绪一点办法没有的题,只是需要套用回溯模板,多一点判断和思考,至少一刷的时候把大框架都写完了,有一两个小问题没改出来,已经很满意了。

既然是回溯问题,就依旧按照递归三步套模板走。但是实际上做的时候,是并没有严格按照三步的。因为一时间想不全我需要什么参数,所以自己思考的步骤如下:

首先感到这道题简单的地方在于,能够把棋盘规划成一个树结构。就像原本for循环遍历层,递归遍历枝叶,那么就相当于棋盘,用row去进行行遍历,col去进行列遍历。等效过来,原本回溯模板中for循环的i,就是col,所需要的参数index,就是row。

写判断函数,从IP地址那道题学来的,判断条件过多的时候单独写判断函数,这样递归函数内就比较清晰。第一是列上不能相等,这时候写的时候不知道用什么来判断,就写了个Q,没想到确实是用Q判断。后面的判断比较复杂,因为代码左上右下这样一条线写不到一起,所以一开始分别写了左上,左下,右上,右下四个方向。后来想了一下发现考虑复杂了,因为递归是一行一行进行的,所以无需判断未来行的情况,所以只有左上,右上。然后写的时候犯错是没有用i,j所以导致写起来很复杂,而且当前chess[row][col]无需判断,直接从-1/+1判断就行。

然后开始递归函数,卡的一个点是不知道怎么填充,原计划是用path分别添加.和Q然后整合加入res,但是发现很复杂,就不太清楚怎么做了。看了题解才发现忘记先搭建个全是.的棋盘,然后到对应位置改为Q就行。

终止条件很简单,当row == n停止就行,就代表已经把0到n-1行遍历完了。然后将棋盘加到res就行。

单层遍历过程:依旧是某种意义上的增加,递归,回溯,只不过这次不是从无到有,而是先画好了棋盘进行修改就行。这里是卡了一下但还是做完了,增加就是保留原本的chess[row][:col]不变,改下一个.为Q,再保留原本chess[row][col+1:]不变就行,回溯就是原方法Q改回.就行。

其他都是常规回溯写法

class Solution:
    def isvalid(self, row, col, chess, n):
        for i in range(row): # 树结构是行遍历,只需检查每行中是否有col相等
            if chess[i][col] == 'Q':
                return False
        
        i, j = row - 1, col - 1
        while i >= 0 and j >= 0:  #左上 
            if chess[i][j] == 'Q':
                return False  
            i -= 1
            j -= 1

        i, j = row - 1, col +1
        while i >= 0 and j < n:  #右上
            if chess[i][j] == 'Q':
                return False
            i -= 1
            j += 1
        return True
        
    def backing(self, row, chess, res, n):
        if row == n:
            res.append(chess[:])
            return

        for col in range(n):
            if self.isvalid(row, col, chess, n):
                chess[row] = chess[row][:col]+'Q'+chess[row][col+1:]
                self.backing(row+1,chess,res,n)
                chess[row] = chess[row][:col]+'.'+chess[row][col+1:]

    def solveNQueens(self, n: int) -> List[List[str]]:
        chess = ['.' * n for _ in range(n)] 
        res = []
        self.backing(0, chess, res, n)

        return res

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值