LeetCode之动态规划

整数拆分

给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

class Solution:
    def integerBreak(self, n: int) -> int:
        dp = [0]*(n+1)
        dp[2] = 1
        for i in range(3, n+1):
            for j in range(1, i-1):
                dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j]))
        return(dp[n])

不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        # 构造一个DP table
        row = len(obstacleGrid)
        col = len(obstacleGrid[0])
        dp = [[0 for _ in range(col)] for _ in range(row)]

        dp[0][0] = 1 if obstacleGrid[0][0] != 1 else 0
        if dp[0][0] == 0: return 0  # 如果第一个格子就是障碍,return 0
        # 第一行
        for i in range(1, col):
            if obstacleGrid[0][i] != 1:
                dp[0][i] = dp[0][i-1]

        # 第一列
        for i in range(1, row):
            if obstacleGrid[i][0] != 1:
                dp[i][0] = dp[i-1][0]
        print(dp)

        for i in range(1, row):
            for j in range(1, col):
                if obstacleGrid[i][j] != 1:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[-1][-1]
class Solution:
    """使用一维dp数组 """
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m, n = len(obstacleGrid), len(obstacleGrid[0])

        # 初始化dp数组
        # 该数组缓存当前行
        curr = [0] * n
        for j in range(n):
            if obstacleGrid[0][j] == 1:
                break
            curr[j] = 1
        for i in range(1, m): # 从第二行开始
            for j in range(n): # 从第一列开始,因为第一列可能有障碍物
                # 有障碍物处无法通行,状态就设成0
                if obstacleGrid[i][j] == 1:
                    curr[j] = 0
                elif j > 0:
                    # 等价于
                    # dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
                    curr[j] = curr[j] + curr[j - 1]
                # 隐含的状态更新
                # dp[i][0] = dp[i - 1][0]
        return curr[n - 1]

不同的二叉搜索树(有点难理解)

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

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

LeetCode之动态规划_第1张图片

01-bag

def test_1_wei_bag_problem():
    weight = [1, 3, 4]
    value = [15, 20, 30]
    bag_weight = 4
    # 初始化: 全为0
    dp = [0] * (bag_weight + 1)

    # 先遍历物品, 再遍历背包容量
    for i in range(len(weight)):
        for j in range(bag_weight, weight[i] - 1, -1):
            # 递归公式
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

    print(dp)

test_1_wei_bag_problem()

分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        taraget = sum(nums)
        if taraget % 2 == 1: return False # 只有 奇+奇或者偶+偶的组合 都是偶数
        taraget //= 2
        dp = [0] * 10001
        for i in range(len(nums)):
            for j in range(taraget, nums[i] - 1, -1):
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
        return taraget == dp[taraget]   ## key point 装满说明找到了

01背包问题是有 N 件物品和一个最多能被重量为 W 的背包,且每个物品只能使用一次。
背包的体积为sum / 2
背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
背包如果正好装满,说明找到了总和为 sum / 2 的子集。
背包中每一个元素是不可重复放入。

炸石头

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。
每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        dp = [0] * 15000
        target = sum(stones) // 2
        for i in range(len(stones)):
            for j in range(target, stones[i]-1, -1):
                dp[j] = max(dp[j], dp[j-stones[i]]+stones[i])        
        return sum(stones) - 2 * dp[target]

放苹果

把m个同样的苹果放在n个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?注意:如果有7个苹果和3个盘子,(5,1,1)和(1,5,1)被视为是同一种分法。

'''
放苹果分为两种情况,一种是有盘子为空,一种是每个盘子上都有苹果。
令(m,n)表示将m个苹果放入n个盘子中的摆放方法总数。
1.假设有一个盘子为空,则(m,n)问题转化为将m个苹果放在n-1个盘子上,即求得(m,n-1)即可
2.假设所有盘子都装有苹果,则每个盘子上至少有一个苹果,即最多剩下m-n个苹果,问题转化为将m-n个苹果放到n个盘子上
即求(m-n,n)
'''
def f(m:int, n:int):
    if m < 0 or n < 0:
        return 0
    elif m == 1 or n == 1:
        return 1
    else:
        return f(m, n-1)+f(m-n,n)

while 1:
    try:
        num1, num2 = map(int, input().split())
        print(f(num1, num2))
    except:
        break

走方格的方案数

请计算n*m的棋盘格子(n为横向的格子数,m为竖向的格子数)从棋盘左上角出发沿着边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。

def func(x, y):
    if x < 0 or y < 0:
        return 0
    elif x == 0 or y == 0:
        return 1
    else:
        return func(x-1, y) + func(x, y-1)
        
while 1:
    try:
        a,b = map(int, input().split())
        c = func(a, b)
        print(c)   
    except:
        break

括号

