代码随想录算法训练营第三十天|491.递增子序列 46.全排列 47.全排列 II 332.重新安排行程 51. N皇后 37. 解数独

491.递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

思路:

不出意外完美踩进所说的陷阱之中,本题不能用子集的思路去解。首先不能改变原数组的序列,其次去重的方式并不是将紧密排列在一起的元素进行去重,而是在一个循环中已经出现过的元素都不能再取,因为相同的元素可能不紧密排列在一起,中间可能会隔着一个不符合条件的小元素。至此,其他思路就和求子集区别不大了,需要注意的是只有符合条件的元素才进行回溯调用,所以调用了就说明进入调用的元素是符合条件的,此时应该直接将此时的结果压入结果数组,同时不进行返回,这样就实现了中途符合条件的所有数组都能被压入。

代码实现如下:

class Solution:

    def findSubsequences(self, nums: List[int]) -> List[List[int]]:

        self.result = []

        self.backtracking(nums, 0, [])

        return self.result



    def backtracking(self, nums:List[int], start:int, path:List):

        if len(path)>=2:

            self.result.append((path[:]))



        dict = {}

        for i in range(start, len(nums)):

            #if (dict.get(nums[i], 0) == 0 and (path and nums[i]>=path[-1])) or not path:

            if dict.get(nums[i], 0) == 0 and (not path or nums[i]>=path[-1]):

                dict[nums[i]] = 1

                path.append(nums[i])

                self.backtracking(nums, i+1, path)

                path.pop()

规范代码:

回溯 利用set去重

class Solution:

    def findSubsequences(self, nums):

        result = []

        path = []

        self.backtracking(nums, 0, path, result)

        return result

    

    def backtracking(self, nums, startIndex, path, result):

        if len(path) > 1:

            result.append(path[:])  # 注意要使用切片将当前路径的副本加入结果集

            # 注意这里不要加return,要取树上的节点

        

        uset = set()  # 使用集合对本层元素进行去重

        for i in range(startIndex, len(nums)):

            if (path and nums[i] < path[-1]) or nums[i] in uset:

                continue

            

            uset.add(nums[i])  # 记录这个元素在本层用过了,本层后面不能再用了

            path.append(nums[i])

            self.backtracking(nums, i + 1, path, result)

            path.pop()

回溯 利用哈希表去重

class Solution:

    def findSubsequences(self, nums):

        result = []

        path = []

        self.backtracking(nums, 0, path, result)

        return result



    def backtracking(self, nums, startIndex, path, result):

        if len(path) > 1:

            result.append(path[:])  # 注意要使用切片将当前路径的副本加入结果集

        

        used = [0] * 201  # 使用数组来进行去重操作,题目说数值范围[-100, 100]

        for i in range(startIndex, len(nums)):

            if (path and nums[i] < path[-1]) or used[nums[i] + 100] == 1:

                continue  # 如果当前元素小于上一个元素,或者已经使用过当前元素,则跳过当前元素

            

            used[nums[i] + 100] = 1  # 标记当前元素已经使用过

            path.append(nums[i])  # 将当前元素加入当前递增子序列

            self.backtracking(nums, i + 1, path, result)

            path.pop()

46.全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

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

思路:

全局定义一个记录元素是否被使用的集合used,每当当前path数组记录某元素后,就将元素压入set中,所以在每一次遍历压入之前应该先检查元素是否在used集合中,如果存在说明当前已被取用,不应该对本元素进行回溯调用,而是调用下一个元素。当遍历的path长度与nums一致时,即所有元素被调用,此时压入结果数组中。

代码实现如下:

class Solution:

    def permute(self, nums: List[int]) -> List[List[int]]:

        self.uset = set()

        self.result = []

        self.backtracking(nums, 0, [])

        return self.result



    def backtracking(self, nums:List[int], pos:int, path:List):

        if pos >= len(nums):

            self.result.append(path[:])



        for i in range(0, len(nums)):

            if nums[i] not in self.uset:

                self.uset.add(nums[i])

                path.append(nums[i])

                self.backtracking(nums, pos+1, path)

                path.pop()

                self.uset.remove(nums[i])

规范代码:

回溯 使用used

