Find the Maximum Sequence Value of Array
HARDDescription
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
Checkout 2 different approaches to solve Find the Maximum Sequence Value of Array. Click on different approaches to view the approach and algorithm in detail.
Greedy Bitwise Search with Dynamic Programming
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.
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 Analysis
Pros and Cons
- 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.
- The logic, especially the DP state within the greedy check, can be complex to formulate and implement correctly.
Code Solutions
Checking out 3 solutions in different languages for Find the Maximum Sequence Value of Array. Click on different languages to view the code.
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 Solution
Watch the video walkthrough for Find the Maximum Sequence Value of Array
Similar Questions
5 related questions you might find useful
Patterns:
Data Structures:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.