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

一维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.xdnf.cn/news/16045.html

相关文章:

  • Qt5线程相关事项
  • C# 转换(is和as运算符)
  • vue-pinia
  • WebkitSpeechRecognition 语音识别
  • QT6 源,七章对话框与多窗体(5) 文件对话框 QFileDialog 篇二:源码带注释
  • nginx + uwsgi + systemd 部署 flask
  • 在Windows Server 2012 R2中安装与配置IIS服务并部署mssql靶机教程
  • springboot实战篇1
  • 基于 HAProxy 搭建 EMQ X 集群
  • C++的“链”珠妙笔:list的编程艺术
  • 解决vscode中vue格式化后缩进太小的问题,并去除分号 - 设置Vetur tabSize从2到4,设置prettier取消分号semi
  • 计算机发展史:人工智能时代的智能变革与无限可能
  • 基于WebSocket的安卓眼镜视频流GPU硬解码与OpenCV目标追踪系统实现
  • 【PTA数据结构 | C语言版】哥尼斯堡的“七桥问题”
  • C# Lambdab表达式 Var 类
  • Elupload实现多个文件上传与已上传列表中做对比,若重复则只保留已上传列表中的数据,同时告诉用户,有哪些文件重复上传了
  • 搭建种草商城框架指南
  • 飞算科技:以原创技术为翼,赋能产业数字化转型
  • Linux第三课:需要自己安装的远程登录工具PuTTY的介绍
  • 【PTA数据结构 | C语言版】求单源最短路的Dijkstra算法
  • Taro 本地存储 API 详解与实用指南
  • G7打卡——Semi-Supervised GAN
  • EMBMS1820芯祥科技18单元电池监控器芯片数据手册
  • 华控的科技布局——全球化战略与合作生态
  • 力扣(LeetCode)第 161 场双周赛
  • macbookpro m1 max本儿上速搭一个elasticsearch+kibana环境
  • 基于deepseek的LORA微调
  • 【设计模式C#】简单工厂模式(用于简化获取对象实例化的复杂性)
  • 个人中心产品设计指南:从信息展示到用户体验的细节把控
  • mongodb源代码分析createCollection命令由create.idl变成create_gen.cpp过程