Facebook Pixel

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:

  1. Taking the OR of all elements in the first half (first k elements)
  2. Taking the OR of all elements in the second half (last k elements)
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 select j elements from the first i elements to achieve OR value x
  • Backward DP (g): g[i][j][y] tells us if we can select j elements starting from position i to achieve OR value y

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 first i 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 select j elements from the first i elements with OR value x

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:

  1. Skip it: f[i + 1][j][x] |= f[i][j][x] - the states remain the same
  2. 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 position i
  • j represents the number of elements selected
  • y represents the OR value achieved
  • g[i][j][y] = True if we can select j elements starting from position i with OR value y

The base case is g[n][0][0] = True.

We iterate backwards from position n to 0:

  1. Skip element at i-1: g[i - 1][j][y] |= g[i][j][y]
  2. 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 where f[i][k][x] = True (we can get OR value x using k elements from the first i elements)
  • Check all possible OR values y where g[i][k][y] = True (we can get OR value y using k elements from position i 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 Evaluator

Example 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 (from f[0][0][0])
    • Take: f[1][1][3] = True (from f[0][0][0], OR value becomes 0|3 = 3)
  • Process element 5 (index 1):

    • Skip: f[2][0][0] = True, f[2][1][3] = True
    • Take: f[2][1][5] = True (from f[1][0][0], OR value becomes 0|5 = 5)
  • 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 (from f[2][0][0], OR value becomes 0|1 = 1)
  • 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 (from f[3][0][0], OR value becomes 0|6 = 6)

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 (from g[4][0][0], OR value becomes 0|6 = 6)
  • 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 (from g[3][0][0], OR value becomes 0|1 = 1)
  • 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 (from g[2][0][0], OR value becomes 0|5 = 5)
  • 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 (from g[1][0][0], OR value becomes 0|3 = 3)

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:

  1. Forward DP computation: The first nested loop iterates through n elements, k+1 possible subset sizes, and m possible OR values. For each state f[i][j][x], we perform two operations (skip element or include element). This gives us O(n × k × m) operations.

  2. Backward DP computation: Similarly, the second nested loop iterates from position n to 0, with k+1 subset sizes and m possible OR values. This also results in O(n × k × m) operations.

  3. Final answer computation: The last part iterates through at most n-2k+1 positions (from k to n-k), and for each position checks all m possible values for the first subset and all m possible values for the second subset. This gives us O(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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More