-->
Showing posts with label Dynamic Programming. Show all posts
Showing posts with label Dynamic Programming. Show all posts

Sunday, March 5, 2017

[LeetCode] 514. Freedom Trail

https://leetcode.com/contest/leetcode-weekly-contest-22/problems/freedom-trail/

Solution:
Basically, there are two ways to switch into the target position. One is to from left to right; the other is the opposite direction. And we need to pick up the shorter one to continue. The difficult part is that there are duplicates in both the ring and key, so we need to try each of them, so we can use brute force DSF and then return the one with the shortest length. To reduce the time complexity, a hash map can be used to built up the relationship between the char in the key and the corresponding positions in the ring. See code below, (even though it is still too slow to pass the OJ)

Code:
class Solution {
public:
    int findRotateSteps(string ring, string key) {
        int m=ring.size(), n=key.size();
        if(!m || !n) return 0;
        int res = m*n;
        unordered_map<int, vector<int>> map;
        for(auto a: key){
            if(map.find(a-'a') == map.end()){
                for(int i=0; i<m; ++i){
                    if(ring[i]==a) map[a-'a'].push_back(i);
                }
            }
        }
        findDSF(ring, 0, key, 0, 0, map, res);
        return res;
    }
private:
    void findDSF(string r, int i, string key, int j, int l, unordered_map<int, vector<int>> &m, int &res){
        if(l>=res) return;
        if(j==key.size()){res = l; return;}
        for(auto k:m[key[j]-'a']){
            int t1 = abs(k-i), t = r.size(), t2 = t - t1;
            findDSF(r, k, key, j+1, l+min(t1, t2)+1, m, res);
        }
    }
}; 


Since the above brute force DSF cannot pass the OJ, we need to think about something else that is faster. DP is one choice. Let's use dp[i][j] represent the shortest length to match key [0, to i] and ring[0-j]. When key[i] == ring[j], 
                             
dp[i][j] = min(dp[i-1][k] + min steps from k to j)  for  all k (0<= k < ring.size())
The min steps from k to j can be obtained by comparing the two way mentioned above: one is from left to right; the other is the opposite way. And we need to choose the shorter one.
Some details for initialization:
1): the maximum steps will be the length of ring times the length of the key; (why? because this is the worst case: for every char in the key, we need to search the whole ring);
2): We can initialize the dp[i][j] with the maximum steps, since we are looking for minimum;
3): dp[0][0] can be set as 0, which is legal for empty ring and empty key.

Code:
class Solution {
public:
    int findRotateSteps(string ring, string key) {
        int m=ring.size(), n=key.size();
        if(!m || !n) return 0;
        int res = m*n;
        vector<vector<int>> dp(n+1, vector<int>(m, res));
        dp[0][0] = 0;
        for(int i=1; i<=n; ++i){
            for(int j=0; j<m; ++j){
                if(ring[j]==key[i-1]){
                    for(int k=0; k<m; ++k){
                        int t1 = abs(k-j), t2 = m-t1;//two ways from k to j;
                        dp[i][j]=min(dp[i][j], dp[i-1][k] + min(t1, t2) + 1); //+1 for the "click" step;
                        if(i==n) res = min(res, dp[i][j]);
                    }
                }
            }
        }
        return res;
    }
};

Sunday, February 19, 2017

[LeetCode] 518. Coin Change 2

https://leetcode.com/problems/coin-change-2/?tab=Description

Solution:
This is one of the classical questions for dynamic programming. And it belongs to the complete 01 knapsack problem. Let's suppose dp[i][j] represents the number of ways to make up amount of j with 1 to ith coins. Then when we move onto the next coin, we have to make choice. If the denomination of the next coin larger than j, then we cannot use it; if smaller, we can use it. Thus, the transition formula is:
                       dp[i+1][j] = dp[i][j] + dp[i+1][j-v[i+1]]
The first term means the way without using the (i+1)th coin; and the second one means the way with using the (i+1)th coin. This equation can be further simplified into an one-dimensional vector,
                      dp[j] = dp[j] + dp[j-v[i+1]]
                  or, dp[j] += dp[j-v[i+1]]
 See code below.

Code:
class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+1, 0);
        dp[0] = 1;
        for(auto a:coins){
            for(int i=1; i<=amount; ++i){
                if(i>=a) dp[i] += dp[i-a];
            }
        }
        return dp[amount];
    }
};

Saturday, February 18, 2017

[leetcode] 322. Coin Change

https://leetcode.com/problems/coin-change/?tab=Description

Solution:
A good question for dynamic programming. It also belongs to 01 knapsack problem. Let's suppose dp[i] represents the minimum coins needed to make up the amount, then the transition formula will be
              dp[i] = min(dp[i], dp[i-c[j]] + 1),   c[j] <= i;

