Facebook Pixel

2771. Longest Non-decreasing Subarray From Two Arrays

Problem Description

You have two arrays nums1 and nums2, both of length n and 0-indexed. Your task is to create a third array nums3 of the same length by selecting elements strategically.

For each position i in nums3, you must choose either nums1[i] or nums2[i] to place at that position. In other words:

  • nums3[i] = nums1[i] OR nums3[i] = nums2[i]

Your goal is to make the choices in such a way that nums3 contains the longest possible non-decreasing subarray. A non-decreasing subarray means each element is greater than or equal to the previous one (elements can be equal or increasing, but not decreasing).

A subarray is a contiguous sequence of elements from the array, and it must be non-empty.

Example to illustrate the concept: If nums1 = [2, 3, 1] and nums2 = [1, 2, 3], you could create:

  • nums3 = [2, 3, 3] by choosing from [nums1[0], nums1[1], nums2[2]]
  • nums3 = [1, 2, 3] by choosing all from nums2

The second choice gives a non-decreasing subarray of length 3, which would be optimal.

The function should return an integer representing the maximum possible length of a non-decreasing subarray that can be achieved in nums3 through optimal selection of elements from nums1 and nums2.

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

Intuition

The key insight is that at each position, we have a choice between two elements, and we need to track the longest non-decreasing subarray that can end at that position. This naturally leads to a dynamic programming approach.

Think about it this way: when we're at position i, we need to know what's the longest non-decreasing subarray we can build that ends at this position. But here's the catch - we have two choices for the element at position i: either nums1[i] or nums2[i].

This means we need to track two pieces of information:

  1. The longest non-decreasing subarray ending with an element from nums1
  2. The longest non-decreasing subarray ending with an element from nums2

Why do we need both? Because the choice we make at position i affects what we can choose at position i+1. If we end with nums1[i-1], then at position i, we can extend the subarray only if:

  • nums1[i] >= nums1[i-1] (if we choose nums1[i])
  • nums2[i] >= nums1[i-1] (if we choose nums2[i])

Similarly, if we ended with nums2[i-1], we have different conditions for extension.

By maintaining two variables f and g that represent the longest non-decreasing subarray ending with elements from nums1 and nums2 respectively, we can make optimal decisions at each step. At each position, we check all four possible transitions:

  • Previous ended with nums1, current chooses nums1
  • Previous ended with nums2, current chooses nums1
  • Previous ended with nums1, current chooses nums2
  • Previous ended with nums2, current chooses nums2

We take the maximum valid extension for each choice, and keep track of the overall maximum length seen so far. This greedy approach with dynamic programming ensures we find the optimal solution in a single pass through the arrays.

Learn more about Dynamic Programming patterns.

Solution Approach

We use dynamic programming with state compression to solve this problem efficiently. The implementation maintains two variables to track the current state:

  • f: Length of the longest non-decreasing subarray ending with an element from nums1
  • g: Length of the longest non-decreasing subarray ending with an element from nums2

Initialization: We start with f = g = 1 since any single element forms a non-decreasing subarray of length 1. The answer ans is also initialized to 1.

Main Algorithm: We iterate through positions i from 1 to n-1. For each position, we calculate new values ff and gg representing the longest non-decreasing subarrays ending at position i with nums1[i] and nums2[i] respectively.

For each position i, we examine four possible transitions:

  1. Extend f to ff: If nums1[i] >= nums1[i-1], we can extend the subarray ending with nums1[i-1] by choosing nums1[i]. Update: ff = max(ff, f + 1)

  2. Extend g to ff: If nums1[i] >= nums2[i-1], we can extend the subarray ending with nums2[i-1] by choosing nums1[i]. Update: ff = max(ff, g + 1)

  3. Extend f to gg: If nums2[i] >= nums1[i-1], we can extend the subarray ending with nums1[i-1] by choosing nums2[i]. Update: gg = max(gg, f + 1)

  4. Extend g to gg: If nums2[i] >= nums2[i-1], we can extend the subarray ending with nums2[i-1] by choosing nums2[i]. Update: gg = max(gg, g + 1)