设计一种算法,打印n对括号的所有合法的(例如,开闭一一对应)组合。

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        res = []
        def dfs(left, right, brackets):
            if left > right:
                return 
            if left == 0 and right == 0:
                res.append(brackets)
            if left > 0:
                dfs(left - 1, right, brackets+'(')
            if right >0:
                dfs(left, right-1, brackets+')')
        dfs(n,n,'')
        return res

子序列问题

最长递增子序列(不要求连续)

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        if n <= 1:
            return n
        max_ = 0
        dp = [1] * n

        for i in range(len(nums)):
            for j in range(i):
                if nums[j] < nums[i]:
                    dp[i] = max(dp[i], dp[j]+1)
            max_ = max(max_, dp[i])
        
        return max_

最长连续递增序列

class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        n = len(nums)
        if n <= 1:
            return n

        max_ = 0
        dp = [1] * n

        for i in range(1, n):
            if nums[i] > nums[i-1]:
                dp[i] = dp[i-1] + 1
            max_ = max(max_, dp[i])
        
        return max_

最长重复子数组

给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

class Solution:
    def findLength(self, nums1: List[int], nums2: List[int]) -> int:
        m, n = len(nums1), len(nums2)
        dp = [[0 for _ in range(n+1)] for _ in range(m+1)]
        max_ = 0
        for i in range(1, (m+1)):
            for j in range(1, (n+1)):
                if nums1[i-1] == nums2[j-1]:
                    dp[i][j] = dp[i-1][j-1] + 1
                max_ = max(max_, dp[i][j])
        return max_

最长公共子序列(不要求连续)

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列 的长度。如果不存在公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        m, n = len(text1), len(text2)
        dp = [[0 for _ in range(n+1)]for _ in range(m+1)]
        max_ = 0
        for i in range(1, m+1):
            for j in range(1, n+1):
                if text1[i-1] == text2[j-1]:
                    dp[i][j] = dp[i-1][j-1] + 1
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1])              
                max_ = max(max_, dp[i][j])    
        return max_

再求公共子数组(连续)或者子序列(不连续)时,关键在于处理遇到不同的字符时,如果是要求连续的那就不用管,不连续的时候得等于两个序列分别加上一个字符时的最大。

最大子数组和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        
        dp = [0] * len(nums)
        dp[0] = nums[0]
        result = dp[0]

        for i in range(1, len(nums)):
            dp[i] = max(dp[i-1] + nums[i], nums[i])
            result = max(result, dp[i])
        
        return result

最大子数组积

n = int(input())
nums = list(map(int, input().split()))
res = nums[0]
maxnum = minnum = nums[0]
if n == 1:
    print(nums[0])
else:
    for x in nums[1:]:
        n1, n2 = x * maxnum, x * minnum
        maxnum = max(n1, n2, x)
        minnum = min(n1, n2, x)
        if res < maxnum:
            res = maxnum
    print(res)

字符串间的编辑距离

指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。

