Skip to content
dsa450for five year olds

Dynamic Programming · 44 of 60

Longest alternating subsequence

Pick numbers in order so they go up, down, up, down like a zigzag, and make the zigzag as long as you can.

Simple way
time O(n^2)
room O(n)
Clever way
time O(n)
room O(1)

The problem, simply

You get a row of numbers. Choose some of them, keeping their order, so that the steps between chosen numbers switch direction every time: up then down then up, or down then up then down. Equal neighbors do not count as a step.

Give back the length of the longest such zigzag. It is called the longest alternating subsequence (LeetCode calls it a wiggle subsequence).

A story to remember it

Picture it

arrayarr
01234567891175101315105168

Ten numbers drawn as a row. The marked ones, 1, 17, 5, 15, 5, 16, 8, go up, down, up, down, up, down. That zigzag has length 7.

  • answer

Examples

A normal case

In
arr = [1, 17, 5, 10, 13, 15, 10, 5, 16, 8]
Out
7

One answer is 1, 17, 5, 15, 5, 16, 8.

Short and perfect

In
arr = [1, 5, 4]
Out
3

Up to 5, down to 4. The whole row zigzags.

Only going up

In
arr = [1, 2, 3, 4, 5]
Out
2

There is no turn, so the best is any two numbers, like 1 then 5.

All the same

In
arr = [4, 4, 4]
Out
1

Equal numbers are not a step up or down. One number alone is the best.

Way 1: the simple way

Make two tables. up[i] is the longest zigzag that ends at arr[i] with a step up. down[i] is the longest one that ends at arr[i] with a step down. (Trying every choice with plain recursion would be O(2^n); these tables remember the answers instead.)

  1. Every number alone: up[i] = down[i] = 1.
  2. For each i, look back at every j < i.
  3. If arr[j] < arr[i] (a step up), then up[i] = max(up[i], down[j] + 1).
  4. If arr[j] > arr[i] (a step down), then down[i] = max(down[i], up[j] + 1).
  5. The answer is the biggest value in either table.
grid
0123456789arrupdown117510131510516812244442661133335537

The full look back tables after Way 1. Column i holds the best zigzag that ends exactly at arr[i]: row up if its last step went up, row down if it went down. The biggest box is 7.

  • checking
  • answer
java
class Solution {
    public int longestAltSubseq(int[] arr) {
        int n = arr.length;
        if (n == 0) return 0;
        int[] up = new int[n], down = new int[n];
        int best = 1;
        for (int i = 0; i < n; i++) {
            up[i] = down[i] = 1;                              // the number alone
            for (int j = 0; j < i; j++) {
                if (arr[j] < arr[i]) up[i] = Math.max(up[i], down[j] + 1);   // step up
                if (arr[j] > arr[i]) down[i] = Math.max(down[i], up[j] + 1); // step down
            }
            best = Math.max(best, Math.max(up[i], down[i]));
        }
        return best;
    }
}

How long it takes: each number looks back at all earlier ones, O(n^2). Extra room: two arrays, O(n).

Way 2: the clever way

You do not need the whole table. Let up be the best zigzag so far whose last step went up, and down the best whose last step went down, over everything seen so far. Then only neighbors matter:

  1. Start with up = down = 1.
  2. If arr[i] > arr[i - 1], set up = down + 1.
  3. If arr[i] < arr[i - 1], set down = up + 1.
  4. If they are equal, change nothing.
  5. The answer is max(up, down).

Why only neighbors? If the path keeps climbing, the best zigzag that ends going up might as well end at the highest point of that climb, which is the newest number. So you can always pretend the zigzag uses the number right before this one.

Two counters ride the ups and downs

01 / 11

grid
0123456789updown11
arrayarr
01234567891175101315105168i

One number alone is a zigzag of length 1. up = best zigzag whose last step went up, down = best whose last step went down. Both start at 1.

up
1
down
1
  • looking now
  • checking
  • answer
  • just added
java
class Solution {
    public int longestAltSubseq(int[] arr) {
        int n = arr.length;
        if (n == 0) return 0;
        int up = 1, down = 1;   // best zigzag whose last step went up / down
        for (int i = 1; i < n; i++) {
            if (arr[i] > arr[i - 1]) up = down + 1;        // a step up turns a down-ending zigzag
            else if (arr[i] < arr[i - 1]) down = up + 1;   // a step down turns an up-ending zigzag
            // equal neighbors: nothing changes
        }
        return Math.max(up, down);
    }
}

How long it takes: one look at each number, O(n). Extra room: two counters, O(1).

Watch out for

  • An empty row must return 0, and a row of one number returns 1.
  • In Way 2, do not reset a counter when the direction does not change. The counters only ever grow.
  • Some versions demand that the first step is up (a < b > c ...). Then answer up and down with a fixed start, or keep separate counters that remember the first direction.

What interviewers ask next

  • Print one zigzag: take the first number, then every number where the direction turns, plus the last number of the run. In other words, keep the peaks and valleys.
  • Greedy view: remove equal neighbors, then the answer is 2 plus the number of times the direction turns (or 1 if all numbers are equal). For the first example the path turns 5 times, so 7.
  • Longest alternating subarray (no skipping): walk once and reset the length whenever two steps in a row go the same way.
  • Alternating by sign instead of size (positive, negative, positive): same two counter idea with signs.

Remember this

Grown-up words: the zigzag is an alternating (wiggle) subsequence, the two counters are the DP states "last step up" and "last step down", and keeping only peaks and valleys is the greedy view of the same answer.