【编程之路】面试必刷TOP101:动态规划(78-82,Python实现)

2023-10-27

面试必刷TOP101:动态规划(78-82,Python实现)

78.打家劫舍(一)(小试牛刀

78.1 动态规划

或许有人认为利用贪心思想,偷取最多人家的钱就可以了,要么偶数家要么奇数家全部的钱,但是有时候会为了偷取更多的钱,或许可能会连续放弃两家不偷,因此这种方案行不通,我们依旧考虑动态规划。

step 1:用 d p [ i ] dp[i] dp[i] 表示长度为 i i i 的数组,最多能偷取到多少钱,只要每次转移状态逐渐累加就可以得到整个数组能偷取的钱。
step 2:(初始状态) 如果数组长度为 1,只有一家人,肯定是把这家人偷了,收益最大,因此 d p [ 1 ] = n u m s [ 0 ] dp[1]=nums[0] dp[1]=nums[0]
step 3:(状态转移) 每次对于一个人家,我们选择偷他或者不偷他,如果我们选择偷那么前一家必定不能偷,因此累加的上上级的最多收益,同理如果选择不偷他,那我们最多可以累加上一级的收益。因此转移方程为 d p [ i ] = m a x ( d p [ i − 1 ] , n u m s [ i − 1 ] + d p [ i − 2 ] ) dp[i]=max(dp[i−1],nums[i−1]+dp[i−2]) dp[i]=max(dp[i1],nums[i1]+dp[i2])。这里的 i i i d p dp dp 中为数组长度,在 n u m s nums nums 中为下标。

class Solution:
    def rob(self , nums: List[int]) -> int:
        # dp[i] 表示长度为 i 的数组,最多能偷多少钱
        dp = [0 for i in range(len(nums)+1)]
        # 初始状态
        dp[1] = nums[0]
        # 状态转移
        for i in range(2,len(nums)+1):
            dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])
        return dp[len(nums)]

时间复杂度: O ( n ) O(n) O(n),其中 n 为数组长度,遍历一次数组。
空间复杂度: O ( n ) O(n) O(n),动态规划辅助数组的空间。

79.打家劫舍(二)(小试牛刀

79.1 动态规划

这道题与上一题比较类似,区别在于这道题是环形,第一家和最后一家是相邻的,既然如此,在原先的方案中第一家和最后一家不能同时取到。

step 1:使用原先的方案是:用dp[i]表示长度为i的数组,最多能偷取到多少钱,只要每次转移状态逐渐累加就可以得到整个数组能偷取的钱。
step 2:(初始状态) 如果数组长度为 1,只有一家人,肯定是把这家人偷了,收益最大,因此 d p [ 1 ] = n u m s [ 0 ] dp[1]=nums[0] dp[1]=nums[0]
step 3:(状态转移) 每次对于一个人家,我们选择偷他或者不偷他,如果我们选择偷那么前一家必定不能偷,因此累加的上上级的最多收益,同理如果选择不偷他,那我们最多可以累加上一级的收益。因此转移方程为 d p [ i ] = m a x ( d p [ i − 1 ] , n u m s [ i − 1 ] + d p [ i − 2 ] ) dp[i]=max(dp[i−1],nums[i−1]+dp[i−2]) dp[i]=max(dp[i1],nums[i1]+dp[i2])。这里的 i i i d p dp dp 中为数组长度,在 n u m s nums nums 中为下标。
step 4:此时第一家与最后一家不能同时取到,那么我们可以分成两种情况讨论:

  • 情况1:偷第一家的钱,不偷最后一家的钱。初始状态与状态转移不变,只是遍历的时候数组最后一位不去遍历。
  • 情况2:偷最后一家的请,不偷第一家的钱。初始状态就设定了 d p [ 1 ] = 0 dp[1]=0 dp[1]=0,第一家就不要了,然后遍历的时候也会遍历到数组最后一位。

step 5:最后取两种情况的较大值即可。

class Solution:
    def rob(self , nums: List[int]) -> int:
        dp1 = [0 for i in range(len(nums)+1)]
        # 选择偷第 1 家
        dp1[1] = nums[0]
        # 则最后一家不偷
        for i in range(2,len(nums)):
            dp1[i] = max(dp1[i-1], dp1[i-2]+nums[i-1])
        res1 = dp1[len(nums)-1]
        
        dp2 = [0 for i in range(len(nums)+1)]
        # 不偷第 1 家
        dp2[1] = 0
        # 则可以偷最后一家
        for i in range(2,len(nums)+1):
            dp2[i] = max(dp2[i-1], dp2[i-2]+nums[i-1])
        res2 = dp2[len(nums)]
        
        return max(res1,res2)

时间复杂度: O ( n ) O(n) O(n),其中 n 为数组长度,单独遍历两次数组。
空间复杂度: O ( n ) O(n) O(n),动态规划辅助数组的空间。

80.买卖股票的最好时机(一)(小试牛刀

80.1 动态规划

对于每天有到此为止的最大收益和是否持股两个状态,因此我们可以用动态规划。

step 1:用 d p [ i ] [ 0 ] dp[i][0] dp[i][0] 表示第 i i i 天不持股到该天为止的最大收益, d p [ i ] [ 1 ] dp[i][1] dp[i][1] 表示第 i i i 天持股,到该天为止的最大收益。
step 2:(初始状态) 第一天不持股,则总收益为 0, d p [ 0 ] [ 0 ] = 0 dp[0][0]=0 dp[0][0]=0;第一天持股,则总收益为买股票的花费,此时为负数, d p [ 0 ] [ 1 ] = − p r i c e s [ 0 ] dp[0][1]=−prices[0] dp[0][1]=prices[0]
step 3:(状态转移) 对于之后的每一天,如果当天不持股,有可能是前面的若干天中卖掉了或是还没买,因此到此为止的总收益和前一天相同,也有可能是当天才卖掉,我们选择较大的状态 d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] ) dp[i][0]=max(dp[i−1][0],dp[i−1][1]+prices[i]) dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i])
step 4:如果当天持股,有可能是前面若干天中买了股票,当天还没卖,因此收益与前一天相同,也有可能是当天买入,此时收益为负的股价,同样是选取最大值: d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , − p r i c e s [ i ] ) dp[i][1]=max(dp[i−1][1],−prices[i]) dp[i][1]=max(dp[i1][1],prices[i])

