Showing posts with label DP. Show all posts
Showing posts with label DP. Show all posts

Tuesday, December 28, 2021

[Leetcode] 813. Largest Sum of Averages

813. Largest Sum of Averages

You are given an integer array nums and an integer k. You can partition the array into at most k non-empty adjacent subarrays. The score of a partition is the sum of the averages of each subarray.

Note that the partition must use every integer in nums, and that the score is not necessarily an integer.

Return the maximum score you can achieve of all the possible partitions. Answers within 10-6 of the actual answer will be accepted.

Constraints:

1 <= nums.length <= 100

1 <= nums[i] <= 10^4

1 <= k <= nums.length


Analysis:

This question asks for the an maximum value, so we usually can consider dp first.

As it is known, the key for dp is to find the transition formula. Or find the key parameters than describe an "intermediate state" completely.

The intermediate state here represents a similar question but with a smaller size.

For this question, we can try to define the state as dp[i][k], where i is the index of the elements and k is the number of subarrays.

The meaning of dp[i][k] is the maximum of average sum ending with the ith element with at most k subarrays.

Then the next step is to derive the formula to calculate the value of dp[i][k] using the previous dp with smaller values of i and k.

dp[i][k] = max(dp[x][k-1] + sum(num[x+1] to nums[i])/(i-x)), where x from 0 to i-1.

With the meaning of dp[x][k-1], the above formula is not hard to understand:

The right-hand side (rhs) enumerates all the possible ways to calculate dp[i][k], so we just need to pick up the maximum of it.

In the implementation, the boundary conditions are tricky. The thumb rule is to make sure the dp[i][k] is valid. For example, dp[i>0][0] is not valid, since there is no such case that we have non-zero elements in 0 subarray.


See the code below:

class Solution {
public:
    double largestSumOfAverages(vector<int>& nums, int k) {
        // dp[i][j]: the max, ending at i, up to j subarray;
        // dp[i][j] = max(dp[d][j-1] + sum(nums[d+1], ..., nums[i])/(i-d))
        // where d from j-1 to i-1.
        double res = 0;
        int n = nums.size();
        vector<double> sums(n+1, 0);
        for(int i=0; i<n; ++i) sums[i+1] = sums[i] + nums[i];
        vector<vector<double>> dp(n+1, vector<double>(k+1, 0));
        for(int i=1; i<=n; ++i) {
            for(int j=min(i, k); j>=1; --j) {
                for(int d=max(j-1, 1); d<=i; ++d) {
                    double v = (j==1&&d>1) ? 0 : (dp[d-1][j-1] + (sums[i]-sums[d-1])/(i-d+1));
                    dp[i][j] = max(dp[i][j], v);
                }
            }
        }
        return dp[n][k];
    }
};


If do not like the boundary conditions of the above dp solution, we can try dfs + memoization.

We can start from the beginning of the array, and stop to make a subarray at any possible places.


See the code below:


class Solution {
public:
    double largestSumOfAverages(vector<int>& nums, int k) {
        int n = nums.size();
        vector<vector<double>> memo(n, vector<double>(k, -1));
        return dfs(nums, 0, k-1, memo);
    }
private:
    double dfs(vector<int>& ns, int id, int k, vector<vector<double>>& memo) {
        // invalid ending since we cannot have more than k subarrays
        if(k<0 && id<ns.size()) return -2;
        if(id==ns.size()) return 0;
        if(memo[id][k] != -1) return memo[id][k];
        double t = 0, sum = 0;
        for(int i=id; i<ns.size(); ++i) {
            sum += ns[i];
            double v = dfs(ns, i+1, k-1, memo);
            if(v>-2) t = max(t, sum/(i-id+1) + v);
        }
        return memo[id][k] = t;
    }
};


  

Sunday, December 26, 2021

[Leetcode] 2104. Sum of Subarray Ranges

2104. Sum of Subarray Ranges

You are given an integer array nums. The range of a subarray of nums is the difference between the largest and smallest element in the subarray.

Return the sum of all subarray ranges of nums.

A subarray is a contiguous non-empty sequence of elements within an array.


Constraints:

1 <= nums.length <= 1000

-10^9 <= nums[i] <= 10^9

 

Follow-up: Could you find a solution with O(n) time complexity? 


Analysis:

First of all, we can use a two-layer for loop to solve this problem, since the maximum length is 10^3, so O(N^2) is acceptable for the online OJ.

For any array with N elements, there are N*(N+1)/2 sub-arrays. So we just need to get the min and max of each sub-array, and sum the diffs together.

See the code below:

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        long long res = 0, n = nums.size();
        for(int i=0; i<n; ++i) {
            int mx = nums[i], mi = mx;
            for(int j=i+1; j<n; ++j) {
                mx = max(nums[j], mx);
                mi = min(nums[j], mi);
                res += (long)mx - (long)mi;
            }
        }
        return res;
    }
};


As asked in the follow up, can we do better?

(Please be noted: the following logic is quite non-intuitive, and requires at least 10 min to read.)

The answer is yes. For example, with a stack, the time complexity can be decreased to O(N).

Think about one classical question about stack: In an integer array with size of N, for each element, what is the first element larger than it?

 One observation is that: if we maintain a monotonically increasing stack, then we can derive many useful information. For example, we can derive the answers to the above question for every elements in a time complexity of O(N), or just in one scan for every element. 

At the same time, we can get the number of the elements larger than every element on its right side continuously!

For example, say we have one array [6, 9, 3, 5, 10, 1, 4, 7], and we will use a stack to scan through the array from left to right.

One trick here is to use the index instead of value (recording index is obvious better than value with an array, since once we know the index, we know the value as well; but not the reverse)

At step 1, we have [0];

            2,                [0, 1]

At step 3, the index is 2 and the value is 3, and 3 < arr[1] == 9 (which is the top of the stack), we need to pop out the top element in the stack, so we get the number of elements larger than arr[1] continuously is (2 - 1 - 1) = 1. So res[1] = 0;

We will continue the comparison between the new top of the stack and arr[2]. Now the top is 0. and arr[0] == 6 > arr[2] == 3, so we need to pop it as well. So res[0] = 2 - 0  - 1= 1.

The stack becomes empty, so we just put 2 into it.

So far, we have obtained res[0] = 1 and res[1] = 0. Are they correct? 

It is straightforward that the answer is yes. For arr[0], there is one which is arr[1]; for arr[1], there is none.

Let continue the scan.

At step 4, index = 3, we have [2, 3];

             5,             4,                [2, 3, 4];

At step 6,              5, and we need to pop out elements again, since arr[5] < top of the stack

In this way, we can get all answers for every elements in a single scan!


The take-home message here is:

With a stack, we can get the number of the elements larger than every element on its right side continuously in one single scan!

Similarly, if we change the scanning direction, we can get the number of the elements larger than every element on its left side continuously in one scan as well.

After having these two values, we can calculate the number of sub-arrays with the current element as the minimum.

If the larger elements on the right side continuously is fr[i] and on the left side continuously is fl[i], then there are (fr[i] + 1) * (fl[i] + 1) sub-arrays with arr[i] as the minimum.

One corner case is when the array have elements with the same values. For example, the array is [5, 5, 5]. In this case, we can use a tiny trick: on the right side, we can calculate the number of elements no-smaller than the current element continuously; on the left side, we just calculate the number of elements strictly larger than the current element continuously.

