Hard
题目描述
给你一个长度为 n 的整数数组 rewardValues,表示奖励的值。
初始时,你的总奖励 x 为 0,所有下标都未标记。你可以执行以下操作任意次:
- 从范围 [0, n - 1] 中选择一个未标记的下标 i。
- 如果 rewardValues[i] 大于你当前的总奖励 x,则将 rewardValues[i] 加到 x 上(即,x = x + rewardValues[i]),并标记下标 i。
返回通过最优执行操作能够收集的最大总奖励。
示例 1:
输入:rewardValues = [1,1,3,3]
输出:4
解释:
在操作过程中,我们可以选择按顺序标记下标 0 和 2,总奖励将为 4,这是最大值。
示例 2:
输入:rewardValues = [1,6,4,3,2]
输出:11
解释:
按顺序标记下标 0、2 和 1。总奖励将为 11,这是最大值。
约束条件:
- 1 <= rewardValues.length <= 5 * 10^4
- 1 <= rewardValues[i] <= 5 * 10^4
提示:
- 首先对奖励数组排序。
- 如果我们决定应用一些奖励,按顺序应用它们总是最优的。
- 状态转移为:如果 j - rewardValues[i] < rewardValues[i],则 dp[i][j] = dp[i-1][j-rewardValues[i]]。
- 注意 dp 数组是布尔数组。每个元素只需要 1 位,因此可以使用位集或类似的东西。
解题思路
这道题的核心思想是动态规划结合位操作优化。
基本思路:
- 首先对奖励数组排序,这样我们就能按从小到大的顺序处理奖励
- 使用动态规划,dp[i][j] 表示前 i 个奖励能否得到总和为 j 的状态
- 关键约束:只有当前总奖励 x < rewardValues[i] 时,才能选择该奖励
优化策略: 由于状态转移只涉及布尔值(能否达到某个和),我们可以用位操作来优化:
- 使用 bitset 来表示所有可能的状态
- 对于每个奖励 v,只考虑当前总和小于 v 的状态
- 通过位移和位运算快速更新所有可达状态
状态转移:
- 对于奖励 v,我们只关心总和在 [0, v-1] 范围内的状态
- 如果状态 j 可达且 j < v,那么状态 j+v 也变为可达
- 使用位操作:dp |= (dp & mask) « v,其中 mask 表示小于 v 的所有位
这种方法将时间复杂度从 O(nsum) 优化到 O(nsum/w),其中 w 是机器字长。
代码实现
class Solution {
public:
int maxTotalReward(vector<int>& rewardValues) {
sort(rewardValues.begin(), rewardValues.end());
rewardValues.erase(unique(rewardValues.begin(), rewardValues.end()), rewardValues.end());
bitset<100001> dp;
dp[0] = 1;
for (int v : rewardValues) {
bitset<100001> mask;
for (int i = 0; i < v; i++) {
mask[i] = 1;
}
dp |= (dp & mask) << v;
}
for (int i = 100000; i >= 0; i--) {
if (dp[i]) return i;
}
return 0;
}
};
class Solution:
def maxTotalReward(self, rewardValues: List[int]) -> int:
rewardValues = sorted(set(rewardValues))
dp = 1 # 用整数的二进制位表示状态
for v in rewardValues:
mask = (1 << v) - 1 # 创建前v位都为1的掩码
dp |= (dp & mask) << v
# 找到最高位的1
return dp.bit_length() - 1
public class Solution {
public int MaxTotalReward(int[] rewardValues) {
Array.Sort(rewardValues);
var uniqueRewards = new HashSet<int>(rewardValues).ToArray();
Array.Sort(uniqueRewards);
var dp = new BitArray(100001);
dp[0] = true;
foreach (int v in uniqueRewards) {
var newDp = new BitArray(dp);
for (int j = 0; j < 100001 - v; j++) {
if (dp[j] && j < v) {
newDp[j + v] = true;
}
}
dp = newDp;
}
for (int i = 100000; i >= 0; i--) {
if (dp[i]) return i;
}
return 0;
}
}
var maxTotalReward = function(rewardValues) {
rewardValues.sort((a, b) => a - b);
const uniqueRewards = [...new Set(rewardValues)];
let dp = new Set([0]);
for (const v of uniqueRewards) {
const newStates = new Set();
for (const sum of dp) {
if (sum < v) {
newStates.add(sum + v);
}
}
for (const state of newStates) {
dp.add(state);
}
}
return Math.max(...dp);
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n * max_sum / w) | 其中 n 是数组长度,max_sum 是最大可能和,w 是机器字长 |
| 空间复杂度 | O(max_sum) | 存储所有可能状态的位集 |