LeetCode每日一题7月记录

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]

7.11 计算右侧小于当前元素的个数

原题地址.
利用插入排序的思想,从原数组的尾元素开始往前遍历,插入并维护一个新的有序数组,插入位置就是该元素右侧小于它的元素个数。

class Solution:
    def countSmaller(self, nums: List[int]) -> List[int]:
        sortns = []
        res = []
        for n in reversed(nums):
            idx = bisect.bisect_left(sortns, n)
            res.append(idx)
            sortns.insert(idx,n)
        return res[::-1]

7.12 地下城游戏

原题地址.
一个经典的dp问题,定义dp[i][j]为从i行j列到最后右下角所需要的最少生命值。就可以写出对应的转移方程

class Solution:
    def calculateMinimumHP(self, dungeon: List[List[int]]) -> int:
        #初始化
        n, m = len(dungeon), len(dungeon[0])
        dp = [[float('inf')] * (m + 1) for _ in range(n + 1)]
        #边界条件
        dp[n][m - 1] = dp[n - 1][m] = 1
        for i in range(n - 1, -1, -1):
            for j in range(m - 1, -1, -1):
                
                #过来所需要的初始值
                minn = min(dp[i + 1][j], dp[i][j + 1])
                #初始值必须大于1,dungeon可能加血
                dp[i][j] = max(minn - dungeon[i][j], 1)

        return dp[0][0]

7.13 两个数组的交集

原题地址.
终于又来简单题了。利用hash表完成常数实际访问,具体为以短数组构建hash表,遍历长数组判断是否在其中。

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        if len(nums1) > len(nums2):
            #hash表用小数组更好
            return self.intersect(nums2, nums1)
        
        m = collections.Counter(nums1)
        intersection = list()
        #看另外一个数组元素是否在hash表
        for num in nums2:
            if num in m.keys():
                intersection.append(num)
                m[num] -= 1
                if m[num] == 0:
                    m.pop(num)
        
        return intersection

7.14 三角形最小路径和

原题地址.
本质上是一个经典的dp问题,而且每一行的路径答案只和上一行相关,所以可以进行状态压缩。

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        n = len(triangle)
        #dp[?][i] 根据?是prev或者curr储存当前行或者上一行i位置的路径最小和
        dp = [[0] * n for _ in range(2)]
        #初始值
        dp[0][0] = triangle[0][0]
        for i in range(1,n):
            #判断当前使用的是那一行
            curr, prev = i % 2, 1 - i % 2
            #边界值
            dp[curr][0] = dp[prev][0] + triangle[i][0]
            dp[curr][i] = dp[prev][i-1] + triangle[i][i]
            #状态转移
            for j in range(1,i):
                dp[curr][j] = min(dp[prev][j-1],dp[prev][j]) + triangle[i][j]
            
        return min(dp[1 - n%2])

7.15不同的二叉搜索树

原题地址.
运用动态规划或者说是带记忆的递归解决。

class Solution:
    def numTrees(self, n: int) -> int:
        G = [0]*(n+1)
        G[0], G[1] = 1, 1
        for i in range(2,n + 1):
            for j in range(0,i):
            	#左右子树节点数为j和i-1-j
                G[i]+= G[j]*G[i-1-j]
        return G[-1]

如果有足够的数学知识,会知道这是卡特兰数,可以直接用递推公式求解。
在这里插入图片描述

class Solution(object):
    def numTrees(self, n):
        C = 1
        for i in range(0, n):
            C = C * 2*(2*i+1)/(i+2)
        return int(C)

7.16 判断二分图

原题地址
在遍历每一个边时,判断端点颜色是否符合规矩以及对未染色的节点染色。

class Solution:
    def isBipartite(self, graph: List[List[int]]) -> bool:
        n = len(graph)
        UNCOLORED, RED, GREEN = 0, 1, 2
        #存储颜色
        color = [UNCOLORED] * n
        valid = True

        def dfs(node,c):
            nonlocal valid
            color[node] = c
            #另外一个节点应该的颜色
            cNei = (GREEN if c == RED else RED)
            for neighbor in graph[node]:
                #未染色的,染上这个颜色
                if color[neighbor] == UNCOLORED:
                    dfs(neighbor, cNei)
                    if not valid:
                        return
                #颜色发生冲突
                elif color[neighbor] != cNei:
                    valid = False
                    return

        for i in range(n):
            #遍历所有节点,因为可能有多个子图
            if color[i] == UNCOLORED:
                dfs(i, RED)
                if not valid:
                    break
        return valid