Some details for implementation:
1): dp[0] can be set as 0; since it means the minimum number of coins needed for 0 is 0, which is legal based on the definition of dp[0];
2): All the rest dp[i] can be set (as amount + 1) since if there's at least one way to make up the amount, then minimum coins needed will be no larger than amount itself, because the worst situation (or the largest) is there are coins with 1 as the denomination and it is the only way to make up the amount.
3): to speed up a little bit, we can sort the coins. For coins bigger than i, we can just ignore them.

Code:
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        if(amount == 0) return 0;
        if(coins.empty()) return -1;
        vector<int> dp(amount+1, amount+1);
        dp[0] = 0;
        sort(coins.begin(), coins.end());
        for(int i=1; i<=amount; ++i){
            for(int j=0; j<coins.size(); ++j){
                if(i>=coins[j]){
                    dp[i] = min(dp[i], dp[i-coins[j]] + 1);
                }
                else break;
            }
        }
        return dp[amount]==amount + 1?-1:dp[amount];
    }
};    

Wednesday, January 25, 2017

[LeetCode] 312. Burst Balloons

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

Solution:
A very good question since that I have to change the way how I do analysis usually. Initially I started with to pick one element (k), then think about how to deal with the rest part. (I think this is may be the common way to think about this question, which will eventually lead to brute force DSF).

But if we change the way to think it a little bit, we will surprise ourselves (at least to me). Instead of picking up element k firstly, how about choose it at the end? Imagine we have elements array from i to j (we can put 1 at each end for boundary conditions). Let's use dp[i][j] represents the max we can get in this range, then dp[i][j] = max{dp[i][j], d[i][k-1] + dp[k+1][j] + nums[i-1]*nums[k]*nums[j+1]}, where i<= k <= j. The key point for understanding this is that we will pick element k as the end.  Amazing, isn't it?

Some details for implementation of dp:
From the transition equation, we can see that dp[i][j] will be calculated from dp[i][k-1] and dp[k+1][j], and k-1 < j and k+1 > i. This indicates that one possible way to do it is to start with larger i and smaller j.  And dp[1][n] is the final answer we need. (for i>j, dp[i][j] is apparently zero due to definition.)

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

Saturday, January 21, 2017

[LeetCode] 486. Predict the Winner

https://leetcode.com/contest/leetcode-weekly-contest-16b/problems/predict-the-winner/

Solution:
Will use dp strategy.Define dp[l][r] as the max gain for one player comparing with his opponent in the range from l to r.

Code:
class Solution {
public:
    bool PredictTheWinner(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(n, 0));
        for(int l=n-2; l>=0; --l){
            for(int r=l+1; r<n; ++r){
                dp[l][r] = max(nums[l]-dp[l+1][r], nums[r]-dp[l][r-1]);
            }
        }
        return dp[0][n-1]>=0;
    }
}; 

Wednesday, January 18, 2017

[LeetCode] 307. Range Sum Query - Mutable

https://leetcode.com/problems/range-sum-query-mutable/

Solution:
Will use dynamic programming strategy. Let dp(i) represent the sum of elements from 0 to i-1. Then the sum of elements from i to j inclusively can be expressed as (dp[j+1] - dp[i]). Once one element is changed, all the dp[i] after that elements need to be updated.

Code:
class NumArray {
public:
    NumArray(vector<int> &nums) {
        sum.push_back(0);
        for(auto a:nums) sum.push_back(sum.back() + a);
    }

    void update(int i, int val) {
        if(i<sum.size()-1){
            int t = val - (sum[i+1]-sum[i]);
            for(int j=i+1; j<sum.size(); ++j) sum[j] += t;  
        }
    }

    int sumRange(int i, int j) {
        if(j<sum.size()-1) return sum[j+1]-sum[i];
    }
private:
    vector<int> sum;
};

Sunday, January 8, 2017

481. Magical String

https://leetcode.com/contest/leetcode-weekly-contest-14/problems/magical-string/

Solution:
Need to find some pattern for this question. After careful observation, it is found that:
1): the 1 and 2 groups appears alternatively;
2): the numbers of char inside each group are determined by the previous sequential char value in the same string (that's one of reasons why it is magical, as indicated);

Code:
public:
    int magicalString(int n) {
        if(n<1) return 0;
        if(n<4) return 1;
        int res = 1;
        string s = "122";
        for(int i=2, t=2; i<n; ++i){// t as the flag to be 1 or 2 for adding;
            if(s[i] == '1'){
                ++res;
                if(t == 1){
                    s += "2";
                    t = 2;
                }
                else{
                    s += "1";
                    t = 1;
                }
            }
            else{
                if(t == 1){
                    s += "22";
                    t = 2;
                }
                else{
                    s += "11";
                    t = 1;
                }
            }
        }
        return res;
    }
};

