贪心算法、动态规划与 MPPI 算法结构相关力扣题目汇总
目录
- 一、总体理解
- 二、贪心算法相关力扣题
- 三、动态规划相关力扣题
- [四、MPPI 算法结构相关力扣题](#四、MPPI 算法结构相关力扣题)
- 五、三类算法对比
- 六、推荐刷题路线
- 七、面向机器人/无人车算法理解的重点题目
一、总体理解
本文主要围绕三类算法结构进行整理:
- 贪心算法:每一步选择当前最优,并希望通过局部最优推出全局最优。
- 动态规划算法:将大问题拆解成小问题,通过状态转移复用已有结果。
- MPPI 相关算法结构:MPPI 本身不是力扣中的固定分类,但其核心结构包括随机采样、轨迹展开、代价累计、候选轨迹选择、有限时域滚动优化等,这些思想可以通过部分力扣题进行训练。
三者可以简单理解为:
text
贪心:当前这一步怎么选最好?
动态规划:当前状态怎么由之前的状态推出来?
MPPI:采样很多条未来轨迹,计算代价,再选择或加权更新更优轨迹。
二、贪心算法相关力扣题
2.1 贪心算法核心思想
贪心算法的本质是:
每一步都选择当前看起来最优的方案,并且这个局部最优能够推出最终的全局最优。
常见题型如下:
| 类型 | 典型特征 | 常见做法 |
|---|---|---|
| 分配贪心 | 资源和需求匹配 | 小资源优先满足小需求 |
| 跳跃贪心 | 判断能否到达或最少跳几次 | 维护当前最远可达位置 |
| 区间贪心 | 区间选择、区间删除、区间合并 | 按右端点排序 |
| 字符串贪心 | 字符划分、字典序、局部截断 | 记录最后出现位置或单调栈 |
| 股票贪心 | 多次买卖求最大利润 | 所有上涨区间都累加 |
| 双向贪心 | 左右两边都有约束 | 左右各扫描一遍 |
2.2 贪心题目汇总
| 题号 | 题目 | 类型 | 难度 | 重点 |
|---|---|---|---|---|
| 455 | 分发饼干 | 分配贪心 | 简单 | 小饼干优先满足小胃口 |
| 55 | 跳跃游戏 | 跳跃贪心 | 中等 | 维护最远可达位置 |
| 45 | 跳跃游戏 II | 跳跃贪心 | 中等 | 当前覆盖范围与下一跳范围 |
| 435 | 无重叠区间 | 区间贪心 | 中等 | 按右端点排序 |
| 452 | 用最少数量的箭引爆气球 | 区间贪心 | 中等 | 区间交集与右端点 |
| 763 | 划分字母区间 | 字符串贪心 | 中等 | 记录字符最后出现位置 |
| 134 | 加油站 | 环形贪心 | 中等 | 局部失败后更换起点 |
| 122 | 买卖股票的最佳时机 II | 股票贪心 | 中等 | 所有上涨段都累加 |
| 406 | 根据身高重建队列 | 排序贪心 | 中等 | 先高后低插入 |
| 135 | 分发糖果 | 双向贪心 | 困难 | 左右两遍扫描 |
2.3 题目讲解:455. 分发饼干
题意
有一些孩子,每个孩子有一个胃口值 g[i]。有一些饼干,每块饼干有大小 s[j]。如果 s[j] >= g[i],这个孩子就可以被满足。
要求:最多能满足多少个孩子。
思路
先将孩子胃口和饼干大小都排序。
贪心策略:
用当前最小的饼干,去满足当前胃口最小的孩子。
这样可以尽量避免用大饼干去满足小胃口孩子,从而保留大饼干给后面胃口更大的孩子。
C++ 解法
cpp
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int i = 0;
int j = 0;
int ans = 0;
while (i < g.size() && j < s.size()) {
if (s[j] >= g[i]) {
ans++;
i++;
j++;
} else {
j++;
}
}
return ans;
}
};
复杂度
text
时间复杂度:O(nlogn + mlogm)
空间复杂度:O(1)
2.4 题目讲解:55. 跳跃游戏
题意
给定数组 nums,nums[i] 表示从位置 i 最多可以向后跳多少步。判断是否可以从下标 0 跳到最后一个位置。
思路
维护一个变量 maxReach,表示当前能够到达的最远位置。
从左到右遍历数组:
- 如果
i > maxReach,说明当前位置根本无法到达,直接返回false。 - 否则用当前位置更新最远可达范围:
cpp
maxReach = max(maxReach, i + nums[i]);
C++ 解法
cpp
class Solution {
public:
bool canJump(vector<int>& nums) {
int maxReach = 0;
for (int i = 0; i < nums.size(); i++) {
if (i > maxReach) return false;
maxReach = max(maxReach, i + nums[i]);
}
return true;
}
};
复杂度
text
时间复杂度:O(n)
空间复杂度:O(1)
2.5 题目讲解:45. 跳跃游戏 II
题意
给定数组 nums,每个位置可以向后跳最多 nums[i] 步。求跳到最后一个位置所需的最少跳跃次数。
思路
这道题可以理解成 BFS 的贪心优化。
维护两个边界:
text
curEnd:当前这一跳能够覆盖的最远边界
far:下一跳能够覆盖的最远边界
从左到右遍历,当遍历到 curEnd 时,说明当前这一跳覆盖范围已经走完,需要再跳一次。
C++ 解法
cpp
class Solution {
public:
int jump(vector<int>& nums) {
int steps = 0;
int curEnd = 0;
int far = 0;
for (int i = 0; i < nums.size() - 1; i++) {
far = max(far, i + nums[i]);
if (i == curEnd) {
steps++;
curEnd = far;
}
}
return steps;
}
};
复杂度
text
时间复杂度:O(n)
空间复杂度:O(1)
2.6 题目讲解:435. 无重叠区间
题意
给定若干区间,删除最少数量的区间,使剩下的区间互不重叠。
思路
贪心策略:
每次优先保留右端点最小的区间。
因为右端点越小,后面留给其他区间的空间越大,越容易保留更多区间。
C++ 解法
cpp
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), [](auto& a, auto& b) {
return a[1] < b[1];
});
int count = 1;
int end = intervals[0][1];
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] >= end) {
count++;
end = intervals[i][1];
}
}
return intervals.size() - count;
}
};
复杂度
text
时间复杂度:O(nlogn)
空间复杂度:O(1)
2.7 题目讲解:763. 划分字母区间
题意
给定字符串 s,尽可能多地划分字符串,使得每个字母最多只出现在一个片段中。
思路
先记录每个字符最后一次出现的位置。
从左到右扫描,维护当前片段必须覆盖到的最远位置 end。
当 i == end 时,说明当前片段内所有字符的最后出现位置都已经被覆盖,可以在这里切开。
C++ 解法
cpp
class Solution {
public:
vector<int> partitionLabels(string s) {
vector<int> last(26);
for (int i = 0; i < s.size(); i++) {
last[s[i] - 'a'] = i;
}
vector<int> ans;
int start = 0;
int end = 0;
for (int i = 0; i < s.size(); i++) {
end = max(end, last[s[i] - 'a']);
if (i == end) {
ans.push_back(end - start + 1);
start = i + 1;
}
}
return ans;
}
};
复杂度
text
时间复杂度:O(n)
空间复杂度:O(1)
三、动态规划相关力扣题
3.1 动态规划核心思想
动态规划的本质是:
把大问题拆成小问题,用之前算过的结果推出当前结果。
做动态规划题目时,最重要的是三步:
text
1. 定义状态:dp[i] 或 dp[i][j] 表示什么?
2. 写状态转移:当前状态怎么由之前状态推出来?
3. 初始化和返回答案。
3.2 动态规划题目汇总
| 题号 | 题目 | 类型 | 难度 | 重点 |
|---|---|---|---|---|
| 70 | 爬楼梯 | 一维 DP | 简单 | 斐波那契结构 |
| 746 | 使用最小花费爬楼梯 | 一维 DP | 简单 | 最小代价 |
| 198 | 打家劫舍 | 一维 DP | 中等 | 选或不选 |
| 213 | 打家劫舍 II | 环形 DP | 中等 | 拆成两个线性问题 |
| 322 | 零钱兑换 | 完全背包 | 中等 | 最少硬币数 |
| 518 | 零钱兑换 II | 完全背包 | 中等 | 方案数 |
| 416 | 分割等和子集 | 01 背包 | 中等 | 是否能装满容量 |
| 494 | 目标和 | 01 背包 | 中等 | 正负号转背包 |
| 300 | 最长递增子序列 | 子序列 DP | 中等 | LIS |
| 1143 | 最长公共子序列 | 二维 DP | 中等 | 字符串匹配 |
| 64 | 最小路径和 | 网格 DP | 中等 | 代价累计 |
| 62 | 不同路径 | 网格 DP | 中等 | 路径数量 |
| 72 | 编辑距离 | 字符串 DP | 困难 | 插入、删除、替换 |
| 121 | 买卖股票的最佳时机 | 股票 DP | 简单 | 最低买入价 |
| 188 | 买卖股票的最佳时机 IV | 股票 DP | 困难 | 状态机 |
3.3 题目讲解:70. 爬楼梯
题意
每次可以爬 1 阶或 2 阶,问爬到第 n 阶有多少种方法。
思路
到第 i 阶有两种来源:
text
从 i - 1 阶爬 1 阶
从 i - 2 阶爬 2 阶
所以状态转移为:
cpp
dp[i] = dp[i - 1] + dp[i - 2];
C++ 解法
cpp
class Solution {
public:
int climbStairs(int n) {
if (n <= 2) return n;
int a = 1;
int b = 2;
for (int i = 3; i <= n; i++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
};
复杂度
text
时间复杂度:O(n)
空间复杂度:O(1)
3.4 题目讲解:198. 打家劫舍
题意
有一排房子,每个房子有一定金额。不能偷相邻房子,问最多能偷多少钱。
思路
对于第 i 个房子,有两种选择:
text
偷当前房子:dp[i - 2] + nums[i]
不偷当前房子:dp[i - 1]
所以状态转移为:
cpp
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]);
C++ 解法
cpp
class Solution {
public:
int rob(vector<int>& nums) {
int pre2 = 0;
int pre1 = 0;
for (int x : nums) {
int cur = max(pre1, pre2 + x);
pre2 = pre1;
pre1 = cur;
}
return pre1;
}
};
复杂度
text
时间复杂度:O(n)
空间复杂度:O(1)
3.5 题目讲解:322. 零钱兑换
题意
给定硬币面额 coins 和总金额 amount,求凑成该金额所需的最少硬币数。如果不能凑成,返回 -1。
思路
这是完全背包问题。
定义状态:
text
dp[i] 表示凑成金额 i 所需的最少硬币数。
状态转移:
cpp
dp[i] = min(dp[i], dp[i - coin] + 1);
C++ 解法
cpp
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
const int INF = 1e9;
vector<int> dp(amount + 1, INF);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (i >= coin) {
dp[i] = min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] == INF ? -1 : dp[amount];
}
};
复杂度
text
时间复杂度:O(amount × coins.size())
空间复杂度:O(amount)
3.6 题目讲解:416. 分割等和子集
题意
给定数组 nums,判断能否把数组分成两个子集,使两个子集的和相等。
思路
如果数组总和是奇数,一定无法分成两个和相等的子集。
如果总和是偶数,问题变成:
能否从数组中选出一些数,使它们的和等于
sum / 2。
这是标准 01 背包问题。
定义状态:
text
dp[j] 表示是否可以从已有数字中凑出和 j。
C++ 解法
cpp
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int x : nums) sum += x;
if (sum % 2 != 0) return false;
int target = sum / 2;
vector<bool> dp(target + 1, false);
dp[0] = true;
for (int num : nums) {
for (int j = target; j >= num; j--) {
dp[j] = dp[j] || dp[j - num];
}
}
return dp[target];
}
};
复杂度
text
时间复杂度:O(n × target)
空间复杂度:O(target)
3.7 题目讲解:300. 最长递增子序列
题意
给定数组 nums,求最长严格递增子序列的长度。
思路
定义状态:
text
dp[i] 表示以 nums[i] 结尾的最长递增子序列长度。
状态转移:
text
如果 nums[j] < nums[i],说明 nums[i] 可以接在 nums[j] 后面。
因此:
cpp
dp[i] = max(dp[i], dp[j] + 1);
C++ 解法
cpp
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 1);
int ans = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]);
}
return ans;
}
};
复杂度
text
时间复杂度:O(n²)
空间复杂度:O(n)
3.8 题目讲解:1143. 最长公共子序列
题意
给定两个字符串 text1 和 text2,求它们最长公共子序列的长度。
思路
定义状态:
text
dp[i][j] 表示 text1 前 i 个字符和 text2 前 j 个字符的最长公共子序列长度。
如果两个字符相同:
cpp
dp[i][j] = dp[i - 1][j - 1] + 1;
否则:
cpp
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
C++ 解法
cpp
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.size();
int n = text2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1[i - 1] == text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
};
复杂度
text
时间复杂度:O(mn)
空间复杂度:O(mn)
3.9 题目讲解:64. 最小路径和
题意
给定一个网格,每个格子有代价,只能向右或向下走,求从左上角到右下角的最小路径代价。
思路
定义状态:
text
dp[i][j] 表示走到 grid[i][j] 的最小代价。
状态转移:
cpp
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
C++ 解法
cpp
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[0][0] = grid[0][0];
for (int i = 1; i < m; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < n; j++) {
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
};
复杂度
text
时间复杂度:O(mn)
空间复杂度:O(mn)
四、MPPI 算法结构相关力扣题
4.1 MPPI 算法基本流程
MPPI 的全称是:
text
Model Predictive Path Integral Control
中文可以理解为:
text
基于路径积分的模型预测控制。
MPPI 的典型流程如下:
text
1. 获取当前状态 x0。
2. 随机采样多组控制序列 u。
3. 利用动力学模型 rollout 多条未来轨迹。
4. 对每条轨迹计算总代价 cost。
5. 根据代价给轨迹分配权重。
6. 用加权平均更新控制序列。
7. 执行第一个控制量。
8. 下一时刻重新规划。
从算法结构上看,MPPI 涉及以下几个核心模块:
| MPPI 模块 | 对应编程结构 | 对应力扣题型 |
|---|---|---|
| 随机采样控制序列 | 随机数、权重采样 | 528、398、382、470 |
| 轨迹 rollout | 路径搜索、状态扩展 | 64、63、1091、1631、787 |
| 代价函数累计 | 路径代价、风险惩罚 | 64、931、746 |
| 最优轨迹选择 | 最小代价、优先队列 | 743、787、1631 |
| 滚动优化窗口 | 滑动窗口、有限视野 | 239、862 |
| 障碍物避让 | 网格障碍、碰撞约束 | 63、1293、1091 |
| 多候选轨迹筛选 | Top-K、堆、排序 | 215、347、973 |
4.2 MPPI 结构相关力扣题汇总
| 题号 | 题目 | 对应 MPPI 结构 | 难度 |
|---|---|---|---|
| 528 | 按权重随机选择 | 按权重采样控制序列 | 中等 |
| 398 | 随机数索引 | 随机采样、蓄水池采样 | 中等 |
| 382 | 链表随机节点 | 随机采样 | 中等 |
| 470 | 用 Rand7 实现 Rand10 | 随机数变换 | 中等 |
| 64 | 最小路径和 | 轨迹代价累计 | 中等 |
| 63 | 不同路径 II | 障碍物约束 | 中等 |
| 1091 | 二进制矩阵中的最短路径 | 障碍物地图搜索 | 中等 |
| 1293 | 网格中的最短路径,带障碍消除 | 带约束状态扩展 | 困难 |
| 1631 | 最小体力消耗路径 | 路径代价最小化 | 中等 |
| 787 | K 站中转内最便宜的航班 | 有限步长最优控制 | 中等 |
| 743 | 网络延迟时间 | Dijkstra 最短路径 | 中等 |
| 239 | 滑动窗口最大值 | 滚动窗口优化 | 困难 |
| 215 | 数组中的第 K 个最大元素 | 候选轨迹筛选 | 中等 |
| 347 | 前 K 个高频元素 | Top-K 筛选 | 中等 |
| 973 | 最接近原点的 K 个点 | 轨迹候选排序 | 中等 |
4.3 题目讲解:528. 按权重随机选择
为什么和 MPPI 有关?
MPPI 中,每条采样轨迹会根据代价分配权重:
text
代价越低,权重越大;
代价越高,权重越小。
然后根据这些权重对控制序列进行加权更新。
力扣 528 练习的就是:
如何按照权重进行随机选择。
题意
给定权重数组 w,下标 i 被选中的概率为:
text
w[i] / sum(w)
思路
先做前缀和:
text
w = [1, 3, 2]
prefix = [1, 4, 6]
随机生成一个 [1, sum] 之间的整数,然后用二分查找找到它落在哪个区间。
C++ 解法
cpp
class Solution {
private:
vector<int> prefix;
int total;
public:
Solution(vector<int>& w) {
total = 0;
for (int x : w) {
total += x;
prefix.push_back(total);
}
}
int pickIndex() {
int target = rand() % total + 1;
int l = 0;
int r = prefix.size() - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (prefix[mid] >= target) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
};
复杂度
text
初始化时间复杂度:O(n)
单次采样时间复杂度:O(logn)
空间复杂度:O(n)
4.4 题目讲解:1631. 最小体力消耗路径
为什么和 MPPI 有关?
MPPI 会评估多条候选轨迹的总代价,例如:
text
路径长度
障碍物距离
控制平滑性
能耗
风险代价
终点误差
这道题也是在网格中寻找一条代价最优的路径。
题意
给定高度地图 heights,从左上角走到右下角。路径的体力消耗定义为路径中相邻格子高度差的最大值。求最小体力消耗。
思路
这题可以用 Dijkstra 算法。
普通最短路是累加边权,而这道题路径代价是:
text
当前路径中最大的高度差。
所以转移时:
cpp
newCost = max(curCost, abs(heights[x][y] - heights[nx][ny]));
C++ 解法
cpp
class Solution {
public:
int minimumEffortPath(vector<vector<int>>& heights) {
int m = heights.size();
int n = heights[0].size();
vector<vector<int>> dist(m, vector<int>(n, INT_MAX));
dist[0][0] = 0;
priority_queue<
tuple<int, int, int>,
vector<tuple<int, int, int>>,
greater<tuple<int, int, int>>
> pq;
pq.push({0, 0, 0});
vector<int> dx = {1, -1, 0, 0};
vector<int> dy = {0, 0, 1, -1};
while (!pq.empty()) {
auto [cost, x, y] = pq.top();
pq.pop();
if (x == m - 1 && y == n - 1) return cost;
if (cost > dist[x][y]) continue;
for (int k = 0; k < 4; k++) {
int nx = x + dx[k];
int ny = y + dy[k];
if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
int diff = abs(heights[x][y] - heights[nx][ny]);
int newCost = max(cost, diff);
if (newCost < dist[nx][ny]) {
dist[nx][ny] = newCost;
pq.push({newCost, nx, ny});
}
}
}
return 0;
}
};
复杂度
text
时间复杂度:O(mnlog(mn))
空间复杂度:O(mn)
4.5 题目讲解:787. K 站中转内最便宜的航班
为什么和 MPPI 有关?
MPPI 是有限时域控制,只在未来一段 horizon 内优化控制序列。
这道题是:
text
只允许最多 K 次中转。
它对应的是:
有限步长内的最优代价搜索。
这和 MPC / MPPI 中的有限预测时域思想很像。
题意
有 n 个城市和若干航班,给定起点 src、终点 dst、最多中转次数 k,求最便宜价格。
思路
最多中转 k 次,最多乘坐 k + 1 条边。
可以使用 Bellman-Ford 的有限轮松弛。
每一轮表示多走一条边。
C++ 解法
cpp
class Solution {
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
const int INF = 1e9;
vector<int> dist(n, INF);
dist[src] = 0;
for (int step = 0; step <= k; step++) {
vector<int> backup = dist;
for (auto& f : flights) {
int from = f[0];
int to = f[1];
int price = f[2];
if (backup[from] != INF) {
dist[to] = min(dist[to], backup[from] + price);
}
}
}
return dist[dst] == INF ? -1 : dist[dst];
}
};
复杂度
text
时间复杂度:O(kE),E 为航班数量
空间复杂度:O(n)
4.6 题目讲解:1293. 网格中的最短路径,带障碍消除
为什么和 MPPI 有关?
MPPI 做移动机器人局部规划时,需要考虑障碍物约束、碰撞风险和状态扩展。
这道题练习的是:
text
状态 = 位置 + 剩余消障次数。
类似于机器人规划中的:
text
状态 = 位姿 + 速度 + 约束条件。
题意
给定一个网格,0 表示空地,1 表示障碍物。你最多可以消除 k 个障碍物,求从左上角到右下角的最短路径。
思路
普通 BFS 只需要记录位置:
text
x, y
但这道题还要记录剩余可消除障碍数量:
text
x, y, remain
因此访问数组也需要三维:
text
visited[x][y][remain]
C++ 解法
cpp
class Solution {
public:
int shortestPath(vector<vector<int>>& grid, int k) {
int m = grid.size();
int n = grid[0].size();
vector<vector<vector<bool>>> visited(
m, vector<vector<bool>>(n, vector<bool>(k + 1, false))
);
queue<tuple<int, int, int, int>> q;
q.push({0, 0, k, 0});
visited[0][0][k] = true;
vector<int> dx = {1, -1, 0, 0};
vector<int> dy = {0, 0, 1, -1};
while (!q.empty()) {
auto [x, y, remain, step] = q.front();
q.pop();
if (x == m - 1 && y == n - 1) return step;
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
int nextRemain = remain - grid[nx][ny];
if (nextRemain < 0) continue;
if (!visited[nx][ny][nextRemain]) {
visited[nx][ny][nextRemain] = true;
q.push({nx, ny, nextRemain, step + 1});
}
}
}
return -1;
}
};
复杂度
text
时间复杂度:O(mnk)
空间复杂度:O(mnk)
五、三类算法对比
| 算法 | 核心问题 | 典型思维 | 适合题型 |
|---|---|---|---|
| 贪心 | 当前怎么选最好? | 局部最优推出全局最优 | 区间、跳跃、分配、股票 |
| 动态规划 | 当前状态怎么由过去推出? | 记忆历史结果,避免重复计算 | 背包、路径、子序列、股票 |
| MPPI 相关结构 | 从多条采样轨迹中选哪条最好? | 采样、rollout、代价计算、加权更新 | 随机采样、路径搜索、Top-K、滑动窗口 |
进一步理解:
text
贪心:不回头,每一步直接选当前最优。
动态规划:把所有子问题都算清楚,再推出最终答案。
MPPI:不枚举所有可能,而是随机采样很多条未来轨迹,用代价函数筛选更优轨迹。
六、推荐刷题路线
6.1 第一阶段:先掌握贪心
建议顺序:
text
455 分发饼干
122 买卖股票的最佳时机 II
55 跳跃游戏
45 跳跃游戏 II
435 无重叠区间
452 用最少数量的箭引爆气球
763 划分字母区间
134 加油站
135 分发糖果
重点掌握:
text
排序
最远覆盖
右端点优先
局部失败换起点
左右两遍扫描
6.2 第二阶段:掌握动态规划
建议顺序:
text
70 爬楼梯
746 使用最小花费爬楼梯
198 打家劫舍
213 打家劫舍 II
322 零钱兑换
416 分割等和子集
494 目标和
300 最长递增子序列
1143 最长公共子序列
64 最小路径和
72 编辑距离
重点掌握:
text
dp[i] 的含义
状态转移方程
初始化
空间优化
一维 DP
二维 DP
背包 DP
6.3 第三阶段:理解 MPPI 相关算法结构
建议顺序:
text
528 按权重随机选择
64 最小路径和
63 不同路径 II
1091 二进制矩阵中的最短路径
1293 网格中的最短路径,带障碍消除
1631 最小体力消耗路径
787 K 站中转内最便宜的航班
239 滑动窗口最大值
215 数组中的第 K 个最大元素
347 前 K 个高频元素
973 最接近原点的 K 个点
重点掌握:
text
随机采样
路径展开
代价累计
障碍约束
有限时域搜索
候选轨迹筛选
Top-K 筛选
滚动窗口优化
七、面向机器人/无人车算法理解的重点题目
如果是为了理解移动机器人、无人车、局部规划器、MPPI 等算法,下面这些题尤其有用:
| 题号 | 题目 | 对应机器人/MPPI思想 |
|---|---|---|
| 64 | 最小路径和 | 轨迹总代价累计 |
| 63 | 不同路径 II | 障碍物约束 |
| 1091 | 二进制矩阵中的最短路径 | 栅格地图搜索 |
| 1293 | 网格中的最短路径,带障碍消除 | 带约束状态扩展 |
| 1631 | 最小体力消耗路径 | 路径风险/最大代价最小化 |
| 787 | K 站中转内最便宜的航班 | 有限预测时域优化 |
| 528 | 按权重随机选择 | 基于权重的采样选择 |
| 239 | 滑动窗口最大值 | 滚动窗口优化结构 |
| 215 | 数组中的第 K 个最大元素 | 候选轨迹筛选 |
| 973 | 最接近原点的 K 个点 | 候选结果排序与筛选 |
八、最终总结
如果是为了面试刷题,重点刷:
text
贪心:55、45、435、763、134
动态规划:198、322、416、300、1143、64、72
MPPI结构理解:528、64、1293、1631、787
如果是为了机器人/无人车/MPPI算法理解,重点关注:
text
64 最小路径和:练代价累计
1631 最小体力消耗路径:练路径代价优化
787 K站中转内最便宜航班:练有限时域优化
1293 网格障碍消除:练带约束状态搜索
528 按权重随机选择:练基于权重的随机采样
一句话总结:
贪心练"局部选择",动态规划练"状态转移",MPPI相关题练"采样轨迹、代价评估、有限时域优化和候选轨迹筛选"。