Python - 深夜数据结构与算法之 DP 串讲

news2024/11/15 11:16:58

目录

一.引言

二.DP 知识点回顾

1.递归

2.分治

3.动态规划

三.DP 经典题目回顾

1.Climb-Stairs [70]

2.Unique-Paths [62]

3.House-Robber [198]

4.Min-Path-Sum [64]

5.Best-Time-Sell-Stock [121]

6.Min-Cost-Climb [746]

7.Edit-Distance [72]

8.Longest-Sub-Seq [300]

四.总结


一.引言

动态规划 DP 作为算法里比较晦涩难懂的一部分,我们这里多多复习几遍也不为过。本文主要重温递归、分治、回溯以及状态转移方程的概念,同时复习一下之前做过的题目。

二.DP 知识点回顾

1.递归

2.分治

3.动态规划

复杂的问题转换为子问题 - 分治 & 最优子结构 - 递推到 n :

三.DP 经典题目回顾

1.Climb-Stairs [70]

爬楼梯: https://leetcode-cn.com/problems/climbing-stairs/

◆ 题目分析

爬楼梯一次可以走1步或者2步,因此推导出 f(n) = f(n-1) + f(n-2),与 Fib 异曲同工。

◆ 傻递归

class Solution(object):
    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n <= 2:
            return n
        
        return self.climbStairs(n-1) + self.climbStairs(n-2)

傻递归存在大量的重复计算,基本都会超时。 

◆ 递归 + Cache

class Solution(object):

    def __init__(self):
        self.mem = {0:0, 1:1, 2:2}

    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n in self.mem:
            return self.mem[n]

        steps = self.climbStairs(n-1) + self.climbStairs(n-2)
        self.mem[n] = steps
        return self.mem[n]

加 Cache 的通法,在 init 初始化 cache,在傻递归返回的时候存入 cache 避免重复计算。 

◆ DP Table

class Solution(object):

    def climbStairs(self, n):
        """
        :type n: int
        :rtype: int
        """

        if n <= 2:
            return n

        a, b = 1, 2
        for i in range(1, n):
            a, b = b, a+b
        
        return a

使用 DP Table 时如果只使用有限个状态,一般都可以使用几个变量完成 dp 推进。 

2.Unique-Paths [62]

不同路径: https://leetcode.cn/problems/unique-paths/

◆ 题目思路 

◆ DP Table

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """

        dp = [[0] * n for _ in range(m)]
        for i in range(n):
            dp[0][i] = 1

        for j in range(m):
            dp[j][0] = 1

        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i-1][j] + dp[i][j-1]

        return dp[m-1][n-1]

dp[i][j] = dp[i-1][j] + dp[i][j-1] 即新位置等于左边和上面的位置之和。

◆ combination 

class Solution(object):

    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """

        total = m + n - 2

        # 减少计算量
        if m > n:
            k = n - 1
        else:
            k = m - 1

        A = 1
        F = 1

        for i in range(k):
            A *= total
            total -= 1
            F *= k
            k -= 1

        return A // F

可以转化为排列组合问题,M+N-2 种走法,只要 M-1 向右,N-1 向下即可,直接套用 C(n,k)。

3.House-Robber [198]

打家劫舍: https://leetcode-cn.com/problems/house-robber/

◆ 题目分析

◆ DP 转移

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        # 两个状态代表偷不偷
        dp = [[0, 0] for _ in range(len(nums))]
        dp[0][1] = nums[0]

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

[0]、[1] 两个状态代表偷或者不偷,遍历即可。 

◆ 空间优化

class Solution(object):
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """

        pre, now = 0, 0

        for i in nums:
            pre, now = now, max(pre + i, now)

        return now
        

当前状态只与前两次状态有关,所以可以压缩 DP 状态空间。 

4.Min-Path-Sum [64]

最小路径和: https://leetcode-cn.com/problems/minimum-path-sum/

◆ 题目分析

◆ DP 状态

class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """

        M, N = len(grid), len(grid[0])

        # 遍历行
        for col in range(1, N):
            grid[0][col] += grid[0][col-1]

        # 遍历行
        for row in range(1, M):
            grid[row][0] += grid[row - 1][0]

        # 遍历内部元素
        for row in range(1, M):
            for col in range(1, N):
                grid[row][col] += min(grid[row - 1][col], grid[row][col - 1])
        
        return grid[M - 1][N - 1]