For the [5, 5, 5] array, the fr array is [2, 1, 0], the fl array is [0, 0, 0].

So the number of the subarray with arr[0] as the minimum is: (2+1)*(0+1) = 3;

So the number of the subarray with arr[1] as the minimum is: (1+1)*(0+1) = 2;

So the number of the subarray with arr[2] as the minimum is: (0+1)*(0+1) = 1;

Similarly, we can count the number of the subarray with arr[i] as the maximum in O(N) as well (with a monotonically decreasing stack).

Once we figure out the frequency of the maximum elements and minimum elements, we can calculate the difference of all of them. (The order does not matter. Or we do not need to figure out the maximum and minimum of each subarray).


See the code below:

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        long long res = 0;
        int n = nums.size();
        stack<int> sk1;
        vector<int> dp1(n, 0), dp2(n, 0), dp3(n, 0), dp4(n, 0);
        for(int i=0; i<n; ++i) {
            while(sk1.size() && nums[sk1.top()] > nums[i]) {
                int id = sk1.top();
                sk1.pop();
                dp1[id] = i - id;
            }
            sk1.push(i);
        }
        while(sk1.size()) {
            int id = sk1.top();
            sk1.pop();
            dp1[id] = n - id;
        }
        for(int i=n-1; i>=0; --i) {
            while(sk1.size() && nums[sk1.top()] >= nums[i]) {
                int id = sk1.top();
                sk1.pop();
                dp2[id] = id - i;
            }
            sk1.push(i);
        }
        while(sk1.size()) {
            int id = sk1.top();
            sk1.pop();
            dp2[id] = id + 1;
        }
        for(int i=0; i<n; ++i) {
            while(sk1.size() && nums[sk1.top()] < nums[i]) {
                int id = sk1.top();
                sk1.pop();
                dp3[id] = i - id;
            }
            sk1.push(i);
        }
        while(sk1.size()) {
            int id = sk1.top();
            sk1.pop();
            dp3[id] = n - id;
        }
        for(int i=n-1; i>=0; --i) {
            while(sk1.size() && nums[sk1.top()] <= nums[i]) {
                int id = sk1.top();
                sk1.pop();
                dp4[id] = id - i;
            }
            sk1.push(i);
        }
        while(sk1.size()) {
            int id = sk1.top();
            sk1.pop();
            dp4[id] = id + 1;
        }
        for(int i=0; i<n; ++i) {
            // cout<<dp1[i]<<" "<<dp2[i]<<" "<<dp3[i]<<" "<<dp4[i]<<endl;
            res += (long)nums[i] * ((dp3[i]) * (dp4[i]) - (dp1[i]) * (dp2[i]));
        }
        return res;
    }
};


We can make the code much more concise. 

With a monotonically non-decreasing stack, when we pop out one element, we can calculate the number of (strictly) larger elements on the left side continuously as well. To get this, we just need to compare the index popped out with the current top of the stack, or left boundary if stack if empty. (Why?)


See the code below:

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        long long res = 0, n = nums.size();
        stack<int> sk;
        for(int i=0; i<=n; ++i) {
            while(sk.size() && (i==n || nums[sk.top()] > nums[i])) {
                int id = sk.top();
                sk.pop();
                int fr = i - id;
                int fl = sk.empty() ? id + 1 : id - sk.top();
                res -= (long long)nums[id]*fr*fl;
            }
            if(i<n) sk.push(i);
        }
        for(int i=0; i<=n; ++i) {
            while(sk.size() && (i==n || nums[sk.top()] < nums[i])) {
                int id = sk.top();
                sk.pop();
                int fr = i - id;
                int fl = sk.empty() ? id + 1 : id - sk.top();
                res += (long long)nums[id]*fr*fl;
            }
            if(i<n) sk.push(i);
        }
        return res;
    }
};



Sunday, September 13, 2020

Leetcode 312: Burst Balloons

 https://leetcode.com/problems/burst-balloons/description/


Notes:

This question is a very typical dp question. (in general, if a question can be down-size to a similar question, then it can be solved by dp)

One of the difficulties to this question is that the "boundary condition" is changing once we burst a balloon, since this balloon will disappear after the bursting.

One way to solve this is that: we can think about this question reversely. Instead of  starting from the first balloon to burst, we can start with the last balloon to burst.

The reason is that the last balloon will always has the fixed boundary condition. If we use dp[i][j] represents the maximum coins that we can obtained from [i, j] range, then

dp[i][j] = max(nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j])

where k in the range of [i, j].

To simplify the coding,, we can insert 1 in the begin and end of the original array, respectively.

The time complexity is O(N^3), and the space complexity if O(N^2).

See the code below:

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        // insert 1 in the begin and end of nums
        // i, j in the range of [1, n]
        // dp[i][j] = max(nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j])
        int n = nums.size();
        if(n==0) return 0;
        nums.insert(nums.begin(), 1);
        nums.push_back(1);
        vector<vector<int>> dp(n+2, vector<int>(n+2, 0));
        for(int i=1; i<n+1; ++i) dp[i][i] = nums[i-1]*nums[i]*nums[i+1];
        for(int i=n; i>0; --i) {
            for(int j=i+1; j<n+1; ++j) {
                for(int k=i; k<=j; ++k) {
                    dp[i][j] = max(dp[i][j], nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j]);
                }
            }
        }
        return dp[1][n];
    }
};

Saturday, September 12, 2020

Leetcode 650: 2 Keys Keyboard

 https://leetcode.com/problems/2-keys-keyboard/description/



Notes:

This question is a weaker version of Leetcode 651 (4 Keys Keyboard), but the idea is similar: 

      if we have solved the same question with a smaller n, then we should think about how to use the calculated results directly.

Let say we have solved all the smaller dp's with a smaller indexes than i, now we want to calculate dp[i].

Here dp[i] means the minimum number of steps to get n 'A's.

If we have dp[j], and i%j == 0, then one way to get dp[i] is: dp[j] + i/j. Why?

We just simply Ctr-C once on j 'A's, then (Ctr-V i/j  - 1) times. So the overall is dp[j] + i/j.

if(i%j != 0) then we cannot get i 'A's from j 'A'  (I will leave this to you to prove...)

Now the question becomes clear: we just need to go though all the smaller ones (which has been calculated at this moment), then find the smallest value.

There is maybe a even faster way to do this, keep tuned.

See the code below:

class Solution {
public:
    int minSteps(int n) {
        vector<int> dp(n+1, 0);
        for(int i=2; i<=n; ++i) {
            dp[i] = n;
            for(int j=1; j*2<=i; ++j) {
                if(i%j==0) dp[i] = min(dp[i], dp[j] + i/j); 
            }
        }
        return dp[n];
    }
};

Leetcode 651: 4 Keys Keyboard

 Imagine you have a special keyboard with the following keys:

Key 1: (A): Print one 'A' on screen.

Key 2: (Ctrl-A): Select the whole screen.

Key 3: (Ctrl-C): Copy selection to buffer.

Key 4: (Ctrl-V): Print buffer on screen appending it after what has already been printed.

Now, you can only press the keyboard for N times (with the above four keys), find out the maximum numbers of 'A' you can print on screen.

Example 1:

Input: N = 3
Output: 3
Explanation: 
We can at most get 3 A's on screen by pressing following key sequence:
A, A, A

 

Example 2:

Input: N = 7
Output: 9
Explanation: 
We can at most get 9 A's on screen by pressing following key sequence:
A, A, A, Ctrl A, Ctrl C, Ctrl V, Ctrl V

 

Note:

  1. 1 <= N <= 50
  2. Answers will be in the range of 32-bit signed integer.


Notes:

This question can be solved by dp.

Imagine that we have solved all the smaller one than i, now we need to solve dp[i].

dp[i] means the maximum number of A that can be printed.

then dp[i] can be derived from the solved dp's but with a smaller index.

If j is one of them, and dp[j] is solved, and j < i

Then dp[i] could be dp[j] * (i - j -1). Why?

Because we need to Ctr-A and Ctr-C before we can Ctr-V. For example, we have dp[3], then dp[5] could be dp[3]*(5 - 3 -1) = dp[3] (which obviously is not the optimal choice. A different way is that we can simply add two As directly (using key 1) after dp[3] which can give dp[3] + 2 for dp[5].)

Thus, in order to find the optimal choice, one way is that we can go through all the smaller ones by the above two ways:
1): use key2 + key3 + kye4s,  (this could be true for larger i), the formula for this is:
      dp[j] * (i - j -1)
2): use key1 directly, (this could be true for smaller i)
       dp[j] + (i - j)

If we decide to go through all the dp's with indexes smaller than i, then it meas the time complexity is O(N^2). In this question, N <= 50, so it should be Okay.

See the code below:

class Solution {
public:
    /**
     * @param N: an integer
     * @return: return an integer
     */
    int maxA(int N) {
        // write your code here
        vector<int> dp(N+1, 0);
        dp[1] = 1;
        dp[2] = 2;
        for(int i=3; i<=N; ++i) {
            for(int j=0; j<i; ++j) {
                dp[i] = max(dp[i], dp[j] + i - j);
                if(j+3<i) dp[i] = max(dp[i], dp[j]*(i-j-1));
            }
        }
        return dp[N];
    }
};


 

Tuesday, September 8, 2020

Leetcode 1542: Find Longest Awesome Substring

 https://leetcode.com/problems/find-longest-awesome-substring/description/



Notes:

This question is similar to another question, Leetcode 1371

The basic idea is the same: use a mask to memorize the counting of each letters. In this question, the letters are numbers from 0 to 9. 

Then we need to know whether the counting of each letter is even or odd. So we can use a bit to indicate this. For each bit, 0 means even count; 1 means odd.

If the same mask shows up again, then the substring between these two masks should have even numbers of every letter; and this is what we need to construct a palindrome.

Another possible situation for a palindrome is to have one letter with odd counting. This can also be checked by add one more letter (for each kind) to the mask.

See the code below:

class Solution {
public:
    int longestAwesome(string s) {
        int n = s.size(), res = 0, mask = 0;
        vector<int> dp(1<<10, -2);
        dp[0] = -1;
        for(int i=0; i<n; ++i) {
            mask ^= 1<<(s[i] - '0');
            if(dp[mask] != -2) res = max(res, i - dp[mask]);
            else dp[mask] = i;// memorize the first position
            for(int j=0; j<10; ++j) {// check one letter with odd count
                int t = mask ^ (1<<j);
                if(dp[t] != -2) res = max(res, i - dp[t]);
            }
        }
        return res;
    }
};

Monday, September 7, 2020

Leetcode 730: Count Different Palindromic Subsequences

 https://leetcode.com/problems/count-different-palindromic-subsequences/description/



Notes:

[update]:    3-dimensional dp

The two dimensional dp  (see the second method) is not easy to understand, a better way (in rationalization) is to use a three dimensional dp.

There is one condition that has not been used: there are only 4 types of letter.

So we can define a dp[i][j][k], meaning the unique number of palindrome ending with letter (k + 'a') in the range of [i, j].  Since it is palindrome, so it should start with (k+'a') as well.

Then the total number of unique palindromes in the range of [i, j] should be the Sum (dp[i][j][k]) over all k values.

Now let us look at the transition formula:

1): if(i>j) dp[i][j][k] = 0;
2): if(i==j && s[i] == k + 'a')  dp[i][j][k] = 1;
3): if (s[i] != s[j])  dp[i][j][k] = dp[i+1][j][k] + dp[i][j-1][k] - dp[i+1][j-1][k];
4): if (s[i] == s[j])  dp[i][j][k] = 2 + Sum (dp[i+1][j-1][a]), where a can be all letters. 
     (note: 2 for single "s[i]" and double "s[i]s[j]")

See the code below:

class Solution {
public:
    int countPalindromicSubsequences(string S) {
        int n = S.size(), mod = 1e9+7;
       // vector<vector<vector<long>>> dp(n, vector<vector<long>>(n, vector<long>(4, 0)));
        for(int i=0; i<n; ++i) dp[i][i][S[i]-'a'] = 1;
        
        for(int i=n-2; i>=0; --i) {
            for(int j=i+1; j<n; ++j) {
                for(int k=0; k<4; ++k) {
                    if(S[i] != S[j] || S[i] != (k + 'a')) {
                        dp[i][j][k] = (dp[i][j][k] + dp[i+1][j][k] + dp[i][j-1][k] - dp[i+1][j-1][k] + mod)%mod;
                    }
                    else {
                        dp[i][j][k] = (dp[i][j][k] + 2)%mod;
                        for(int t=0; t<4; ++t) {
                            dp[i][j][k] = (dp[i][j][k] + dp[i+1][j-1][t])%mod;
                        }
                    }
                }
            }
        }
        long res = 0;
        for(int i=0; i<4; ++i) {
            res = (res + dp[0][n-1][i])%mod;
        }
        return res;
    }
private:
    int dp[1000][1000][4];
};


 2-dimensional dp

This question is not easy to solve. Even though it may indicate using dp, but it is not easy to derive the correct state transition formula. 

One of the difficulties is how to avoid duplicated counting. If we distinguish the palindromes by their indexes in the string rather than themselves, then this question becomes relative easier. So let start with this first.

number of palindromes distinguished by their indexes in the string

Let use dp[i][j] to represent the number of palindromes in the range [i, j] with i <= j. Then,

1): if s[i] != s[j], dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1];
2): if s[i]== s[j], we have to choices: a): using s[i] and s[j]; b): not using s[i] and s[j]

Apparently, b) is the same as the case in 1). And in a), all the palindrome have to start with s[i] and end with s[j]. Thus, there are dp[i+1][j-1] + 1 in total. The 1 means "s[i]s[j]".

So the total palindromes in case 2) is:  number in a) + number in b)
       dp[i+1][j-1] + 1 + dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1] = dp[i+1][j] + dp[i][j-1] +1.

But this is not what this question asks about! In this question, distinguished palindromes are defined by their letters not by their indexes in the string. So we need to remove the duplicates.

number of palindromes distinguished by their letters

To start, let us define two kinds of dps: one is dp[i][j], another one is bp[i][j].

dp[i][j]: means the number of unique palindromes in the range [i, j] not necessary starting with the ith letter and ending with the jth letter;

bp[i][j]: means the number of unique palindromes in the range [i, j] starting with the ith letter and ending with the jth letter;

Then let re-look at the two situations we had analyzed above:

1): when s[i] != s[j], then dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]
2): when s[i] == s[j], we still have two cases: a): using s[i] and s[j]; b) not using s[i] and s[j]

