Leetcode编程训练2 动态规划

无关紧要

从这次接触的题目角度看,动态规划是在递归的基础上进行运算效率的优化,也就是通过存储中间计算结果减少重复计算。
递归的问题的关键是找到可以不停迭代的结果相关变量【状态】、迭代过程之间的关系【状态转移方程】。求解过程就是先解决边界位置的小问题,然后不断递推解决上层问题。

虽然预留的时间够长,但我每天2道题的计划下还是因为拖拉呀、理解慢呀、代码出错呀,浪费时间而没有收获,不如以大神写的算法介绍帖为主,学习精神和模式,学会怎么用吧

学习目的

  • 完成动态规划相关经典题:5最长回文子串 72编辑距离 198打家劫舍 213打家劫舍2 516最长回文子序列 674最长连续递增序列;
  • 学习动态规划算法思维。

Leetcode198. 打家劫舍

问题:

你是一个专业的XT,计划TQ沿街的房屋。每间房内都藏有一定的现金,影响你TQ的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被XT闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够TQ到的最高金额。
示例:
输入:[2,7,9,3,1]
输出:12
解释:TQ 1 号房屋 (金额 = 2), TQ3 号房屋 (金额 = 9),接着TQ 5 号房屋 (金额 = 1)。
TQ到的最高金额 = 2 + 9 + 1 = 12 。
提示:0 <= nums.length <= 1000 <= nums[i] <= 400

思路

  • 确定状态,用数组dp记录方便复用
    状态:累计DQ金额?或者是最临近房屋是否被D?本题选择截至当前位置的累计DQ金额为状态。
  • 确定当前位置是否选择对状态的影响——状态转移方程
    对于第k个位置,是是否DQ第k个房屋的状态比较。如果DQ第k个房屋,那位置k-2的累计DQ金额+第k个房屋的金额是总价值,否则不DQ第k个,以k-1个房屋的累计DQ金额为结果,两者进行比较,取较大值。 状态转移方程可表示为:dp[k] = max(dp[k-2]+nums[k], dp[k-1])
    带入套路代码:
for i in range(len(nums)):    
	dp[i] = max(dp[i-1], dp[i-2]+nums[i])
  • 边界值和dp相关
    dp数组整体的初始值和dp数组i=0和j=0的地方以及dp数组的长度
    dp的初始化方法:
dp = [1 for _ in range(n)] 或者dp =[1]*n
dp = [[0]*n for _ in range(m)]或者dp=[[0 for _ in range(n)] for _ in range(m)]

当i=0时,仅一间房,自然只抢那间房,当有两件会抢金额较大的那间,即:

dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
  • 原始问题的输出
    最后一个值?最大的数字?保存的最大值?
    对于本题直接返回状态数组dp的最后一个值。

代码实现

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0
        if len(nums) == 1:
            return nums[0]
        elif len(nums) == 2:
            return max(nums[0], nums[1])
        
        dp = [0] * len(nums)
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])
        for i in range(2, len(nums)):
            dp[i] = max(dp[i-2]+nums[i], dp[i-1])
        return max(dp)

Leetcode213. 打家劫舍 II

问题:

你是一个专业的XT,计划TQ沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被XT闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够TQ到的最高金额。

示例 1:

输入: [2,3,2]
输出: 3
解释: 你不能先TQ 1 号房屋(金额 = 2),然后TQ 3 号房屋(金额 = 2), 因为他们是相邻的。

思路:

  • 确定状态,用数组dp记录方便复用
    状态:累计DQ金额?或者是最临近房屋是否被D?
    本题选择截至当前位置的累计DQ金额为状态。
  • 确定当前位置是否选择对状态的影响——状态转移方程
    相比于前一道题,房屋的线性排布变成了环状排列,可以把问题转成第0到n-1个房屋DQ与1到n个房屋DQ的比较。而两个问题都可以用前一道题的内容计算
    带入套路代码:
for i in range(len(nums)):
    dp[i] = max(dp[i-1], dp[i-2]+nums[i])
  • 边界值和dp相关
    dp数组整体的初始值和dp数组i=0和j=0的地方以及dp数组的长度
    dp的初始化方法:
dp = [1 for _ in range(n)] 或者dp =[1]*n
dp = [[0]*n for _ in range(m)]或者dp=[[0 for _ in range(n)] for _ in range(m)]

当i=0时,仅一间房,自然只抢那间房,当有两件会抢金额较大的那间,即:

dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
  • 原始问题的输出
    最后一个值?最大的数字?保存的最大值?
    对于本题直接返回两个状态数组的最大值。

代码实现

class Solution:
	'''存在重复代码,分别计算抢第一家和不抢第一家的结果,最后求最大'''
    def rob(self, nums: List[int]) -> int:
        size = len(nums)
        if not nums:
            return 0
        if size == 1:
            return nums[0]
        elif size in [2,3]:
            return max(nums[0], nums[1])
        else:
            # 抢第一个
            first, second = nums[0], max(nums[0], nums[1])
            for i in range(2, size):
                if i != size - 1:
                    first, second = second, max(first+nums[i], second)

            # 不抢第一个,抢最后一个
            first2, second2 = 0,  nums[1]
            for i in range(2, size):
                first2, second2 = second2, max(first2+nums[i], second2)         
            print(second, second2)
        return max(second, second2)

