
题目描述
给你一个整数数组 rewardValues
,长度为 n
,代表奖励的值。
最初,你的总奖励 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 <= 2000
1 <= rewardValues[i] <= 2000
解法
方法一:排序 + 记忆化搜索 + 二分查找
我们可以对奖励值数组 rewardValues
进行排序,然后使用记忆化搜索的方法求解最大总奖励。
我们定义一个函数 \(\textit{dfs}(x)\),表示当前总奖励为 \(x\) 时,能够获得的最大总奖励。那么答案为 \(\textit{dfs}(0)\)。
函数 \(\textit{dfs}(x)\) 的执行过程如下:
- 二分查找数组
rewardValues
中第一个大于 \(x\) 的元素的下标 \(i\);
- 遍历数组
rewardValues
中从下标 \(i\) 开始的元素,对于每个元素 \(v\),计算 \(v + \textit{dfs}(x + v)\) 的最大值。
- 将结果返回。
为了避免重复计算,我们使用记忆化数组 f
记录已经计算过的结果。
时间复杂度 \(O(n \times (\log n + M))\),空间复杂度 \(O(M)\)。其中 \(n\) 是数组 rewardValues
的长度,而 \(M\) 是数组 rewardValues
中的最大值的两倍。
1
2
3
4
5
6
7
8
9
10
11
12 | class Solution:
def maxTotalReward(self, rewardValues: List[int]) -> int:
@cache
def dfs(x: int) -> int:
i = bisect_right(rewardValues, x)
ans = 0
for v in rewardValues[i:]:
ans = max(ans, v + dfs(x + v))
return ans
rewardValues.sort()
return dfs(0)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | class Solution {
private int[] nums;
private Integer[] f;
public int maxTotalReward(int[] rewardValues) {
nums = rewardValues;
Arrays.sort(nums);
int n = nums.length;
f = new Integer[nums[n - 1] << 1];
return dfs(0);
}
private int dfs(int x) {
if (f[x] != null) {
return f[x];
}
int i = Arrays.binarySearch(nums, x + 1);
i = i < 0 ? -i - 1 : i;
int ans = 0;
for (; i < nums.length; ++i) {
ans = Math.max(ans, nums[i] + dfs(x + nums[i]));
}
return f[x] = ans;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | class Solution {
public:
int maxTotalReward(vector<int>& rewardValues) {
sort(rewardValues.begin(), rewardValues.end());
int n = rewardValues.size();
int f[rewardValues.back() << 1];
memset(f, -1, sizeof(f));
function<int(int)> dfs = [&](int x) {
if (f[x] != -1) {
return f[x];
}
auto it = upper_bound(rewardValues.begin(), rewardValues.end(), x);
int ans = 0;
for (; it != rewardValues.end(); ++it) {
ans = max(ans, rewardValues[it - rewardValues.begin()] + dfs(x + *it));
}
return f[x] = ans;
};
return dfs(0);
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | func maxTotalReward(rewardValues []int) int {
sort.Ints(rewardValues)
n := len(rewardValues)
f := make([]int, rewardValues[n-1]<<1)
for i := range f {
f[i] = -1
}
var dfs func(int) int
dfs = func(x int) int {
if f[x] != -1 {
return f[x]
}
i := sort.SearchInts(rewardValues, x+1)
f[x] = 0
for _, v := range rewardValues[i:] {
f[x] = max(f[x], v+dfs(x+v))
}
return f[x]
}
return dfs(0)
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | function maxTotalReward(rewardValues: number[]): number {
rewardValues.sort((a, b) => a - b);
const search = (x: number): number => {
let [l, r] = [0, rewardValues.length];
while (l < r) {
const mid = (l + r) >> 1;
if (rewardValues[mid] > x) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
};
const f: number[] = Array(rewardValues.at(-1)! << 1).fill(-1);
const dfs = (x: number): number => {
if (f[x] !== -1) {
return f[x];
}
let ans = 0;
for (let i = search(x); i < rewardValues.length; ++i) {
ans = Math.max(ans, rewardValues[i] + dfs(x + rewardValues[i]));
}
return (f[x] = ans);
};
return dfs(0);
}
|
方法二:动态规划
我们定义 \(f[i][j]\) 表示用前 \(i\) 个奖励值,能否得到总奖励 \(j\)。初始时 \(f[0][0] = \textit{True}\),其余值均为 \(\textit{False}\)。
我们考虑第 \(i\) 个奖励值 \(v\),如果我们不选择它,那么 \(f[i][j] = f[i - 1][j]\);如果我们选择它,那么 \(f[i][j] = f[i - 1][j - v]\),其中 \(0 \leq j - v \lt v\)。即状态转移方程为:
\[
f[i][j] = f[i - 1][j] \vee f[i - 1][j - v]
\]
最终答案为 \(\max\{j \mid f[n][j] = \textit{True}\}\)。
由于 \(f[i][j]\) 只与 \(f[i - 1][j]\) 和 \(f[i - 1][j - v]\) 有关,我们可以优化掉第一维,只使用一个一维数组进行状态转移。
时间复杂度 \(O(n \times M)\),空间复杂度 \(O(M)\)。其中 \(n\) 是数组 rewardValues
的长度,而 \(M\) 是数组 rewardValues
中的最大值的两倍。
方法三:动态规划 + 位运算
我们可以对方法二进行优化,定义一个二进制数 \(f\) 保存当前的状态,其中 \(f\) 的第 \(i\) 位为 \(1\) 表示当前总奖励为 \(i\) 是可达的。
观察方法二的状态转移方程 \(f[j] = f[j] \vee f[j - v]\),这相当于取 \(f\) 的低 \(v\) 位,再左移 \(v\) 位,然后与原来的 \(f\) 进行或运算。
那么答案为 \(f\) 的最高位的位置。
时间复杂度 \(O(n \times M / w)\),空间复杂度 \(O(n + M / w)\)。其中 \(n\) 是数组 rewardValues
的长度,而 \(M\) 是数组 rewardValues
中的最大值的两倍。整数 \(w = 32\) 或 \(64\)。
| class Solution:
def maxTotalReward(self, rewardValues: List[int]) -> int:
nums = sorted(set(rewardValues))
f = 1
for v in nums:
f |= (f & ((1 << v) - 1)) << v
return f.bit_length() - 1
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | import java.math.BigInteger;
class Solution {
public int maxTotalReward(int[] rewardValues) {
int[] nums = Arrays.stream(rewardValues).distinct().sorted().toArray();
BigInteger f = BigInteger.ONE;
for (int v : nums) {
BigInteger mask = BigInteger.ONE.shiftLeft(v).subtract(BigInteger.ONE);
BigInteger shifted = f.and(mask).shiftLeft(v);
f = f.or(shifted);
}
return f.bitLength() - 1;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | class Solution {
public:
int maxTotalReward(vector<int>& rewardValues) {
sort(rewardValues.begin(), rewardValues.end());
rewardValues.erase(unique(rewardValues.begin(), rewardValues.end()), rewardValues.end());
bitset<100000> f{1};
for (int v : rewardValues) {
int shift = f.size() - v;
f |= f << shift >> (shift - v);
}
for (int i = rewardValues.back() * 2 - 1;; i--) {
if (f.test(i)) {
return i;
}
}
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12 | func maxTotalReward(rewardValues []int) int {
slices.Sort(rewardValues)
rewardValues = slices.Compact(rewardValues)
one := big.NewInt(1)
f := big.NewInt(1)
p := new(big.Int)
for _, v := range rewardValues {
mask := p.Sub(p.Lsh(one, uint(v)), one)
f.Or(f, p.Lsh(p.And(f, mask), uint(v)))
}
return f.BitLen() - 1
}
|
| function maxTotalReward(rewardValues: number[]): number {
rewardValues.sort((a, b) => a - b);
rewardValues = [...new Set(rewardValues)];
let f = 1n;
for (const x of rewardValues) {
const mask = (1n << BigInt(x)) - 1n;
f = f | ((f & mask) << BigInt(x));
}
return f.toString(2).length - 1;
}
|