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]
ORnums3[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 fromnums2
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
.
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:
- The longest non-decreasing subarray ending with an element from
nums1
- 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 choosenums1[i]
)nums2[i] >= nums1[i-1]
(if we choosenums2[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 choosesnums1
- Previous ended with
nums2
, current choosesnums1
- Previous ended with
nums1
, current choosesnums2
- Previous ended with
nums2
, current choosesnums2
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 fromnums1
g
: Length of the longest non-decreasing subarray ending with an element fromnums2
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:
-
Extend
f
toff
: Ifnums1[i] >= nums1[i-1]
, we can extend the subarray ending withnums1[i-1]
by choosingnums1[i]
. Update:ff = max(ff, f + 1)
-
Extend
g
toff
: Ifnums1[i] >= nums2[i-1]
, we can extend the subarray ending withnums2[i-1]
by choosingnums1[i]
. Update:ff = max(ff, g + 1)
-
Extend
f
togg
: Ifnums2[i] >= nums1[i-1]
, we can extend the subarray ending withnums1[i-1]
by choosingnums2[i]
. Update:gg = max(gg, f + 1)
-
Extend
g
togg
: Ifnums2[i] >= nums2[i-1]
, we can extend the subarray ending withnums2[i-1]
by choosingnums2[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 EvaluatorExample 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
andgg
- 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
andg
to track the maximum non-decreasing lengths ending at the previous position - Variables
ff
andgg
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.
What are the most two important steps in writing a depth first search function? (Select 2)
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!