动态规划及leetcode练习

一、动态规划

  • 动态规划思想:

    动态规划往往用于优化递归问题。例如斐波那契数列,如果运用递归的方式来求解会重复计算很多相同的子问题,动态规划法仅仅解决每个子问题一次,具有天然剪枝的功能,从而减少计算量,一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。

  • 动态规划模板步骤:

    • 确定动态规划状态
    • 写出状态转移方程(画出状态转移表)
    • 考虑初始化条件
    • 考虑输出状态
    • 考虑对时间,空间复杂度的优化(Bonus)

二、leetcode练习

2.1一维dp

leetcode300.最长上升子序列

题目:给定一个无序的整数数组,找到其中最长上升子序列的长度。

def lengthOfLIS(nums: list) -> int:
    if len(nums) == 0:
        return 0
    dp = [1]*len(nums)
    for i in range(1, len(nums)):
        for j in range(i):
            if nums[i] > nums[j]:
                dp[i] = max(dp[i], dp[j]+1)
    return max(dp)

leetcode53.最大子序和

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

思路:

  • 如何找到最优子问题?

    假设数组为[-2,1,-3,4,-1,2,1,-5,4],共有9个数字,那么会有 1 + 2 + … + 9 = 45 1+2+…+9=45 1+2++9=45个子序列。我们首先可以把45个子序列划分成9个子组合。
    第一个子组合是以第一个数字结尾的连续序列,也就是 [-2],最大值-2
    第二个子组合是以第二个数字结尾的连续序列,也就是 [-2,1], [1],最大值1
    第三个子组合是以第三个数字结尾的连续序列,也就是 [-2,1,3], [1,3], [3],最大值4
    ……
    如果我们能够得到每一个子组合内子序列的最大值,整体的最大值就可以通过比较这9个子组合的最大值来得到了。

  • 每个组合之间的最大值有何关系?

    以第二个子组合和第三个子组合为例,子组合3分成两种情况:
    ①继承子组合二得到的序列,也就是[-2,1,3], [1,3] (最大值 1 = 第二个组合的最大值 + 第三个数字)
    ②单独第三个数字的序列,也就是[3] (最大值 2 = 第三个数字)
    那么组子合三的最大值为max(最大值1,最大值2)

  • 状态及状态转移方程

    由上所述,定义 d p [ i ] dp[i] dp[i]为以 n u m s [ i ] nums[i] nums[i]为子序列末端的最大子序和,则 d p [ i ] = m a x ( d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] ) dp[i] = max(dp[i-1]+nums[i], nums[i]) dp[i]=max(dp[i1]+nums[i],nums[i])

def maxSubArray(nums: list) -> int:
    if len(nums) == 0:
        return 0
    dp = nums      # 初始化
    for i in range(1, len(nums)):
        dp[i] = max(dp[i], dp[i-1]+nums[i])
    return max(dp)

leetcode674.最长连续递增序列

题目:给定一个未经排序的整数数组,找到最长且连续的 的递增序列,并返回该序列的长度。

def findLengthOfLIS(nums: list) -> int:
    if len(nums) == 0:
        return 0
    dp = [1]*len(nums)
    for i in range(1, len(nums)):
        if nums[i] > nums[i-1]:
            dp[i] = dp[i-1] + 1
        else:
            dp[i] = 1
    return max(dp)

leetcode198.打家劫舍

题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下,一夜之内能够偷窃到的最高金额。

思路:定义数组dp,dp[k]表示在前k间房子中偷到的最大金额 状态转移方程: d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) , i > = 2 dp[i] = max(dp[i-2]+nums[i], dp[i-1]),i>=2 dp[i]=max(dp[i2]+nums[i],dp[i1])i>=2 初始化:dp[0]=nums[0], dp[1]=max(nums[0],nums[1])

def rob(nums: list) -> int:
    n = len(nums)
    if n == 1:
        return nums[0]
    if n == 2:
        return max(nums)
    dp = [0]*n
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    for i in range(2, n):
        dp[i] = max(dp[i-2]+nums[i], dp[i-1])
    return dp[-1]

leetcode213.打家劫舍II

题目:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

思路:环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:

  • 在不偷窃第一个房子的情况下(即nums[1:]),最大金额是v1
  • 在不偷窃最后一个房子的情况下(即nums[:n−1]),最大金额是v2
  • 综合偷窃最大金额: 为以上两种情况的较大值,即max(v1,v2)。
def rob(nums: list) -> int:
    def my_rob(my_nums):
        n = len(my_nums)
        if n == 1:
            return my_nums[0]
        if n == 2:
            return max(my_nums)
        dp = [0] * n
        dp[0] = my_nums[0]
        dp[1] = max(my_nums[0], my_nums[1])
        for i in range(2, n):
            dp[i] = max(dp[i - 2] + my_nums[i], dp[i - 1])
        return dp[-1]
    return max(my_rob(nums[1:]), my_rob(nums[:-1])) if len(nums) > 1 else nums[0]

2.2 二维dp