class Solution:

    def permute(self, nums):

        result = []

        self.backtracking(nums, [], [False] * len(nums), result)

        return result



    def backtracking(self, nums, path, used, result):

        if len(path) == len(nums):

            result.append(path[:])

            return

        for i in range(len(nums)):

            if used[i]:

                continue

            used[i] = True

            path.append(nums[i])

            self.backtracking(nums, path, used, result)

            path.pop()

            used[i] = False

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]]

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

思路:

与上一题全排列不同的是,本题将会出现重复元素,所以在每一层for循环中,需要有一个cur_uset来记录本层for循环是否已经取用过某一种元素,确保不会在相同的位置取到相同的元素。而关于一个结果数组中,确保元素出现次数正确方面的去重,应该在全局下定义一个记录每种元素出现过的次数,当元素还有余量时可以继续取用。确保这两方面的条件之后,和上一题一样的思路进行回溯即可。

代码实现如下:

class Solution:

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

        self.result = []

        #self.uset = set()

        self.count = defaultdict(int)

        for num in nums:

            self.count[num] += 1

        self.backtracking(nums, 0, [])

        return self.result



    def backtracking(self, nums: List[int], pos: int, path: List):

        if pos >= len(nums):

            self.result.append(path[:])



        cur_uset = set()

        for i in range(0, len(nums)):

            if nums[i] in cur_uset:

                continue

            #if nums[i] not in self.uset:

            if self.count[nums[i]] > 0:

                #self.uset.add(nums[i])

                self.count[nums[i]] -= 1

                cur_uset.add(nums[i])

                path.append(nums[i])

                self.backtracking(nums, pos + 1, path)

                path.pop()

                #self.uset.remove(nums[i])

                self.count[nums[i]] += 1

规范代码:

class Solution:

    def permuteUnique(self, nums):

        nums.sort()  # 排序

        result = []

        self.backtracking(nums, [], [False] * len(nums), result)

        return result



    def backtracking(self, nums, path, used, result):

        if len(path) == len(nums):

            result.append(path[:])

            return

        for i in range(len(nums)):

            if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]:

                continue

            used[i] = True

            path.append(nums[i])

            self.backtracking(nums, path, used, result)

            path.pop()

            used[i] = False

332.重新安排行程

思路:没想出来,记录思路。字典记载:key为出发点,value为可到达的机场位置(同时以value为依据进行排序)。从出发点开始遍历,每次都以出发点对应的可到达位置的首个位置进行递归调用并pop,路径长度刚好等于机票数+1,说明得到答案进行返回。

规范代码:

回溯 使用字典

class Solution:

    def findItinerary(self, tickets: List[List[str]]) -> List[str]:

        self.adj = {}



        # sort by the destination alphabetically

        # 根据航班每一站的重点字母顺序排序

        tickets.sort(key=lambda x:x[1])



        # get all possible connection for each destination

        # 罗列每一站的下一个可选项

        for u,v in tickets:

            if u in self.adj: self.adj[u].append(v)

            else: self.adj[u] = [v]



        # 从JFK出发

        self.result = []

        self.dfs("JFK")  # start with JFK



        return self.result[::-1]  # reverse to get the result



    def dfs(self, s):

        # if depart city has flight and the flight can go to another city

        while s in self.adj and len(self.adj[s]) > 0:

            # 找到s能到哪里,选能到的第一个机场

            v = self.adj[s][0]  # we go to the 1 choice of the city

            # 在之后的可选项机场中去掉这个机场

            self.adj[s].pop(0)  # get rid of this choice since we used it

            # 从当前的新出发点开始

            self.dfs(v)  # we start from the new airport



        self.result.append(s)  # after append, it will back track to last node, thus the result list is in reversed order

回溯 使用字典 逆序

from collections import defaultdict

class Solution:

    def findItinerary(self, tickets):

        targets = defaultdict(list)  # 创建默认字典,用于存储机场映射关系

        for ticket in tickets:

            targets[ticket[0]].append(ticket[1])  # 将机票输入到字典中

        

        for key in targets:

            targets[key].sort(reverse=True)  # 对到达机场列表进行字母逆序排序

        

        result = []

        self.backtracking("JFK", targets, result)  # 调用回溯函数开始搜索路径

        return result[::-1]  # 返回逆序的行程路径

    

    def backtracking(self, airport, targets, result):

        while targets[airport]:  # 当机场还有可到达的机场时

            next_airport = targets[airport].pop()  # 弹出下一个机场

            self.backtracking(next_airport, targets, result)  # 递归调用回溯函数进行深度优先搜索

        result.append(airport)  # 将当前机场添加到行程路径中