每一步的最小值等于当前位置加上左边或上面的最小值。

5.Best-Time-Sell-Stock [121]

股票买卖: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

◆ 题目分析

上面为股票问题的 dp 套路模版,其中 dp 空间 i 为天数,k 为交易次数,s 为是否持有股票。

要维持没有股票的状态,只能 rest 或者卖,要维持有股票的状态,只能 rest 或者买。

rest 代表保持状态,buy 时因为要从 Profit 中付出代价,所以要减去 prices[i]。

◆ DP 状态

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """

        # dp[i] = max(dp[i] - pre_min, dp[i-1])

        dp = [0] * len(prices)
        pre_min = prices[0]

        for i in range(1, len(prices)):
            dp[i] = max(prices[i] - pre_min, dp[i-1])
            if pre_min > prices[i]:
                pre_min = prices[i]

        return dp[-1]

dp[i] = max(dp[i] - pre_min, dp[i-1]) 当前位置等于卖了和上一次的最大值。

◆ DP + 双指针

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """

        # dp[i] = max(dp[i] - pre_min, 0)

        cur_min = prices[0]
        cur_max = 0

        for i in range(1, len(prices)):
            cur_max = max(cur_max, prices[i] - cur_min)
            cur_min = min(cur_min, prices[i])
        
        return cur_max

每次维护 max 和 min 即可。 

6.Min-Cost-Climb [746]

最小花费爬楼梯: https://leetcode.cn/problems/min-cost-climbing-stairs/description/

◆ 题目分析

到达第 i 个楼梯,起点是 0 级或 1 级台阶。我们到达 i 级台阶的费用:

dp[i] = min(dp[i-1] + cost[i-1], dp[i-1] + cost[i-2])

由于起始点 0,1 都可以自由选择,所以其初始化为 0 。

 ◆ DP 实现

class Solution(object):
    def minCostClimbingStairs(self, cost):
        """
        :type cost: List[int]
        :rtype: int
        """

        # dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-1])

        dp = [0] * (len(cost) + 1)

        for i in range(2, len(cost) + 1):
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])

        return dp[-1]

这个题比较坑的点是楼顶对应索引 n,而索引从 0 开始,所以需要遍历 range(0, n+1)。不过题目的状态转移方程还是很漂亮,借鉴了前面爬楼梯的题目。 

7.Edit-Distance [72]

编辑距离: https://leetcode-cn.com/problems/edit-distance/

◆ 题目分析

 这个题和之前的单词接龙比较像,但是一拿到又无从下手,这里直接给出 DP 的思路,构建二维 DP 状态矩阵,大小为 [M+1, N+1],如果 w1[i] == w2[j] 字符相同,那么此处无需操作;否则我们需要借助插入、删除、替换的操作,这里插入和删除其实是相对的,无需严格的区分:

所以 dp[i][j] 的最小编辑距离就可以从 min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) 里找最小再 + 1。这个题好写但是难想,把上面的 dp table 写出来更方便理解,这里理解不好就记忆好了,也不错。

◆ DP 实现