State Update: After calculating ff and gg, we update our state variables: f = ff and g = gg. We also update the global answer: ans = max(ans, f, g).

Time and Space Complexity:

  • Time Complexity: O(n) - We iterate through the array once
  • Space Complexity: O(1) - We only use a constant number of variables

The beauty of this approach is that we don't need to store the entire DP table. By only keeping track of the previous state (f and g), we can compute the current state (ff and gg) and find the optimal solution in a single pass.

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 concrete example to understand how the algorithm works.

Given:

  • nums1 = [1, 4, 2, 3]
  • nums2 = [2, 1, 3, 4]

We'll track f (longest non-decreasing subarray ending with nums1) and g (longest ending with nums2).

Initial State (i=0):

  • f = 1 (choosing nums1[0] = 1)
  • g = 1 (choosing nums2[0] = 2)
  • ans = 1

Iteration 1 (i=1): Looking at position 1: nums1[1] = 4, nums2[1] = 1

Calculate ff (if we choose nums1[1] = 4):

  • Can extend from f? Check if 4 >= 1 (nums1[0]): ✓ → ff = 1 + 1 = 2
  • Can extend from g? Check if 4 >= 2 (nums2[0]): ✓ → ff = max(2, 1 + 1) = 2

Calculate gg (if we choose nums2[1] = 1):

  • Can extend from f? Check if 1 >= 1 (nums1[0]): ✓ → gg = 1 + 1 = 2
  • Can extend from g? Check if 1 >= 2 (nums2[0]): ✗ → gg stays 1

Update: f = 2, g = 2, ans = 2

Iteration 2 (i=2): Looking at position 2: nums1[2] = 2, nums2[2] = 3

Calculate ff (if we choose nums1[2] = 2):

  • Can extend from f? Check if 2 >= 4 (nums1[1]): ✗ → ff = 1
  • Can extend from g? Check if 2 >= 1 (nums2[1]): ✓ → ff = max(1, 2 + 1) = 3

Calculate gg (if we choose nums2[2] = 3):

  • Can extend from f? Check if 3 >= 4 (nums1[1]): ✗ → gg = 1
  • Can extend from g? Check if 3 >= 1 (nums2[1]): ✓ → gg = max(1, 2 + 1) = 3

Update: f = 3, g = 3, ans = 3

Iteration 3 (i=3): Looking at position 3: nums1[3] = 3, nums2[3] = 4

Calculate ff (if we choose nums1[3] = 3):

  • Can extend from f? Check if 3 >= 2 (nums1[2]): ✓ → ff = 3 + 1 = 4
  • Can extend from g? Check if 3 >= 3 (nums2[2]): ✓ → ff = max(4, 3 + 1) = 4

Calculate gg (if we choose nums2[3] = 4):

  • Can extend from f? Check if 4 >= 2 (nums1[2]): ✓ → gg = 3 + 1 = 4
  • Can extend from g? Check if 4 >= 3 (nums2[2]): ✓ → gg = max(4, 3 + 1) = 4

Update: f = 4, g = 4, ans = 4

Result: The maximum length is 4.

One optimal nums3 would be [2, 1, 2, 3] (choosing nums2[0], nums2[1], nums1[2], nums1[3]), which forms the non-decreasing subarray [1, 2, 3] from indices 1 to 3. Another optimal choice is [1, 1, 2, 3] (choosing nums1[0], nums2[1], nums1[2], nums1[3]), forming the complete non-decreasing array of length 4.

Solution Implementation