51. N皇后

记录思路:每个棋盘的每一行就是我们的一层for循环,依次遍历放置Queen,当遍历到最后一行并且可以放置Queen的时候就到了终止条件,记录数据。在每次遍历的时候,需要检查位置是否有效,所以定义一个函数来进行检查所在位置的上方、左上方、右上方是否存在Queen,由于是从左向右遍历寻找放置Queen的位置,所以不需要检索行。

规范代码:

class Solution:

    def solveNQueens(self, n: int) -> List[List[str]]:

        result = []  # 存储最终结果的二维字符串数组



        chessboard = ['.' * n for _ in range(n)]  # 初始化棋盘

        self.backtracking(n, 0, chessboard, result)  # 回溯求解

        return [[''.join(row) for row in solution] for solution in result]  # 返回结果集



    def backtracking(self, n: int, row: int, chessboard: List[str], result: List[List[str]]) -> None:

        if row == n:

            result.append(chessboard[:])  # 棋盘填满,将当前解加入结果集

            return



        for col in range(n):

            if self.isValid(row, col, chessboard):

                chessboard[row] = chessboard[row][:col] + 'Q' + chessboard[row][col+1:]  # 放置皇后

                self.backtracking(n, row + 1, chessboard, result)  # 递归到下一行

                chessboard[row] = chessboard[row][:col] + '.' + chessboard[row][col+1:]  # 回溯,撤销当前位置的皇后



    def isValid(self, row: int, col: int, chessboard: List[str]) -> bool:

        # 检查列

        for i in range(row):

            if chessboard[i][col] == 'Q':

                return False  # 当前列已经存在皇后,不合法



        # 检查 45 度角是否有皇后

        i, j = row - 1, col - 1

        while i >= 0 and j >= 0:

            if chessboard[i][j] == 'Q':

                return False  # 左上方向已经存在皇后,不合法

            i -= 1

            j -= 1



        # 检查 135 度角是否有皇后

        i, j = row - 1, col + 1

        while i >= 0 and j < len(chessboard):

            if chessboard[i][j] == 'Q':

                return False  # 右上方向已经存在皇后,不合法

            i -= 1

            j += 1



        return True  # 当前位置合法

37. 解数独

记录思路:重点在于二维递归,即对9*9的方格,每一个[i, j]都要进行一次遍历。本题由于限定了一定是9*9的方格,所以在循环完这(9*9)*9次递归回溯后,一定会退出循环,所以可以不需要终止条件,如果跳出了这个循环说明没有正确答案,可以直接返回False。每一个格子的递归逻辑是,如果有数字则跳过。为空则在每一个格子都遍历1~9来进行回溯调用,确认放置元素之前要检查该数是否有效,依次排查行、列、九宫格内是否满足条件(该数唯一),如果有效则对下一个格子进行1~9的遍历调用。

规范代码:

class Solution:

    def solveSudoku(self, board: List[List[str]]) -> None:

        """

        Do not return anything, modify board in-place instead.

        """

        self.backtracking(board)



    def backtracking(self, board: List[List[str]]) -> bool:

        # 若有解,返回True;若无解,返回False

        for i in range(len(board)): # 遍历行

            for j in range(len(board[0])):  # 遍历列

                # 若空格内已有数字,跳过

                if board[i][j] != '.': continue

                for k in range(1, 10):

                    if self.is_valid(i, j, k, board):

                        board[i][j] = str(k)

                        if self.backtracking(board): return True

                        board[i][j] = '.'

                # 若数字1-9都不能成功填入空格,返回False无解

                return False

        return True # 有解



    def is_valid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool:

        # 判断同一行是否冲突

        for i in range(9):

            if board[row][i] == str(val):

                return False

        # 判断同一列是否冲突

        for j in range(9):

            if board[j][col] == str(val):

                return False

        # 判断同一九宫格是否有冲突

        start_row = (row // 3) * 3

        start_col = (col // 3) * 3

        for i in range(start_row, start_row + 3):

            for j in range(start_col, start_col + 3):

                if board[i][j] == str(val):

                    return False

        return True

第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值