LeetCode: Wiggle Sub-sequence (Dynamic Programming)

https://leetcode.com/problems/wiggle-subsequence/, problem description:

A sequence of numbers is called a wiggle sequence if 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 fewer than two elements is trivially a wiggle sequence.
For example, [1,7,4,9,2,5] is a wiggle sequence because the differences (6,-3,5,-7,3) are alternately positive and negative. In contrast, [1,4,7,2,5] and [1,7,4,5,5] are not wiggle sequences, the first because its first two differences are positive and the second because its last difference is zero.
Given a sequence of integers, return the length of the longest subsequence that is a wiggle sequence. A subsequence is obtained by deleting some number of elements (eventually, also zero) from the original sequence, leaving the remaining elements in their original order.
Examples:
Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.

Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].

Input: [1,2,3,4,5,6,7,8,9]
Output: 2

A naive approach would try all the permutations, on N! (intractable for large N's). The Dynamic Programming (DP) idea to solve this problem would be the following:

  • DP assumes that you know the solution for sub-problems, and then use those solutions to solve the current problem
  • We can assume that we know the solutions for all values from 1 to N-1
  • Initially the solutions for each number from 1..N is 1
  • The idea is that for each number from "i" 1..N-1 we'll have the best solution ending at index "i" wiggling positively, and ending at index "i" wiggling negatively
  • Then for each number "i", go to all the best solutions from 1..i-1 and based on the previous best solutions update the current solution for "i"
  • At the same time keep track of the best solution seen
Not extremely fast (not O(n)), but O(n^2). Code down below, cheers, Marcelo.



public class Solution {
    class Item
    {
        public int topLastNegative = 0;
        public int topLastPositive = 0;

        public Item(int topLastNegative, int topLastPositive)
        {
            this.topLastNegative = topLastNegative;
            this.topLastPositive = topLastPositive;
        }
    }

    public int WiggleMaxLength(int[] nums) {
        if(nums == null || nums.Length == 0) return 0;
                Item[] items = new Item[nums.Length];

            int solution = 1;

            //Init the items with the best solution thus far per item
            for (int i = 0; i < nums.Length; i++)
                items[i] = new Item(1, 1);

            for (int i = 0; i < nums.Length; i++)
            {
                for (int j = 0; j < i; j++)
                {
                    //Switch the current "i" in case this is a better solution
                    if (nums[i] > nums[j] && items[j].topLastNegative + 1 > items[i].topLastPositive) items[i].topLastPositive = items[j].topLastNegative + 1;
                    else if (nums[i] < nums[j] && items[j].topLastPositive + 1 > items[i].topLastNegative) items[i].topLastNegative = items[j].topLastPositive + 1;

                    //Change the global solution variable whenever applicable
                    if (items[i].topLastNegative > solution) solution = items[i].topLastNegative;
                    else if (items[i].topLastPositive > solution) solution = items[i].topLastPositive;
                }
            }

            return solution;
    
    }
}

Comments

  1. Very nice, Marcelo! I decided to use parallel arrays to decrease memory usage and instead of storing topLast* elements I story only best + the last wiggle turn but the general idea is the same.

    class Solution {
    private:
    template constexpr char signOf(T val) const {
    return (T(0) < val) - (val < T(0));
    }
    public:
    int wiggleMaxLength(const vector& nums) {
    if (nums.size() < 2) return nums.size();
    vector lengths(nums.size(), 1);
    vector signs(nums.size(), 0);
    for (int i = 1; i < nums.size(); ++i) {
    for (int j = 0; j < i; ++j) {
    if (nums[i] == nums[j]) continue;
    char sign = signOf(nums[j] - nums[i]);
    if (lengths[i] < lengths[j] + 1 && signs[j] != sign) {
    lengths[i] = lengths[j] + 1;
    signs[i] = sign;
    }
    }
    }
    return *max_element(lengths.cbegin(), lengths.cend());
    }
    };

    http://ideone.com/OD23yJ

    Hereby you've proven yet another time that you rock at DP ;)

    ReplyDelete

Post a Comment

Popular posts from this blog

Golang vs. C#: performance characteristics (simple case study)

Claude vs ChatGPT: A Coder's Perspective on LLM Performance

ProjectEuler Problem 719 (some hints, but no spoilers)