九章算法 - 二、序列型动态规划 515. 664. 516. 392. 534. 149. 150. 151. 393.

这篇博客探讨了一系列与动态规划相关的序列型问题,包括房屋染色、二进制位计数、打劫问题和股票交易等。通过具体题目解析,展示了如何利用动态规划求解这些问题,涉及到滚动数组、状态转移方程和不同场景下的优化策略,如空间复杂度的降低和贪心策略的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
        
    }
};

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值