HOT100(八)动态规划

news2024/11/13 15:01:59

1、爬楼梯

①动态规划

(1)时间复杂度 O(n) ,空间复杂度 O(n)的做法

开辟一个长度为 n+1 的状态数组f,f[i]表示走到第i个台阶的方案数。初始化f[0]=1(在台阶底部,不需要移动也视为一种方法),f[1]=1(走到台阶1的方案只有一种,就是爬一步)。

爬楼梯的状态转移公式是f[i]=f[i-1]+f[i-2],因为走到第i个台阶,必然是从第i-1个台阶或者第i-2个台阶上爬上来的,因此走到第i个台阶的方案数等于走到第i-1个台阶的方案数与走到第i-2个台阶的方案数之和。

最后返回f(n)就是爬到n级阶梯的方案总数。

class Solution:
    def climbStairs(self, n: int) -> int:
        if n<=1:
            return n
        f=[0]*(n+1)
        f[0],f[1]=1,1
        for i in range(2,n+1):
            f[i]=f[i-1]+f[i-2]
        return f[n]

(2)时间复杂度 O(n) ,空间复杂度 O(1)的做法

采用滚动数组思想,将空间复杂度优化到O(1)。

ab 分别存储了到达当前台阶前的两个状态的爬法数量。循环每次迭代时,ab 依次滚动更新,使得 a 总是 b 的前一个状态,而 b 总是当前状态

class Solution:
    def climbStairs(self, n: int) -> int:
        if n<=1:
            return n
        a,b=1,1
        for i in range(2,n+1):
            a,b=b,a+b
        return b

②爬楼梯进阶

题目描述:给定n阶台阶,一次可以跳1到n阶,计算有多少种不同的方法可以从地面跳到第n阶台阶。

(1)时间复杂度 O(n²)的做法

dp[i]=dp[i-1]+dp[i-2]+...+dp[1]+dp[0]

class Solution:
    def climbStairs(self, n: int) -> int:
        if n<=1:
            return 1
        dp=[0]*(n+1)
        dp[0]=1
        for i in range(1,n+1):
            for j in range(i):
                dp[i]+=dp[j]
        return dp[n]

(2)优化后时间复杂度O(n)的做法

dp[i]=dp[i-1]+dp[i-2]+...+dp[1]+dp[0],而dp[i-1]=dp[i-2]+...+dp[1]+dp[0],所以可以得到dp[i]=dp[i-1]*2。

def climbStairs(n: int) -> int:
    dp = [1] * (n + 1)
    for i in range(2, n + 1):
        dp[i] = 2 * dp[i - 1]
    return dp[n]

也可以维护一个total_sum变量记录到目前为止的累积和。

def climbStairs(n: int) -> int:
        dp=[0]*(n+1);dp[0]=1;dp[1]=1
        total_sum=dp[0]+dp[1]
        for i in range(2,n+1):
            dp[i]=total_sum
            total_sum+=dp[i]
        return dp[n]

2、杨辉三角

左上角和右上角同时有元素的元素状态转移式:c[i][j]=c[i−1][j−1]+c[i−1][j] 。

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        c=[[1]*(i+1) for i in range(numRows)]
        for i in range(2,numRows):
            for j in range(1,i):
                c[i][j]=c[i-1][j-1]+c[i-1][j]
        return c

3、打家劫舍

①数组存储

维护一个状态数组dp,dp[i]表示打劫前i个房子所能获得的最大收益数。

由于打劫了当前房子就不能打劫邻近的房子,因此状态转移方程如下所示:

dp[i]=max(dp[i-1],dp[i-2]+nums[i])

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0
        n=len(nums)
        if n==1:
            return nums[0]
        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[n-1]

②滚动数组

用dp[i-1]更新r,用dp[i]更新p,滚动下去p=dp[n]就是最后答案。

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

4、完全平方数

  • 定义状态: 定义 dp[i] 表示最少需要多少个完全平方数的和来表示整数 i

  • 状态转移方程: 对于每个数 i,尝试减去一个完全平方数j²(其中j²<=i),此时状态转移方程为:dp[i]=min(dp[i],dp[i−j²]+1),这里的 dp[i-j^2] 表示去掉一个完全平方数j²后,剩下的数的最小完全平方数数量,加上 1 是因为用了一个j²。

  • 初始化: 对于 dp[0],需要 0 个完全平方数来表示;其余 dp[i] 初始化为正无穷,表示暂时未知最小值。

  • 结果: dp[n]即为最少需要的完全平方数个数。

