动态规划—2140. 解决智力问题
题目描述
前言
解决智力问题 是一个典型的动态规划问题。给定一个数组 questions
,其中 questions[i] = [points_i, brainpower_i]
表示解决第 i
个问题可以获得的积分是 points_i
,但解决它后你必须跳过接下来的 brainpower_i
个问题。目标是通过选择一系列问题,最大化你可以获得的总积分。
基本思路
1. 问题定义
给定一个问题数组 questions
,每个问题由两个数值组成:points_i
和 brainpower_i
,表示解决当前问题能得到的积分以及跳过的题目数量。你需要从左到右遍历题目,选择一系列问题以最大化总积分。
举例:
- 输入:
questions = [[3, 2], [4, 3], [4, 4], [2, 5]]
- 输出:
5
- 解释:
- 选择第一个问题(积分 3),然后跳过下两个问题。
- 然后选择第四个问题(积分 2),总积分为
3 + 2 = 5
。
2. 理解问题和递推关系
动态规划思想:
- 我们从右向左遍历问题。对于每个问题,我们有两种选择:
- 选择当前问题:那么我们可以获得它的积分,但接下来需要跳过
brainpower_i
个问题。 - 不选择当前问题:则我们直接考虑下一个问题。
- 选择当前问题:那么我们可以获得它的积分,但接下来需要跳过
状态定义:
dp[i]
表示从第i
个问题开始能获得的最大总积分。
状态转移方程:
- 对于每个问题
i
,可以选择不解决或者解决它:- 不解决问题
i
:则dp[i] = dp[i + 1]
。 - 解决问题
i
:则dp[i] = points[i] + dp[i + brainpower[i] + 1]
,如果跳过后的索引超出了数组长度,则积分为 0。
- 不解决问题
- 最终我们取两者中的最大值:
d p [ i ] = max ( d p [ i + 1 ] , p o i n t s i + d p [ i + b r a i n p o w e r i + 1 ] ) dp[i] = \max(dp[i + 1], points_i + dp[i + brainpower_i + 1]) dp[i]=max(dp[i+1],pointsi+dp[i+brainpoweri+1])
边界条件:
- 当我们到达最后一个问题时,
dp[i]
只等于当前问题的积分points_i
,因为后面已经没有问题可跳过了。
3. 解决方法
动态规划方法
- 初始化:创建一个数组
dp
,其中dp[i]
表示从第i
个问题开始可以获得的最大积分。 - 状态转移:从右到左遍历问题,通过状态转移方程计算每个
dp[i]
的值。 - 返回结果:
dp[0]
即为从第一个问题开始可以获得的最大积分。
伪代码:
initialize dp array with dp[len(questions)] = 0
for i from len(questions) - 1 to 0:
points_i = questions[i][0]
brainpower_i = questions[i][1]
dp[i] = max(dp[i + 1], points_i + dp[i + brainpower_i + 1])
return dp[0]
4. 进一步优化
- 时间复杂度:时间复杂度为
O(n)
,其中n
是问题的数量,因为我们需要遍历每个问题。 - 空间复杂度:空间复杂度为
O(n)
,因为我们需要维护一个dp
数组。
可以进一步优化空间,将 dp
数组简化为两个变量,减少空间复杂度为 O(1)
。
5. 小总结
- 递推思路:我们通过自右向左遍历问题,并使用动态规划数组
dp
来记录从每个问题开始的最大积分。最后返回dp[0]
。 - 时间复杂度:时间复杂度为
O(n)
,空间复杂度可以优化为O(1)
。
以上就是解决智力问题的基本思路。
Python代码
class Solution:
def mostPoints(self, questions: list[list[int]]) -> int:
n = len(questions)
dp = [0] * (n + 1) # dp[i] 表示从第i个问题开始能获得的最大积分
# 从右到左遍历每个问题
for i in range(n - 1, -1, -1):
points, brainpower = questions[i]
# 选择不做问题i 或 做问题i
dp[i] = max(dp[i + 1], points + dp[min(i + brainpower + 1, n)])
return dp[0] # 返回从第0个问题开始的最大积分
Python代码解释
- 初始化:定义一个
dp
数组,dp[i]
表示从第i
个问题开始能获得的最大积分。 - 状态转移:从右向左遍历问题,更新每个
dp[i]
,比较选择做或不做当前问题的最大积分。 - 返回结果:返回
dp[0]
,即从第一个问题开始的最大积分。
C++代码
class Solution {
public:
long long mostPoints(vector<vector<int>>& questions) {
int n = questions.size();
vector<long long> dp(n + 1, 0); // dp[i] 表示从第i个问题开始能获得的最大积分
// 从右到左遍历每个问题
for (int i = n - 1; i >= 0; --i) {
int points = questions[i][0];
int brainpower = questions[i][1];
// 选择不做问题i 或 做问题i
dp[i] = max(dp[i + 1], points + dp[min(i + brainpower + 1, n)]);
}
return dp[0]; // 返回从第0个问题开始的最大积分
}
};
C++代码解释
- 初始化:定义一个
dp
数组,dp[i]
表示从第i
个问题开始能获得的最大积分。 - 状态转移:从右向左遍历问题,计算并更新
dp[i]
的值,选择做或不做当前问题的最大积分。 - 返回结果:返回
dp[0]
,即从第一个问题开始的最大积分。
总结
- 核心思路:这是一个典型的动态规划问题。通过自右向左遍历问题,可以使用
dp
数组记录从每个问题开始能获得的最大积分,从而解决问题。 - 时间复杂度:时间复杂度为
O(n)
,适合处理中等规模的输入。 - 空间优化:可以进一步优化空间复杂度,将
dp
数组简化为两个变量,减少空间占用到O(1)
。