Based on our destinations above, a) is bp[i][j] = dp[i+1][j-1] + 1, and b) is dp[i+1][j] + dp[i][j-1] - (duplicated part). If there are still duplicates between a) and b), we still need to remove them.

There are another three different situations for 2). 
(i) when there is no more s[i] letter in the range of [i+1, j-1]
     in this case, the count in b) is:
         dp[i+1][j] = dp[i+1][j-1] + 1   (1 for s[i])
         dp[i][j-1] = dp[i+1][j-1] + 1    (1 for s[j]) 
     Thus, the duplicated part is  dp[i+1][j-1] + 1   (since s[i] and s[j] are the same).
     So the total count for b) is  dp[i+1][j-1] + 1.
    
      Now let check the overlap between a) and b). In a), palindrome has to start with s[i] and end with s[i] as well, so there is no overlap between them. 
      Thus, the overall count is:  union( a + b)
      union(bq[i][j]   +  dp[i+1][j-1] + 1)  = dp[i+1][j-1] + 1 + dp[i+1][j-1] + 1 
       = 2*dp[i+1][j-1] + 2

(ii) there is only one more s[i] letter in the range [i+1, j-1], let say it is s[k], where i<k<j.
      then the count for b) is:
       dp[i+1][j] = dp[i+1][j-1] + bp[k][j],   the single "s[i]" has been included in dp[i+1][j-1], s[i] == s[k]
       dp[i][j-1] = dp[i+1][j-1] + bp[i][k],    the single "s[i]" has been included in dp[i+1][j-1], s[i] == s[k]

      Apparently, one overlap part between dp[i+1][j] and dp[i][j-1] is dp[i+1][j-1], so we just take one of them in the final counting. 

      But how to deal with bp[k][j] and bp[i][k]? They may have overlap as well!

      It seems very complicated, but actually is not. Since we have to remove the duplicates between 1) and 2) as well, so we just look at 1) first. In 1): the total counting (before removing duplicates with 2))  is bp[i][j]. It is easy to tell that both bp[k][j] and bp[i][k] are subsets of bp[i][j]. Thus, both of them can be removed directly!
      Then, the overall count becomes:  union( a + b)
      
       union(bq[i][j]   +  dp[i+1][j-1] + bp[k][j] + bp[i][k]) = dp[i+1][j-1] + 1 + dp[i+1][j-1] 
       = 2 * dp[i+1][j-1] + 1

(iii) where there are more than 1 s[i] letter in the range of [i+1, j-1]. 
        Let say, s[l] and s[r], i<l<r<j, and
        s[l] is the first letter equals to s[i] from the left in the range of [i+1, j-1];
        s[r] is the first letter equals to s[i] from the right in the range of [i+1, j-1];
       then the count for b) is:
       dp[i+1][j] = dp[i+1][j-1] + bp[l][j],   both single "s[i]" and "s[i]s[j]" have been included in dp[i+1][j-1],  due to that s[l] == s[r] == s[i];
       dp[i][j-1] = dp[i+1][j-1] + bp[i][r],   both single "s[i]" and "s[i]s[j]" have been included in dp[i+1][j-1],  due to that s[l] == s[r] == s[i].

        Similar to (ii), both bp[l][j] and bp[i][r] can be removed directly, since both of them are the subsets to bp[i][j]. For b) only one dp[i+1][j-1] remaining. Is there more overlap between a) and b)?

         The answer is yes. 
         dp[i][j-1] should include bp[l][r], and bp[i][j] should also include bp[l][r].

         Thus, we need to remove one bp[l][r] by - bp[l][r] = -(dp[l+1][r-1] + 1);

         Then the overall union is:  union( a + b)

        union(bq[i][j]   +  dp[i+1][j-1] + bp[l][j] + bp[i][r]) = union(bq[i][j] +  dp[i+1][j-1])
        = union (dp[i+1][j-1] + 1 + dp[i+1][j-1] - bq[l][j] - 1) =
        = 2 * dp[i+1][j-1]  - dp[l+1][r-1]


In summary, the station transition formula are:

1): if s[i] != s[j], dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1];
2): if s[i] == s[j],
      a): if there is no more letter s[i] in [i+1, j-1]
            dp[i][j] = 2 * dp[i+1][j-1] + 2;
      b): if these is only one letter s[i] in [i+1, j-1]
            dp[i][j] = 2 * dp[i+1][j-1] + 1;
      c): if there are 2 or more letter s[i] in [i+1, j-1]
           dp[i][j] = 2 * dp[i+1][j-1]  - dp[l+1][r-1]. (for the meaning of l and r, see the above analysis)

See the code below:

class Solution {
public:
    int countPalindromicSubsequences(string S) {
        int n = S.size(), mod = 1e9+7;
        vector<vector<long>> dp(n+1, vector<long>(n, 0));
        for(int i=0; i<n; ++i) dp[i][i] = 1;
        for(int i=n-2; i>=0; --i) {
            for(int j=i+1; j<n; ++j) {
                if(S[i] != S[j]) dp[i][j] = (dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1] + mod)%mod;
                else {
                    int left = i + 1, right = j-1;
                    while(left<n && S[left] != S[i]) ++left;
                    while(right>=0 && S[right] != S[i]) --right;
                    if(left>right) dp[i][j] = (dp[i+1][j-1]*2 + 2)%mod;
                    else if(left == right) dp[i][j] = (dp[i+1][j-1]*2 + 1)%mod;
                    else dp[i][j] = (dp[i+1][j-1]*2 - dp[left+1][right-1] + mod)%mod;
                }
            }
        }
        return dp[0][n-1];
    }
};

Sunday, September 6, 2020

Leetcode 1371: Find the Longest Substring Containing Vowels in Even Counts

 https://leetcode.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/description/


Notes:

This question can be solved by a method similar to "how many sub-array sum equals to a target value". If you have question about this, we can go step by step.

The naive method is to scan all the sub-strings, and then check the counts in each sub-string, and find the longest one. This needs O(n^2) time, so this is the upper-bound for this question.

Actually the largest case is n = 5 x 10^5, so we need a better method.

Let think about one letter only: what is the longest sub-string containing even count of one specific letter?

There are many ways to do this, but in order to achieve O(n) time complexity, we can use a bit to represents the count of the letter: 0 for even, 1 for odd. Then as we scan through the string, at each position, we know the count is even or odd. 

If even: the current longest sub-string is from the first even to the current position;
if odd:  the current longest sub-string is from the first odd to the current position.

Now, we need to monitor five letters, but the idea is the same. We just use a bit mask to replace one bit.

If we use a bit mask to record the counts of each letter; 0 for even, 1 for odd, then when the mask appear again, the sub-string between these two mask should contain even number of each letter.

One hash-map can be used to record the position (or index) of each mask when it appears for the first time, since we need this information to calculate the length of the current longest valid sub-string.

See the code below:

class Solution {
public:
    int findTheLongestSubstring(string s) {
        int res = 0, n = s.size(), mask = 0;
        vector<int> dp(1<<5, s.size());// only 5 letters, so 5 bits
        dp[0] = -1;// special case for boundary condition
        for(int i=0; i<n; ++i) {
            char a = s[i];
            if(a=='a') mask ^= 1<<0;
            else if(a=='e') mask ^= 1<<1;
            else if(a=='i') mask ^= 1<<2;
            else if(a=='o') mask ^= 1<<3;
            else if(a=='u') mask ^= 1<<4;
            res = max(res, i - dp[mask]);
            dp[mask] = min(i, dp[mask]);// record the first position
        }
        return res;
    }
};