class Solution:
    def maxProfit(self , prices: List[int]) -> int:
        n = len(prices)
        # dp[i][0] 表示某天不持股,到该天为止的最大收益
        # dp[i][1] 表示某天持股,到该天为止的最大收益
        dp = [[0] * 2 for i in range(n)]
        # 初始状态
        # 第一天不持股,总收益为0
        dp[0][0] = 0
        # 第一天持股,总收益为减去该天的股价
        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], -prices[i])
        return dp[n-1][0]

时间复杂度: O ( n ) O(n) O(n),其中 n 为数组长度,遍历一次数组。
空间复杂度: O ( n ) O(n) O(n),动态规划辅助数组的空间。

80.2 贪心算法

如果我们在某一天卖出了股票,那么要想收益最高,一定是它前面价格最低的那天买入的股票才可以。因此我们可以利用贪心思想解决,每次都将每日收入与最低价格相减维护最大值。

step 1:首先排除数组为空的特殊情况。
step 2:将第一天看成价格最低,后续遍历的时候遇到价格更低则更新价格最低。
step 3:每次都比较最大收益与当日价格减去价格最低的值,选取最大值作为最大收益。

class Solution:
    def maxProfit(self , prices: List[int]) -> int:
        res = 0
        if len(prices) == 0:
            return res
        min_price = prices[0]
        for i in range(1,len(prices)):
            min_price = min(min_price, prices[i])
            res = max(res, prices[i] - min_price)
        return res

时间复杂度: O ( n ) O(n) O(n),其中 n 为数组长度,一次遍历数组。
空间复杂度: O ( 1 ) O(1) O(1),常数级变量,无额外辅助空间。