while 1:
    try:
        s1, s2 = input(),input()
        m,n = len(s1), len(s2)
        
        dp = [[1 for i in range(n+1)] for j in range(m+1)]
        for i in range(n+1):
            dp[0][i] = i
        for j in range(m+1):
            dp[j][0] = j
        for i in range(1, m+1):
            for j in range(1, n+1):
                if s1[i-1] == s2[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1
        print(dp[m][n])  
    except:
        break

LeetCode之动态规划_第2张图片

(1) dp[i][j] = dp[i-1][j-1] if word1[i-1] == word[j-1]

(2) dp[i][j] = dp[i-1][j-1] + 1 if word1[i-1] != word[j-1] and dp[i-1][j] == dp[i][j-1]

(3) dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + 1 if word1[i-1] != word[j-1] and dp[i-1][j] != dp[i][j-1]

对于1,如果新的字母是一样的,那就不用增加额外的操作

对于2,如果dp[i-1][j] == dp[i][j-1],说明新加入的两个字符可以通过替换的方式处理,只需要额外操作一次,所以dp[i][j] 
= dp[i-1][j-1] + 1

对于3,如果dp[i-1][j] != dp[i][j-1],说明新加入的两个字符可以有其他的处理方式(删除,插入),所以dp[i][j] 
= min(dp[i-1][j], dp[i][j-1]) + 1

回文序列问题

回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

s[i]与s[j]相等,s[i]与s[j]不相等这两种。
当不相等,一定是false。
当相等时,这就复杂一些了,有如下三种情况
情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
情况二:下标i 与 j相差为1,例如aa,也是文子串
情况三:下标:i 与 j相差大于1的时候,看区间 i + 1、j - 1 是否为回文。


class Solution:
    def countSubstrings(self, s: str) -> int:        
        dp = [[False for _ in range(len(s)+1)]for _ in range(len(s)+1)]
        cnt = 0
        for i in range(len(s)-1, -1, -1):   # notice here
            for j in range(i, len(s)):
                if s[i] == s[j] and (j - i <= 1 or dp[i+1][j-1]):
                    dp[i][j] = True
                    cnt += 1
        return cnt

最长回文子序列

dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。
注意遍历的顺序

class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        dp = [[0 for _ in range(len(s))] for _ in range(len(s))]
        # 一个字符的回文子序列长度就是1
        for i in range(len(s)):
            dp[i][i] = 1

        for i in range(len(s)-1, -1, -1): # notice here
            for j in range(i+1, len(s)):
                if s[i] == s[j]:
                    dp[i][j]  = dp[i+1][j-1] + 2
                else:
                    dp[i][j] = max(dp[i+1][j], dp[i][j-1])    
        
        return dp[0][-1]

买卖股票问题

买卖股票的最佳时机1

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        dp0 = 0 				# 一直不买
        dp1 = -prices[0]		# 只买了一次
        dp2 = float('-inf')		# 买了一次,卖了一次

        for i in range(len(prices)):
            dp1 = max(dp1, dp0-prices[i])
            dp2 = max(dp2, dp1+prices[i])
            
        return max(dp1, dp2)

买卖股票的最佳时机2

在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        dp0 = 0             # have
        dp1 = -prices[0]    # no have 

        for i in range(len(prices)):
            dp0 = max(dp0, dp1 + prices[i])
            dp1 = max(dp1, dp0 - prices[i])
        
        return max(dp0, dp1)

买卖股票的最佳时机3

你最多可以完成 两笔 交易

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        
        dp0 = 0                 # 一直不买
        dp1 = -prices[0]        # 买 1
        dp2 = float('-inf')     # 买 1 卖 1
        dp3 = float('-inf')     # 买 2 卖 1
        dp4 = float('-inf')     # 买 2 卖 2
    
        for i in range(len(prices)):
            dp1 = max(dp1, dp0-prices[i])
            dp2 = max(dp2, dp1+prices[i])
            dp3 = max(dp3, dp2-prices[i])
            dp4 = max(dp4, dp3+prices[i])
        
        return max(dp1, dp2, dp3, dp4)

买卖股票的最佳时机4

最多可以完成 k 笔交易

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:

        n = len(prices)
        if n < 2 or k == 0: return 0
        k = min(k, n//2)

        dp = [float('-inf')] * (2*k+1)
        dp[0] = 0
        dp[1] = -prices[0]

        for i in range(1,n):
            for j in range(1, 2*k+1):
                if j & 1:
                    dp[j] = max(dp[j], dp[j-1]-prices[i])
                else:
                    dp[j] = max(dp[j], dp[j-1]+prices[i])
        return max(dp)

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

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if len(prices) < 2:
            return 0
        
        dp0 = 0                 # donot have && no frozen
        dp1 = float('-inf')     # donot have && frozen
        dp2 = -prices[0]        # have

        for i in range(1, len(prices)):
            new_dp0 = max(dp0, dp1)
            new_dp1 = dp2 + prices[i]
            new_dp2 = max(dp2, dp0 - prices[i])
            dp0, dp1, dp2 = new_dp0, new_dp1, new_dp2

        return max(dp0, dp1) 
        # the maxvalue must be got from dp0&dp1 because they donnot have stock

买卖股票的最佳时机含手续费

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

class Solution:
    def maxProfit(self, prices: List[int], fee: int) -> int:
        dp0 = 0             # have
        dp1 = -prices[0]    # no have 

        for i in range(len(prices)):
            dp0 = max(dp0, dp1 + prices[i] - fee)
            dp1 = max(dp1, dp0 - prices[i])
        
        return max(dp0, dp1)

跳跃游戏

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

class Solution:
    def jump(self, nums: List[int]) -> int:
        dp = [0] * 10000
        # dp[i] 表示能跳i步最远能跳到哪里
        dp[1] = nums[0]

        n = len(nums)
        if n == 1 or nums[0] == 0:
            return 0
        elif dp[1] >= n-1:
            return 1
        
        i = 2 
        while dp[i-1] < n-1:
            max_ = 0
            for j in range(dp[i-2]+1, dp[i-1]+1):
                max_ = max(max_, j+nums[j])
            dp[i] = max_
            i += 1
            
        return i-1

最大正方形

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:

        col = len(matrix[0])
        row = len(matrix)
        res = 0
#	dp[i][j]表示以第i行,第j列处为右下角的最大正方形的边长
        dp = [[0 for _ in range(col+1)]for _ in range(row+1)]

        for i in range(1, row+1):
            for j in range(1, col+1):
            	if matrix[i-1][j-1] == '1':
                	dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])+1
                	res = max(res, dp[i][j])
        return res**2

你可能感兴趣的:(LeetCode,leetcode)