Monday, August 31, 2020

Leetcode 1449. Form Largest Integer With Digits That Add up to Target

https://leetcode.com/problems/form-largest-integer-with-digits-that-add-up-to-target/description/



Notes:

This question belongs to knapsack problem.

Since each element (or digit) can be used as many times as needed, so it is a 01 knapsack problem.

To form the largest number, the first step should find the longest digits that can be formed, this is a typical knapsack problem.

After finding the longest digits that can be formed, we can greedily add the digits from the larger one to the smaller one. 

One of the key condition for the greedy way: 
         dp[target] = 1 + dp[target - cost[i]]

where dp[target] is the length of the longest digits that can be formed.

This equation means that: after taking the largest digit available at this moment, the rest part (target - cost[i]) can still matches the longest digits overall. Thus, it can always give the optimal solution.

See the code below:

class Solution {
public:
    string largestNumber(vector<int>& cost, int target) {
        vector<int> dp(target+1, -5000);
        dp[0] = 0;
        for(int i=0; i<=target; ++i) {
            for(int j=0; j<9; ++j) {
                if(i>= cost[j]) dp[i] = max(dp[i], 1 + dp[i - cost[j]]);
            }
        }
        if(dp[target]<=0) return "0";
        // cout<<dp[target]<<endl;
        string res = "";
        for(int i=8; i>=0; --i) {
            while(target>=cost[i] && dp[target] == 1 + dp[target - cost[i]]) {
                res.push_back(i+1+'0');
                target -= cost[i];
            }
        }
        return res;
    }
};

Sunday, August 30, 2020

Leetcode 1569. Number of Ways to Reorder Array to Get Same BST

 https://leetcode.com/problems/number-of-ways-to-reorder-array-to-get-same-bst/description/


Notes:

If I say if you know how to solve the walking robot question, then you should know how to solve this question, you will not believe it. Will you? 

If you do know how to solve the robot question, but still cannot solve this one, no worry, let start this question step by step.

This question can be transformed to a similar question with a smaller size. Thus it can be solved recursively or using dp for optimization.

Let us look at an example: [3, 4, 5, 1, 2]
in the first step: the left sub tree = [1, 2], and the right sub tree = [4, 5]
then in order to solve the original question, we need to solve the same question for these two sub trees.

Now let us work on the relation from the sub tree to the root tree. If we know there are x ways for the left sub tree, and y ways for the right sub tree. Then the question becomes how many ways for the root tree?

The first response is that: if left sub tree and right sub tree cannot be mixed, then there will be x*y ways for root tree. However, the left sub tree can mix with the right sub tree. And this is why this question is labeled as hard.

Let us continue to use the above example: there are 2 elements in the left sub tree and 2 as well in the right. As an initial try, we can place 1 from the left to three different positions: before 4, before 5, after 5, which are listed below:
1): [1, 4, 5]
2): [4, 1, 5]
3): [4, 5, 1]

Then we need to place 2 from left to each of the above three cases: the rule is that 2 has to be placed after 1
Thus, 
for 1): there are 3 places for 2; 
for 2): there are 2 places for 2;
for 3): there are 1 place for 2.

In total, there are 3 + 2 + 1 = 6 ways. Based on the question example, we need to remove the original one, thus the answer is 5.

So one of an important observations for mixing is that: for a fixed left and right array (or serialized tree sequence), the relative order of the left and right needs to be maintained. Then this question becomes a pure combination/permutation problem!

Let say we have m elements in the left, n elements in the right, and we are going to mix them, and we need to keep the relative orders of the elements in the left and right for the mixing. Then the answer is: (m+n)!/(m!*n!). So basically it is the full permutation of all these elements, divided by the full permutation of m and full permutation of n (since we have to keep the relative orders in the left and right.)

So you can use mathematical formula directly to calculate this one, but for fun, let us look at a different way to solve it using programming.

If you by any chance be aware of another question: walking robot (from most left up corner to the most right down corner, and can only walk down or right one step a time), then the ways of mixing is the same as the ways of the robot can go from the most left up corner to the most right down corner!!!

So we can use dp to solve the ways of mixing: dp[i][j] = dp[i][j-1] + dp[i-1][j]

With mixing solved, we need to look at back to the original question: there are x*y ways for the left and right without mixing; if the mixing is allowed, then there are x*y*dp[i][j], where i and j are the element numbers in the left sub tree and the right sub tree, respectively.

If you still have question, please leave it below. Any comment/discussion is welcome!

See the code below:

class Solution {
public:
    int numOfWays(vector<int>& nums) {
        int n = nums.size();
        vector<vector<long>> dp(n, vector<long>(n, 1));
        for(int i=1; i<n; ++i) {
            for(int j=1; j<n; ++j) {
                dp[i][j] = dp[i][j-1] + dp[i-1][j];
                dp[i][j] %= mod;
            }
        }
        return (dfs(nums, dp) - 1)%mod;
    }
private:
    int mod = 1e9+7;
    long dfs(vector<int>& nums, vector<vector<long>>& dp) {
        if(!nums.size()) return 1;
        vector<int> left, right;
        for(int i=1; i<nums.size(); ++i) {
            if(nums[i]>nums[0]) right.push_back(nums[i]);
            else left.push_back(nums[i]);
        }
        long lt = dfs(left, dp)%mod, rt = dfs(right, dp)%mod;
        long res =lt*rt%mod;
        return res*dp[left.size()][right.size()]%mod;
    }
};
 

Monday, August 10, 2020

Leetcode 1547. Minimum Cost to Cut a Stick

 https://leetcode.com/problems/minimum-cost-to-cut-a-stick/description/



Notes:

One way to think about this question is to down-size the question: for example, if we cut the wood [0, m] at position i, then it becomes two parts: the first part is [0, i], and the second part is [i, m], then we need to do the same thing to the first and second parts until it cannot be cut any more.

If we define dp[0][m] is the minimum cost to cut the wood from 0 to m, and there are several choices, then 

dp[0][m] = min(dp[0][m], dp[0][i] + dp[i][m])    where i>0 and i<m

dp[0][i] means the minimu cost to cut the wood from 0 to i with the same rule.

dp[i][m] means the minimu cost to cut the wood from i to m with the same rule.

The general formula becomes: 
dp[a][b] = min(dp[a][b], dp[a][i] + dp[i][b]), where i > a && i < b

See the code below:

class Solution {
public:
    int minCost(int n, vector<int>& cuts) {
        // dp[0][n-1] = min(dp[0][i] + dp[i][n-1]) + cuts[n-1] - cuts[0] + 1;
        sort(cuts.begin(), cuts.end());
        cuts.insert(cuts.begin(), 0);
        cuts.push_back(n);
        int m = cuts.size();
        vector<vector<int>> dp(m, vector<int>(m, 0));
        for(int i=2; i<m; ++i) {
            for(int j=i-2; j>=0; --j) {
                dp[j][i] = INT_MAX;
                for(int k=j+1; k<i; ++k) {
                    dp[j][i] = min(dp[j][i], dp[j][k] + dp[k][i]);
                }
                dp[j][i] += cuts[i] - cuts[j];
            }
        }
        return dp[0][m-1];
    }
};

Sunday, July 26, 2020

Leetcode 1531. String Compression II

