3201. Find the Maximum Length of Valid Subsequence I
Problem Description
You are given an integer array nums
.
A subsequence sub
of nums
with length x
is called valid if it satisfies the following condition:
(sub[0] + sub[1]) % 2 == (sub[1] + sub[2]) % 2 == ... == (sub[x - 2] + sub[x - 1]) % 2
In other words, when you take consecutive pairs of elements in the subsequence and compute their sum modulo 2, all these results must be equal.
Your task is to return the length of the longest valid subsequence of nums
.
A subsequence is an array that can be derived from another array by deleting some or no elements without changing the order of the remaining elements.
For example, if we have a subsequence [a, b, c, d]
, it would be valid if:
(a + b) % 2 = (b + c) % 2 = (c + d) % 2
This means all consecutive pairs in the subsequence must have sums with the same parity (all even or all odd).
Intuition
Let's think about what the constraint (a + b) % 2 = (b + c) % 2
really means.
If (a + b) % 2 = (b + c) % 2
, we can expand this:
(a + b) % 2 = (b + c) % 2
- This means
a % 2 + b % 2
andb % 2 + c % 2
have the same parity - Since
b % 2
appears in both sums, for them to have the same parity, we needa % 2 = c % 2
Following this pattern for a longer subsequence [a, b, c, d, e, ...]
:
(a + b) % 2 = (b + c) % 2
impliesa % 2 = c % 2
(b + c) % 2 = (c + d) % 2
impliesb % 2 = d % 2
(c + d) % 2 = (d + e) % 2
impliesc % 2 = e % 2
This reveals a pattern: all elements at odd positions (1st, 3rd, 5th...) must have the same parity, and all elements at even positions (2nd, 4th, 6th...) must have the same parity.
Since we're working with modulo 2, each element can only be 0 or 1 after taking modulo. This means we have limited patterns for valid subsequences:
- All odd-positioned elements are 0, all even-positioned elements are 0
- All odd-positioned elements are 0, all even-positioned elements are 1
- All odd-positioned elements are 1, all even-positioned elements are 0
- All odd-positioned elements are 1, all even-positioned elements are 1
To track the longest valid subsequence, we can use dynamic programming where f[x][y]
represents the length of the longest valid subsequence ending with the last element having value x % 2
and the second-to-last element having value y % 2
. When we encounter a new element with value x % 2
, we can extend any valid subsequence ending with y
where the pattern (y + x) % 2
matches what we need.
Learn more about Dynamic Programming patterns.
Solution Approach
Based on our intuition, we implement a dynamic programming solution where we track the longest valid subsequences based on the modulo values of the last two elements.
We define f[x][y]
as the length of the longest valid subsequence where:
- The last element modulo 2 equals
x
- The second-to-last element modulo 2 equals
y
Initially, all values in f
are set to 0.
Algorithm Steps:
-
Initialize the DP table: Create a 2×2 matrix
f
since we're working with modulo 2 (k=2). -
Process each element: For each number in
nums
:- Calculate
x = num % 2
to get its parity
- Calculate
-
Update all possible subsequences: For each possible value
j
from 0 to 1:- Calculate what the previous element's modulo value should be:
y = (j - x + 2) % 2
- Here,
j
represents the target sum modulo value(y + x) % 2 = j
- Update the DP state:
f[x][y] = f[y][x] + 1
- This means we're extending a subsequence that previously ended with value
y
and second-to-last valuex
, by adding the current element with valuex
- Calculate what the previous element's modulo value should be:
-
Track the maximum: Keep updating the answer with the maximum value found in any
f[x][y]
.
Why the formula y = (j - x + k) % k
works:
- We want
(y + x) % k = j
- Solving for
y
:y = (j - x) % k
- Adding
k
ensures the result is non-negative:y = (j - x + k) % k
Example walkthrough:
For nums = [1, 2, 3, 4]
with modulo values [1, 0, 1, 0]
:
- Processing 1 (x=1): Updates states for possible previous elements
- Processing 2 (x=0): Can extend subsequences ending with appropriate values
- Processing 3 (x=1): Can form valid subsequences like [1,2,3] where odd positions have value 1
- Processing 4 (x=0): Can extend to form [1,2,3,4]
The beauty of this approach is that it automatically handles all four possible patterns (00, 01, 10, 11 for odd/even positions) by tracking all possible last-two-element combinations.
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 the algorithm with nums = [1, 2, 3, 4]
.
First, convert to modulo 2: [1, 0, 1, 0]
Initialize f[2][2] = [[0,0], [0,0]]
and ans = 0
.
Step 1: Process nums[0] = 1 (x = 1)
- For j = 0: y = (0 - 1 + 2) % 2 = 1
f[1][1] = f[1][1] + 1 = 0 + 1 = 1
- This starts a subsequence [1]
- For j = 1: y = (1 - 1 + 2) % 2 = 0
f[1][0] = f[0][1] + 1 = 0 + 1 = 1
- This also starts a subsequence [1]
- Current f:
[[0,0], [1,1]]
, ans = 1
Step 2: Process nums[1] = 2 (x = 0)
- For j = 0: y = (0 - 0 + 2) % 2 = 0
f[0][0] = f[0][0] + 1 = 0 + 1 = 1
- Starts subsequence [2]
- For j = 1: y = (1 - 0 + 2) % 2 = 1
f[0][1] = f[1][0] + 1 = 1 + 1 = 2
- Extends [1] to [1,2] (sum: 1+0=1, odd)
- Current f:
[[1,2], [1,1]]
, ans = 2
Step 3: Process nums[2] = 3 (x = 1)
- For j = 0: y = (0 - 1 + 2) % 2 = 1
f[1][1] = f[1][1] + 1 = 1 + 1 = 2
- Extends some subsequence
- For j = 1: y = (1 - 1 + 2) % 2 = 0
f[1][0] = f[0][1] + 1 = 2 + 1 = 3
- Extends [1,2] to [1,2,3] (sums: 1+0=1, 0+1=1, both odd)
- Current f:
[[1,2], [3,2]]
, ans = 3
Step 4: Process nums[3] = 4 (x = 0)
- For j = 0: y = (0 - 0 + 2) % 2 = 0
f[0][0] = f[0][0] + 1 = 1 + 1 = 2
- For j = 1: y = (1 - 0 + 2) % 2 = 1
f[0][1] = f[1][0] + 1 = 3 + 1 = 4
- Extends [1,2,3] to [1,2,3,4] (sums: 1+0=1, 0+1=1, 1+0=1, all odd)
- Final f:
[[2,4], [3,2]]
, ans = 4
The longest valid subsequence is [1,2,3,4] with length 4, where all consecutive pair sums are odd.
Solution Implementation
1class Solution:
2 def maximumLength(self, nums: List[int]) -> int:
3 # Modulo value for the problem
4 modulo = 2
5
6 # dp[i][j] represents the maximum length of alternating subsequence
7 # ending with remainder i, where the previous element had remainder j
8 dp = [[0] * modulo for _ in range(modulo)]
9
10 # Track the maximum subsequence length found
11 max_length = 0
12
13 # Process each number in the array
14 for num in nums:
15 # Get the remainder when divided by modulo
16 remainder = num % modulo
17
18 # Try all possible previous remainders
19 for prev_remainder in range(modulo):
20 # Calculate what the remainder before prev_remainder should be
21 # for the alternating pattern to continue
22 target_remainder = (prev_remainder - remainder + modulo) % modulo
23
24 # Update dp: extend the subsequence ending at (target_remainder, prev_remainder)
25 # by adding current element with remainder
26 dp[remainder][target_remainder] = dp[target_remainder][prev_remainder] + 1
27
28 # Update the maximum length found so far
29 max_length = max(max_length, dp[remainder][target_remainder])
30
31 return max_length
32
1class Solution {
2 public int maximumLength(int[] nums) {
3 // Define modulo value
4 int modValue = 2;
5
6 // dp[i][j] represents the maximum length of subsequence ending with
7 // two consecutive elements having remainders i and j respectively
8 int[][] dp = new int[modValue][modValue];
9
10 // Track the maximum subsequence length found
11 int maxLength = 0;
12
13 // Process each number in the array
14 for (int num : nums) {
15 // Get the remainder when divided by modValue
16 int currentRemainder = num % modValue;
17
18 // Try all possible previous remainders
19 for (int targetSum = 0; targetSum < modValue; ++targetSum) {
20 // Calculate what the previous remainder should be
21 // such that (previousRemainder + currentRemainder) % modValue = targetSum
22 int previousRemainder = (targetSum - currentRemainder + modValue) % modValue;
23
24 // Update dp table: extend the subsequence ending with (previousRemainder, currentRemainder)
25 // by using the previous subsequence that ended with (currentRemainder, previousRemainder)
26 dp[currentRemainder][previousRemainder] = dp[previousRemainder][currentRemainder] + 1;
27
28 // Update the maximum length found so far
29 maxLength = Math.max(maxLength, dp[currentRemainder][previousRemainder]);
30 }
31 }
32
33 return maxLength;
34 }
35}
36
1class Solution {
2public:
3 int maximumLength(vector<int>& nums) {
4 // Define modulo value
5 const int MOD = 2;
6
7 // dp[i][j] represents the maximum length of subsequence ending with value i,
8 // where the previous element modulo MOD plus current element modulo MOD equals j
9 int dp[MOD][MOD];
10 memset(dp, 0, sizeof(dp));
11
12 int maxLength = 0;
13
14 // Process each number in the array
15 for (int num : nums) {
16 // Get the remainder when divided by MOD
17 int currentRemainder = num % MOD;
18
19 // Try all possible target sums (0 to MOD-1)
20 for (int targetSum = 0; targetSum < MOD; ++targetSum) {
21 // Calculate what the previous remainder should be
22 // to achieve the target sum with current remainder
23 int previousRemainder = (targetSum - currentRemainder + MOD) % MOD;
24
25 // Update dp table: extend the subsequence ending at previousRemainder
26 // by adding the current element
27 dp[currentRemainder][previousRemainder] = dp[previousRemainder][currentRemainder] + 1;
28
29 // Update the maximum length found so far
30 maxLength = max(maxLength, dp[currentRemainder][previousRemainder]);
31 }
32 }
33
34 return maxLength;
35 }
36};
37
1/**
2 * Finds the maximum length of a subsequence where the sum of any two adjacent elements is divisible by k=2
3 * @param nums - The input array of numbers
4 * @returns The maximum length of valid subsequence
5 */
6function maximumLength(nums: number[]): number {
7 // Define modulo value k
8 const k: number = 2;
9
10 // Create a 2D frequency table to track subsequence lengths
11 // frequencyTable[i][j] represents the length of subsequence ending with remainders i and j
12 const frequencyTable: number[][] = Array.from({ length: k }, () => Array(k).fill(0));
13
14 // Track the maximum subsequence length found
15 let maxLength: number = 0;
16
17 // Process each number in the input array
18 for (let currentNum of nums) {
19 // Get the remainder when divided by k
20 currentNum %= k;
21
22 // Try to extend existing subsequences by appending current number
23 for (let prevRemainder = 0; prevRemainder < k; ++prevRemainder) {
24 // Calculate the required remainder for the sum to be divisible by k
25 // (prevRemainder + currentNum) % k should equal 0
26 const targetRemainder: number = (prevRemainder - currentNum + k) % k;
27
28 // Update the frequency table by extending the subsequence
29 // The new subsequence ends with (currentNum, targetRemainder)
30 frequencyTable[currentNum][targetRemainder] = frequencyTable[targetRemainder][currentNum] + 1;
31
32 // Update the maximum length if we found a longer subsequence
33 maxLength = Math.max(maxLength, frequencyTable[currentNum][targetRemainder]);
34 }
35 }
36
37 return maxLength;
38}
39
Time and Space Complexity
The time complexity is O(n × k)
, where n
is the length of the array nums
and k = 2
.
For each element in nums
, we iterate through k
possible values (the inner loop with j
ranging from 0 to k-1
). Since we process all n
elements and perform k
iterations for each element, the total time complexity is O(n × k)
. With k = 2
, this simplifies to O(2n) = O(n)
.
The space complexity is O(k²)
, where k = 2
.
The 2D array f
is initialized with dimensions k × k
, which requires k²
space. Since k = 2
is a constant in this code, the space complexity is O(2²) = O(4) = O(1)
constant space.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect DP State Update Order
A critical pitfall is updating the wrong DP states or in the wrong order. The code shows dp[remainder][target_remainder] = dp[target_remainder][prev_remainder] + 1
, but this can be confusing because we're simultaneously reading from and writing to the DP table.
The Issue: When we update dp[remainder][target_remainder]
, we might accidentally overwrite a value we need to read later in the same iteration.
Solution: The current implementation is actually correct because for each element, we only update states where the first index is the current element's remainder. Since we're iterating through different prev_remainder
values, we won't read and write to the same cell in one iteration.
2. Misunderstanding the Subsequence Validity Condition
Developers often misinterpret what makes a subsequence valid. The condition requires that ALL consecutive pairs have the same sum parity, not that elements alternate in some pattern.
The Issue: Assuming subsequences must follow patterns like [odd, even, odd, even] or [even, odd, even, odd].
Solution: Recognize that valid subsequences can have patterns like:
- All same parity: [1, 3, 5, 7] where all sums are even
- Mixed parities: [1, 2, 3, 4] where consecutive sums alternate but remain consistent
3. Off-by-One Error in Modulo Calculation
The formula target_remainder = (prev_remainder - remainder + modulo) % modulo
might be implemented incorrectly.
The Issue: Forgetting to add modulo
before taking the modulo operation, which can result in negative values in languages that preserve sign in modulo operations.
Incorrect:
target_remainder = (prev_remainder - remainder) % modulo # Can be negative!
Correct:
target_remainder = (prev_remainder - remainder + modulo) % modulo
4. Not Handling Edge Cases
The Issue: Not considering arrays with length 1 or 2, where any subsequence is automatically valid.
Solution: The current DP approach naturally handles these cases since:
- A single element forms a valid subsequence of length 1
- Any two elements form a valid subsequence of length 2
- The DP correctly counts these cases
5. Confusion About DP Table Meaning
The Issue: Misinterpreting what dp[i][j]
represents. It's NOT the length of subsequences with i odd elements and j even elements.
Clarification: dp[i][j]
represents the maximum length of a valid subsequence where:
- The last element has remainder
i
when divided by 2 - The second-to-last element has remainder
j
when divided by 2 - All consecutive pairs in this subsequence have the same sum parity
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
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!