题目描述
给你一个整数数组 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
15 | import java.math.BigInteger;
import java.util.Arrays;
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;
}
|