https://leetcode.com/problems/string-compression-ii/description/
Notes:

One way to solve this problem is that: for each char, we can either delete it or keep it. If we try all the possibilities within the limits, then we will find the optimal answer. But the complexity is factorial (2^N, where N is the length of the string).

This method can be optimized by dynamic programming (dp). For dp, the key is to find a set of parameters that can describe a state completely.

To this question, we can use 4 parameters:
index:  the position in the string
k:  the number of char that can be deleted
last:  the last char
ct:  the count of the last char.

Then the transition formula can be written as:
if we delete a char:
    dp[index][k][last][ct] = dp[index+1][k-1][last][ct];
if we keep it:
there are two sub cases:
    1): when s[index] != last char
        dp[index][k][last][ct] = 1 + dp[index+1][k][s[index]][1];
    2): when s[index] == last char
    there are another 4 sub cases:
        a): if the ct == 1
        based on the question description, when the count is 1, it will not be written in the compressed string. But now the count will becomes 2, so it will be written into the compressed string, thus
            dp[index][k][last][ct] = 1 + dp[index+1][k][last][2];
        b): if the ct == 9
        the new count will be 10, thus
            dp[index][k][last][ct] = 1 + dp[index+1][k][last][10];
        c): if the ct == 99
            dp[index][k][last][ct] = 1 + dp[index+1][k][last][100];
        d): others, the count will be increased by 1, but there is no change on the length of the compressed string
            dp[index][k][last][ct] = dp[index+1][k][last][ct+1];

for each step:  we need to get the minimum of all these possible choices.

Further optimization can be done to reduce the size of the dp array. For example, we do not have to set the count (ct) range up to 100, since when ct in the range from 10 to 99, there is no change on the compressed string. So we can handle the case when s.size() == 100 separately. Then the ct can be set in the range of 10 (since 10 to 99 contributes then same length to the compressed string).

Another optimization is that: we can group some sub-cases together.

A top-down dfs + memo can be employed to this dp method.

See the code below:

class Solution {
public:
    int getLengthOfOptimalCompression(string s, int k) {
        // special case: otherwise needs too much memory for the memo:  27 x 4 M vs 2.7 x 4 M
        this->s = s;
        if(k==0) {
            int ct = 1;
            for(int i=1; i<s.size(); ++i) {
                if(s[i] == s[i-1]) ++ct;
            }
            if(ct == 100) return 4;
        }
        std::memset(memo, -1, sizeof memo);
        return dfs(0, k, 26, 0);
    }

private:
    int memo[101][101][27][11];
    string s;
    int dfs(int id, int k, int last, int ct) {
        if(k<0) return 99999;
        if(id == s.size()) return 0;
        if(memo[id][k][last][ct] != -1) return memo[id][k][last][ct];
        // remove s[id]
        int res = dfs(id+1, k-1, last, ct);
        // use s[id]
        if(s[id] - 'a' != last) {
            res = min(res, 1 + dfs(id+1, k, s[id] - 'a', 1));
        }
        else {
            int t = (ct == 1 || ct == 9) ? 1 : 0;
            res = min(res, t + dfs(id+1, k, last, min(10, ct + 1)));
        }
        return memo[id][k][last][ct] = res;
    }
};

Saturday, December 28, 2019

Leetcode 1301: Number of Paths with Max Score

https://leetcode.com/problems/number-of-paths-with-max-score/description/


Notes:

This question is designed for dp, or dynamic programming. And it is not an easy question, as labeled as hard.

Let focus on the first step first: to find the maximum score.

Let dp[i][j] represents the maximum score when reaching the position board[i][j] from the bottom right corner.

Then we can derive the following state relations:

if board[i][j] >= '1' && board[i][j]<='9',  dp[i][j] = max(dp[i+1][j], dp[i][j+1], dp[i+1][j+1]) + board[i][j] - '0';

if board[i][j] == 'S' or 'E', dp[i][j] = max(dp[i+1][j], dp[i][j+1], dp[i+1][j+1]) ;

if board[i][j] == 'X', dp[i][j] = -1;

Then there is a special case:  (no available previous steps...)

if(dp[i+1][j] == dp[i][j+1] == dp[i+1][j+1] == -1), dp[i][j] = -1;

After these, we still need to consider boundary condition.

if board[i][j] == 'X', and i==m-1, or j == n-1.

when i == m-1, we need to set dp[i+1][j] = -1; if j>0, dp[i+1][j-1] = -1;

when j == n-1, we need to set dp[i][j+1] = -1; if(i>0), dp[i-1][j+1] = -1;

(we can avoid the above boundary setting, if we set all dp values as -1 originally. Then we just need to set dp[m-1][n-1] = 0).

Okay, now we have finished the first step. So let move on to the second part.

If dp[0][0] < 0, meaning there is no path, we just return {0, 0}.

If not, we will use dfs (depth-first search) + memorization for the second part. The memorization is used to increase the time complexity.

Let memo[i][j] represents the number of ways reaching dp[i][j] from the bottom right corner.

Let set val = dp[i][j].

If(board[i][j] >='1' && board[i][j]<='9') we need to update the val -= board[i][j] - '0'.

Then memo[i][j] = (val==dp[i+1][j] ? memo[i+1][j] : 0) + (val==dp[i][j+1] ? memo[i][j+1] : 0) + (val==dp[i+1][j+1] ? memo[i+1][j+1] : 0).

One sentence summary: dp for the maximum score, then dfs + memo (or dp) for the first dp.

See the code below:

class Solution {
public:
    vector<int> pathsWithMaxScore(vector<string>& board) {
        int m = board.size(), n = board.front().size();
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
        for(int i=m-1; i>=0; --i) {
            for(int j=n-1; j>=0; --j) {
                if(dp[i+1][j] <0 && dp[i][j+1] <0 && dp[i+1][j+1] < 0 || board[i][j] == 'X') {
                    dp[i][j] = -1;
                    if(board[i][j] == 'X') {
                        if(i==m-1) {dp[i+1][j] = -1; if(j>0) dp[i+1][j-1] = -1;}
                        if(j==n-1) {dp[i][j+1] = -1; if(i>0) dp[i-1][j+1] = -1;}
                    }
                    continue;
                }
                dp[i][j] = max(max(dp[i+1][j], dp[i][j+1]), dp[i+1][j+1]);
                if(board[i][j] >= '1' && board[i][j] <='9') dp[i][j] += board[i][j] - '0';
            }
        }
        if(dp[0][0] < 0) return {0, 0};
        vector<vector<long>> memo(m+1, vector<long>(n+1, -1));
        int ways = dfs(0, 0, board, dp, memo);
        return {dp[0][0], ways};
    }

private:
    int cv = 1e9+7;
    int dfs(int i, int j, vector<string>& bs, vector<vector<int>>& dp, vector<vector<long>>& memo) {
        if(i+1==bs.size() && j+1 == bs.front().size()) return 1;
        if(i>=bs.size() || j>=bs.front().size()) return 0;
        if(memo[i][j] != - 1) return memo[i][j];
        int val = dp[i][j];
        if(bs[i][j]>='1' && bs[i][j] <='9') val -= bs[i][j] - '0';
        int a = 0, b = 0, c = 0;
        if(val == dp[i+1][j]) a = dfs(i+1, j, bs, dp, memo)% cv;
        if(val == dp[i][j+1]) b = dfs(i, j+1, bs, dp, memo)% cv;
        if(val == dp[i+1][j+1]) c = dfs(i+1, j+1, bs, dp, memo)%cv;
        return memo[i][j] = (a + b + c)%cv;
    }
};