81.买卖股票的最好时机(二)(小试牛刀

81.1 动态规划

这道题与上一题的区别在于可以多次买入卖出。 但是对于每天还是有到此为止的最大收益和是否持股两个状态,因此我们照样可以用动态规划。

step 1:用 d p [ i ] [ 0 ] dp[i][0] dp[i][0] 表示第 i i i 天不持股到该天为止的最大收益, d p [ i ] [ 1 ] dp[i][1] dp[i][1] 表示第 i i i 天持股,到该天为止的最大收益。
step 2:(初始状态) 第一天不持股,则总收益为 0, d p [ 0 ] [ 0 ] = 0 dp[0][0]=0 dp[0][0]=0;第一天持股,则总收益为买股票的花费,此时为负数, d p [ 0 ] [ 1 ] = − p r i c e s [ 0 ] dp[0][1]=−prices[0] dp[0][1]=prices[0]
step 3:(状态转移) 对于之后的每一天:

  • 如果当天不持股,有可能是前面的若干天中卖掉了或是还没买,因此到此为止的总收益和前一天相同,也有可能是当天卖掉股票,我们选择较大的状态 d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] ) dp[i][0]=max(dp[i−1][0],dp[i−1][1]+prices[i]) dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i])
  • 如果当天持股,可能是前几天买入的还没卖,因此收益与前一天相同,也有可能是当天买入,减去买入的花费,同样是选取最大值: d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1]=max(dp[i−1][1],dp[i−1][0]−prices[i]) dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i])
class Solution:
    def maxProfit(self , prices: List[int]) -> int:
        n = len(prices)
        # dp[i][0]:第 i 天不持股到该天为止的最大收益
        # dp[i][1]:第 i 天持股到该天为止的最大收益
        dp = [[0] * 2 for i in range(n)]
        # 初始状态
        dp[0][0] = 0
        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-1][0]-prices[i])
        # 最后一天不持股,到该天为止的最大收益
        return dp[n-1][0]

时间复杂度: O ( n ) O(n) O(n),其中 n 为数组长度,遍历一次数组。
空间复杂度: O ( n ) O(n) O(n),动态规划辅助数组相当于两个一维数组。

81.2 贪心思想

其实我们要想获取最大收益,只需要在低价买入高价卖出就可以了,因为可以买卖多次。利用贪心思想:只要一段区间内价格是递增的,那么这段区间的差值就是我们可以有的收益。

step 1:遍历数组,只要数组后一个比前一个更大,就可以有收益。
step 2:将收益累加,得到最终结果。

class Solution:
    def maxProfit(self , prices: List[int]) -> int:
        res = 0
        for i in range(1,len(prices)):
            # 只要在某段递增就会有收益
            if prices[i-1] < prices[i]:
                res = res + (prices[i]-prices[i-1])
        return res

时间复杂度: O ( n ) O(n) O(n),其中 n n n 为数组长度,遍历一次数组。
空间复杂度: O ( 1 ) O(1) O(1),常数级变量,没有使用额外辅助空间。

82.买卖股票的最好时机(三)(小试牛刀

82.1 动态规划

这道题与第80题的区别在于最多可以买入卖出2次,那实际上相当于它的状态多了几个,对于每天有到此为止的最大收益和持股情况两个状态,持股情况有了5种变化,我们用:

d p [ i ] [ 0 ] dp[i][0] dp[i][0] 表示到第 i i i 天为止没有买过股票的最大收益。
d p [ i ] [ 1 ] dp[i][1] dp[i][1] 表示到第 i i i 天为止买过一次股票还没有卖出的最大收益。
d p [ i ] [ 2 ] dp[i][2] dp[i][2] 表示到第 i i i 天为止买过一次也卖出过一次股票的最大收益。
d p [ i ] [ 3 ] dp[i][3] dp[i][3] 表示到第 i i i 天为止买过两次只卖出过一次股票的最大收益。
d p [ i ] [ 4 ] dp[i][4] dp[i][4] 表示到第 i i i 天为止买过两次同时也买出过两次股票的最大收益。

于是使用动态规划,有了如下的状态转移:
step 1:(初始状态) 与上述提到的题类似,第0天有买入了和没有买两种状态: d p [ 0 ] [ 0 ] = 0 dp[0][0]=0 dp[0][0]=0 d p [ 0 ] [ 1 ] = − p r i c e s [ 0 ] dp[0][1]=−prices[0] dp[0][1]=prices[0]

step 2:状态转移: 对于后续的每一天:

  • 如果当天还是状态 0,则与前一天相同,没有区别;
  • 如果当天状态为 1,可能是之前买过了或者当天才第一次买入,选取较大值: d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1]=max(dp[i−1][1],dp[i−1][0]−prices[i]) dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i])
  • 如果当天状态是 2,那必须是在 1 的状态下(已经买入了一次)当天卖出第一次,或者早在之前就卖出只是还没买入第二次,选取较大值: d p [ i ] [ 2 ] = m a x ( d p [ i − 1 ] [ 2 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] ) dp[i][2]=max(dp[i−1][2],dp[i−1][1]+prices[i]) dp[i][2]=max(dp[i1][2],dp[i1][1]+prices[i])
  • 如果当天状态是 3,那必须是在 2 的状态下(已经卖出了第一次)当天买入了第二次,或者早在之前就买入了第二次,只是还没卖出,选取较大值: d p [ i ] [ 3 ] = m a x ( d p [ i − 1 ] [ 3 ] , d p [ i − 1 ] [ 2 ] − p r i c e s [ i ] ) dp[i][3]=max(dp[i−1][3],dp[i−1][2]−prices[i]) dp[i][3]=max(dp[i1][3],dp[i1][2]prices[i]);
  • 如果当天是状态 4,那必须是在 3 的状态下(已经买入了第二次)当天再卖出第二次,或者早在之前就卖出了第二次,选取较大值: d p [ i ] [ 4 ] = m a x ( d p [ i − 1 ] [ 4 ] , d p [ i − 1 ] [ 3 ] + p r i c e s [ i ] ) dp[i][4]=max(dp[i−1][4],dp[i−1][3]+prices[i]) dp[i][4]=max(dp[i1][4],dp[i1][3]+prices[i])

