LeetCode每日一题7月记录

LeetCode每日一题7月记录

  • 7.1最长重复子数组
  • 7.2有序矩阵中第k小的元素
  • 7.3将有序数据转换为二叉搜索树
  • 7.4最长有效括号
  • 7.5通配符匹配
  • 7.6 不同路径Ⅱ
  • 7.7路径总和
  • 7.8跳水板
  • 7.9 恢复空格
  • 7.10 最佳买卖股票时机含冷冻期

7.1最长重复子数组

原题地址.
A[i:]和B[j:]的最长重复长度为A[i+1:]和B[j+1:]最长长度加1(如果A[i]==B[j]),所以可以用动态规划思想解决。

class Solution:
    def findLength(self, A: List[int], B: List[int]) -> int:
        n = len(A)
        m = len(B)
        dp = [[0] * (m+1) for _ in range(n+1)]
        res = 0
        #最小的字串开始,往更长的字串去计算dp[i][j]表示A[i:]与B[j:]的最大长度
        for i in range(n-1,-1,-1):
            for j in range(m-1,-1,-1):
                dp[i][j] = dp[i+1][j+1] + 1 if A[i] == B[j] else 0
                res = max(res,dp[i][j])
        return res

7.2有序矩阵中第k小的元素

原题地址.
首先利用矩阵的性质,可以写出一个便捷的函数判断矩阵中有多少元素小于某一个值。

        def check(mid):
            i,j = n-1,0
            #小于mid的元素个数
            num = 0
            while i >= 0 and j < n:
                if matrix[i][j] <= mid:
                	#一升序,所以上面全部小于mid
                    num += i+1
                    j += 1
                else:
                    i -= 1
            return num >= k

进而用二分搜索就可以找找到满足条件的值

left, right = matrix[0][0], matrix[-1][-1]
        while left < right:
            mid = (left + right) // 2
            if check(mid):
                right = mid
            else:
                left = mid + 1
        
        return left

7.3将有序数据转换为二叉搜索树

原题地址.
转换主要考虑选取根节点的问题,方式不止一种,但是最容易方便的就是二分法,每次选取中间的点当根,这样左右子树就会平衡

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        def helper(l,r):
            if l > r:
                return
            mid = l + (r-l)//2
            root = TreeNode(nums[mid])
            #递归构建左右子树
            root.left = helper(l,mid-1)
            root.right = helper(mid+1,r)
            return root
        return helper(0,len(nums)-1)

7.4最长有效括号

原题地址.
遇到括号匹配问题,首先想到利用栈的一边扫描,一边出栈,一边判断长度。

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        out = 0
        #方便计数初始值
        stack = [-1]
        for i in range(len(s)):
            if s[i] == '(':
                stack.append(i)
            else:
                stack.pop()
                if stack:
                    #比较当前长度
                    out = max(out,i - stack[-1])
                else:
                    #出现了多余的右括号,所以起始计数位置要重置
                    stack.append(i)
        return out

7.5通配符匹配

原题地址.
匹配问题可以采用动态规划思想。
用dp[i][j] 标记s[:i]与p[:j]是否匹配,那么可写出转移方程。
如果p[j-1]==s[i-1] or '?',就和前缀字串相同了,如果p[j-1] == ‘*’则分不进行匹配dp[i][j] = dp[i][j-1]和匹配当前dp[i][j] = dp[i-1][j]

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        m, n = len(s), len(p)

        #初始化
        dp = [[False] * (n + 1) for _ in range(m + 1)]
        dp[0][0] = True
        for i in range(1, n + 1):
        #连续为‘*’号才True
            if p[i - 1] == '*':
                dp[0][i] = True
            else:
                break
        
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                #通匹符号可以匹配任意多个或者不匹配
                if p[j-1] == '*':
                	#不用匹配就是不用‘*’出马,j往前看一个就行;匹配的话‘*’肯定那把s[i]顶掉,所以i往前看就行。
                    dp[i][j] = dp[i][j-1] or dp[i-1][j]
                #如果接下来一个字符可以对上,就和前缀字串一致
                elif p[j-1] =='?' or p[j-1] == s[i-1]:
                    dp[i][j] = dp[i-1][j-1]
        return dp[-1][-1]