Saturday, December 14, 2019

Leetcode 115: Distinct Subsequences

https://leetcode.com/problems/distinct-subsequences/description/


Notes:

This question can be solved by dynamic programming.

Let dp[i][j] represents the distinct subsequences in S[0, i] to T[0, j].

When S[i] == T[j], S[i] can be either used to match T[j] or not. Thus,

dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

When S[i] != T[j], S[i] cannot be used to match T[j]. Thus,

dp[i][j] = dp[i-1][j].

See the code below:

class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(), n = t.size();
        if(m<n) return 0;
        vector<vector<long>> dp(m+1, vector<long>(n+1, 0));
        for(int i=0; i<=m; ++i) dp[i][0] = 1;
        for(int i=1; i<=m; ++i) {
            for(int j=1; j<=n; ++j) {
                if(s[i-1] == t[j-1]) dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
                else dp[i][j] = dp[i-1][j];
            }
        }
        return dp[m][n];
    }
};

The space complexity can be further improved to O(N).

[update 12/22/2019]: remove 2 unnecessary lines below. See the commented two lines.

See the code below:

class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(), n = t.size();
        //if(m<n) return 0;
        vector<long> dp(n+1, 0);
        dp[0] = 1;
        for(int i=1; i<=m; ++i) {
            for(int j=n; j>0; --j) {
                if(s[i-1] == t[j-1]) dp[j] += dp[j-1];
              //  else dp[j] = dp[j];
            }
        }
        return dp[n];
    }
};

Tuesday, December 10, 2019

Leetcode 1277: Count Square Submatrices with All Ones

https://leetcode.com/problems/count-square-submatrices-with-all-ones/description/



Notes:

This question can be solved by dynamic programming.

This question essentially is the same as the one asking for the largest square of all 1s. (Leetcode 221 Maximal Square)

Let use dp[i][j] represent the edge length of the largest square ending with matrix[i][j] which is the bottom-right corner element.

Then dp[i][j] = 1 + min(dp[i-1][j-1], dp[i][j-1], dp[i-1][j]).

This question is asking for the number of the squares, so the new found squares should always ending with matrix[i][j] which is the bottom-right corner element.

Thus, we just need to update the counting by adding dp[i][j].

See the code below:

class Solution {
public:
    int countSquares(vector<vector<int>>& matrix) {
        int res = 0, m = matrix.size(), n = matrix.front().size();
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
        for(int i=1; i<=m; ++i) {
            for(int j=1; j<=n; ++j) {
                if(matrix[i-1][j-1] == 0) continue;
                dp[i][j] = 1 + min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1]));
                res += dp[i][j];
            }
        }
        return res;
    }
};

Sunday, December 8, 2019

Leetcode 801: Minimum Swaps To Make Sequences Increasing

https://leetcode.com/problems/minimum-swaps-to-make-sequences-increasing/description/


Notes:

This question is not easy.

One way to solve this question is dynamic programming. The key is to find the sub-state of the question. (or similar questions but with a smaller size).

This question is guaranteed to have a solution, which means that:

if A[i-1] >= A[i], then A[i-1] must < B[i];
if B[i-1] >= B[i], then B[i-1] must < A[i].

So there are only several valid situations:

1): A[i-1] < A[i] && A[i-1] < B[i] && B[i-1] < B[i] && B[i-1] < A[i]

in this case, we either swap the ith elements or not;

2): A[i-1] < A[i] && B[i-1] < B[i]

in this case, if we swap the (i-1)th element, then we need to swap the ith element as well;

3):  A[i-1] < B[i] && B[i-1] < A[i]

in this case, we need to either swap the ith elements or the (i-1)th elements.


If we use two one dimensional array to memorize the results up to ith element:

noSwap[i]: represents the minimum of swaps up to the ith element, with the ith one not swapped.
swap[i]: represents the minimum of swaps up to the ith element, with the ith one swapped;

(For each element, it can either swap or not swap, as long as it can make the two sequence increasing)

The transition formulas for the above situations are:

1):  noswap[i] = min(noswap[i-1], swap[i-1]);
      swap[i] = min(noswap[i-1], swap[i-1]) + 1;

2):  noswap[i] = noswap[i-1];
       swap[i] = swap[i-1] + 1;

3):   noswap[i] = swap[i-1];
       swap[i] = noswap[i-1] + 1;

The T-C is O(N), and the S-C is O(N) too.

See the code below:
class Solution {
public:
    int minSwap(vector<int>& A, vector<int>& B) {
        int n = A.size();
        vector<int> noSwap(n, n), swap(n, n);
        noSwap[0] = 0;
        swap[0] = 1;
        for(int i=1; i<n; ++i) {
            if(A[i-1] < A[i] && A[i-1] < B[i] && B[i-1] < B[i] && B[i-1] < A[i]) {
                noSwap[i] = min(noSwap[i-1], swap[i-1]);
                swap[i] = min(noSwap[i-1], swap[i-1]) + 1;
            }
            else if(A[i-1] < A[i] && B[i-1] < B[i]) {
                noSwap[i] = noSwap[i-1];
                swap[i] = swap[i-1] + 1;
            }
            else {
                noSwap[i] = swap[i-1];
                swap[i] = noSwap[i-1] + 1;
            }
        }
        return min(noSwap[n-1], swap[n-1]);
    }
};

From the above code, we can see that dp[i] is derived from dp[i-1] only. This means that we can further optimize the space complexity to O(1).

See the code below:
class Solution {
public:
    int minSwap(vector<int>& A, vector<int>& B) {
        int pre_noSwap = 0, pre_swap = 1;
        for(int i=1; i<A.size(); ++i) {
            int cur_noSwap = 0, cur_swap = 1;
            if(A[i-1] < A[i] && A[i-1] < B[i] && B[i-1] < B[i] && B[i-1] < A[i]) {
                cur_noSwap = min(pre_noSwap, pre_swap);
                cur_swap = min(pre_noSwap, pre_swap) + 1;
            }
            else if(A[i-1] < A[i] && B[i-1] < B[i]) {
                cur_noSwap = pre_noSwap;
                cur_swap = pre_swap + 1;
            }
            else {
                cur_noSwap = pre_swap;
                cur_swap = pre_noSwap + 1;
            }
            pre_noSwap = cur_noSwap;
            pre_swap = cur_swap;
        }
        return min(pre_noSwap, pre_swap);
    }
};

Wednesday, December 4, 2019

Leetcode 673: Number of Longest Increasing Subsequence

https://leetcode.com/problems/number-of-longest-increasing-subsequence/description/



Notes:

This question can be viewed as a follow-up to the longest increasing subsequence (LIS).

The key is to use another dp array to memorize the number of the longest LIS ending with the ith element: dp[i]

When we find val[i] > val[j], the dp[i] should be updated by dp[j]. (why? see below)

The logic is very straightforward: there are dp[j] LIS ending with the jth element, so now there should be the same number of LIS ending with the ith element.

Another detail is that we need to update the longest LIS as we scan through the array. When the current longest length equals to the previous longest length, we need to add them up; if current is longer, we need to replace the previous one with the current.

The T-C is O(N^2), and the S-C is O(N).