1class Solution:
2    def maxNonDecreasingLength(self, nums1: List[int], nums2: List[int]) -> int:
3        """
4        Find the maximum length of a non-decreasing subsequence where at each index i,
5        we can choose either nums1[i] or nums2[i].
6      
7        Dynamic Programming approach:
8        - dp_nums1: max length ending at current position if we choose from nums1
9        - dp_nums2: max length ending at current position if we choose from nums2
10        """
11        n = len(nums1)
12      
13        # Initialize: at position 0, we can choose either array element
14        # Both choices give us a subsequence of length 1
15        dp_nums1 = 1  # Max length if we choose nums1[i-1]
16        dp_nums2 = 1  # Max length if we choose nums2[i-1]
17        max_length = 1
18      
19        # Iterate through each position starting from index 1
20        for i in range(1, n):
21            # Calculate new dp values for current position
22            new_dp_nums1 = 1  # Min possible length if choosing nums1[i]
23            new_dp_nums2 = 1  # Min possible length if choosing nums2[i]
24          
25            # If choosing nums1[i], check what we can extend from previous position
26            if nums1[i] >= nums1[i - 1]:
27                # Can extend from previous nums1 choice
28                new_dp_nums1 = max(new_dp_nums1, dp_nums1 + 1)
29            if nums1[i] >= nums2[i - 1]:
30                # Can extend from previous nums2 choice
31                new_dp_nums1 = max(new_dp_nums1, dp_nums2 + 1)
32          
33            # If choosing nums2[i], check what we can extend from previous position
34            if nums2[i] >= nums1[i - 1]:
35                # Can extend from previous nums1 choice
36                new_dp_nums2 = max(new_dp_nums2, dp_nums1 + 1)
37            if nums2[i] >= nums2[i - 1]:
38                # Can extend from previous nums2 choice
39                new_dp_nums2 = max(new_dp_nums2, dp_nums2 + 1)
40          
41            # Update dp values for next iteration
42            dp_nums1, dp_nums2 = new_dp_nums1, new_dp_nums2
43          
44            # Update the maximum length found so far
45            max_length = max(max_length, dp_nums1, dp_nums2)
46      
47        return max_length
48
1class Solution {
2    public int maxNonDecreasingLength(int[] nums1, int[] nums2) {
3        int arrayLength = nums1.length;
4      
5        // Dynamic programming states:
6        // dpEndingWithNums1: max length of non-decreasing sequence ending with nums1[i-1]
7        // dpEndingWithNums2: max length of non-decreasing sequence ending with nums2[i-1]
8        int dpEndingWithNums1 = 1;
9        int dpEndingWithNums2 = 1;
10      
11        // Track the maximum length found so far
12        int maxLength = 1;
13      
14        // Iterate through each position starting from index 1
15        for (int i = 1; i < arrayLength; ++i) {
16            // Temporary variables to store new DP values for current position
17            int newDpEndingWithNums1 = 1;  // Minimum length is 1 (start new sequence)
18            int newDpEndingWithNums2 = 1;  // Minimum length is 1 (start new sequence)
19          
20            // Case 1: Choose nums1[i] to extend sequence ending with nums1[i-1]
21            if (nums1[i] >= nums1[i - 1]) {
22                newDpEndingWithNums1 = Math.max(newDpEndingWithNums1, dpEndingWithNums1 + 1);
23            }
24          
25            // Case 2: Choose nums1[i] to extend sequence ending with nums2[i-1]
26            if (nums1[i] >= nums2[i - 1]) {
27                newDpEndingWithNums1 = Math.max(newDpEndingWithNums1, dpEndingWithNums2 + 1);
28            }
29          
30            // Case 3: Choose nums2[i] to extend sequence ending with nums1[i-1]
31            if (nums2[i] >= nums1[i - 1]) {
32                newDpEndingWithNums2 = Math.max(newDpEndingWithNums2, dpEndingWithNums1 + 1);
33            }
34          
35            // Case 4: Choose nums2[i] to extend sequence ending with nums2[i-1]
36            if (nums2[i] >= nums2[i - 1]) {
37                newDpEndingWithNums2 = Math.max(newDpEndingWithNums2, dpEndingWithNums2 + 1);
38            }
39          
40            // Update DP states for next iteration
41            dpEndingWithNums1 = newDpEndingWithNums1;
42            dpEndingWithNums2 = newDpEndingWithNums2;
43          
44            // Update the global maximum length
45            maxLength = Math.max(maxLength, Math.max(dpEndingWithNums1, dpEndingWithNums2));
46        }
47      
48        return maxLength;
49    }
50}
51
1class Solution {
2public:
3    int maxNonDecreasingLength(vector<int>& nums1, vector<int>& nums2) {
4        int n = nums1.size();
5      
6        // dp[i] represents the max length of non-decreasing sequence ending at index i
7        // dpEndingWithNums1: max length when choosing nums1[i] at current position
8        // dpEndingWithNums2: max length when choosing nums2[i] at current position
9        int dpEndingWithNums1 = 1;
10        int dpEndingWithNums2 = 1;
11        int maxLength = 1;
12      
13        // Iterate through each position starting from index 1
14        for (int i = 1; i < n; ++i) {
15            // Calculate new dp values for current position
16            int newDpEndingWithNums1 = 1;  // At least we can start a new sequence from current position
17            int newDpEndingWithNums2 = 1;
18          
19            // If we choose nums1[i] at current position
20            // Check if we can extend from previous position where nums1[i-1] was chosen
21            if (nums1[i] >= nums1[i - 1]) {
22                newDpEndingWithNums1 = max(newDpEndingWithNums1, dpEndingWithNums1 + 1);
23            }
24            // Check if we can extend from previous position where nums2[i-1] was chosen
25            if (nums1[i] >= nums2[i - 1]) {
26                newDpEndingWithNums1 = max(newDpEndingWithNums1, dpEndingWithNums2 + 1);
27            }
28          
29            // If we choose nums2[i] at current position
30            // Check if we can extend from previous position where nums1[i-1] was chosen
31            if (nums2[i] >= nums1[i - 1]) {
32                newDpEndingWithNums2 = max(newDpEndingWithNums2, dpEndingWithNums1 + 1);
33            }
34            // Check if we can extend from previous position where nums2[i-1] was chosen
35            if (nums2[i] >= nums2[i - 1]) {
36                newDpEndingWithNums2 = max(newDpEndingWithNums2, dpEndingWithNums2 + 1);
37            }
38          
39            // Update dp values for next iteration
40            dpEndingWithNums1 = newDpEndingWithNums1;
41            dpEndingWithNums2 = newDpEndingWithNums2;
42          
43            // Update the maximum length found so far
44            maxLength = max(maxLength, max(dpEndingWithNums1, dpEndingWithNums2));
45        }
46      
47        return maxLength;
48    }
49};
50
1/**
2 * Finds the maximum length of a non-decreasing subarray that can be formed
3 * by choosing elements from either nums1 or nums2 at each position
4 * @param nums1 - First array of numbers
5 * @param nums2 - Second array of numbers
6 * @returns Maximum length of non-decreasing subarray
7 */
8function maxNonDecreasingLength(nums1: number[], nums2: number[]): number {
9    const n: number = nums1.length;
10  
11    // dp1: max length ending at current position with nums1[i] chosen
12    // dp2: max length ending at current position with nums2[i] chosen
13    let dp1: number = 1;
14    let dp2: number = 1;
15    let maxLength: number = 1;
16  
17    // Iterate through arrays starting from second element
18    for (let i = 1; i < n; ++i) {
19        // Calculate new dp values for current position
20        let newDp1: number = 1;  // Min length if starting fresh at position i with nums1[i]
21        let newDp2: number = 1;  // Min length if starting fresh at position i with nums2[i]
22      
23        // If nums1[i] can extend the sequence ending with nums1[i-1]
24        if (nums1[i] >= nums1[i - 1]) {
25            newDp1 = Math.max(newDp1, dp1 + 1);
26        }
27      
28        // If nums1[i] can extend the sequence ending with nums2[i-1]
29        if (nums1[i] >= nums2[i - 1]) {
30            newDp1 = Math.max(newDp1, dp2 + 1);
31        }
32      
33        // If nums2[i] can extend the sequence ending with nums1[i-1]
34        if (nums2[i] >= nums1[i - 1]) {
35            newDp2 = Math.max(newDp2, dp1 + 1);
36        }
37      
38        // If nums2[i] can extend the sequence ending with nums2[i-1]
39        if (nums2[i] >= nums2[i - 1]) {
40            newDp2 = Math.max(newDp2, dp2 + 1);
41        }
42      
43        // Update dp values for next iteration
44        dp1 = newDp1;
45        dp2 = newDp2;
46      
47        // Update the maximum length found so far
48        maxLength = Math.max(maxLength, dp1, dp2);
49    }
50  
51    return maxLength;
52}
53

