九章算法 - 二、序列型動態規劃 515. 664. 516. 392. 534. 149. 150. 151. 393.

515. Paint House

這裏有n個房子在一列直線上,現在我們需要給房屋染色,分別有紅色藍色和綠色。每個房屋染不同的顏色費用也不同,你需要設計一種染色方案使得相鄰的房屋顏色不同,並且費用最小,返回最小的費用。

費用通過一個nx3 的矩陣給出,比如cost[0][0]表示房屋0染紅色的費用,cost[1][2]表示房屋1染綠色的費用。

提示:f[i][j]爲第i棟房子塗成j顏色所需的最小花費。最後返回的是第n-1棟房子(從0開始),三個裏面的最小值。

          由於計算每一個房子時只需要前一次的數據,所以用滾動數組來減少空間。

答案:

class Solution {
public:
    /**
     * @param costs: n x 3 cost matrix
     * @return: An integer, the minimum cost to paint all houses
     */
    int minCost(vector<vector<int>> &costs) {
        int n = costs.size();
        if (!n) return 0;
        
        vector<vector<int>> f(2, vector<int>(3, 0x7fffffff));
        f[0][0] = costs[0][0];
        f[0][1] = costs[0][1];
        f[0][2] = costs[0][2];
        for (int i = 1; i < n; i++)
            for (int j = 0; j < 3; j++) {
                f[i&1][j] = 0x7fffffff;
                for (int k = 0; k < 3; k++) 
                    if (j != k)
                        f[i&1][j] = min(f[i&1][j], f[!(i&1)][k] + costs[i][j]);
            }
                        
        return min(f[(n-1)&1][0], min(f[(n-1)&1][1], f[(n-1)&1][2]));
    }
};

664. Counting Bits

給出一個 非負 整數 num,對所有滿足 0 ≤ i ≤ num 條件的數字 i 均需要計算其二進制表示中數字 1 的個數並以數組的形式返回。

提示:設dp[i]dp[i]表示i的二進制表示中數字1的個數。狀態轉移方程爲:dp[i]=dp[i>>1]+i\%2dp[i]=dp[i>>1]+i%2。

           第二種取巧的方法是《劍指offer》中提到的i&(i-1)就是使其最右邊的1變爲0。

答案:

class Solution {
public:
    /**
     * @param num: a non negative integer number
     * @return: an array represent the number of 1's in their binary
     */
    vector<int> countBits(int num) {
        int len = num + 1;
        vector<int> dp(len, 0);
        
        for(int i = 1; i <= num; ++i) {
            dp[i] = dp[i>>1] + i % 2;
        }
        
        return dp;
    }
};
public class Solution {
    public int[] countBits(int num) {
        int[] f = new int[num+1];
        int i;
        f[0] = 0;
        for (i=1; i<=num; ++i) {
            f[i] = f[i&(i-1)] + 1;
        }
        
        return f;
    }
}

 

516. Paint House II

這裏有n個房子在一列直線上,現在我們需要給房屋染色,共有k種顏色。每個房屋染不同的顏色費用也不同,你需要設計一種染色方案使得相鄰的房屋顏色不同,並且費用最小。

費用通過一個nxk 的矩陣給出,比如cost[0][0]表示房屋0染顏色0的費用,cost[1][2]表示房屋1染顏色2的費用。

提示:按照原先的思路時間複雜度爲O(NK^2),爲了減小時間複雜度到O(NK),我們只需記錄第i棟房子的最小值與次小值。

         前者在lintcode上只能通過94%的數據。

答案:時間O(NK^2),空間O(1)。 i&1相當於將原來的行數每兩行一組, !(i&1)相當於i-1。

//O(NK^2)
class Solution {
public:
    /**
     * @param costs: n x k cost matrix
     * @return: an integer, the minimum cost to paint all houses
     */
    int minCostII(vector<vector<int>> &costs) {
        // write your code here
         int n = costs.size();
        if (!n) return 0;
        
        int m = costs[0].size();
        
        vector<vector<int>> f(2, vector<int>(m, 0x7fffffff));
        f[0][0] = costs[0][0];
        f[0][1] = costs[0][1];
        f[0][2] = costs[0][2];
        for(int k = 0; k < m; k++){
            f[0][k] = costs[0][k];
        }
        for (int i = 1; i < n; i++)
            for (int j = 0; j < m; j++) {
                f[i&1][j] = 0x7fffffff;
                for (int k = 0; k < m; k++) 
                    if (j != k)
                        f[i&1][j] = min(f[i&1][j], f[!(i&1)][k] + costs[i][j]);
            }
                        
        //return min(f[(n-1)&1][0], min(f[(n-1)&1][1], f[(n-1)&1][2]));
        int result = 0x7fffffff;
        for(int k = 0; k < m; k++){
            if(f[(n-1)&1][k] < result){
                result = f[(n-1)&1][k];
            }
            
        }
        return result;
    }
};

