算法札记:DP中的滚动数组

👇

++核心:从数组到滚动数组:&1就行! dpij <=> fi\&1j (若有 int dpNM,f2M;)++

👆

滚动数组是动态规划中一种非常实用的空间优化技巧。它的核心思想很简单:‌**既然当前状态只依赖于前面少数几个状态,那就没必要把整个表格都存下来,用少数几个变量或一小块数组空间"滚动"着用就行了。**‌

你可以把它想象成一个滑动的窗口,只保留计算当前状态所必需的历史信息,旧的数据一旦不再需要,就被新数据覆盖掉。


一、为什么需要滚动数组?

在经典的动态规划问题中,我们通常会用一个二维甚至三维的数组来存储所有状态。比如在 0-1 背包问题里,dp[i][j] 表示前 i 件物品放入容量为 j 的背包能获得的最大价值。但仔细观察状态转移方程:

复制代码

text

dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i])

你会发现,dp[i][j] 的值‌只依赖于上一行 i-1 的状态‌,和更早的行完全没有关系。既然如此,我们为什么要保留整个二维数组呢?完全可以用一个一维数组来模拟"上一行"和"当前行"的交替更新,这就是滚动数组的由来。


二、滚动数组的两种常见形式

1. 交替行滚动(二维降为两行)

如果状态转移需要用到上一行的多个位置,但只涉及相邻两行,我们可以只开一个 dp[m] 的数组。通过 i % 2 或 i & 1 来交替使用第 0 行和第 1 行,新一行覆盖旧一行的数据。这样空间复杂度就从 O(n*m) 降到了 O(2*m),也就是 O(m)。

2. 一维滚动数组(进一步压缩为一行)

在 0-1 背包中,我们甚至可以直接用一个一维数组 dp[j] 来表示容量为 j 的背包当前能装的最大价值。状态转移方程变为:

复制代码

text

dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

但这里有一个非常关键的细节:‌遍历顺序必须倒序‌。

为什么?因为如果正序遍历 j,当你计算 dp[j] 时,dp[j - weight[i]] 可能已经被当前这一轮更新过了,它代表的就不再是"上一轮"的状态,而是"本轮已经可能放过物品 i"的状态,这会导致同一个物品被重复放入多次,违背了 0-1 背包的规则。倒序遍历则能保证在更新 dp[j] 时,dp[j - weight[i]] 还是上一轮留下来的旧值,逻辑上完全等价于二维数组中的 dp[i-1][j - weight[i]]。


三、经典例子:斐波那契数列

斐波那契数列的递推公式是 f[i] = f[i-1] + f[i-2]。如果求第 n 项,我们完全不需要开一个长度为 n 的数组,只需要三个变量(或一个长度为 3 的数组)不断滚动更新即可:

复制代码

text

f0 = 0, f1 = 1 for i = 2 to n: f2 = f1 + f0 f0 = f1 f1 = f2

这里每次计算只用到前两个状态,旧的状态被新值覆盖,空间复杂度从 O(n) 降到了 O(1)。这就是滚动数组最直观的体现。


四、完全背包的滚动数组有何不同?

完全背包问题中,每个物品可以选无限次。此时状态转移方程在形式上和一维 0-1 背包一样,但遍历顺序必须‌正序 ‌。因为正序遍历允许在同一轮中,dp[j - weight[i]] 已经包含了"可能已经选过当前物品"的状态,从而实现了物品的重复选取。这恰好符合完全背包的定义。


五、适用场景总结

滚动数组适用于‌当前状态只依赖于前一个或前几个固定数量状态‌的动态规划问题。常见场景包括:

  • 0-1 背包、完全背包、多重背包
  • 最长公共子序列(LCS)
  • 编辑距离
  • 斐波那契数列及类似递推问题
  • 任何状态转移只涉及相邻几行或几个阶段的问题

它的本质是‌用时间换空间‌------计算量不变,但内存占用大幅降低,尤其在数据规模很大时,这种优化往往是必须的。

相关推荐
Code_Solitude2 小时前
C语言动态内存 + 柔性数组 + 数组名退化
c语言·算法·柔性数组
上海潭龙东海科技有限公司2 小时前
客运站管控之动态孪生技术方案(新增一屏管控模块
数据仓库·人工智能·算法·重构·动态规划
YLCHUP4 小时前
【思维&图论建模】题解:债权(debt)_图论_建模_思路转化_思维题目_C++算法竞赛
c++·笔记·算法·数学建模·贪心算法·深度优先·图论
思麟呀5 小时前
背包DP(01背包+完全背包)
c++·算法
笑容掩饰我的失落6 小时前
区块链从小白到精通(七):Solidity 语法精讲——数据类型、函数、修饰器与事件
算法·区块链·php·智能合约·哈希算法
Pniubi7 小时前
力扣24. 两两交换链表中的节点(递归)
算法·leetcode·链表
꧁细听勿语情꧂7 小时前
直接选择排序,堆排序,冒泡排序,递归快速排序、hoare版本、挖坑法、lomuto前后指针,非递归快速排序
c语言·开发语言·数据结构·算法
俊俊几年的8 小时前
动态规划(Dynamic Programming)详解:从入门到 LeetCode Hot 100
linux·c++·算法·leetcode·嵌入式·动态规划·c
小雉系统17 小时前
BcoreOS安全通信架构:FsSession 抗算法后门安全设计
算法·安全·架构