class Solution:
    def numSquares(self, n: int) -> int:
        dp=[float('inf')]*(n+1)
        dp[0]=0
        for i in range(1,n+1):
            for j in range(1,int(math.sqrt(i))+1):
                dp[i]=min(dp[i],dp[i-j*j]+1)
        return dp[n]

5、零钱兑换

  • 定义状态: 定义 dp[i] 表示组成金额 i 需要的最少硬币数。

  • 状态转移方程: 对于每个金额 i,尝试减去一种硬币的面额 coin,此时状态转移方程为:

    dp[i]=min⁡(dp[i],dp[i−coin]+1),dp[i-coin] 表示减去硬币 coin 之后剩余金额的最小硬币数,加 1 是因为用了一个 coin
  • 初始化:

    • dp[0] = 0,表示凑成金额 0 需要 0 个硬币。
    • 其余的 dp[i] 初始化为无穷大,表示暂时还无法凑出这些金额。
  • 结果: 最终的结果是 dp[amount],如果 dp[amount] 仍然是无穷大,说明无法凑成该金额,返回 -1。

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        if amount==0:
            return 0
        dp=[float('inf')]*(amount+1)
        dp[0]=0
        for i in range(1,amount+1):
            for coin in coins:
                if i-coin>=0:
                    dp[i]=min(dp[i],dp[i-coin]+1)
        return dp[amount] if dp[amount]!=float('inf') else -1

6、单词拆分

首先将wordDict转换成集合,因为在集合中查找的效率更高些。

  • 定义状态: 我们定义一个布尔数组 dp,其中 dp[i] 表示前 i 个字符的子字符串 s[0:i] 是否可以由 wordDict 中的单词拆分。

  • 状态转移方程: 对于每个 i,需要检查在 s[0:i] 之前的每一个分割点 j,如果 dp[j]True,且 s[j:i] 在wordSet 中,那么 dp[i] 就可以被置为 True,表示可以拆分成合法的单词组合:dp[i]=dp[j]∧(s[j:i]∈wordSet)。

  • 初始化:dp[0] = True,表示空字符串可以被成功拆分。

  • 结果: 最终 dp[n] 就表示整个字符串是否可以被成功拆分。

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        n=len(s)
        wordSet=set(wordDict)
        dp=[False]*(n+1)
        dp[0]=True
        for i in range(1,n+1):
            for j in range(i):
                if dp[j] and s[j:i] in wordSet:
                    dp[i]=True
                    break
        return dp[n]

7、最长递增子序列

①动态规划

  • 定义状态: 我们使用一个数组 dp,其中 dp[i] 表示以第 i 个元素结尾的最长递增子序列的长度。

  • 状态转移方程: 对于每个元素 nums[i],我们遍历它之前的所有元素 nums[j]j < i),如果 nums[i] > nums[j],则表示 nums[i] 可以接在 nums[j] 后面构成递增子序列,因此:dp[i]=max(dp[i],dp[j]+1)。其中 dp[j] 是以 nums[j] 结尾的最长递增子序列的长度,加上 1 表示再加上当前元素 nums[i]

  • 初始化: 每个元素都至少可以作为一个长度为 1 的子序列,因此 dp 数组初始化为全 1。

  • 结果: 最终答案是 dp 数组中的最大值,即最长递增子序列的长度。

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

②贪心+二分查找

维护一个序列d来存储当前得到的最大递增子序列。

让序列 d 尽可能保持递增,并且在可以替换的情况下,优先用较小的值来替换 d 中的某个元素,这样就有更多机会在未来找到更长的递增子序列。

具体如下:

每次遍历数组时,考虑当前数字 nums[i]

  • 如果 nums[i]比序列d中最后一个元素 d [-1]还大,就把 nums[i]加入 d,增长序列d。
  • 否则,我们在序列 d 中找到第一个大于等于 n 的元素n 替换它这个操作是为了尽可能地保持较小的值,从而增加后续的递增潜力

比如说,nums=[1,4,2,3,5]。初始d=[],d=[1],d=[1,4],d=[1,2],d=[1,2,3],d=[1,2,3,5]。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        n=len(nums)
        d=[]
        for i in range(n):
            if not d or nums[i]>d[-1]:
                d.append(nums[i])
            else:
                l,r=0,len(d)-1
                while l<r:
                    mid=(l+r)//2
                    if d[mid]>=nums[i]:
                        r=mid
                    else:
                        l=mid+1
                d[l]=nums[i]
        return len(d)

8、乘积最大子数组