時間O(NK),空間O(1) 

class Solution {
public:
    /**
     * @param costs: n x k cost matrix
     * @return: an integer, the minimum cost to paint all houses
     */
    int minCostII(vector<vector<int>> &costs) {
        // write your code here
        int n = costs.size();
        if (!n) return 0;
        int m = costs[0].size();
        
        vector<vector<int>> f(2, vector<int>(m, 0x7fffffff));
        
        for(int k = 0; k < m; k++){
            f[0][k] = costs[0][k];
        }
        
        int min1,min2,j1,j2;
        
        
        for (int i = 1; i < n; i++){
            min1 = min2 = 0x7fffffff;
            for (int j = 0; j < m; j++) {
                if(f[!(i&1)][j] < min1){
                    min2 = min1;
                    j2 = j1;
                    min1 = f[!(i&1)][j];
                    j1 = j;
                }
                else if(f[!(i&1)][j] < min2){
                    min2 = f[!(i&1)][j];
                    j2 = j;
                }
            }
            for(int k = 0; k < m; k++){
                if(k != j1){
                    f[i&1][k] = min1 + costs[i][k];
                }
                else{
                    f[i&1][k] = min2 + costs[i][k];
                }
            }
        }
        int result = 0x7fffffff;
        for(int k = 0; k < m; k++){
            if(f[(n-1)&1][k] < result){
                result = f[(n-1)&1][k];
            }
            
        }
        
        return result;
    }
};

392. House Robber

假設你是一個專業的竊賊,準備沿着一條街打劫房屋。每個房子都存放着特定金額的錢。你面臨的唯一約束條件是:相鄰的房子裝着相互聯繫的防盜系統,且 當相鄰的兩個房子同一天被打劫時,該系統會自動報警

給定一個非負整數列表,表示每個房子中存放的錢, 算一算,如果今晚去打劫,在不觸動報警裝置的情況下, 你最多可以得到多少錢 。

提示:線性DP題目.

          設 dp[i] 表示前i家房子最多收益, 答案是 dp[n], 狀態轉移方程是

dp[i] = max(dp[i-1], dp[i-2] + A[i-1])

       考慮到dp[i]的計算只涉及到dp[i-1]和dp[i-2], 因此可以O(1)空間解決.

答案:其中的取餘運算可以用幾個變量代替但是可讀性不好。 

class Solution {
public:
    /**
     * @param A: An array of non-negative integers
     * @return: The maximum amount of money you can rob tonight
     */
    long long houseRobber(vector<int> &A) {
        // write your code here
        int n = A.size();
        if(n == 0) return 0;
        long long int res[2];
        
        res[0] = A[0];
        res[1] = max(A[0], A[1]);
        for(int i = 2; i < n; i++){
            res[i%2] = max(res[(i-1)%2], res[(i-2)%2] + A[i]);
        }
        
        return res[(n-1)%2];
    }
};

 

534. House Robber II

在上次打劫完一條街道之後,竊賊又發現了一個新的可以打劫的地方,但這次所有的房子圍成了一個圈,這就意味着第一間房子和最後一間房子是挨着的。每個房子都存放着特定金額的錢。你面臨的唯一約束條件是:相鄰的房子裝着相互聯繫的防盜系統,且 當相鄰的兩個房子同一天被打劫時,該系統會自動報警

給定一個非負整數列表,表示每個房子中存放的錢, 算一算,如果今晚去打劫,在不觸動報警裝置的情況下, 你最多可以得到多少錢 。

提示:在之前的基礎上分兩種情況,要麼偷第一個房子不偷最後一個,要麼偷最後一個不偷第一個。兩個都不偷的情況包含在第           二種情況。 如果不另外寫一個計算函數的話,一定要注意兩種情況的初始條件。

答案:

class Solution {
public:
    /**
     * @param nums: An array of non-negative integers.
     * @return: The maximum amount of money you can rob tonight
     */
    int houseRobber2(vector<int> &nums) {
        // write your code here
        int n = nums.size();
        if(n == 0) return 0;
        if(n == 1) return nums[0];
        if(n == 2) return max(nums[0], nums[1]);
        
        long long int res[2];
        
        //condition 1
        res[0] = nums[0];
        res[1] = max(nums[0], nums[1]);
        for(int i = 2; i < n - 1; i++){
            res[i%2] = max(res[(i-1)%2], res[(i-2)%2] + nums[i]);
        }
        long long int tmpMax =  res[(n-2)%2];
        
        
        //condition 2
        res[0] = 0;
        res[1] = nums[1];
        for(int i = 2; i < n; i++){
            res[i%2] = max(res[(i-1)%2], res[(i-2)%2] + nums[i]);
        }
       
        tmpMax = max(tmpMax, res[(n-1)%2]);
       
        return tmpMax;
    }
};

149. Best Time to Buy and Sell Stock

假設有一個數組,它的第i個元素是一支給定的股票在第i天的價格。如果你最多隻允許完成一次交易(例如,一次買賣股票),設計一個算法來找出最大利潤。

提示:記錄兩個值,一個是目前爲止的最小值,目前爲止的最大利潤。最大利潤爲min之後的最大值減去min。

答案:

class Solution {
public:
    /**
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    int maxProfit(vector<int> &prices) {
        // write your code here
        if(prices.size() == 0){
            return 0;
        }
        int min = 0x7fffffff;
        int profit = 0;
        for(int i : prices){
            min = i < min ? i : min;
            profit = (i - min) > profit ? i - min : profit;
        }
        return profit;
    }
};

150. Best Time to Buy and Sell Stock II

給定一個數組 prices 表示一支股票每天的價格.

你可以完成任意次數的交易, 不過你不能同時參與多個交易 (也就是說, 如果你已經持有這支股票, 在再次購買之前, 你必須先賣掉它).

設計一個算法求出最大的利潤.

提示:這個最簡單,只要明天比今天價格高就買入。

答案:

class Solution {
public:
    /**
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    int maxProfit(vector<int> &prices) {
        int n = prices.size();
        int profit = 0;
        for (int i = 1; i < n; i++) {
            if (prices[i] - prices[i - 1] > 0) {
                profit += prices[i] - prices[i - 1];
            } 
        }
        return profit;
    }
};

151. Best Time to Buy and Sell Stock III

假設你有一個數組,它的第i個元素是一支給定的股票在第i天的價格。設計一個算法來找到最大的利潤。你最多可以完成兩筆交易。

提示:分爲五種情況。前面的題基本用座標型來寫的,此題用序列型來寫比較好,前0天只能處於階段1。當天買的不計算紅利。

最下面的式子有問題,最後一項無意義可刪除,因爲其賣了接着買相當於一次買賣。

答案:

class Solution {
public:
    /**
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    const int MinValue = 0x80000000;
    int maxProfit(vector<int> &prices) {
        // write your code here
        int n = prices.size();
        if(n == 0) return 0;
        vector<vector<int>> dp(n + 1,vector<int>(5 + 1));
        
        for(int k = 1; k <= 5; k++){
            dp[0][k] = MinValue;
        }
        dp[0][1] = 0;
  
        for(int i = 1; i <= n; i++){
            //手中未持有股票
            for(int j = 1; j <= 5; j+=2){
                //前一天也未持有股票
                dp[i][j] = dp[i-1][j];
                if(j > 1 && i > 1 && dp[i-1][j-1] != MinValue){
                    //                      前一天持有,今天獲得紅利
                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + prices[i-1] - prices[i-2]);
                }
                
            }
            //手中持有股票
            for(int j = 2; j <= 5; j+=2){
                //前一天未持有股票今天買入
                dp[i][j] = dp[i-1][j-1];
                if(i > 1 && dp[i-1][j] != MinValue){
                    //                        前一天持有股票今天獲得紅利
                    dp[i][j] = max(dp[i][j], dp[i-1][j] + prices[i-1] - prices[i-2]);
                }
            }
        }
        int res = 0;
        for (int j = 1; j <= 5; j += 2) {
            res = max(res, dp[n][j]);
        }
        return res;
        
    }
};

 

393. Best Time to Buy and Sell Stock IV

 

提示:如果K > N/2,那麼相當於BTBSS II,否則將 III 擴展到 2K+1,五種階段不變。

答案:

class Solution {
public:
    /**
     * @param K: An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    int maxProfit(int K, vector<int> &prices) {
        // write your code here
        const int MinValue = 0x80000000;     
        int n = prices.size();
        if(n == 0) return 0;
        int res = 0;
        //相當於任意次買賣
        if(K > n/2){
            for (int i = 1; i < n; i++) {
                if (prices[i] - prices[i - 1] > 0) {
                    res += prices[i] - prices[i - 1];
                } 
            }
            return res;
            
        }
        
        vector<vector<int>> dp(n + 1,vector<int>(2*K + 2));
        
        for(int k = 1; k <= 2*K + 1; k++){
            dp[0][k] = MinValue;
        }
        dp[0][1] = 0;
  
        for(int i = 1; i <= n; i++){
            //手中未持有股票
            for(int j = 1; j <= 2*K + 1; j+=2){
                //前一天也未持有股票
                dp[i][j] = dp[i-1][j];
                if(j > 1 && i > 1 && dp[i-1][j-1] != MinValue){
                    //                      前一天持有,今天獲得紅利
                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + prices[i-1] - prices[i-2]);
                }
                
            }
            //手中持有股票
            for(int j = 2; j <= 2*K + 1; j+=2){
                //前一天未持有股票今天買入
                dp[i][j] = dp[i-1][j-1];
                if(i > 1 && dp[i-1][j] != MinValue){
                    //                        前一天持有股票今天獲得紅利
                    dp[i][j] = max(dp[i][j], dp[i-1][j] + prices[i-1] - prices[i-2]);
                }
            }
        }
        
        for (int j = 1; j <= 2*K + 1; j += 2) {
            res = max(res, dp[n][j]);
        }
        return res;
        
    }
};

76. Longest Increasing Subsequence

給定一個整數序列,找到最長上升子序列(LIS),返回LIS的長度。

提示:此題用動態規劃時間複雜度爲O(N^2),O(NlogN)的二分查找方法放在第七講。動態規劃的思路是建立數組,f[i]表示以i爲結尾的最長子序列,轉移方程:Dp[i] = max{Dp[j]} + 1

答案:

class Solution {
public:
    /**
     * @param nums: The integer array
     * @return: The length of LIS (longest increasing subsequence)
     */
    int longestIncreasingSubsequence(vector<int> nums) {
        int f[nums.size()];
        int max = 0;
        for (int i = 0; i < nums.size(); i++) {
            f[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    f[i] = f[i] > f[j] + 1 ? f[i] : f[j] + 1;
                }
            }
            if (f[i] > max) {
                max = f[i];
            }
        }
        return max;
    }
};

