Find the Maximum Sequence Value of Array
HardPrompt
You are given an integer array nums and a positive integer k.
The value of a sequence seq of size 2 * x is defined as:
(seq[0] OR seq[1] OR ... OR seq[x - 1]) XOR (seq[x] OR seq[x + 1] OR ... OR seq[2 * x - 1]).
Return the maximum value of any subsequence of nums having size 2 * k.
Example 1:
Input: nums = [2,6,7], k = 1
Output: 5
Explanation:
The subsequence [2, 7] has the maximum value of 2 XOR 7 = 5.
Example 2:
Input: nums = [4,2,5,6,7], k = 2
Output: 2
Explanation:
The subsequence [4, 5, 6, 7] has the maximum value of (4 OR 5) XOR (6 OR 7) = 2.
Constraints:
2 <= nums.length <= 4001 <= nums[i] < 271 <= k <= nums.length / 2
Approaches
2 approaches with complexity analysis and trade-offs.
A much more efficient approach involves a greedy strategy combined with dynamic programming. We can construct the maximum value bit by bit, from most significant to least significant. For each bit position, we greedily check if we can make that bit a '1' in our final answer, given the choices we've made for the higher-order bits.
Algorithm
- The core idea is to build the maximum possible result bit by bit, from the most significant bit (MSB) to the least significant bit (LSB).
- Initialize the result
ans = 0. - Iterate from bit
b = 6down to 0 (sincenums[i] < 2^7). - In each iteration, try to set the
b-th bit of the answer to 1. Lettarget = ans | (1 << b). - Check if it's possible to find two disjoint
k-element subsequences A and B such that their OR-sumsorAandorBsatisfy(orA XOR orB)havingtargetas a prefix. This means for all bitsj >= b, thej-th bit of(orA XOR orB)must match thej-th bit oftarget. - This check is performed by a helper function,
isPossible(target, b). - If
isPossiblereturns true, it means we can achieve thistargetprefix, so we updateans = target. - If it returns false, we cannot set the
b-th bit to 1 (while satisfying higher bits), so we leaveansas is and proceed to the next smaller bit. - The
isPossiblefunction uses its own DP. Letdp[j1][pA]be the minimum sizej2of the second set (B) required to satisfy the prefix condition, given that the first set (A) hasj1elements and its OR-sum prefix ispA. The prefix is calculated byval & mask, wheremaskincludes all bits from MSB down tob. - The DP transition for
isPossibleconsiders adding the current number to set A, set B, or neither, and updates the minimumj2values accordingly. - After iterating through all bits,
ansholds the maximum possible sequence value.
Walkthrough
We iterate from bit 6 down to 0. Let's say our current best-achievable value is ans. For the current bit b, we test if we can achieve a value of ans | (1 << b). This test involves checking if there exist two disjoint k-element subsequences, A and B, such that (OR(A) XOR OR(B)) matches ans | (1 << b) on all bits from 6 down to b.
To perform this check, we use a DP helper function, isPossible(target). This function determines if a given target prefix is achievable. The state for this DP is dp[j1][pA], which stores the minimum number of elements (j2) needed for set B to satisfy the prefix condition, given that set A has j1 elements and its OR-sum, when masked with the current bitmask, results in pA.
The DP table dp has dimensions (k+1) x 128. dp[j1][pA] is initialized to a value larger than k (e.g., k+1), with dp[0][0] = 0.
We iterate through each num in nums. For each num, we create a new DP table new_dp based on the old dp table to represent the states after considering num. For each state (j1, pA) with j2 = dp[j1][pA], we have three choices for num:
- Don't use
num: The state is carried over, which is implicitly handled by copyingdptonew_dp. - Add
numto set A: Ifj1 < k, we can transition to a state withj1+1elements in A. The new OR-sum prefix for A will bepA | (num & mask). We updatenew_dp[j1+1][pA | (num & mask)]withmin(current_value, j2). - Add
numto set B: Ifj2 < k, we can transition to a state withj2+1elements in B. The OR-sum prefix for B changes, which in turn changes the required prefix for A to maintain thetargetXOR relationship. The new requiredpAis calculated, and we updatenew_dpaccordingly.
After processing all numbers, if there is any pA for which dp[k][pA] <= k, it means we can form two valid sets of size k, and the function returns true.
class Solution { public int maxSequenceValue(int[] nums, int k) { int ans = 0; for (int b = 6; b >= 0; b--) { int target = ans | (1 << b); if (isPossible(nums, k, target, b)) { ans = target; } } return ans; } private boolean isPossible(int[] nums, int k, int target, int bit) { int mask = 0; for (int i = 6; i >= bit; i--) { mask |= (1 << i); } int[][] dp = new int[k + 1][128]; for (int i = 0; i <= k; i++) { for (int j = 0; j < 128; j++) { dp[i][j] = k + 1; // Use k+1 as infinity } } dp[0][0] = 0; for (int num : nums) { int numPrefix = num & mask; int[][] newDp = new int[k + 1][128]; for(int i=0; i<=k; i++) { System.arraycopy(dp[i], 0, newDp[i], 0, 128); } for (int j1 = 0; j1 <= k; j1++) { for (int pA = 0; pA < 128; pA++) { if (dp[j1][pA] > k) continue; int j2 = dp[j1][pA]; // Option 1: Add num to set A if (j1 < k) { newDp[j1 + 1][pA | numPrefix] = Math.min(newDp[j1 + 1][pA | numPrefix], j2); } // Option 2: Add num to set B if (j2 < k) { int pB = pA ^ target; int newPB = pB | numPrefix; int newPA = newPB ^ target; newDp[j1][newPA] = Math.min(newDp[j1][newPA], j2 + 1); } } } dp = newDp; } for (int pA = 0; pA < 128; pA++) { if (dp[k][pA] <= k) { return true; } } return false; }}Complexity
Time
O(B * n * k * C), where B is the number of bits (constant, 7), n is the length of `nums`, k is the subsequence size, and C is the maximum possible OR-sum (128). This simplifies to O(n * k) as B and C are small constants.
Space
O(k * C), where C is the maximum possible OR-sum (128). The DP table inside the `isPossible` function is of size `(k+1) x 128`.
Trade-offs
Pros
Significantly more efficient than the brute-force DP approach.
The time and space complexity are well within the limits for the given constraints.
The greedy bit-by-bit strategy is a powerful technique for maximization problems involving bitwise operations.
Cons
The logic, especially the DP state within the greedy check, can be complex to formulate and implement correctly.
Solutions
Solution
class Solution {public int maxValue(int[] nums, int k) { int m = 1 << 7; int n = nums.length; boolean[][][] f = new boolean[n + 1][k + 2][m]; f[0][0][0] = true; for (int i = 0; i < n; i++) { for (int j = 0; j <= k; j++) { for (int x = 0; x < m; x++) { if (f[i][j][x]) { f[i + 1][j][x] = true; f[i + 1][j + 1][x | nums[i]] = true; } } } } boolean[][][] g = new boolean[n + 1][k + 2][m]; g[n][0][0] = true; for (int i = n; i > 0; i--) { for (int j = 0; j <= k; j++) { for (int y = 0; y < m; y++) { if (g[i][j][y]) { g[i - 1][j][y] = true; g[i - 1][j + 1][y | nums[i - 1]] = true; } } } } int ans = 0; for (int i = k; i <= n - k; i++) { for (int x = 0; x < m; x++) { if (f[i][k][x]) { for (int y = 0; y < m; y++) { if (g[i][k][y]) { ans = Math.max(ans, x ^ y); } } } } } return ans; }}Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.