Time and Space Complexity

Time Complexity: O(n)

The algorithm iterates through the arrays once with a single for loop from index 1 to n-1. Within each iteration, it performs a constant number of operations:

  • Four comparison operations to check if elements are non-decreasing
  • Several max operations to update ff and gg
  • Variable assignments and one final max operation to update ans

Since all operations inside the loop take constant time O(1) and the loop runs n-1 times, the overall time complexity is O(n), where n is the length of the input arrays.

Space Complexity: O(1)

The algorithm uses only a fixed number of variables regardless of input size:

  • Variables f and g to track the maximum non-decreasing lengths ending at the previous position
  • Variables ff and gg as temporary storage for the current position
  • Variable ans to store the maximum length found
  • Loop variable i

No additional data structures that scale with input size are created. Therefore, the space complexity is O(1) - constant space.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect State Initialization for New Positions

A common mistake is forgetting that at each position, we can always start a new non-decreasing subarray of length 1, regardless of previous choices. Some implementations might incorrectly initialize new_dp_nums1 and new_dp_nums2 to 0 or forget to consider this base case.

Incorrect approach:

# Wrong: Assuming we must extend from previous
new_dp_nums1 = 0  # This would fail if no extension is possible
new_dp_nums2 = 0

Correct approach:

# Right: We can always start fresh with length 1
new_dp_nums1 = 1
new_dp_nums2 = 1

