当前位置: 首页 > news >正文

一维DP深度解析

一维DP深度解析

    • 一、一维动态规划基础知识
      • 1.1 核心思想
      • 1.2 适用场景
    • 二、经典案例详解
      • 2.1 案例1:斐波那契数列(入门级)
        • 问题描述
        • 动态规划设计
        • 代码实现
        • 优化:空间压缩
      • 2.2 案例2:爬楼梯(基础应用)
        • 问题描述
        • 动态规划设计
        • 代码实现
        • 思路拓展
      • 2.3 案例3:打家劫舍(中等难度)
        • 问题描述
        • 动态规划设计
        • 代码实现
        • 空间优化
      • 2.4 案例4:最长递增子序列(LIS,进阶应用)
        • 问题描述
        • 动态规划设计
        • 代码实现
        • 复杂度分析
    • 三、一维DP的设计步骤与技巧
      • 3.1 通用设计步骤
      • 3.2 常见误区与解决

动态规划(Dynamic Programming,DP)是算法设计中的重要思想,通过将复杂问题分解为重叠子问题,并利用子问题的解高效推导原问题答案。其中一维动态规划是最基础也最常用的形式——仅需一个一维数组存储子问题的解,就能“以空间换时间”解决一系列经典问题。

一、一维动态规划基础知识

1.1 核心思想

一维动态规划的核心是**“用已知子问题的解推导当前问题的解”**,具体表现为:

  • 分解问题:将原问题(规模为n)分解为规模更小的子问题(如规模n-1n-2)。
  • 定义状态:用dp[i]表示“规模为i的子问题的解”(一维数组存储)。
  • 递推关系:找到dp[i]dp[i-1]dp[i-2]等前驱状态的关系(核心难点)。
  • 边界条件:确定最小规模子问题的解(如dp[0]dp[1]),作为递推的起点。

相比递归(可能重复计算子问题),一维DP通过存储子问题的解,将时间复杂度从指数级降至线性或线性对数级。

1.2 适用场景

一维DP适用于以下特征的问题:

  1. 问题与规模相关:解依赖于更小规模的同类问题(如“前i个元素的最优解”)。
  2. 子问题重叠:不同规模的问题会共享相同的子问题(如斐波那契数列中f(5)f(6)都依赖f(4))。
  3. 无后效性:子问题的解一旦确定,就不会受后续计算影响(如dp[i]确定后,不依赖dp[i+1])。

典型场景:斐波那契数列、爬楼梯、最长递增子序列、打家劫舍等。

二、经典案例详解

2.1 案例1:斐波那契数列(入门级)

问题描述

求斐波那契数列的第n项,定义为:

  • f(0) = 0f(1) = 1
  • f(n) = f(n-1) + f(n-2)n ≥ 2
动态规划设计
  1. 定义状态dp[i]表示第i项斐波那契数。
  2. 递推关系dp[i] = dp[i-1] + dp[i-2](直接由定义得到)。
  3. 边界条件dp[0] = 0dp[1] = 1