7.17搜索插入位置

原题地址
没啥好说的,二分搜索注意一下边界条件。

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        l = 0
        r = len(nums) - 1
        #<=的写法因为对终止循环前的元素判断了对于target的大小,l就是该插入的地方
        while l <= r:
            mid = (l+r)//2
            if nums[mid] < target:   
                l = mid + 1
            elif nums[mid] > target:
                r = mid - 1
            else:
                #提前返回
                return mid
        else:
            return l

7.18交错字符串

原题地址
利用动态规划解决,类别编辑距离处理。又可以用滚动数组优化二维dp数据。

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        m = len(s1)
        n = len(s2)
        t = len(s3)
        if m+n != t:
            return False
        #dp数组初始化
        dp = [False]*(n+1)
        dp[0] = True
        #原来二维i每一行之和上一行有关
        for i in range(m+1):
            for j in range(n+1):
                p = i + j - 1
                if i > 0:
                    dp[j] = dp[j] and s3[p] == s1[i-1]
                if j > 0:
                    dp[j] = dp[j] or (dp[j-1] and s3[p] == s2[j-1])
        return dp[-1]

7.19 戳气球

原题地址
自底向上得动态规划。dp[i][j]记录以ij为左右边界得最大值。

class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        n = len(nums)
        rec = [[0] * (n + 2) for _ in range(n + 2)]
        val = [1] + nums + [1]
        for i in range(n - 1, -1, -1):
            #右边界
            for j in range(i + 2, n + 2):
                #左边界
                for k in range(i + 1, j):
                    #中间元素
                    total = val[i] * val[k] * val[j]
                    total += rec[i][k] + rec[k][j]
                    rec[i][j] = max(rec[i][j], total)
        return rec[0][n+1]

7.20 两数之和Ⅱ

原题地址
使用双指针从两端向中间搜索。

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        i = 0
        j = len(numbers) - 1
        while numbers[i] + numbers[j] != target:
            if numbers[i] + numbers[j] > target:
                j -= 1
            else:
                i += 1
        return [i+1,j+1]

7.21 不同的二叉搜索树Ⅱ

原题地址
递归成生成左右子树的小问题解决。

class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        def generateTrees(start, end):
            if start > end:
                return [None,]
            res = []
            for i in range(start,end + 1):
                #递归
                leftTrees = generateTrees(start, i - 1)
                rightTrees = generateTrees(i + 1, end)
                #拼接
                for l in leftTrees:
                    for r in rightTrees:
                        currTree = TreeNode(i)
                        currTree.left = l
                        currTree.right = r
                        res.append(currTree)
            
            return res
        
        return generateTrees(1, n) if n else []

7.22 剑指 Offer 11. 旋转数组的最小数字

原题地址
其实就是寻找旋转数组旋转点,利用二分法可以方便的找到。

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        l,r = 0,len(numbers)-1
        while l < r:
            mid = (l+r)//2
            if numbers[mid] > numbers[r]:
                l = mid + 1
            elif numbers[mid] < numbers[r]:
                r = mid
            else:
                r -= 1
        return numbers[l]

7.23 最小路径和

原题地址
常规的动态规划问题,利用滚动数组优化。

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        m = len(grid)
        n = len(grid[0])
        dp = [0] * n
        #一行行求解
        for i in range(m):
            for j in range(n):
                if i == 0 and j != 0:
                    dp[j] = dp[j-1] + grid[i][j]
                elif j ==0 and i !=0:
                    dp[j] = dp[j] + grid[i][j]
                elif i ==0 and j == 0:
                    dp[j] = grid[i][j]
                else:
                    dp[j] = min(dp[j],dp[j-1]) + grid[i][j]
        return dp[-1]

7.24 除数博弈

原题地址
需要思考一个数的转化过程,拿到一个奇数运算后肯定变成一个偶数,拿到一个偶数之后肯定变成一个奇数;而最后拿到2这个偶数的获胜,拿到1这个奇数的就输了。所以先手偶数的就会获胜。

class Solution:
    def divisorGame(self, N: int) -> bool:
        return N%2==0

7.25 分割数组的最大值