See the code below:

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        if(nums.empty()) return 0;
        int res = 1, len = nums.size(), pre_maxL = 1;
        vector<int> dp1(len, 1), dp2(len, 1);
        //dp1[i]: the maximum length of the increasing subsequence ending with the ith element;
        //dp2[i]: the number of the LIS ending with the ith element; 
        for(int i=1; i<len; ++i) {
            int cur_ct = 1, cur_maxL = 1;
            for(int j=0; j<i; ++j) {
                if(nums[i] > nums[j]) {
                    dp1[i] = max(dp1[i], dp1[j] + 1);
                    if(cur_maxL < dp1[i]) {
                        cur_maxL = dp1[i];
                        cur_ct = dp2[j];
                    }
                    else if(cur_maxL == dp1[j] + 1) cur_ct += dp2[j]; //need to compare with dp1[j]+1, not dp1[i]
                }
            }
            dp2[i] = cur_ct;
            if(cur_maxL > pre_maxL) {
                pre_maxL = cur_maxL;
                res = cur_ct;
            }
            else if(cur_maxL == pre_maxL) res += cur_ct;
        }
        return res;
    }
};

Sunday, November 24, 2019

Leetcode 1269: Number of Ways to Stay in the Same Place After Some Steps

https://leetcode.com/problems/number-of-ways-to-stay-in-the-same-place-after-some-steps/description/


Notes:

This question can be solved by dp, or dynamic programming.

Let us use dp[i][j] represents the number of ways reaching the position i after j steps, then

dp[i][j] = dp[i-1][j-1] + dp[i][j-1] + dp[i+1][j-1]

So we can use a rolling array to reduce the dimension from 2D to 1D.

dp[i] = dp[i-1] + dp[i] + dp[i+1], or

dp[i] += dp[i-1] + dp[i+1]

Since we need to use the previous value from both sides (left and right of the i position), we cannot directly scan the array from one side to the other.

This can be solved by two ways:

one is that we use two arrays: pre and cur. In this way, we can use pre and cur alternatively: pre record the results from the previous steps; and cur is the current results.

the other is use a temporary variable to record the dp[i-1] from the previous step. Then we can update  the array from the left to right.

Now let us look at the time complexity: it should be O(M*N), where M is the length of the steps, and N is the length of the array.

But N can be very large in this question (1 million), and M is small (<= 100). Do we really need to have an array of 10^6 for a step only of 100?

The boundary case is that: we can only reach the position of 99 if we start from the position of 0, with 100 steps. (It is easy to see this. If not, please take a deep breath and then rethink, :))

Thus, we just need to have an array with a size of <= 100.

Please see the code below:

class Solution {
public:
    int numWays(int steps, int arrLen) {
        int len = min(steps, arrLen);
        vector<long> dp(len, 0);
        dp[0] = 1;
        int mod = 1e9+7;
        for(int i=0; i<steps; ++i) {
            long p = 0, c = 0;
            for(int j=0; j<len; ++j) {
                c = dp[j];
                dp[j] += p + (j+1<len ? dp[j+1] : 0);
                dp[j] %= mod;
                p = c;
            }
        }
        return dp[0];
    }
};

Saturday, November 16, 2019

Leetcode 1259: Handshakes That Don't Cross

https://leetcode.com/problems/handshakes-that-dont-cross/description/


Notes:

This problem can be solved by dp.

Why? because, as shown in the above figures, it can be easily converted to similar questions but with smaller sizes.

Let us use dp[n] to represent the total ways for n people.

The first person can shake hands with the second one, but cannot shake hands with the second one. Why?

Because if the first person connects with the third one, then the second one have to cross the "line" to shake hands with other people.

Thus, if the first person shake hands with the jth person, this essentially divide the original circle into two parts:

one is from i+1 to the j-1 people, and the other is from j+1 to the nth people.

So we need to leave even number of people in both sides.

Thus, dp[n] = dp[0]* dp[n-2] + dp[2]*dp[n-2-2] + ... + dp[j]*dp[n-j-2].

In addition, due to symmetry, we can just calculate half of the circle.

See the code below:

class Solution {
public:
    int numberOfWays(int num_people) {
        int n = num_people, mod = 1e9+7;
        vector<long> dp(n+1, 0);
        dp[0] = 1;
        for(int i=2; i<=n; i += 2) {
            for(int j=0; j<=i-j-2; j += 2) {//calculate half circle
                if(j==i-j-2) dp[i] += dp[j]*dp[i-j-2]%mod;
                else dp[i] += 2*((dp[j]%mod)*(dp[i-j-2]%mod)%mod);
                dp[i] %=mod;
            }
        }
        return dp[n];
    }
};

Saturday, November 2, 2019

Leetcode 1246: Palindrome Removal

https://leetcode.com/problems/palindrome-removal/description/

Given an integer array arr, in one move you can select a palindromic subarray arr[i], arr[i+1], ..., arr[j] where i <= j, and remove that subarray from the given array. Note that after removing a subarray, the elements on the left and on the right of that subarray move to fill the gap left by the removal.
Return the minimum number of moves needed to remove all numbers from the array.

Example 1:
Input: arr = [1,2]
Output: 2
Example 2:
Input: arr = [1,3,4,1,5]
Output: 3
Explanation: Remove [4] then remove [1,3,1] then remove [5].

Constraints:
  • 1 <= arr.length <= 100
  • 1 <= arr[i] <= 20

Notes:

This question can be solved by dp. So the key is find the key logic, or state transfer equation.

Let use dp[i][j] represents the minimum number of moves needed to remove all the numbers from the array from i to j inclusive. Then,

(1) dp[i][j] = 1 + dp[i+1][j] //since it can ways be done by removing one by one;

(2) if(dp[i] == dp[i+1]), dp[i][j] = min(dp[i][j], 1 + dp[i+2][j]);

(3) if(dp[i] == dp[j]),  dp[i][j] = min(dp[i][j], dp[i+1][j-1])//both ends can be removed along with previous palindromes;

(4) if(dp[i] == dp[k]) dp[i][j] = min(dp[i][j], dp[i][k-1] + dp[k+1][j])  for k = i + 2, to j - 1;

Pay attention to the boundary conditions. The above (2) and (3) actually are the left and right boundaries of case (4).

Also pay attention to the for loops. The time complexity is O(N^3), and the dp is in a bottom-up building fashion. So we need to calculate each subarr with a smaller size or len. Then gradually build up the final dp[0][len-1].

See the code below:

class Solution {
public:
    int minimumMoves(vector<int>& arr) {
        int n = arr.size();
        vector<vector<int>> dp(n+1, vector<int>(n+1, 0));
        for(int len = 1; len <= n; ++len) {
            for(int i=0, j = len-1; j<n; ++i, ++j) {
                if(len == 1) {dp[i][j] = 1; continue;}
                dp[i][j] = 1 + dp[i+1][j];//can always remove the first one;
                if(arr[i] == arr[i+1]) dp[i][j] = min(dp[i][j], 1 + dp[i+2][j]);
                if(arr[i] == arr[j] && j>i+1) dp[i][j] = min(dp[i][j], dp[i+1][j-1]);
                for(int k=i+2; k<j; ++k) {
                    if(arr[i] == arr[k]) dp[i][j] = min(dp[i][j], dp[i+1][k-1] + dp[k+1][j]);
                }
            }
        }
        return dp[0][n-1];
    }
};