Thursday, December 22, 2016

139. Word Break

https://leetcode.com/problems/word-break/

Solution:
1) Brute force DSF Recursion. But will cause TLE (not surprisingly);
2) DP. Suppose dp[i] represents the true status (can be separated into words) of substring from 0 to i, then dp[i+1] will be true if both dp[j] and wordDict.find(s.substr(j, i+1-j)) are true for 0<= j <i. Similar dp argument can also applied from right to left, and actually this one can be used to reduce the time complexity for the next question (II) (the details for the implementation will be a little bit different, see below. And also dp[i] now means the substring from i to n can be segmented).

Code:
DSF Recursion:
class Solution {
public:
    bool wordBreak(string s, unordered_set<string>& wordDict) {
        if(s.empty()) return false;
        if(wordDict.find(s) != wordDict.end()) return true;
        for(int i=0; i<s.size(); ++i){
            string t = s.substr(0, i+1);
            if(wordDict.find(t)) != wordDict.end() && wordBreak(s.substr(i+1), wordDict)) return true;
        }
        return false;
    }
};
DP: from left to right
class Solution {
public:
    bool wordBreak(string s, unordered_set<string>& wordDict) {
        int l = s.size();
        if(!l) return false;
        vector<bool> dp(l+1, false);
        dp[0] = true;
        for(int i=1; i<=s.size(); ++i){
            for(int j=0; j<i; ++j){
                if(wordDict.find(s.substr(j, i-j)) != wordDict.end() && dp[j]){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[l];
    }
};
DP: from right to left
class Solution {
public:
    bool wordBreak(string s, unordered_set<string>& wordDict) {
        int l = s.size();
        if(!l) return false;
        vector<bool> dp(l+1, false);
        dp[l] = true;
        for(int i=l-1; i>=0; --i){
            for(int j=l; j>i; --j){
                if(wordDict.find(s.substr(i, j-i)) != wordDict.end() && dp[j]){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[0];
    }
};

140. Word Break II

https://leetcode.com/problems/word-break-ii/

Solution:
May use DSF. In order to reduce the time complexity, we can pre-build a one dimensional array dp[i] to represents that the sub-string from i to n can be segmented into words.

Code:
class Solution {
public:
    vector<string> wordBreak(string s, unordered_set<string>& wordDict) {
        vector<string> res;
        int l = s.size(); 
        if(!l) return res;
        vector<bool> dp(l+1, false);
        dp[l] = true;
        for(int i=l-1; i>=0; --i){
            for(int j=l; j>i; --j){
                if(wordDict.find(s.substr(i, j-i)) != wordDict.end() && dp[j]){
                    dp[i] = true;
                    break;
                }
            }
        }
        wBDSF(s, wordDict, dp, 0, "", res);
        return res;
    }
private:
    void wBDSF(string s, unordered_set<string> &w, vector<bool> &dp, int ind, string t, vector<string> &r){
        if(ind == s.size()) r.push_back(t);
        else{
            for(int i=ind; i<s.size(); ++i){
                string t1 = s.substr(ind, i-ind+1);
                if(w.find(t1) != w.end() && dp[i+1]){
                    wBDSF(s, w, dp, i+1, t.empty()?t+t1:t+" "+t1, r);
                }
            }
        }
    }
}; 

Thursday, December 15, 2016

115. Distinct Subsequences

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

Solution:
Need dynamic programming (dp). The key is to find the transition equation. Let suppose dp[i][j] represents the number of distinct subsquences in the first i elements of s for the first j elements of t. When t[j] != s[i], then s[i] cannot be used, and dp[i+1][j+1] = dp[i][j+1]; if t[j] == s[i], then s[i] can be either used nor not used, and dp[i+1][j+1] = dp[i][j] + dp[i][j+1]; so,
                          
         dp[i+1][j+1] = dp[i][j+1];                 if(s[i] ! = t[j])
                      = dp[i][j] + dp[i][j+1];      else

And the space can be further optimized to O(n).

Code:
class Solution {
public:
    int numDistinct(string s, string t) {
        int m = s.size(),  n = t.size();
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
        for(int i=0; i<=m; ++i) dp[i][0] = 1;  //empty string is always a valid sub-sequence.
        for(int i=1; i<=n; ++i) dp[0][i] = 0;   //Non-empty string is always non-valid .. for an empty str.
        for(int i=0; i<m; ++i){
            for(int j=0; j<n; ++j){
                if(s[i] == t[j]) dp[i+1][j+1] = dp[i][j] + dp[i][j+1];
                else dp[i+1][j+1] = dp[i][j+1];
            }
        }
        return dp[m][n];
    }
};