LeetCode 3181 – Max Total Reward

LeetCode 3181 – Max Total Reward
https://leetcode.com/problems/maximum-total-reward-using-operations-ii/
1. Problem Restatement
- You are given an array
rewardValues. - You may pick rewards in any order, but:
- If current total sum is
s, you may only pick a valuexsuch thatx > s. - Each value can be picked at most once.
- If current total sum is
- Goal: maximize the final total reward.
2. Key Observations
2.1 Upper Bound
Let m = max(rewardValues).
- Before picking
m, the accumulated sum must be< m. - Therefore, the final total reward is strictly less than
2m. - The theoretical maximum possible answer is
2m - 1.
2.2 Duplicate Values Are Useless
- If a value
xis picked once, the new sum becomes>= x. - Therefore, the same value
xcan never be picked again. - Duplicate values do not create new reachable states.
Conclusion: The array can be safely deduplicated.
3. Why Sorting Is Necessary
3.1 Core Insight
Any valid picking sequence can always be reordered into strictly increasing order:
- At the moment you pick
x, the current sums < x. - Hence all previously picked values must be
< x.
So every valid solution corresponds to picking values in ascending order.
3.2 Why Unsorted DP Fails
The DP transition for a value x is:
- You may only transition from sums
s < xtos + x.
If values are processed in arbitrary order, a large value processed too early:
- Cannot benefit from smaller values processed later.
- Leads to missing valid sequences.
Example: [9, 4]
- Process
9first → reachable{0, 9} - Process
4next → reachable{0, 4, 9} - Missing
13(4 → 9), because9was already processed.
Conclusion:
To process each value only once (single-pass DP), values must be sorted in ascending order.
4. DP Modeling
4.1 DP Definition
Let:
dp[s] = trueif sumsis reachable under the rules.
Initial state:
dp[0] = true
Transition for value x:
- If
dp[s] = trueands < x, thendp[s + x] = true.
This is a constrained 0/1 knapsack with a value-dependent feasibility condition.
5. Bitset Optimization (Core Idea)
5.1 Bitset Representation
Use a bitset f where:
- Bit
sis1⇔dp[s] = true
Initial:
f = 1 // only sum 0 is reachable
5.2 Encoding the Constraint s < x
To select x, we may only use states with s < x.
Define:
mask = (1 << x) - 1
maskkeeps only the lowestxbits.f & maskextracts all reachable sumss < x.
5.3 Transition via Bit Operations
For all valid s < x, selecting x yields s + x.
This is encoded as:
f |= (f & mask) << x
Meaning:
(f & mask)→ all valid predecessor sums<< x→ addxto each of them|=→ merge “pick x” and “skip x” states
This single operation performs all valid DP transitions for x in bulk.
6. Early Termination Optimization
If at any point:
dp[m - 1] == true
then:
- We can pick
mnext (sincem > m - 1) - Final sum becomes
2m - 1 - This equals the theoretical upper bound
Conclusion: We can immediately return 2m - 1.
7. Final Algorithm Outline
- Compute
m = max(rewardValues). - Early check for direct construction of
m - 1. - Sort
rewardValuesin ascending order. - Deduplicate values.
- Initialize bitset
f = 1. - For each value
xin ascending order:mask = (1 << x) - 1f |= (f & mask) << x
- Answer is
highest set bit in f.
8. Conceptual Summary
- Sorting aligns DP order with all valid pick sequences.
- The
s < xconstraint is enforced via masking. - Bitset DP transforms a quadratic DP into fast word-level operations.
- The solution is a mix of greedy ordering + constrained knapsack + bitset optimization.
9. Takeaway Insight
This problem is not just about optimization; it is about choosing a DP order that respects the problem’s feasibility constraints. Bitsets make it fast, but sorting makes it correct.
3180. Code
class Solution {
/**
Each rewardValues[i] can be collect once; can only collect rewardValues[i] > current reward value j.
dp problem: decide whether collect or not at each position; dp[i][j] = if we can get reward value `j` from `rewardValues[0...i]`.
At each rewardValues[i] = x, consider choose or not choose:
if not choose, `dp[i][j] = dp[i - 1][j]`, i.e. if the same subproblem that if we can get reward value x from [0...i - 1].
if choose, `dp[i][j] = dp[i - 1][j - x]`, where j must satisfy j - x < x and j - x >= 0, i.e. x <= j < 2x. The problem becomes "getting value j when choosing x depends on the subproblem that can we get value `j - x` from rewardValues[0...i - 1]".
Associate two situations:
{
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - x], when x <= j < 2x;
dp[i][j] = dp[i - 1][j], when j < x or j >= 2x
}
Since we cannot choose any smaller value if we choose bigger values first, the optimal approach is to choose from the smallest values first. Hence, the array must be sorted under the problem's description.
Assuming the maximum of rewardValues[] is m, the maximum reward we can collect must < 2*m.
[a1, a2, ..., an], an = m
a1 + a2 + ... + an-1 + an, if we can choose all values, then a1 + a2 + ... + an-1 < m, so the total maximum value must < 2*m.
*/
public int maxTotalReward(int[] rewardValues) {
int n = rewardValues.length;
Arrays.sort(rewardValues);
int c = 2 * rewardValues[n - 1];
boolean[] dp = new boolean[c + 1];
dp[0] = true;
int ans = 0;
int pre = -1;
for(int i = 0; i < n; i++){
int x = rewardValues[i];
if(x == pre) continue;
for(int j = c; j >= x; j--){
if(j < 2 * x){
dp[j] |= dp[j - x];
}
if(dp[j]){
ans = Math.max(ans, j);
}
}
pre = x;
}
return ans;
}
}3181. Code
import java.math.BigInteger;
class Solution {
/**
Same problem as 3180.
But 3181's dataset is much larger, optimization has to be done.
From the dp equation: dp[j] |= dp[j - x], this can be done using a bitset.
Use a binary number, the initial state is the lowest digit being 1, indicating dp[0] = true;
For each rewardValues[i] = x, the state transfer equation is dp[j] |= dp[j - x], which is equivalent to: if dp[s] = true, for all s < x, dp[s + x] = true, where s is the current reachable sum.
This is equivalent to taking all reachable sums `s < x`, shifting them by `x`, and OR-ing back.
Final result is the highest set bit of the bitset.
In this way, each `j` is not processed separately, but processed in a batch(32/64) with a bitset.
*/
public int maxTotalReward(int[] rewardValues) {
int n = rewardValues.length;
int mx = 0;
for(int x : rewardValues) mx = Math.max(mx, x);
// The maximum value we can get < 2 * mx
// 1) If we can get mx - 1, we can get mx.
// i.e. the answer is certainly = mx - 1 + mx = 2*mx - 1
// 2) If there are two values sum to mx - 1, we can get 2*mx - 1.
Set<Integer> set = new HashSet<>();
int target = mx - 1;
for(int x : rewardValues){
if(x == mx - 1) return 2 * mx - 1;
if(set.contains(x)) continue;
if(set.contains(target - x)) return 2 * mx - 1;
set.add(x);
}
Arrays.sort(rewardValues);
BigInteger f = BigInteger.ONE;
int ans = 0;
int pre = -1;
for(int i = 0; i < n; i++){
int x = rewardValues[i];
if(pre == x) continue;
BigInteger mask = BigInteger.ONE.shiftLeft(x).subtract(BigInteger.ONE);
// do operation for all bits <= x
f = f.or(f.and(mask).shiftLeft(x)); // dp[s + x] |= dp[s]
pre = x;
}
return f.bitLength() - 1;
}
}