step 3:最后我们还要从0、第一次卖出、第二次卖出中选取最大值,因为有可能没有收益,也有可能只交易一次收益最大。
在这里插入图片描述

class Solution:
    def maxProfit(self , prices: List[int]) -> int:
        n = len(prices)
        # 初始化 dp 为最小
        dp = [[-10000] * 5 for i in range(n)]
        # 初始状态
        dp[0][0] = 0
        dp[0][1] = -prices[0]
        # 状态转移
        for i in range(1,n):
            dp[i][0] = dp[i-1][0]
            dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])
            dp[i][2] = max(dp[i-1][2], dp[i-1][1]+prices[i])
            dp[i][3] = max(dp[i-1][3], dp[i-1][2]-prices[i])
            dp[i][4] = max(dp[i-1][4], dp[i-1][3]+prices[i])
        return max(0, max(dp[n-1][2], dp[n-1][4]))

时间复杂度: O ( n ) O(n) O(n),其中 n 为数组长度,只遍历一次数组。
空间复杂度: O ( n ) O(n) O(n),动态规划二维辅助相当于 5 个一维数组。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【编程之路】面试必刷TOP101:动态规划(78-82,Python实现) 的相关文章

随机推荐

  • 加拿大见!拓数派受邀参加第17届PostgreSQL国际开发者大会(PGCon 2023)

    5月30日 6月2日 作为疫情后的第一场线下会议第17届 PostgreSQL 国际开发者大会 PGCon 2023 将在加拿大渥太华隆重举行 拓数派将作为黄金赞助商 受邀参与本次盛会 与全球数据库爱好者们共聚加拿大 此外 拓数派 PieC
  • 由“2013软考之不完美结果”来剖析自己的学习方式误区

    2013年5 25日的软考 昨天晚上可以查成绩了 当时同学走到我面前让查成绩时 心开始跳了 手开始抖了 那一刻的紧张 那一刻的激动 既担心又想知道结果 别急别急 成绩看到了 不高 上午50 下午45 也就是说按百分制算的话 下午只考了60分
  • VScode 安装 ESP-idf 5.0报错:LookupError: unknown encoding: utf-8,gbk

    原因 说明从 pip 源返回的是 utf 8 gbk 编码类型 但是 pip 不能解析 请求了一下 pip 源后发现确实如此 尝试更新 pip python m pip install upgrade pip 但也会报同样的问题 这是因为镜
  • 服务器不能全屏显示,远程服务器如何全屏显示

    远程服务器如何全屏显示 内容精选 换一换 本节操作介绍在Windows和Linux环境中使用SSH密码方式远程登录Linux云服务器的操作步骤 弹性云服务器状态为 运行中 弹性云服务器已经绑定弹性公网IP 绑定方式请参见绑定弹性公网IP 所
  • firemonkey开发通讯录app

    1 添加AddressBook1 MultiView1 speedbuttonX3 labelX editboxX3 buttonX2 listbox组件 2 对listbox右键additem选择tsearchbox类型 3 设置Mult
  • Jlink SWD和Jtag下载失败总结

    学习STM32或者说使用Jlink的同学都有很多的困扰 我把自己遇到的情况总结一下 并给出解决方法 希望后来人少走点弯路 第一次写博客 勿喷 一 提示No Jlink Device Found 错误 没有发现Jlink 可能原因 1 Jli
  • 函数式编程总结

    函数式编程的概念 函数式编程理念来自于数学中的函数 函数的概念 对于两个变量x和y 如果每给定x的一个值 都有一个y值与之对应 那么我们就说y是x的函数 如下即是一些函数 f x 5x 2 4x 3 g x 2f x 5 10x 2 8x
  • cmath常用库函数

    cmath int abs int i double fabs double x long labs long n double exp double x double log double x double pow double x do
  • KVM 性能调优

    CPU Tuning Cache share tuning 对于物理 CPU 同一个 core 的 threads 共享 L2 Cache 同一个 socket 的 cores 共享 L3 cache 所以虚拟机的 vcpu 应当尽可能在同
  • 深度学习日记

    一 基于连续帧排序的语言分割 BubbleNets Learning to Select the Guidance Frame in Video Object Segmentation by Deep Sorting Frames 论文地址
  • cmd命令行如何快速进入当前目录

    我们在cmd命令行中如果想要进入某一个目录 相信大家一般都是先按D 或者E 进入相应的盘符 然后再输入cd 当前目录 以下有两种快捷方式可以进入当前目录 1 部分绿色windows版本可以支持以下操作 按住shift 鼠标右键 即可看见 在
  • 华为机试HJ8 合并表记录

    HJ8 合并表记录 Python 题目 解题思路 代码 结果 题目 解题思路 1 题目中没有说有多组输入 不考虑循环 2 结果列表先初始化 3 键值对的处理简单 合并按位置加和即可 4 要记录出现过的键 最后输出要决定显示哪些键值对 代码
  • STM32学习之SHT20温湿度传感器

    一 产品综述 SHT20 新一代 Sensirion 湿度和温度传感器在尺寸与智能方面建立了新的标准 它嵌入了适于回流焊的双列扁平无引脚 DFN 封装 底面 3 x3mm 高度 1 1mm 传感器输出经过标定的数字信号 标准 I 2 C 格
  • LAMP平台部署及论坛搭建

    部署LAMP平台实验 一 编译安装APACHE 1 准备工作 2 解压到opt目录 3 将解压好的apr文件放到httpd源码文件代码库中 4 配置Apache 5 make编译安装 6 优化httpd服务执行方式需要先优化路径 7 设置系
  • Error: Unable to execute “/usr/bin/vmware-uninstall-tools.pl.终极解决方案

    如何快速安装VMware Tool 可以参考这篇文章 https allen5g blog csdn net article details 102759282 Error Unable to execute usr bin vmware
  • Fiddler使用方法小结

    Fiddler基本使用 简介 Fiddler是一个http协议调试代理工具 它能够记录并检查所有你的电脑和互联网之间的http通讯 设置断点 查看所有的 进出 Fiddler的数据 指cookie html js css等文件 这些都可以让
  • CSS:三个div放在一排

    div 1 div div 2 div div 3 div 方法一 div display inline block 方法二 div float left 方法三 flex布局 123外面加一个div 这个div的style为 displa
  • HttpServletRequestWrapper替代HttpServletRequest

    本文解析以下两个方面 1 HttpServletRequestWrapper的作用 HttpServletRequest采用装饰者模式包装了HttpServletRequest 客户端发送请求后 容器实例化了一个org apache cat
  • 大数据构建数据生态系列02——与研发的爱恨情仇

    1 写在之前 接上一章的架构图 我们知道我们只是起了个头 后续还有待完善的部分 这一章节暂时不讲 我们在上一章成果的基础上 讲述一下整个数据收集的相关故事 以及期间的一些收获和思考 主要是和研发团队之间的 爱情火花 在数据生态的第一环中 最
  • 【编程之路】面试必刷TOP101:动态规划(78-82,Python实现)

    面试必刷TOP101 动态规划 78 82 Python实现 78 打家劫舍 一 小试牛刀 78 1 动态规划 或许有人认为利用贪心思想 偷取最多人家的钱就可以了 要么偶数家要么奇数家全部的钱 但是有时候会为了偷取更多的钱 或许可能会连续放