2. Mixing Up Comparison Indices

Another frequent error is confusing which elements to compare when checking if we can extend a subarray. The key is to compare the current element we want to choose with the previous element that was actually chosen.

Incorrect approach:

# Wrong: Comparing with wrong array's previous element
if nums1[i] >= nums1[i-1]:
    new_dp_nums2 = max(new_dp_nums2, dp_nums1 + 1)  # Wrong assignment

Correct approach:

# Right: If choosing nums1[i], compare with what was chosen at i-1
if nums1[i] >= nums1[i-1]:  # Previous choice was nums1
    new_dp_nums1 = max(new_dp_nums1, dp_nums1 + 1)
if nums1[i] >= nums2[i-1]:  # Previous choice was nums2
    new_dp_nums1 = max(new_dp_nums1, dp_nums2 + 1)

3. Forgetting to Update the Global Maximum

Some solutions might only return the final state values (dp_nums1 or dp_nums2) without tracking the maximum throughout the iteration. The longest non-decreasing subarray might end at any position, not necessarily at the last index.

Incorrect approach:

# Wrong: Only considering final position
for i in range(1, n):
    # ... calculate new_dp_nums1 and new_dp_nums2
    dp_nums1, dp_nums2 = new_dp_nums1, new_dp_nums2
return max(dp_nums1, dp_nums2)  # Misses optimal subarrays ending earlier

Correct approach:

# Right: Track maximum at every position
max_length = 1
for i in range(1, n):
    # ... calculate new_dp_nums1 and new_dp_nums2
    dp_nums1, dp_nums2 = new_dp_nums1, new_dp_nums2
    max_length = max(max_length, dp_nums1, dp_nums2)
return max_length

4. Edge Case: Single Element Arrays

While the provided solution handles this correctly, a pitfall could be not considering arrays of length 1, where the answer is always 1.

Solution: Always initialize the maximum length to 1, as done in the correct implementation.

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

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More