leetcode120.三角形最小路径和

题目:给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

思路:
状态转移方程
f [ i , j ] = { f [ i − 1 , 0 ] + c [ i , 0 ] j = 0 m i n ( f [ i − 1 , j − 1 ] , f [ i − 1 , j ] ) + c [ i , j ] 0 < j < i f [ i − 1 , i − 1 ] + c [ i , i ] j = i f[i,j]=\begin{cases} f[i-1,0]+c[i,0] & j=0\\ min(f[i-1,j-1],f[i-1,j])+c[i,j] & 0<j<i\\ f[i-1,i-1]+c[i,i] & j=i \end{cases} f[i,j]=f[i1,0]+c[i,0]min(f[i1,j1],f[i1,j])+c[i,j]f[i1,i1]+c[i,i]j=00<j<ij=i

def minimumTotal(triangle):
    n = len(triangle)
    dp = [[0]*n for _ in range(n)]
    dp[0][0] = triangle[0][0]
    for i in range(1, n):
        dp[i][0] = dp[i-1][0]+triangle[i][0]
        dp[i][i] = dp[i-1][i-1]+triangle[i][i]
        for j in range(1, i):
            dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]
    return min(dp[-1])

leetcode5.最长回文子串

题目:给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 (注意:子串是连续的

思路:
定义二维数组dp,dp[i,j]表示字符串s从i到j是否为回文子串。
回文的条件:

  • 字符串首尾两个字符必须相等。
  • 当字符串首尾两个字符相等时:
    ①若字符串长度为2或3,则整体为回文;
    ②若字符串长度大于3,则子串是回文时整体为回文、子串不是回文时整体也不是。即dp[i][j]=dp[i+1][j-1]
def longestPalindrome(s: str) -> str:
    length = len(s)
    if length < 2:
        return s
    dp = [[False]*length for _ in range(length)]
    for i in range(length):
        dp[i][i] = True

    max_len = 1
    start = 0
    for j in range(1, length):
        for i in range(j):
            if s[i] == s[j]:
                if j-i < 3:           # 若不加此判断,s[0]==s[1]时,dp[0][1]=dp[1][0]=False
                    dp[i][j] = True
                else:
                    dp[i][j] = dp[i+1][j-1]
            if dp[i][j]:
                cur_len = j-i+1
                if cur_len > max_len:
                    start = i
                    max_len = cur_len
    return s[start: start+max_len]

leetcode1143.最长公共子序列

题目:给定一个字符串s,找到其中最长的回文子序列,并返回该序列的长度。可以假设s的最大长度为1000。 (注意:子序列可以不连续。

思路:定义二维数组dp,dp[i,j]为从i到j的回文子序列最大长度。则
d p [ i ] [ j ] ( j > = i ) = { d p [ i − 1 ] [ j − 1 ] + 2 s [ i ] = = s [ j ] m a x ( d p [ i ] [ j − 1 ] , d p [ i + 1 ] [ j ] ) s [ i ] ! = s [ j ] dp[i][j](j>=i)=\begin{cases} dp[i-1][j-1]+2 & s[i]==s[j] \\ max(dp[i][j-1],dp[i+1][j]) & s[i]!=s[j] \end{cases} dp[i][j](j>=i)={dp[i1][j1]+2max(dp[i][j1],dp[i+1][j])s[i]==s[j]s[i]!=s[j]
此题不同的是,第i行的情况取决于i+1行,因此i应从大到小遍历。

def longestPalindromeSubseq(s: str) -> int:
    n = len(s)
    dp = [[0]*n for _ in range(n)]
    for i in range(n):
        dp[i][i] = 1
    for i in range(n-1, -1, -1):
        for j in range(i+1, n):
            if s[i] == s[j]:
                dp[i][j] = dp[i+1][j-1] + 2
            else:
                dp[i][j] = max(dp[i][j-1], dp[i+1][j])
    return dp[0][-1]

leetcode72.编辑距离

题目:给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。

思路:定义二维数组dp,dp[i][j]代表word1中前i个字符变换到word2中前j个字符需要的最短操作次数。

  • 初始化:dp[i][0]=i dp[0][j]=j

  • 状态转移:

    • word1[i] == word2[j]
      dp[i][j] = dp[i-1][j-1]

    • word1[i]!= word2[j]
      增:dp[i][j] = dp[i][j-1] +1
      删:dp[i][j] = dp[i-1][j] + 1
      改:dp[i][j] = dp[i-1][j-1] + 1

def minDistance(word1: str, word2: str) -> int:
    m = len(word1)
    n = len(word2)
    dp = [[0]*(m+1) for _ in range(n+1)]
    for i in range(m+1):
        dp[i][0] = i
    for j in range(n+1):
        dp[0][j] = j
    for i in range(1, m+1):
        for j in range(1, n+1):
            if word1[i-1] == word2[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
    return dp[-1][-1]

参考:https://github.com/datawhalechina/team-learning-program/blob/master/LeetCodeClassification/2.动态规划.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值