3287. Find the Maximum Sequence Value of Array
Problem Description
You are given an integer array nums
and a positive integer k
.
The problem asks you to find a subsequence of nums
with exactly 2 * k
elements, then split this subsequence into two equal halves. The value of this subsequence is calculated by:
- Taking the OR of all elements in the first half (first
k
elements) - Taking the OR of all elements in the second half (last
k
elements) - Computing the XOR of these two OR values
More formally, for a sequence seq
of size 2 * x
, its value is:
(seq[0] OR seq[1] OR ... OR seq[x - 1]) XOR (seq[x] OR seq[x + 1] OR ... OR seq[2 * x - 1])
Your task is to find the maximum possible value among all subsequences of nums
that have exactly 2 * k
elements.
For example, if you have a subsequence [a, b, c, d]
where k = 2
, the value would be (a OR b) XOR (c OR d)
.
Note that a subsequence maintains the relative order of elements from the original array but doesn't need to be contiguous.
Intuition
The key insight is that we need to select 2k
elements from the array and split them into two groups of k
elements each. The challenge is that there are exponentially many ways to choose these elements, making brute force infeasible.
However, notice that the value computation only depends on the OR values of each half. Since we're dealing with integers that fit in a small range (the solution uses m = 1 << 7 = 128
, suggesting values are at most 7 bits), there are only 128 possible OR values for any subset.
This leads us to think about dynamic programming where we track which OR values are achievable rather than tracking specific subsets. The question becomes: can we achieve OR value x
using k
elements from some portion of the array?
The crucial observation is that we can split the problem at any position i
in the array. Elements before position i
can contribute to the first group (with OR value x
), and elements from position i
onwards can contribute to the second group (with OR value y
). The final answer would be x XOR y
.
To implement this, we use:
- Forward DP (
f
):f[i][j][x]
tells us if we can selectj
elements from the firsti
elements to achieve OR valuex
- Backward DP (
g
):g[i][j][y]
tells us if we can selectj
elements starting from positioni
to achieve OR valuey
By computing both forward and backward states, we can enumerate all possible split points i
and check if we can form k
elements with OR value x
before position i
, and k
elements with OR value y
from position i
onwards. The maximum x XOR y
among all valid combinations gives us our answer.
This approach is efficient because instead of tracking specific element combinations (which would be exponential), we only track achievable OR values (at most 128 possibilities), making the solution polynomial in time.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution uses dynamic programming with prefix and suffix decomposition to efficiently find all possible OR values.
Forward DP Construction:
We build a 3D DP table f[i][j][x]
where:
i
represents considering the firsti
elements (0 to n)j
represents the number of elements selected (0 to k)x
represents the OR value achieved (0 to 127)f[i][j][x] = True
if we can selectj
elements from the firsti
elements with OR valuex
The base case is f[0][0][0] = True
(selecting 0 elements gives OR value 0).
For each element at position i
, we have two choices:
- Skip it:
f[i + 1][j][x] |= f[i][j][x]
- the states remain the same - Take it:
f[i + 1][j + 1][x | nums[i]] |= f[i][j][x]
- we increment the count and update the OR value
Backward DP Construction:
Similarly, we build g[i][j][y]
where:
i
represents considering elements starting from positioni
j
represents the number of elements selectedy
represents the OR value achievedg[i][j][y] = True
if we can selectj
elements starting from positioni
with OR valuey
The base case is g[n][0][0] = True
.
We iterate backwards from position n
to 0:
- Skip element at i-1:
g[i - 1][j][y] |= g[i][j][y]
- Take element at i-1:
g[i - 1][j + 1][y | nums[i - 1]] |= g[i][j][y]
Finding the Maximum Value:
After building both DP tables, we enumerate all possible split points i
from k
to n - k
(ensuring we have at least k
elements on each side).
For each split point i
:
- Check all possible OR values
x
wheref[i][k][x] = True
(we can get OR valuex
usingk
elements from the firsti
elements) - Check all possible OR values
y
whereg[i][k][y] = True
(we can get OR valuey
usingk
elements from positioni
onwards) - If both are true, update the answer:
ans = max(ans, x ^ y)
The time complexity is O(n * k * m^2)
where n
is the array length, k
is the parameter, and m = 128
is the range of OR values. The space complexity is O(n * k * m)
for the DP tables.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with nums = [3, 5, 1, 6]
and k = 1
.
We need to select a subsequence of exactly 2 * k = 2
elements, split them into two halves (1 element each), and maximize (first_element) XOR (second_element)
.
Step 1: Build Forward DP Table f[i][j][x]
We track whether we can select j
elements from the first i
elements to achieve OR value x
.
-
Initial:
f[0][0][0] = True
(0 elements selected, OR value = 0) -
Process element 3 (index 0):
- Skip:
f[1][0][0] = True
(fromf[0][0][0]
) - Take:
f[1][1][3] = True
(fromf[0][0][0]
, OR value becomes 0|3 = 3)
- Skip:
-
Process element 5 (index 1):
- Skip:
f[2][0][0] = True
,f[2][1][3] = True
- Take:
f[2][1][5] = True
(fromf[1][0][0]
, OR value becomes 0|5 = 5)
- Skip:
-
Process element 1 (index 2):
- Skip:
f[3][0][0] = True
,f[3][1][3] = True
,f[3][1][5] = True
- Take:
f[3][1][1] = True
(fromf[2][0][0]
, OR value becomes 0|1 = 1)
- Skip:
-
Process element 6 (index 3):
- Skip:
f[4][0][0] = True
,f[4][1][3] = True
,f[4][1][5] = True
,f[4][1][1] = True
- Take:
f[4][1][6] = True
(fromf[3][0][0]
, OR value becomes 0|6 = 6)
- Skip:
Step 2: Build Backward DP Table g[i][j][y]
We track whether we can select j
elements starting from position i
to achieve OR value y
.
-
Initial:
g[4][0][0] = True
(0 elements selected from position 4 onwards) -
Process from position 4 to 3 (element 6):
- Skip:
g[3][0][0] = True
- Take:
g[3][1][6] = True
(fromg[4][0][0]
, OR value becomes 0|6 = 6)
- Skip:
-
Process from position 3 to 2 (element 1):
- Skip:
g[2][0][0] = True
,g[2][1][6] = True
- Take:
g[2][1][1] = True
(fromg[3][0][0]
, OR value becomes 0|1 = 1)
- Skip:
-
Process from position 2 to 1 (element 5):
- Skip:
g[1][0][0] = True
,g[1][1][6] = True
,g[1][1][1] = True
- Take:
g[1][1][5] = True
(fromg[2][0][0]
, OR value becomes 0|5 = 5)
- Skip:
-
Process from position 1 to 0 (element 3):
- Skip:
g[0][0][0] = True
,g[0][1][6] = True
,g[0][1][1] = True
,g[0][1][5] = True
- Take:
g[0][1][3] = True
(fromg[1][0][0]
, OR value becomes 0|3 = 3)
- Skip:
Step 3: Find Maximum XOR Value
We check split points from i = k = 1
to i = n - k = 3
:
-
Split at i = 1:
f[1][1][3] = True
(can select element 3 from first part)g[1][1][5] = True
(can select element 5 from second part)- Value: 3 XOR 5 = 6
-
Split at i = 2:
f[2][1][3] = True
(can select element 3 from first part)g[2][1][6] = True
(can select element 6 from second part)- Value: 3 XOR 6 = 5
f[2][1][5] = True
(can select element 5 from first part)g[2][1][6] = True
(can select element 6 from second part)- Value: 5 XOR 6 = 3
f[2][1][5] = True
(can select element 5 from first part)g[2][1][1] = True
(can select element 1 from second part)- Value: 5 XOR 1 = 4
-
Split at i = 3:
f[3][1][3] = True
(can select element 3 from first part)g[3][1][6] = True
(can select element 6 from second part)- Value: 3 XOR 6 = 5
f[3][1][5] = True
(can select element 5 from first part)g[3][1][6] = True
(can select element 6 from second part)- Value: 5 XOR 6 = 3
f[3][1][1] = True
(can select element 1 from first part)g[3][1][6] = True
(can select element 6 from second part)- Value: 1 XOR 6 = 7
Maximum value = 7 (achieved by selecting subsequence [1, 6])
Solution Implementation
1class Solution:
2 def maxValue(self, nums: List[int], k: int) -> int:
3 MAX_VALUE = 1 << 7 # Maximum possible value (128) for bitwise operations
4 n = len(nums)
5
6 # dp_left[i][j][mask] = True if we can select j elements from first i elements
7 # such that their OR equals mask
8 dp_left = [[[False] * MAX_VALUE for _ in range(k + 2)] for _ in range(n + 1)]
9 dp_left[0][0][0] = True # Base case: 0 elements selected, OR value is 0
10
11 # Build DP table for left side selections
12 for i in range(n):
13 for j in range(k + 1):
14 for mask in range(MAX_VALUE):
15 if dp_left[i][j][mask]:
16 # Option 1: Don't include nums[i]
17 dp_left[i + 1][j][mask] = True
18 # Option 2: Include nums[i] in the selection
19 dp_left[i + 1][j + 1][mask | nums[i]] = True
20
21 # dp_right[i][j][mask] = True if we can select j elements from nums[i:n]
22 # such that their OR equals mask
23 dp_right = [[[False] * MAX_VALUE for _ in range(k + 2)] for _ in range(n + 1)]
24 dp_right[n][0][0] = True # Base case: 0 elements selected from empty suffix
25
26 # Build DP table for right side selections (traverse backwards)
27 for i in range(n, 0, -1):
28 for j in range(k + 1):
29 for mask in range(MAX_VALUE):
30 if dp_right[i][j][mask]:
31 # Option 1: Don't include nums[i-1]
32 dp_right[i - 1][j][mask] = True
33 # Option 2: Include nums[i-1] in the selection
34 dp_right[i - 1][j + 1][mask | nums[i - 1]] = True
35
36 max_xor = 0
37
38 # Try all possible split points where we take k elements from left
39 # and k elements from right
40 for split_point in range(k, n - k + 1):
41 # Check all possible OR values from left side
42 for left_or in range(MAX_VALUE):
43 if dp_left[split_point][k][left_or]:
44 # Check all possible OR values from right side
45 for right_or in range(MAX_VALUE):
46 if dp_right[split_point][k][right_or]:
47 # Calculate XOR and update maximum
48 max_xor = max(max_xor, left_or ^ right_or)
49
50 return max_xor
51
1class Solution {
2 public int maxValue(int[] nums, int k) {
3 // Maximum possible OR value (assuming values are < 128)
4 int maxOrValue = 1 << 7;
5 int n = nums.length;
6
7 // leftDp[i][j][orValue] = true if we can select j elements from first i elements
8 // with OR value equal to orValue
9 boolean[][][] leftDp = new boolean[n + 1][k + 2][maxOrValue];
10 leftDp[0][0][0] = true; // Base case: 0 elements selected, OR value is 0
11
12 // Build DP table for subsequences from the left
13 for (int i = 0; i < n; i++) {
14 for (int selectedCount = 0; selectedCount <= k; selectedCount++) {
15 for (int orValue = 0; orValue < maxOrValue; orValue++) {
16 if (leftDp[i][selectedCount][orValue]) {
17 // Option 1: Don't select current element
18 leftDp[i + 1][selectedCount][orValue] = true;
19 // Option 2: Select current element
20 leftDp[i + 1][selectedCount + 1][orValue | nums[i]] = true;
21 }
22 }
23 }
24 }
25
26 // rightDp[i][j][orValue] = true if we can select j elements from elements [i, n)
27 // with OR value equal to orValue
28 boolean[][][] rightDp = new boolean[n + 1][k + 2][maxOrValue];
29 rightDp[n][0][0] = true; // Base case: 0 elements selected from empty suffix
30
31 // Build DP table for subsequences from the right
32 for (int i = n; i > 0; i--) {
33 for (int selectedCount = 0; selectedCount <= k; selectedCount++) {
34 for (int orValue = 0; orValue < maxOrValue; orValue++) {
35 if (rightDp[i][selectedCount][orValue]) {
36 // Option 1: Don't select current element
37 rightDp[i - 1][selectedCount][orValue] = true;
38 // Option 2: Select current element
39 rightDp[i - 1][selectedCount + 1][orValue | nums[i - 1]] = true;
40 }
41 }
42 }
43 }
44
45 int maxXorResult = 0;
46
47 // Try all possible split points where we take k elements from left and k from right
48 for (int splitPoint = k; splitPoint <= n - k; splitPoint++) {
49 // Check all possible OR values from left subsequence
50 for (int leftOrValue = 0; leftOrValue < maxOrValue; leftOrValue++) {
51 if (leftDp[splitPoint][k][leftOrValue]) {
52 // Check all possible OR values from right subsequence
53 for (int rightOrValue = 0; rightOrValue < maxOrValue; rightOrValue++) {
54 if (rightDp[splitPoint][k][rightOrValue]) {
55 // Calculate XOR of the two OR values and update maximum
56 maxXorResult = Math.max(maxXorResult, leftOrValue ^ rightOrValue);
57 }
58 }
59 }
60 }
61 }
62
63 return maxXorResult;
64 }
65}
66
1class Solution {
2public:
3 int maxValue(vector<int>& nums, int k) {
4 // Maximum possible OR value with 7 bits (0-127)
5 const int MAX_OR_VALUE = 1 << 7;
6 int n = nums.size();
7
8 // DP table for left side:
9 // leftDP[i][j][orValue] = true if we can select j elements from first i elements
10 // with OR value equal to orValue
11 vector<vector<vector<bool>>> leftDP(n + 1,
12 vector<vector<bool>>(k + 2, vector<bool>(MAX_OR_VALUE, false)));
13
14 // Base case: selecting 0 elements from first 0 elements gives OR value 0
15 leftDP[0][0][0] = true;
16
17 // Fill the left DP table
18 for (int i = 0; i < n; i++) {
19 for (int selectedCount = 0; selectedCount <= k; selectedCount++) {
20 for (int orValue = 0; orValue < MAX_OR_VALUE; orValue++) {
21 if (leftDP[i][selectedCount][orValue]) {
22 // Option 1: Skip current element
23 leftDP[i + 1][selectedCount][orValue] = true;
24
25 // Option 2: Include current element
26 leftDP[i + 1][selectedCount + 1][orValue | nums[i]] = true;
27 }
28 }
29 }
30 }
31
32 // DP table for right side:
33 // rightDP[i][j][orValue] = true if we can select j elements from elements [i, n-1]
34 // with OR value equal to orValue
35 vector<vector<vector<bool>>> rightDP(n + 1,
36 vector<vector<bool>>(k + 2, vector<bool>(MAX_OR_VALUE, false)));
37
38 // Base case: selecting 0 elements from empty suffix gives OR value 0
39 rightDP[n][0][0] = true;
40
41 // Fill the right DP table (backwards)
42 for (int i = n; i > 0; i--) {
43 for (int selectedCount = 0; selectedCount <= k; selectedCount++) {
44 for (int orValue = 0; orValue < MAX_OR_VALUE; orValue++) {
45 if (rightDP[i][selectedCount][orValue]) {
46 // Option 1: Skip current element
47 rightDP[i - 1][selectedCount][orValue] = true;
48
49 // Option 2: Include current element
50 rightDP[i - 1][selectedCount + 1][orValue | nums[i - 1]] = true;
51 }
52 }
53 }
54 }
55
56 int maxXorValue = 0;
57
58 // Try all possible split points
59 // We need k elements from left and k elements from right
60 for (int splitPoint = k; splitPoint <= n - k; splitPoint++) {
61 // Check all possible OR values for left k elements
62 for (int leftOrValue = 0; leftOrValue < MAX_OR_VALUE; leftOrValue++) {
63 if (leftDP[splitPoint][k][leftOrValue]) {
64 // Check all possible OR values for right k elements
65 for (int rightOrValue = 0; rightOrValue < MAX_OR_VALUE; rightOrValue++) {
66 if (rightDP[splitPoint][k][rightOrValue]) {
67 // Calculate XOR of left and right OR values
68 maxXorValue = max(maxXorValue, leftOrValue ^ rightOrValue);
69 }
70 }
71 }
72 }
73 }
74
75 return maxXorValue;
76 }
77};
78
1/**
2 * Finds the maximum XOR value between two subsequences of size k
3 * @param nums - Array of numbers to process
4 * @param k - Size of each subsequence
5 * @returns Maximum XOR value between two non-overlapping subsequences
6 */
7function maxValue(nums: number[], k: number): number {
8 const MAX_VALUE = 1 << 7; // Maximum possible value (128) for bitwise operations
9 const arrayLength = nums.length;
10
11 // Dynamic programming table for forward pass
12 // dpForward[i][j][x] = true if we can select j elements from first i elements with OR value x
13 const dpForward: boolean[][][] = Array.from({ length: arrayLength + 1 }, () =>
14 Array.from({ length: k + 2 }, () => Array(MAX_VALUE).fill(false))
15 );
16 dpForward[0][0][0] = true; // Base case: 0 elements selected with OR value 0
17
18 // Build forward DP table
19 for (let i = 0; i < arrayLength; i++) {
20 for (let selectedCount = 0; selectedCount <= k; selectedCount++) {
21 for (let orValue = 0; orValue < MAX_VALUE; orValue++) {
22 if (dpForward[i][selectedCount][orValue]) {
23 // Option 1: Don't select current element
24 dpForward[i + 1][selectedCount][orValue] = true;
25 // Option 2: Select current element
26 dpForward[i + 1][selectedCount + 1][orValue | nums[i]] = true;
27 }
28 }
29 }
30 }
31
32 // Dynamic programming table for backward pass
33 // dpBackward[i][j][y] = true if we can select j elements from elements i to n with OR value y
34 const dpBackward: boolean[][][] = Array.from({ length: arrayLength + 1 }, () =>
35 Array.from({ length: k + 2 }, () => Array(MAX_VALUE).fill(false))
36 );
37 dpBackward[arrayLength][0][0] = true; // Base case: 0 elements selected with OR value 0
38
39 // Build backward DP table
40 for (let i = arrayLength; i > 0; i--) {
41 for (let selectedCount = 0; selectedCount <= k; selectedCount++) {
42 for (let orValue = 0; orValue < MAX_VALUE; orValue++) {
43 if (dpBackward[i][selectedCount][orValue]) {
44 // Option 1: Don't select current element
45 dpBackward[i - 1][selectedCount][orValue] = true;
46 // Option 2: Select current element
47 dpBackward[i - 1][selectedCount + 1][orValue | nums[i - 1]] = true;
48 }
49 }
50 }
51 }
52
53 let maxXorValue = 0;
54
55 // Find the maximum XOR value by trying all split points
56 // Split at position i means first subsequence uses elements [0, i) and second uses [i, n)
57 for (let splitPoint = k; splitPoint <= arrayLength - k; splitPoint++) {
58 // Check all possible OR values for first subsequence
59 for (let firstOrValue = 0; firstOrValue < MAX_VALUE; firstOrValue++) {
60 if (dpForward[splitPoint][k][firstOrValue]) {
61 // Check all possible OR values for second subsequence
62 for (let secondOrValue = 0; secondOrValue < MAX_VALUE; secondOrValue++) {
63 if (dpBackward[splitPoint][k][secondOrValue]) {
64 // Calculate XOR and update maximum
65 maxXorValue = Math.max(maxXorValue, firstOrValue ^ secondOrValue);
66 }
67 }
68 }
69 }
70 }
71
72 return maxXorValue;
73}
74
Time and Space Complexity
Time Complexity: O(n × m × k)
The algorithm consists of three main parts:
-
Forward DP computation: The first nested loop iterates through
n
elements,k+1
possible subset sizes, andm
possible OR values. For each statef[i][j][x]
, we perform two operations (skip element or include element). This gives usO(n × k × m)
operations. -
Backward DP computation: Similarly, the second nested loop iterates from position
n
to0
, withk+1
subset sizes andm
possible OR values. This also results inO(n × k × m)
operations. -
Final answer computation: The last part iterates through at most
n-2k+1
positions (fromk
ton-k
), and for each position checks allm
possible values for the first subset and allm
possible values for the second subset. This gives usO(n × m²)
operations.
Since m = 2^7 = 128
is a constant and k ≤ n
, the dominant term is O(n × m × k)
when considering m
as a parameter. The total time complexity is O(n × m × k) + O(n × m × k) + O(n × m²) = O(n × m × (k + m))
. Given that m = 128
is constant and typically k
is the varying parameter, this simplifies to O(n × m × k)
.
Space Complexity: O(n × m × k)
The algorithm uses two 3D boolean arrays:
- Array
f
with dimensions[n+1][k+2][m]
- Array
g
with dimensions[n+1][k+2][m]
Both arrays require O((n+1) × (k+2) × m) = O(n × k × m)
space. The total space complexity is therefore O(n × m × k)
, where n
is the length of the array, k
is the parameter for subset size, and m = 2^7 = 128
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Split Point Iteration Range
One of the most common mistakes is incorrectly defining the range for split points when combining left and right subsequences.
Pitfall Code:
# Wrong: This assumes we need exactly k elements before the split point
for split_point in range(k, n - k + 1):
for left_or in range(MAX_VALUE):
if dp_left[split_point][k][left_or]: # Requires exactly k from first split_point elements
for right_or in range(MAX_VALUE):
if dp_right[split_point][k][right_or]: # Requires exactly k from position split_point onwards
Issue: The split point doesn't represent where we must have selected exactly k elements. Instead, it represents a position in the array where we can select k elements from before it and k elements from after (or including) it. The current logic restricts valid subsequences unnecessarily.
Solution:
# Correct: Try all positions as potential split points
for split_point in range(n + 1):
for left_or in range(MAX_VALUE):
if dp_left[split_point][k][left_or]: # Can select k from first split_point elements
for right_or in range(MAX_VALUE):
if dp_right[split_point][k][right_or]: # Can select k from position split_point onwards
max_xor = max(max_xor, left_or ^ right_or)
2. Memory Optimization Oversight
Pitfall: Using full 3D arrays when only adjacent layers are needed, leading to unnecessary memory usage.
Issue: The DP only depends on the previous layer (i-1 to i), so maintaining all n+1 layers wastes memory.
Solution:
# Use rolling arrays to reduce space complexity from O(n*k*m) to O(k*m)
dp_curr = [[False] * MAX_VALUE for _ in range(k + 2)]
dp_prev = [[False] * MAX_VALUE for _ in range(k + 2)]
dp_prev[0][0] = True
for i in range(n):
# Reset current layer
dp_curr = [[False] * MAX_VALUE for _ in range(k + 2)]
for j in range(k + 1):
for mask in range(MAX_VALUE):
if dp_prev[j][mask]:
dp_curr[j][mask] = True # Don't take element i
dp_curr[j + 1][mask | nums[i]] = True # Take element i
# Store results needed for final answer before swapping
if i >= k - 1: # Can potentially have k elements selected
# Store dp_curr[k] values for later use
pass
dp_prev, dp_curr = dp_curr, dp_prev
3. Off-by-One Errors in DP Table Dimensions
Pitfall Code:
# Wrong: Using k+1 instead of k+2 for the j dimension
dp_left = [[[False] * MAX_VALUE for _ in range(k + 1)] for _ in range(n + 1)]
Issue: When we select j elements and then include one more, we access index j+1. If j can be k, then j+1 = k+1, requiring dimension k+2.
Solution: Always allocate k+2 for the selection count dimension to avoid index out of bounds errors when j+1 is accessed with j=k.
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!