原题地址
这种需要多次求解子问题的问题一般都用动态规划思想,设数组dp[i][j]为前i个元素分为j段的结果,那么状态转移方程就是在i个元素中选择第k个划分为j-1段以及后面一段求和。

class Solution:
    def splitArray(self, nums: List[int], m: int) -> int:
        n = len(nums)
        #dp初始化
        dp = [[float('inf')] * (m + 1) for _ in range(n + 1)]
        dp[0][0] = 0
        #sub是序列和,用来快速求出段落之和
        sub = [0]
        for elem in nums:
            sub.append(sub[-1] + elem)

        for i in range(1,n+1):
            for j in range(1,min(i,m) + 1):
                for k in range(i):
                	#k分为2段选择其中大的。
                    dp[i][j] = min(dp[i][j],max(dp[k][j-1],sub[i]-sub[k]))
        return dp[n][m]

7.26 矩阵中的最长递增路径

原题地址
经典的dfs搜索可以解决,但是会超时,所以加入记忆化搜索。

class Solution:
    def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
        direcs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
        if not matrix:
            return 0
        #记忆化数组
        meme = {}
        def dfs(row: int, column: int) -> int:
            best = 1
            for dx, dy in direcs:
                newRow, newColumn = row + dx, column + dy

                if 0 <= newRow < rows and 0 <= newColumn < columns and matrix[newRow][newColumn] > matrix[row][column]:
                	#直接提取
                    if (newRow,newColumn) in meme:
                        best = max(best, meme[(newRow,newColumn)] + 1)
                    else:
                    #计算,仅第一次
                        best = max(best, dfs(newRow,newColumn) + 1)
                    meme[(row,column)] = best
            return best

        ans = 0
        rows, columns = len(matrix), len(matrix[0])
        for i in range(rows):
            for j in range(columns):
                ans = max(ans, dfs(i, j))

7.27 判断子序列

原题地址
因为在两个字符串中都要遍历,考虑使用双指针法遍历。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        n, m = len(s), len(t)
        #双指针分别在s,t中遍历
        i = j = 0
        while i < n and j < m:
            #找到一个,s指针后移
            if s[i] == t[j]:
                i += 1
            #不管有没有,t指针都要后移
            j += 1
        return i == n

如果需要处理很多个s判断子序列的话,可以用动态规划,先把t的信息储存。f[i][j]表示位置i之后第一个出现字母j的坐标

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        n, m = len(s), len(t)
        #dp数组,第二维代表26个字母
        f = [[0] * 26 for _ in range(m)]
        f.append([m] * 26)
		#填充数组
        for i in range(m - 1, -1, -1):
            for j in range(26):
                f[i][j] = i if ord(t[i]) == j + ord('a') else f[i + 1][j]
        
        add = 0
        for i in range(n):
        	#无效位置
            if f[add][ord(s[i]) - ord('a')] == m:
                return False
            add = f[add][ord(s[i]) - ord('a')] + 1
        return True

7.28 二叉树的最大深度

原题地址
利用栈遍历,出栈时比较最大深度

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        stack = []
        if root is not None:
            stack.append((1, root))

        res = 0
        while stack:
            current_depth, root = stack.pop()
            if root:
                res = max(res, current_depth)
                stack.append((current_depth+1,root.left))
                stack.append((current_depth+1,root.right))
        return res

7.29 寻宝

原题地址
这题建议直接看题解.。。。
这道困难题让我的青春结束了。

7.30 整数拆分

原题地址
采用动态规划思想,数字i拆分成数字j和其余部分处理。

class Solution:
    def integerBreak(self, n: int) -> int:
        dp = [0] * (n + 1)
        for i in range(2, n + 1):
            for j in range(i):
                #子问题,j这个数字可以选择拆分或者不拆分。
                dp[i] = max(dp[i],j * (i - j),dp[j]*(i-j))
        return dp[n]

7.31 魔术索引

原题地址
暴力遍历也可以,但是既然是有序的,就要利用好这个性质,省略一下对比操作。

class Solution:
    def findMagicIndex(self, nums: List[int]) -> int:
        n = len(nums)
        i = 0
        while i < n:
            if nums[i] == i:
                return i
            if nums[i] > i: # 此时我们可以排除索引i到nums[i-1]这一整段
                i = nums[i] # 由于数组可以保持平稳,所以nums[i]这一元素不可排除
            else:
                i += 1
                
        return -1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值