class Solution(object):
    def minDistance(self, word1, word2):
        """
        :type word1: str
        :type word2: str
        :rtype: int
        """
        # w[i] = w[j] -> w[i][j] = w[i-1]w[j-1]
        # w[i] != w[j] -> w[i][j] = min(w[i-1][j-1] + 1, w[i-1][j] + 1, w[i][j-1] +1)

        M, N = len(word1), len(word2)

        # 状态空间
        dp = [[0] * (N + 1) for _ in range(M + 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]

8.Longest-Sub-Seq [300]

 最长递增子序列: https://leetcode.cn/problems/longest-increasing-subsequence

◆ 题目分析 

对于 i 位置,其等于 range(0, i) 之间的元素,如果 nums[i] > nums[j] 则 dp[i] = max_sub + 1。

◆ DP 实现

class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        dp = [1] * len(nums)

        for i in range(1, len(nums)):
            for j in range(0, i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)

        return max(dp)

四.总结

如果同学们是看了前面 DP 和 DP 进阶并且做了习题的话,相信今天再看这一个章节会有一种醍醐灌顶,豁然开朗的状态,后面我们会继续讲解回顾高级 DP 动态规划问题。

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

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

相关文章

Android PendingIntent 闪退

先来给大家推荐一个我日常会使用到的图片高清处理在线工具&#xff0c;主要是免费&#xff0c;直接白嫖 。 有时候我看到一张图片感觉很不错&#xff0c;但是图片清晰度不合我意&#xff0c;就想有没有什么工具可以处理让其更清晰&#xff0c; 网上随便搜下就能找到&#xff…

C++设计模式(李建忠)笔记1

C设计模式&#xff08;李建忠&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考链接 Youtube: C设计模式 Gtihub源码与PPT&#xff1a;https://github.com/ZachL1/Bilibili-plus 豆瓣: 设计模式–可复用面向对象软件的基础 文章目录 C设计模…

编译原理1.1习题 语言处理器

图源&#xff1a;文心一言 编译原理习题整理~&#x1f95d;&#x1f95d; 作为初学者的我&#xff0c;这些习题主要用于自我巩固。由于是自学&#xff0c;答案难免有误&#xff0c;非常欢迎各位小伙伴指正与讨论&#xff01;&#x1f44f;&#x1f4a1; 第1版&#xff1a;自…

目标检测-One Stage-YOLOv7

文章目录 前言一、YOLOv7的不同版本二、YOLOv7的网络结构二、YOLOv7的创新点三、创新点的详细解读ELAN和E-ELANBoF训练技巧计划型重参化卷积辅助训练模块标签分配Lead head guided label assignerCoarse-to-fine lead head guided label assigner 基于级联模型的复合缩放方法 总…

开发知识点-JAVA-springboot

springboot springbootConfiguration注解的底层核心原理Bean注解的底层核心原理 springboot Configuration注解的底层核心原理 https://www.bilibili.com/video/BV1rq4y1E7gK/?spm_id_from333.999.0.0&vd_sourcef21773b7086456ae21a58a6cc59023be spring.io 全家桶 24…

【Emgu CV教程】5.4、几何变换之图像翻转

今天讲解的两个函数&#xff0c;可以实现以下样式的翻转。 水平翻转&#xff1a;将图像沿Y轴(图像最左侧垂直边缘)翻转的操作。原始图像中位于左侧的内容将移动到目标图像的右侧&#xff0c;原始图像中位于右侧的内容将移动到目标图像的左侧。垂直翻转&#xff1a;将图像沿X轴…

智能小程序小部件(Widget)导航、地图、画布等组件,以及开放能力、原生组件说明

智能小程序小部件(Widget)导航、地图、画布等组件&#xff0c;以及开放能力、原生组件说明。 导航组件 navigator 页面链接&#xff0c;控制小程序的跳转。navigator 子节点的背景色应为透明色。 属性说明 属性名类型默认值必填说明urlstring是跳转地址deltanumber1否当 …

用Spark在大数据平台DataBricks轻松处理数据

Apache Spark是一个强大的开源分布式计算系统&#xff0c;专为大规模数据处理而设计。而DataBricks则提供了一个基于云的环境&#xff0c;使得在Spark上处理数据变得更加高效和便捷。本文将介绍如何在DataBricks平台上使用Spark轻松处理大数据。DataBricks是一个基于云的大数据…

8.临床预测模型验证——交叉验证/Bootstrap法

基本概念 交叉验证&#xff1a; 将一定比例的数据挑选出来作为训练集&#xff0c;将其余未选中的样本作为测试集&#xff0c;先在训练集中构建模型&#xff0c;再在测试集中做预测。 内部验证&#xff1a;手动将样本随机分为训练集和测试集&#xff0c;先在训练集中构建模型…

MySQL面试题 | 11.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

vue2 pdfjs-2.8.335-dist pdf文件在线预览功能

1、首先先将 pdfjs-2.8.335-dist 文件夹从网上搜索下载&#xff0c;复制到public文件夹下. 2、在components下新建组件PdfViewer.vue文件 3、在el-upload 中调用 pdf-viewer 组件 4、在el-upload 中的 on-preview方法中加上对应的src路径 internalPreview(file) { //判断需要…

【Python】箱型图和热图绘制详解和示例

箱型图&#xff08;Box Plot&#xff09;和热图&#xff08;Heatmap&#xff09;是两种常用的数据可视化工具&#xff0c;它们各自有着不同的特点和用途。在写总结和文献时对数据的表达更加直观&#xff0c;本文对这两种图像的绘制进行详解和示例。 箱型图由一组数据的最小值、…

中国1981-2023年逐年每15天8km植被指数数据集

摘要 中国1981-2023年逐年每15天8km植被指数数据集来源于GIMMS NDVI数据&#xff0c;包括了1981年7月&#xff0d;2023年12月的长时间序列逐年每15天植被指数变化&#xff0c;格式为arcgis grid格式&#xff0c;投影为WGS84&#xff0c;其时间分辨率是15天&#xff0c;空间分辨…

【机组】算术逻辑运算单元实验的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《机组 | 模块单元实验》⏰诗赋清音&#xff1a;云生高巅梦远游&#xff0c; 星光点缀碧海愁。 山川深邃情难晤&#xff0c; 剑气凌云志自修。 ​ 目录 &#x1f33a; 一、 实验目的…

Java NIO (二)NIO Buffer类的重要方法

1 allocate()方法 在使用Buffer实例前&#xff0c;我们需要先获取Buffer子类的实例对象&#xff0c;并且分配内存空间。需要获取一个Buffer实例对象时&#xff0c;并不是使用子类的构造器来创建&#xff0c;而是调用子类的allocate()方法。 public class AllocateTest {static…

3.goLand基础语法

目录 概述语法for常量与变量数组切片 slice切片问题问题1问题2 Make 和 New结构体和指针结构体标签 结束 概述 从 java 转来学 go &#xff0c;在此记录&#xff0c;方便以后翻阅。 语法 for package mainimport "fmt"func main() {for i : 0; i < 3; i {fmt.…

关于java的封装

关于java的封装 我们在前面的文章中&#xff0c;了解到了类和对象的知识&#xff0c;以及做了创建对象的时候对内存的分析&#xff0c;我们本篇文章来了解一下面向对象的三大基本特征之一&#xff0c;封装&#x1f600;。 一、初识封装 封装就好比&#xff0c;我们把一些物品…

如何在 Python3 中使用变量

介绍 变量是一个重要的编程概念&#xff0c;值得掌握。它们本质上是在程序中用于表示值的符号。 本教程将涵盖一些变量基础知识&#xff0c;以及如何在您创建的 Python 3 程序中最好地使用它们。 理解变量 从技术角度来说&#xff0c;变量是将存储位置分配给与符号名称或标…

YOLOv8改进 | 主干篇 | 低照度增强网络PE-YOLO改进主干(改进暗光条件下的物体检测模型)

一、本文介绍 本文给大家带来的改进机制是低照度图像增强网络PE-YOLO中的PENet,PENet通过拉普拉斯金字塔将图像分解成多个分辨率的组件,增强图像细节和低频信息。它包括一个细节处理模块(DPM),用于通过上下文分支和边缘分支增强图像细节,以及一个低频增强滤波器(LEF),…

<软考高项备考>《论文专题 - 71 风险管理(3)》

3 过程2-识别风险 3.1 问题 4W1H过程做什么是识别单个项目风险以及整体项目风险的来源&#xff0c;并记录风险特征的过程。作用:1、记录现有的单个项目风险&#xff0c;以及整体项目风险的来源:2、汇总相关信息&#xff0c;以便项目团队能够恰当地应对已识别的风险。为什么做…