由于负数乘积可能使得结果反转为正数,因此在处理乘积问题时,除了维护当前的最大值,还需要同时维护当前的最小值(因为负数乘以负数可能会变成正数)。

维护三个变量max_product、min_product、max_global,分别记录当前以 i 结尾的子数组的最大乘积、当前以 i 结尾的子数组的最小乘积和全局最大乘积。

 遍历数组中每个元素:

  • 当前元素为负数:max_product和min_product交换一下,因为负数乘上负数是正值。
  • 当前元素不为负:比较、更新max_production、min_production和max_product。
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        max_res=max_product=min_product=nums[0]
        n=len(nums)
        for i in range(1,n):
            if nums[i]<0:
                max_product,min_product=min_product,max_product
            max_product=max(nums[i],nums[i]*max_product)
            min_product=min(nums[i],nums[i]*min_product)
            max_res=max(max_res,max_product)
        return max_res

9、分割等和子集

整体思路如下:

  • 当列表中元素个数小于2,分割不了等和子集,直接返回 False
  • 首先,如果数组的总和是奇数,那么肯定无法将其分成两个和相等的子集,直接返回 False
  • 如果总和是偶数,目标就是找出是否可以从数组中挑选出一些数字,它们的和等于数组总和的一半(即 sum(nums)// 2)。

这样问题就转换成了一个0-1背包问题,可以把这个问题看作一个容量为half_sum的背包,数组中的每个数字就是物品,问是否能够恰好填满这个背包。

  • 使用一个布尔数组 dp,其中 dp[i] 表示是否存在子集和等于 i
  • 状态转移:对于每个数字 num,我们更新 dp 数组的状态。如果 dp[j-num]True,则 dp[j] 也应为 True,即表示我们可以通过加入当前的 num 形成和为 j 的子集。【dp[j]=dp[j] or dp[j−num]】
  • 最终,检查 dp[half_num] 是否为 True,如果是,则说明可以找到一个子集和等于目标值。
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        if len(nums)<2:
            return False
        total_sum=sum(nums)
        if total_sum%2==1:
            return False
        half_sum=total_sum//2
        dp=[False]*(half_sum+1)
        dp[0]=True
        for num in nums:
            for j in range(half_sum,num-1,-1):
                dp[j]=dp[j] or dp[j-num]
        return dp[half_sum]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115713.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HNU-2023电路与电子学-实验3

写在前面&#xff1a; 本次实验是完成cpu设计的剩余部分&#xff0c;整体难度比上一次要小&#xff0c;细心完成就能顺利通过全部测评 一、实验目的 1.了解简易模型机的内部结构和工作原理。 2.分析模型机的功能&#xff0c;设计 8 重 3-1 多路复用器。 3.分析模型机的功能…

Oracle再度发起开发人员调查,细节满满

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

GO学习笔记(4) strconv/time

目录 strconv包1、string与bool之间的转换2、string与int之间的转换 time包1、常用常量定义2、Now&#xff08;&#xff09;获取当前年月日时分秒3、Format&#xff08;&#xff09;时间格式化4、Parse&#xff08;&#xff09;/ ParseInLocation&#xff08;&#xff09;解析时…

anaconda启动jupyter notebook

1.在Windows搜索框搜索anaconda prompt点击打开 2.然后输入命令jupyter notebook 3.在这个页面编写你的程序

基于Java的宿舍报修管理系统的设计与实现(论文+源码)_kaic

基于Java的宿舍报修管理系统的设计与实现(论文源码)_kaic 摘  要 随着教育改革‎‏的不断‎‏深入&#xff0c;‎‏学校宿‎‏舍的管‎‏理体系‎‏也在不‎‏断地完‎‏善&#xff0c;校园后勤服务是学校管理的重要工作&#xff0c;学校提供优秀的后勤服务&#xff0c;能提…

【Jupyter Notebook】安装与使用

打开Anaconda Navigator点击"Install"(Launch安装前是Install)点击"Launch"点击"File"-"New"-"Notebook"​ 5.点击"Select"选择Python版本 6.输入测试代码并按"Enter+Shift"运行代码: 代码如下: …

C++万字解析类和对象(上)

1.类的定义 class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后面分号不能省略。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。 为了区分成员变量&…

Linux(RedHat或CentOS)下如何开启telnet服务

一、Telnet服务介绍 Telnet协议是TCP/IP协议族中的一员&#xff0c;是Internet远程登录服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序&#xff0c;用它连接到服务器。终端使用者可以在telnet程序中输入…

【STM32】串口

1.串口-printf 使用printf函数向串口发送东西 使用微库&#xff0c;用到了printf, 但是我们发现是不能发送的 因为底层printf是fputc,我们需要自己实现 后面FILE*P不用管&#xff0c;在fputc中调用 第一个参数为uart1的句柄 第二个为要输出的字符 第三个为一次要发送几…

【c++进阶[五]】list相关接口介绍及list和vector的对比

&#x1f493;博主CSDN主页::Am心若依旧&#x1f493; ⏩专栏分类c从入门到精通⏪ &#x1f69a;代码仓库:青酒余成&#x1f69a; &#x1f339;关注我&#x1faf5;带你学习更多c   &#x1f51d;&#x1f51d; 1.前言 本章重点 本章重点讲解list的接口函数的熟悉&#xf…

W11系统电脑便捷设置

1【win11】取消右键菜单“更多选项”&#xff0c;直接展示完整右键菜单 取消右键菜单“更多选项”&#xff0c;直接展示完整右键菜单 (本质上是修改注册表&#xff0c;通过命令行的方式更加直接快捷) 只需要使用 winR 输入cmd 打开命令行&#xff0c;然后输入以下指令&#xf…

使用ChatGPT润色论文的10大分步技巧,效果立竿见影

大家好,感谢关注。我是七哥,一个在高校里不务正业,折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥(yida985)交流,多多交流,相互成就,共同进步,为大家带来最酷最有效的智能AI学术科研写作攻略。 撰写学术论文确实是一项非常具有挑…

HTML 转 PDF API 接口

HTML 转 PDF API 接口 网络工具 / 文件处理 支持网页转 PDF 高效生成 PDF / 提供永久链接。 1. 产品功能 超高性能转换效率&#xff1b;支持将传递的 HTML 转换为 PDF&#xff0c;支持转换 HTML 中的 CSS 格式&#xff1b;支持传递网站 URL&#xff0c;直接转换页面成对应的 …

C++笔记20•数据结构:哈希(Hash)•

哈希 1.无序的关联式容器&#xff08;unordered_map&unordered_set&#xff09; unordered_map与unordered_set几乎与map与set是一样的&#xff0c;只是性能unordered_map与unordered_set比map与set更优一些。还有就是unordered_map与unordered_set是无序的&#xff0c;…

梳理2024年,螺丝钉们爱用的3款剪辑软件

这年头&#xff0c;视频到处都是&#xff0c;就跟天上的星星一样数不清。不管你是公司里的新面孔&#xff0c;还是职场上的老狐狸&#xff0c;学会怎么剪视频&#xff0c;就好比找到了赢的秘诀。不管是给上司汇报工作&#xff0c;展示你的产品&#xff0c;还是自己搞点小视频记…

C#顺序万年历自写的求余函数与周位移算法

static int 返回月的天数(int 年, int 月){return (月 2 ?(((年 % 4 0 && 年 % 100 > 0) || 年 % 400 0) ? 29 : 28) :(((月 < 7 && 月 % 2 > 0) || (月 > 7 && 月 % 2 0)) ? 31 : 30));}static int 返回年总天数(int 年, int 标 …

Mac设置文件夹的显示方式:独立窗口显示或者标签页显示

目录 00 Mac文件夹的两种显示方式  0.1 独立窗口显示   0.2 标签页显示 01 独立窗口显示  02 标签页显示 00 Mac文件夹的两种显示方式 0.1 独立窗口显示 0.2 标签页显示 01 独立窗口显示 系统偏好设置System Setting -> 程序坞Desktop & Dock -> 窗口W…

【一文读懂】北斗卫星导航系统介绍

前言 本文来自鲜枣课堂。 本文是关于北斗卫星导航系统的详细介绍&#xff0c;主要阐述了北斗系统的组成、功能、发展历程以及在全球范围内的应用和影响。以下是文件的核心内容提炼&#xff1a; 系统概述&#xff1a; 二级要点关键短语&#xff1a;全球卫星导航系统 北斗卫星…

Learn ComputeShader 11 Star Glow Effect

这次要使用到顶点和片段着色器。同样是制作屏幕后处理效果。 下面是一开始的效果&#xff0c;只是一个循环播放的粒子系统。 我们首先要对源图像的亮部区域进行提亮&#xff0c;然后进行模糊添加光芒。然后进行混合&#xff0c;最后进行一次合成 我们需要创建一些临时纹理来存…

基于Java+SpringBoot+Vue+MySQL的智能菜谱推荐管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于SpringBootVue的智能菜谱推荐管理系统【附源码文档】、…