7.6 不同路径Ⅱ

原题地址.
还是dp思想,但是判断条件更多,边界条件也更麻烦。

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        #一开始就堵了
        if obstacleGrid[0][0] == 1:
            return 0

        dp = [0] * n
        #第一行一直往右走,一旦堵了就全堵了
        for i in range(n):
            if obstacleGrid[0][i] == 0:
                dp[i] = 1
            else:
                break

        for i in range(1,m):
            #第一列只能往下走,要看上一行第一个走不走得通
            dp[0] = dp[0] if obstacleGrid[i][0] == 0 else 0
            for j in range(1,n):
                #本身堵了就是0,没有就看上和左的路径了
                dp[j] = dp[j-1] + dp[j]  if obstacleGrid[i][j] == 0 else 0 
        return dp[-1]

7.7路径总和

原题地址.
一边递归一边更新总和值即可。

class Solution:
    def hasPathSum(self, root: TreeNode, sum: int) -> bool:
        if not root:
            return False
        #满足条件
        if not root.left and not root.right:
            return sum == root.val
        return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val)

7.8跳水板

原题地址.
利用数量总和一定解答

class Solution:
    def divingBoard(self, shorter: int, longer: int, k: int) -> List[int]:
        if k == 0:
            return []
        if shorter == longer:
            #只有一种情况,其实不要也能通过,但是可以剪枝加速
            return [k * shorter]
        else:
            #数量和是一定的
            return [(k - i) * shorter + i * longer for i in range(k + 1)]

7.9 恢复空格

原题地址.
暴力也可以通过,但是为了优化,需要用到前缀树和dp。

class TreeNode:
    def __init__(self):
        #定义前缀树,is_word是单词结尾标志
        self.child = {}
        self.is_word = False

class Solution:  
    def make_tree(self, dictionary):
        #递归构建前缀树
        for word in dictionary:
            node = self.root
            for s in word:
                if not s in node.child:
                    node.child[s] = TreeNode()
                node = node.child[s]
            node.is_word = True

    def respace(self, dictionary: List[str], sentence: str) -> int:
        self.root = TreeNode()
        self.make_tree(dictionary)
        n = len(sentence)
        #dp数组,dp[i]是sentence[i:]的未识别字符数
        dp = [0] * (n + 1)
        for i in range(n-1, -1, -1):
            dp[i] = n - i
            node = self.root
            #判断sentence[i:]里的字串是否在字典
            for j in range(i, n):
                c = sentence[j]
                #转移方程
                if c not in node.child:
                    #认命,sentence[i:j]也是未识别字符
                    dp[i] = min(dp[i], dp[j+1]+j-i+1)
                    break
                if node.child[c].is_word:
                    #是一个单词,可以不考虑sentence[i:j]
                    dp[i] = min(dp[i], dp[j+1])
                else:
                    #认命,sentence[i:j]也是未识别字符
                    dp[i] = min(dp[i], dp[j+1]+j-i+1)
                node = node.child[c]
        return dp[0]

7.10 最佳买卖股票时机含冷冻期

原题地址.
分持有和未持有两种情况使用动态规划思想。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n= len(prices)
        if not n:
            return 0
        #该天不持有和持有股票的收益
        dp = [[0,0]for i in range(n)]

        #第一天购入
        dp[0][1] = -prices[0]
        for i in range(1,n):
            #当天不持有,卖出去了或者一直没有
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i])
            #当天持有,一直持有或者当前买了
            dp[i][1] = max(dp[i-1][1],dp[i-2][0]-prices[i])
        return dp[-1][0]

你可能感兴趣的:(LeetCode)