代码实现
public class Fibonacci {public int fib(int n) {if (n <= 1) {return n;  // 边界条件直接返回}// 定义dp数组存储子问题的解int[] dp = new int[n + 1];dp[0] = 0;dp[1] = 1;// 从2开始递推for (int i = 2; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}public static void main(String[] args) {Fibonacci solution = new Fibonacci();System.out.println(solution.fib(5));  // 输出5(0,1,1,2,3,5)}
}
优化:空间压缩

由于dp[i]仅依赖dp[i-1]dp[i-2],无需存储整个数组,用两个变量即可:

public int fibOptimized(int n) {if (n <= 1) {return n;}int prevPrev = 0;  // dp[i-2]int prev = 1;      // dp[i-1]for (int i = 2; i <= n; i++) {int current = prev + prevPrev;prevPrev = prev;prev = current;}return prev;
}
  • 时间复杂度O(n)O(n)O(n)(遍历一次)。
  • 空间复杂度:优化后从O(n)O(n)O(n)降至O(1)O(1)O(1)

2.2 案例2:爬楼梯(基础应用)

问题描述

每次可以爬1或2个台阶,求爬到第n级台阶的不同方法数。

动态规划设计
  1. 定义状态dp[i]表示爬到第i级台阶的方法数。
  2. 递推关系
    最后一步要么从i-1级爬1级,要么从i-2级爬2级,因此:
    dp[i] = dp[i-1] + dp[i-2]
  3. 边界条件
    • dp[1] = 1(仅1种方法:爬1级)
    • dp[2] = 2(两种方法:1+1或2)
代码实现
public class ClimbStairs {public int climbStairs(int n) {if (n == 1) {return 1;}int[] dp = new int[n + 1];dp[1] = 1;dp[2] = 2;for (int i = 3; i <= n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];}public static void main(String[] args) {ClimbStairs solution = new ClimbStairs();System.out.println(solution.climbStairs(4));  // 输出5(1+1+1+1等5种)}
}
思路拓展

若每次可爬1、2、3个台阶,递推关系变为:
dp[i] = dp[i-1] + dp[i-2] + dp[i-3](需相应调整边界条件)。

2.3 案例3:打家劫舍(中等难度)

问题描述

沿街有一排房屋,不能抢劫相邻的房屋,求能抢劫到的最大金额。

动态规划设计
  1. 定义状态dp[i]表示抢劫前i间房屋的最大金额。
  2. 递推关系
    • 若抢劫第i间房屋,则不能抢劫第i-1间,金额为dp[i-2] + nums[i-1]nums下标从0开始)。
    • 若不抢劫第i间房屋,则金额为dp[i-1]
      因此:dp[i] = max(dp[i-1], dp[i-2] + nums[i-1])
  3. 边界条件
    • dp[0] = 0(0间房屋,金额0)
    • dp[1] = nums[0](仅1间房屋,抢它)
代码实现
public class RobHouse {public int rob(int[] nums) {int n = nums.length;if (n == 0) {return 0;}int[] dp = new int[n + 1];dp[0] = 0;dp[1] = nums[0];for (int i = 2; i <= n; i++) {// 选择:抢第i间(i-1下标)或不抢dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);}return dp[n];}public static void main(String[] args) {RobHouse solution = new RobHouse();int[] nums = {2, 7, 9, 3, 1};System.out.println(solution.rob(nums));  // 输出12(抢2+9+1)}
}
空间优化

dp[i]仅依赖dp[i-1]dp[i-2],用两个变量压缩空间:

public int robOptimized(int[] nums) {if (nums.length == 0) return 0;int prevPrev = 0;  // dp[i-2]int prev = nums[0]; // dp[i-1]for (int i = 2; i <= nums.length; i++) {int current = Math.max(prev, prevPrev + nums[i - 1]);prevPrev = prev;prev = current;}return prev;
}

2.4 案例4:最长递增子序列(LIS,进阶应用)

问题描述

求数组中最长的严格递增子序列的长度(子序列可不连续)。

动态规划设计
  1. 定义状态dp[i]表示以nums[i]为结尾的最长递增子序列长度。
  2. 递推关系
    对于所有j < i,若nums[j] < nums[i],则dp[i]可由dp[j] + 1更新,取最大值:
    dp[i] = max(dp[j] + 1) (j < i 且 nums[j] < nums[i])
    若没有符合条件的j,则dp[i] = 1(自身为子序列)。
  3. 边界条件:所有dp[i]初始化为1(每个元素至少是自身的子序列)。
代码实现
public class LongestIncreasingSubsequence {public int lengthOfLIS(int[] nums) {int n = nums.length;if (n == 0) return 0;int[] dp = new int[n];// 初始化:每个元素自身是长度为1的子序列Arrays.fill(dp, 1);int maxLen = 1;for (int i = 1; i < n; i++) {// 遍历所有前驱元素jfor (int j = 0; j < i; j++) {if (nums[j] < nums[i]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}maxLen = Math.max(maxLen, dp[i]);}return maxLen;}public static void main(String[] args) {LongestIncreasingSubsequence solution = new LongestIncreasingSubsequence();int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};System.out.println(solution.lengthOfLIS(nums));  // 输出4(2,3,7,18)}
}
复杂度分析
  • 时间复杂度O(n2)O(n^2)O(n2)(两层循环,i遍历n次,j遍历i次)。
  • 空间复杂度O(n)O(n)O(n)dp数组存储n个状态)。
    (注:该问题可通过二分查找优化至O(nlog⁡n)O(n \log n)O(nlogn),但核心仍是一维DP思想。)

三、一维DP的设计步骤与技巧

3.1 通用设计步骤

