376. Wiggle Subsequence
Problem Description
A wiggle sequence is a sequence where the differences between successive numbers strictly alternate between positive and negative. The first difference (if one exists) may be either positive or negative. A sequence with one element and a sequence with two non-equal elements are trivially wiggle sequences.
For example:
[1, 7, 4, 9, 2, 5]
is a wiggle sequence because the differences(6, -3, 5, -7, 3)
alternate between positive and negative[1, 4, 7, 2, 5]
is not a wiggle sequence because its first two differences are both positive[1, 7, 4, 5, 5]
is not a wiggle sequence because its last difference is zero
A subsequence is obtained by deleting some elements (possibly zero) from the original sequence, leaving the remaining elements in their original order.
Given an integer array nums
, you need to find and return the length of the longest wiggle subsequence that can be formed from nums
.
The key insight is that you're looking for the longest subsequence (not necessarily consecutive elements) where the differences between adjacent elements in the subsequence alternate between positive and negative. You can skip elements from the original array to create this subsequence while maintaining the relative order of the selected elements.
Intuition
The problem asks for the longest wiggle subsequence, which means we need to track sequences that alternate between going up and down. This naturally leads us to think about maintaining two different states at each position - one for sequences ending with an upward trend and one for sequences ending with a downward trend.
Consider what happens at each element: if we're currently at position i
, we can either extend a wiggle sequence by going up (if the previous element was smaller) or going down (if the previous element was larger). The key observation is that:
- To go up at position
i
, we need the previous part of the sequence to have been going down - To go down at position
i
, we need the previous part of the sequence to have been going up
This alternating nature suggests we need to track two things separately:
f[i]
: the longest wiggle sequence ending at positioni
with an upward trend (the last move was up)g[i]
: the longest wiggle sequence ending at positioni
with a downward trend (the last move was down)
For each position i
, we look at all previous positions j
where j < i
:
- If
nums[j] < nums[i]
, we can appendnums[i]
to create an upward move. The best previous sequence for this would be one that ended with a downward move at positionj
, which isg[j]
. Sof[i] = max(f[i], g[j] + 1)
- If
nums[j] > nums[i]
, we can appendnums[i]
to create a downward move. The best previous sequence for this would be one that ended with an upward move at positionj
, which isf[j]
. Sog[i] = max(g[i], f[j] + 1)
By maintaining these two states and ensuring we alternate between them, we naturally capture the wiggle property of the sequence. The final answer is the maximum value among all f[i]
and g[i]
values.
Solution Approach
We implement a dynamic programming solution using two arrays to track the wiggle sequences:
-
Initialize the DP arrays: Create two arrays
f
andg
, both of sizen
and initialized to1
:f[i]
represents the length of the longest wiggle sequence ending at indexi
with an upward trendg[i]
represents the length of the longest wiggle sequence ending at indexi
with a downward trend- All positions start with value
1
since a single element forms a valid wiggle sequence of length1
-
Set up the answer variable: Initialize
ans = 1
to track the maximum length found so far. -
Fill the DP arrays: For each position
i
from1
ton-1
:- Examine all previous positions
j
from0
toi-1
- When
nums[j] < nums[i]
: This creates an upward trend fromj
toi
- Update
f[i] = max(f[i], g[j] + 1)
- We use
g[j]
because to have an upward trend ati
, we need the sequence atj
to have ended with a downward trend
- Update
- When
nums[j] > nums[i]
: This creates a downward trend fromj
toi
- Update
g[i] = max(g[i], f[j] + 1)
- We use
f[j]
because to have a downward trend ati
, we need the sequence atj
to have ended with an upward trend
- Update
- When
nums[j] == nums[i]
: Skip this case as equal consecutive elements don't form a wiggle
- Examine all previous positions
-
Update the answer: After processing each position
i
, updateans = max(ans, f[i], g[i])
to keep track of the longest wiggle sequence found so far. -
Return the result: The final answer is the maximum length among all possible wiggle sequences ending at any position.
The time complexity is O(n²)
due to the nested loops, and the space complexity is O(n)
for storing the two DP arrays.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with the array nums = [1, 7, 4, 9, 2, 5]
.
Initial Setup:
n = 6
f = [1, 1, 1, 1, 1, 1]
(longest wiggle ending with upward trend)g = [1, 1, 1, 1, 1, 1]
(longest wiggle ending with downward trend)ans = 1
Processing i = 1 (nums[1] = 7):
- Check j = 0 (nums[0] = 1):
- Since 1 < 7 (upward trend), update
f[1] = max(1, g[0] + 1) = max(1, 2) = 2
- Since 1 < 7 (upward trend), update
- Result:
f = [1, 2, 1, 1, 1, 1]
,g = [1, 1, 1, 1, 1, 1]
- Update
ans = max(1, 2, 1) = 2
Processing i = 2 (nums[2] = 4):
- Check j = 0 (nums[0] = 1):
- Since 1 < 4 (upward),
f[2] = max(1, g[0] + 1) = 2
- Since 1 < 4 (upward),
- Check j = 1 (nums[1] = 7):
- Since 7 > 4 (downward),
g[2] = max(1, f[1] + 1) = max(1, 3) = 3
- Since 7 > 4 (downward),
- Result:
f = [1, 2, 2, 1, 1, 1]
,g = [1, 1, 3, 1, 1, 1]
- Update
ans = max(2, 2, 3) = 3
Processing i = 3 (nums[3] = 9):
- Check j = 0: 1 < 9,
f[3] = max(1, g[0] + 1) = 2
- Check j = 1: 7 < 9,
f[3] = max(2, g[1] + 1) = max(2, 2) = 2
- Check j = 2: 4 < 9,
f[3] = max(2, g[2] + 1) = max(2, 4) = 4
- Result:
f = [1, 2, 2, 4, 1, 1]
,g = [1, 1, 3, 1, 1, 1]
- Update
ans = 4
Processing i = 4 (nums[4] = 2):
- Check j = 0: 1 < 2,
f[4] = max(1, g[0] + 1) = 2
- Check j = 1: 7 > 2,
g[4] = max(1, f[1] + 1) = 3
- Check j = 2: 4 > 2,
g[4] = max(3, f[2] + 1) = max(3, 3) = 3
- Check j = 3: 9 > 2,
g[4] = max(3, f[3] + 1) = max(3, 5) = 5
- Result:
f = [1, 2, 2, 4, 2, 1]
,g = [1, 1, 3, 1, 5, 1]
- Update
ans = 5
Processing i = 5 (nums[5] = 5):
- Check j = 0: 1 < 5,
f[5] = max(1, g[0] + 1) = 2
- Check j = 1: 7 > 5,
g[5] = max(1, f[1] + 1) = 3
- Check j = 2: 4 < 5,
f[5] = max(2, g[2] + 1) = max(2, 4) = 4
- Check j = 3: 9 > 5,
g[5] = max(3, f[3] + 1) = max(3, 5) = 5
- Check j = 4: 2 < 5,
f[5] = max(4, g[4] + 1) = max(4, 6) = 6
- Result:
f = [1, 2, 2, 4, 2, 6]
,g = [1, 1, 3, 1, 5, 5]
- Update
ans = 6
Final Answer: 6
The longest wiggle subsequence is [1, 7, 4, 9, 2, 5]
with differences (6, -3, 5, -7, 3)
alternating between positive and negative.
Solution Implementation
1class Solution:
2 def wiggleMaxLength(self, nums: List[int]) -> int:
3 """
4 Find the length of the longest wiggle subsequence.
5 A wiggle sequence is one where differences between successive numbers
6 strictly alternate between positive and negative.
7
8 Args:
9 nums: List of integers
10
11 Returns:
12 Length of the longest wiggle subsequence
13 """
14 n = len(nums)
15 max_length = 1
16
17 # up[i]: length of longest wiggle subsequence ending at i with an upward trend
18 # (last difference is positive, i.e., nums[i] > nums[previous])
19 up = [1] * n
20
21 # down[i]: length of longest wiggle subsequence ending at i with a downward trend
22 # (last difference is negative, i.e., nums[i] < nums[previous])
23 down = [1] * n
24
25 # Dynamic programming: for each position i, check all previous positions j
26 for i in range(1, n):
27 for j in range(i):
28 if nums[j] < nums[i]:
29 # Current element is greater than previous
30 # Can extend a down-ending subsequence to create an up-ending one
31 up[i] = max(up[i], down[j] + 1)
32 elif nums[j] > nums[i]:
33 # Current element is less than previous
34 # Can extend an up-ending subsequence to create a down-ending one
35 down[i] = max(down[i], up[j] + 1)
36
37 # Update the maximum length found so far
38 max_length = max(max_length, up[i], down[i])
39
40 return max_length
41
1class Solution {
2 public int wiggleMaxLength(int[] nums) {
3 int n = nums.length;
4
5 // dp[i] represents the max wiggle length ending at index i with an upward trend
6 int[] dpUp = new int[n];
7 // dp[i] represents the max wiggle length ending at index i with a downward trend
8 int[] dpDown = new int[n];
9
10 // Initialize: single element can be considered as both up and down ending
11 dpUp[0] = 1;
12 dpDown[0] = 1;
13
14 int maxLength = 1;
15
16 // Fill the dp arrays
17 for (int i = 1; i < n; i++) {
18 // For each position i, check all previous positions j
19 for (int j = 0; j < i; j++) {
20 // If current > previous, we can extend a down-ending sequence
21 if (nums[j] < nums[i]) {
22 dpUp[i] = Math.max(dpUp[i], dpDown[j] + 1);
23 }
24 // If current < previous, we can extend an up-ending sequence
25 else if (nums[j] > nums[i]) {
26 dpDown[i] = Math.max(dpDown[i], dpUp[j] + 1);
27 }
28 }
29
30 // Update the maximum wiggle length found so far
31 maxLength = Math.max(maxLength, Math.max(dpUp[i], dpDown[i]));
32 }
33
34 return maxLength;
35 }
36}
37
1class Solution {
2public:
3 int wiggleMaxLength(vector<int>& nums) {
4 int n = nums.size();
5
6 // dp[i] represents the max length of wiggle subsequence ending at index i with an upward trend
7 vector<int> dp_up(n, 1);
8 // dp[i] represents the max length of wiggle subsequence ending at index i with a downward trend
9 vector<int> dp_down(n, 1);
10
11 int max_length = 1;
12
13 // Iterate through each position as potential end of subsequence
14 for (int i = 1; i < n; ++i) {
15 // Check all previous positions
16 for (int j = 0; j < i; ++j) {
17 // If current element is greater, we can extend a down-ending subsequence
18 if (nums[j] < nums[i]) {
19 dp_up[i] = max(dp_up[i], dp_down[j] + 1);
20 }
21 // If current element is smaller, we can extend an up-ending subsequence
22 else if (nums[j] > nums[i]) {
23 dp_down[i] = max(dp_down[i], dp_up[j] + 1);
24 }
25 }
26
27 // Update the maximum wiggle subsequence length
28 max_length = max({max_length, dp_up[i], dp_down[i]});
29 }
30
31 return max_length;
32 }
33};
34
1/**
2 * Finds the length of the longest wiggle subsequence in the given array.
3 * A wiggle sequence is one where differences between successive numbers strictly alternate between positive and negative.
4 *
5 * @param nums - Input array of numbers
6 * @returns The length of the longest wiggle subsequence
7 */
8function wiggleMaxLength(nums: number[]): number {
9 const arrayLength: number = nums.length;
10
11 // Dynamic programming array where upSequence[i] represents the length of the longest wiggle subsequence
12 // ending at index i with an upward trend (current element > previous element)
13 const upSequence: number[] = Array(arrayLength).fill(1);
14
15 // Dynamic programming array where downSequence[i] represents the length of the longest wiggle subsequence
16 // ending at index i with a downward trend (current element < previous element)
17 const downSequence: number[] = Array(arrayLength).fill(1);
18
19 // Track the maximum length found so far
20 let maxLength: number = 1;
21
22 // Iterate through each position in the array
23 for (let currentIndex: number = 1; currentIndex < arrayLength; ++currentIndex) {
24 // Check all previous positions to build the wiggle sequence
25 for (let previousIndex: number = 0; previousIndex < currentIndex; ++previousIndex) {
26 // If current element is greater than previous, we can extend a down sequence
27 if (nums[currentIndex] > nums[previousIndex]) {
28 upSequence[currentIndex] = Math.max(upSequence[currentIndex], downSequence[previousIndex] + 1);
29 }
30 // If current element is less than previous, we can extend an up sequence
31 else if (nums[currentIndex] < nums[previousIndex]) {
32 downSequence[currentIndex] = Math.max(downSequence[currentIndex], upSequence[previousIndex] + 1);
33 }
34 }
35
36 // Update the maximum length considering both up and down sequences ending at current position
37 maxLength = Math.max(maxLength, upSequence[currentIndex], downSequence[currentIndex]);
38 }
39
40 return maxLength;
41}
42
Time and Space Complexity
The time complexity is O(n^2)
, where n
is the length of the array nums
. This is because we have two nested loops - the outer loop runs from index 1 to n-1 (which is O(n)
), and for each iteration of the outer loop, the inner loop runs from 0 to i-1 (which in the worst case is also O(n)
). Therefore, the overall time complexity is O(n) * O(n) = O(n^2)
.
The space complexity is O(n)
, where n
is the length of the array nums
. This is because we create two arrays f
and g
, each of size n
, to store the dynamic programming states. These arrays require O(n) + O(n) = O(2n) = O(n)
space. The other variables (ans
, i
, j
) only require constant space O(1)
, so the overall space complexity is O(n)
.
Common Pitfalls
1. Inefficient Time Complexity - O(n²) Can Be Improved to O(n)
The current solution uses nested loops resulting in O(n²) time complexity, which is inefficient for large inputs. A critical observation is that we don't actually need to check all previous positions - we only need to track the best wiggle sequence lengths for the most recent up and down trends.
The Issue: The nested loop approach checks every pair (i, j) where j < i, but this is unnecessary. When processing position i, we only care about:
- The longest down-ending sequence before i (to potentially create an up-ending sequence at i)
- The longest up-ending sequence before i (to potentially create a down-ending sequence at i)
Optimized Solution:
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
n = len(nums)
if n <= 1:
return n
# Track the length of the longest wiggle sequence with:
# - up: last difference is positive (going up)
# - down: last difference is negative (going down)
up = down = 1
for i in range(1, n):
if nums[i] > nums[i-1]:
# Current trend is upward
# Can extend the best down-ending sequence
up = down + 1
elif nums[i] < nums[i-1]:
# Current trend is downward
# Can extend the best up-ending sequence
down = up + 1
# If nums[i] == nums[i-1], no update needed
return max(up, down)
Why This Works:
- At each position, we only need to know the best up-ending and down-ending sequences seen so far
- When we see an upward trend, we can extend any down-ending sequence by 1
- When we see a downward trend, we can extend any up-ending sequence by 1
- The order of updates matters: we use the previous values of up/down to compute new values
This reduces time complexity from O(n²) to O(n) and space complexity from O(n) to O(1), making it much more efficient for large inputs while maintaining correctness.
What's the relationship between a tree and a graph?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
Want a Structured Path to Master System Design Too? Don’t Miss This!