class Solution:
	'''思路一样,复用前一题的代码'''
    def rob(self, nums: List[int]) -> int:
        size = len(nums)
        if not nums:
            return 0
        if size == 1:
            return nums[0]
        def do(ccc):
            dp = [0]*(len(ccc)+2)
            for j in range(0,len(ccc)):
                dp[j+2] = max(dp[j+1], dp[j]+ccc[j])
            return max(dp)
        return max(do(nums[:-1]), do(nums[1:]))

Leetcode5. 最长回文子串

问题:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

思路:

  • 确定状态,用数组dp记录方便复用
    状态:回文子串的起止位置?回文子串的起止位置及最长长度?
    本题选择 二维的dp数组,以起止位置+是否为回文子串为状态。
  • 确定当前位置是否选择对状态的影响——状态转移方程
    • 字符串首尾两个字符必须相等,否则不是回文
    • 当字符串手尾相等时:如果子串是回文,则整体就是回文。所以把s[i,j]的问题转变成s[i+1,j-1]的问题。P(i,j) = P(i+1,j-1)∩(Si==Sj)

带入套路代码:

for i in range(len(s)):
    for j in range(len(s)):
        if s[i] == s[j]:
            if j-1 -(i+1)+1<2:
                dp[i][j] = 1
            else:
                dp[i][j] = dp[i+1][j-1]
  • 边界值和dp相关
    dp数组整体的初始值和dp数组i=0和j=0的地方以及dp数组的长度
    dp的初始化方法:
dp = [[0]*n for _ in range(m)]或者dp=[[0 for _ in range(n)] for _ in range(m)]

数组dp对角线位置代表仅一个字符的子串,所以是回文串,dp[i][i] = 1

  • 原始问题的输出
    dp数组中找到是回文子串的位置,可知起止位点,继而计算子串长度即可返回结果。

代码实现


class Solution:
    def longestPalindrome(self, s: str) -> str:
        def expand(left, right):
            while left>=0 and right<=len(s)-1 and s[left] == s[right]:
                left -= 1
                right += 1
            return left+1,right-1,right+1-left  #因为不通过while才return的,所有要+1-1地还原
        
        start, end, _len = 0,0,0
        for i in range(len(s)):
            start0, end0, len0 = expand(i, i)
            start1, end1, len1 = expand(i, i+1)
            if len0 >= len1:
                _start = start0
                _end = end0
            else:
                _start = start1
                _end = end1
            if max(len0, len1) > _len:
                start = _start
                end = _end
                _len = max(len0, len1)
        return s[start:(end+1)]

class Solution:
	'''耗时更久,内存消耗更大'''
    def longestPalindrome(self, s: str) -> str:
        _len = len(s)
        matrix = [[0]*_len for i in range(_len)]
        ans = ''

        for num in range(1, _len+1, 1):
            for i in range(0, _len-num+1, 1):
                j = num+i-1
                if i == j:
                    matrix[i][j] = True
                elif i + 1 == j:
                    matrix[i][j] = (s[i] == s[j])
                else:
                    matrix[i][j] = matrix[i+1][j-1] and s[i] == s[j]
                if matrix[i][j] and num > len(ans):
                    print(i,j,num)
                    ans = s[i:(j+1)]
        return ans

Leetcode516. 最长回文子序列

问题:

给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。

示例 1:
输入:
“bbbab”
输出:
4

思路:

  • 确定状态,用数组dp记录方便复用
    状态:回文子串的起止位置?回文子串的起止位置及最长长度?
    本题选择 二维的dp数组,以回文子串的起止位置及最长长度为状态。
  • 确定当前位置是否选择对状态的影响——状态转移方程
    • 字符串首尾两个字符相等,则s[i+1,j-1]+2为最长回文子序列长度,否则只有一个出现在最长回文子序列中,即max(s[i,j-1],s[i+1,j])
  • 边界值和dp相关
    dp数组整体的初始值和dp数组i=0和j=0的地方以及dp数组的长度
    dp的初始化方法:
dp = [[0]*n for _ in range(m)]或者dp=[[0 for _ in range(n)] for _ in range(m)]

数组dp对角线位置代表仅一个字符的子串,所以是回文串,dp[i][i] = 1

  • 原始问题的输出
    dp[0][-1]。

代码实现


class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        n = len(s)
        maxL = -1
        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 max(max(dp))

Leetcode 674.最长连续递增序列

问题:

给定一个未经排序的整数数组,找到最长且连续的的递增序列。

示例 1:
输入: [1,3,5,4,7]
输出: 3
解释: 最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。

思路:

  • 确定状态,用数组dp记录方便复用
    状态:截至当前数的最长连续递增子序列长度?
    本题选择 二维的dp数组,以当前数的最长连续递增子序列长度为状态。
  • 确定当前位置是否选择对状态的影响——状态转移方程
    • 如果当前数比前一个小,则这个数的最长连续递增序列为1
    • 如果当前数可以继续追加到连续递增序列,则dp[k] = dp[k-1]+1
  • 边界值和dp相关
    dp的初始化方法: 一维的全为1的数组
dp = [1 for _ in range(n)] 或者dp =[1]*n
  • 原始问题的输出
    max(dp)。

代码实现:

def findLengthOfLCIS(self, nums: List[int]) -> int:
    if not nums:return 0 #判断边界条件
    dp=[1]*len(nums) #初始化dp数组状态
    #注意需要得到前一个数,所以从1开始遍历,否则会超出范围
    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) #确定输出状态
class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        s, e, ans = 0, 0, 1
        for i in range(1, len(nums)):
            if nums[i] > nums[i - 1]:
                e = i
            else:
                s = e = i
            ans = max(ans, e - s + 1)

        return ans

Leetcode72. 编辑距离
题太多,这道放弃了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值