602. Russian Doll Envelopes

給一定數量的信封,帶有整數對 (w, h) 分別代表信封寬度和高度。一個信封的寬高均大於另一個信封時可以放下另一個信封。
求最大的信封嵌套層數。

提示:此題目用動態規劃做時間複雜度也是O(N^2)。使用二分法優化的方法後面再講。

           信封存在兩個維度,首先貪心按照其中一個維度將信封排序,然後在另一個維度上面尋找最長上升子序列。

           如排序按照長度從小到大,同樣長度按照寬度從大到小排序

答案:

bool cmp(const pair<int,int>&x, const pair<int, int>&y) {
  return x.first != y.first ? x.first < y.first : x.second > y.second;
}
class Solution {
public:
    /*
     * @param envelopes: a number of envelopes with widths and heights
     * @return: the maximum number of envelopes
     */
    int maxEnvelopes(vector<pair<int, int>>& envelopes) {
        // write your code here
        int n = envelopes.size();
        if (n == 0) {
            return 0;
        }
    
        sort(envelopes.begin(), envelopes.end(), cmp);
        vector<int> f(n);
        int i, j, res = 0;
        for(i = 0; i < n; i++){
            f[i] = 1;
            for(j = 0; j < i; j++){
                if(envelopes[j].first < envelopes[i].first && envelopes[j].second < envelopes[i].second){
                    f[i] = max(f[i], f[j] + 1);
                }
            }
            res = max(res, f[i]);
        }
        return res;
        
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章