  1. 明确问题目标:确定要求解的“最优值”“方案数”等(如最大和、最长长度)。
  2. 定义状态dp[i]:关键是让dp[i]能表示“规模为i的子问题”,通常与“前i个元素”相关。
  3. 推导递推关系
    思考“如何用dp[0..i-1]得到dp[i]”,可从“最后一步操作”入手(如爬楼梯的最后一步是1级还是2级)。
  4. 确定边界条件:初始化最小规模子问题的解(如dp[0]dp[1])。
  5. 计算并优化:先实现基础版本,再通过“滚动变量”压缩空间(若状态仅依赖少数前驱)。

3.2 常见误区与解决

  • 状态定义模糊:若递推关系难以推导,可能是dp[i]定义不合理。例如“打家劫舍”中,dp[i]定义为“前i间房屋”而非“第i间房屋”,更易找到递推关系。
  • 忽略边界条件:如n=0n=1的特殊情况,需在代码中单独处理。
  • 过度优化:先实现基础版本保证正确性,再考虑空间优化(避免因优化导致逻辑混乱)。

总结
一维DP是理解DP思想的入门钥匙,其核心是通过定义状态和递推关系,将复杂问题拆解为可逐步求解的子问题,始终遵循“分解-定义-递推-优化”的逻辑。

That’s all, thanks for reading~~
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

http://www.dtcms.com/a/289962.html

相关文章:

  • 三菱A1SJ PLC以太网模块:上位机与触摸屏高效通讯解决方案
  • 深入解析:如何在Kafka中配置Source和Sink连接器构建高效数据管道
  • 金仓数据库:融合进化,智领未来——2025年数据库技术革命的深度解析
  • 【Linux指南】Linux系统 -权限全面解析
  • Windows下编译libarchive
  • JavaWeb笔记四
  • 深入详解随机森林在医学图像质量评估中的应用与实现细节
  • OCR 身份识别:让身份信息录入场景更高效安全
  • PHP反序列化漏洞详解
  • 第十八节:第七部分:java高级:注解的应用场景:模拟junit框架
  • 【c++】leetcode5 最长回文子串
  • 【Project】ELK 7.17.16 日志分析系统部署
  • Day07_网络编程20250721(网络编程考试试卷)
  • 关于 URL 中 “+“ 号变成空格的问题
  • CentOS 7安装 FFmpeg问题可以按照以下步骤进行安装
  • Spring Boot 3核心技术面试指南:从迁移升级到云原生实战,9轮技术攻防(含架构解析)
  • Django实战:基于Django和openpyxl实现Excel导入导出功能
  • 基于python django的BOSS直聘网站计算机岗位数据分析与可视化系统,包括薪酬预测及岗位推荐,推荐算法为融合算法
  • 智能体性能优化:延迟、吞吐量与成本控制
  • django filter按两个属性 去重
  • JAVA面试宝典 -《 架构演进:从单体到 Service Mesh》
  • Go从入门到精通(26) - 一个简单web项目-实现服务注册
  • Go语言实战案例-读取CSV文件并打印
  • python 正则表达式
  • 借助 Amazon SageMaker Catalog 功能,简化从数据到洞察的路径
  • FastLLVE:实时低光视频增强新突破
  • 大端小端:数据存储的核心密码
  • Apache IoTDB(2):时序数据库 IoTDB 集群安装部署的技术优势与适用场景分析
  • 论文Review Lidar 3DGS Splat-LOAM: Gaussian Splatting LiDAR Odometry and Mapping
  • 【软件基础学习配置那些事 4-3】3ds Max2026 菜单栏常用命令-----文件、视图、编辑、工具、组