这篇文章上次修改于 911 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
  • 给定一个序列
  • dp[i] 表示前 i 个元素 a[0], a[1], ..., a[i-1] 的某种性质
  • 特征:...前 i 个...最小/方式数/可行性
  • 在设计动态规划时,发现需要知道油漆前 i 栋房子的最优策略中,房子 i-1 的颜色。
  • 如果只用 dp[i-1] 无法区分。
  • 解决方法:记录房子 i-1 的颜色。在房子 i-1 是红/蓝/绿的情况下,油漆前 i 栋房子的最小花费。
  • 序列 + 状态

1 房屋染色

这里有n个房子在一列直线上,现在我们需要给房屋染色,分别有红色蓝色和绿色。每个房屋染不同的颜色费用也不同,你需要设计一种染色方案使得相邻的房屋颜色不同,并且费用最小,返回最小的费用。

费用通过一个nx3 的矩阵给出,比如cost0表示房屋0染红色的费用,cost1表示房屋1染绿色的费用,依此类推。找到油漆所有房子的最低成本。

输入: [[14,2,11],[11,14,5],[14,3,10]]
输出: 10
解释: 第一个屋子染蓝色,第二个染绿色,第三个染蓝色,最小花费:2 + 5 + 3 = 10.
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 == 0) return 0;
        vector<vector<int>> dp(n, vector<int>(3, INT_MAX));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < 3; j++) {
                if (i == 0) {
                    dp[i][j] = costs[i][j];
                    continue;
                }
                for (int k = 0; k < 3; k++) {
                    if (k != j && dp[i - 1][k] + costs[i][j] < dp[i][j]) {
                        dp[i][j] = dp[i - 1][k] + costs[i][j];
                    }
                }
            }
        }
        return min(dp[n - 1][0], min(dp[n - 1][3], dp[n - 1][4]));
    }
};

2 房屋染色 II

这里有n个房子在一列直线上,现在我们需要给房屋染色,共有k种颜色。每个房屋染不同的颜色费用也不同,你希望每两个相邻的房屋颜色不同

费用通过一个nxk 的矩阵给出,比如cost0表示房屋0染颜色0的费用,cost1表示房屋1染颜色2的费用。找到油漆所有房子的最低成本。

输入:
costs = [[14,2,11],[11,14,5],[14,3,10]]
输出: 10
说明:
三个屋子分别使用第1,2,1种颜色,总花费是10。
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) {
        int n = costs.size();
        if (n == 0) return 0;
        int k = costs[0].size();
        if (k == 0) return 0;
        vector<vector<int>> dp(n, vector<int>(k, INT_MAX));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < k; j++) {
                if (i == 0) {
                    dp[i][j] = costs[i][j];
                    continue;
                }
                for (int p = 0; p < k; p++) {
                    if (p != j && dp[i-1][p] + costs[i][j] < dp[i][j]) {
                        dp[i][j] = dp[i-1][p] + costs[i][j];
                    }
                }
            }
        }

        int ans = INT_MAX;
        for (int i = 0; i < k; i++) {
            if (dp[n-1][i] < ans) {
                ans = dp[n-1][i];
            }
        }
        return ans;
    }
};

优化:记录最小值和次小值

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) {
        // 
        int n = costs.size();
        if (n == 0) return 0;
        int k = costs[0].size();
        if (k == 0) return 0;
        vector<vector<int>> dp(n + 1, vector<int>(k, 0));
        int j1 = 0, j2 = 0;
        int min1 = 0, min2 = 0;

        for (int i = 1; i <= n; i++) {
            min1 = min2 = INT_MAX;
            for (int j = 0; j < k; j++) {
                if (dp[i-1][j] < min1) {
                    min2 = min1;
                    j2 = j1;
                    min1 = dp[i-1][j];
                    j1 = j;
                } else if (dp[i-1][j] < min2) {
                    min2 = dp[i-1][j];
                    j2 = j;
                }
            }
            for (int j = 0; j < k; j++) {
                if (j != j1) {
                    dp[i][j] = dp[i-1][j1] + costs[i-1][j];
                } else {
                    dp[i][j] = dp[i-1][j2] + costs[i-1][j];
                    cout << "--" << dp[i][j] << " " << dp[i-1][j2] << " " << costs[i-1][j] << endl;
                }
            }
        }

        int ans = INT_MAX;
        for (int i = 0; i < k; i++) {
            if (dp[n][i] < ans) {
                ans = dp[n][i];
            }
        }
        return ans;
    }
};

3 打劫房屋

假设你是一个专业的窃贼,准备沿着一条街打劫房屋。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且当相邻的两个房子同一天被打劫时,该系统会自动报警。

给定一个非负整数列表,表示每个房子中存放的钱, 算一算,如果今晚去打劫,在不触动报警装置的情况下, 你最多可以得到多少钱 。

输入: [3, 8, 4]
输出: 8
解释: 仅仅打劫第二个房子.
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) {
        int ans = 0;
        int n = a.size();
        if (n == 0) return 0;
        vector<long long> dp(n + 1, 0);
        dp[1] = a[0];
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i-1];
            if (i >= 2 && dp[i] < dp[i-2] + a[i-1]) {
                dp[i] = dp[i-2] + a[i-1];
            }
        }
        return dp[n];
    }
};

4 打劫房屋 II

在上次打劫完一条街道之后,窃贼又发现了一个新的可以打劫的地方,但这次所有的房子围成了一个圈,这就意味着第一间房子和最后一间房子是挨着的。每个房子都存放着特定金额的钱。你面临的唯一约束条件是:相邻的房子装着相互联系的防盗系统,且当相邻的两个房子同一天被打劫时,该系统会自动报警。

给定一个非负整数列表,表示每个房子中存放的钱,算一算,如果今晚去打劫,在不触动报警装置的情况下, 你最多可以得到多少钱。

输入: nums = [3,6,4]
输出: 6

枚举:没偷房子 0;没偷房子 n-1。取两种情况的最大值。

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) {
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return nums[0];

        vector<int> nums1(nums.begin(), nums.end()-1);
        int cnt1 = solve(nums1);

        vector<int> nums2(nums.begin()+1, nums.end());
        int cnt2 = solve(nums2);

        return cnt1 > cnt2 ? cnt1 : cnt2;
    }

    int solve(vector<int> &a) {
        int ans = 0;
        int n = a.size();
        if (n == 0) return 0;
        vector<long long> dp(n + 1, 0);
        dp[1] = a[0];
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i-1];
            if (i >= 2 && dp[i] < dp[i-2] + a[i-1]) {
                dp[i] = dp[i-2] + a[i-1];
            }
        }
        return dp[n];
    }
};

5 买卖股票的最佳时机

假设有一个数组,它的第i个元素是一支给定的股票在第i天的价格。如果你最多只允许完成一次交易(例如,一次买卖股票),设计一个算法来找出最大利润。

输入: [3, 2, 3, 1, 2]
输出: 1
说明:你可以在第三天买入,第四天卖出,利润是 2 - 1 = 1 
class Solution {
public:
    /**
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    int maxProfit(vector<int> &prices) {
        int n = prices.size();
        if (n == 0) return 0;
        int mn = prices[0];
        int ans = 0;
        for (int i = 1; i < n; i++) {
            if (prices[i] < mn) {
                mn = prices[i];
            }
            ans = max(ans, prices[i] - mn);
        }
        return ans;
    }
};

6 买卖股票的最佳时机 II

给定一个数组 prices 表示一支股票每天的价格.

交易次数不限, 不过你不能同时参与多个交易 (也就是说, 如果你已经持有这支股票, 在再次购买之前, 你必须先卖掉它).

设计一个算法求出最大的利润.

输入: [2, 1, 2, 0, 1]
输出: 2
解释: 
    1. 在第 2 天以 1 的价格买入, 然后在第 3 天以 2 的价格卖出, 利润 1
    2. 在第 4 天以 0 的价格买入, 然后在第 5 天以 1 的价格卖出, 利润 1
    总利润 2.
class Solution {
public:
    /**
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    int maxProfit(vector<int> &prices) {
        if (prices.size() == 0) return 0;
        int ans = 0;
        for (int i = 0; i < prices.size()-1; i++) {
            if (prices[i+1] > prices[i]) {
                ans += prices[i+1] - prices[i];
            }
        }
        return ans;
    }
};

7 买卖股票的最佳时机 III

假设你有一个数组,它的第i个元素是一支给定的股票在第i天的价格。设计一个算法来找到最大的利润。你最多可以完成两笔交易。

输入 : [4,4,6,1,1,4,2,5]
输出 : 6

记录阶段:[1]第一次买之前,[2]持有股票,[3]第一次卖之后/第二次买之前,[4]持有股票,[5]第二次卖之后。
如果想买股票,只要知道前一天是什么阶段,就可以知道能不能买。

第 i 天结束后,在阶段 5 的最大获利为 dp[i][12]

  • 情况 1:第 i-1 天就在阶段 5 dp[i-1][13]
  • 情况 2:第 i-1 天在阶段 4 dp[i-1][14] + (p[i] - p[i-1])

第 i 天结束后,阶段 4 的最大获利为 dp[i][15]

  • 情况 1:第 i-1 天就在阶段 4 dp[i-1][16] + (p[i-1] - p[i-2])
  • 情况 2:第 i-1 天在阶段 3 dp[i-1][17]
  • 情况 3:第 i-1 天在阶段 2,第 i 天买完了立即买 dp[i-1][18] + (p[i-1] - p[i-2])

第 i 天结束后,处在阶段 j,最大获利:

  • 阶段 1,3,5:dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + p[i-1] - p[i-2])
  • 阶段 2,4:dp[i][j] = max(dp[i-1][j] + p[i-1] - p[i-2], dp[i-1][j-1], dp[i-1][j-2] + p[i-1] - p[i-2])
class Solution {
public:
    /**
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    int maxProfit(vector<int> &prices) {
        int n = prices.size();
        if (n == 0) return 0;
        vector<vector<int>> dp(n + 1, vector<int>(6));
        dp[0][1] = 0;
        dp[0][2] = dp[0][3] = dp[0][4] = dp[0][5] = INT_MIN;
        for (int i = 1; i <= n; i++) {
            // 阶段 1,3,5
            for (int j = 1; j <= 5; j += 2) {
                dp[i][j] = dp[i-1][j];
                if (i > 1 && j > 1 && dp[i-1][j-1] != INT_MIN) {
                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + prices[i-1] - prices[i-2]);
                }
            }
            // 阶段 2,4
            for (int j = 2; j <= 5; j += 2) {
                dp[i][j] = dp[i-1][j-1];
                if (i > 1 && dp[i-1][j] != INT_MIN) {
                    dp[i][j] = max(dp[i][j], dp[i-1][j] + prices[i-1] - prices[i-2]);
                }
                if (i > 1 && j > 2 && dp[i-1][j-2] != INT_MIN) {
                    dp[i][j] = max(dp[i][j], dp[i-1][j-2] + prices[i-1] - prices[i-2]);
                }
            }
        }
        return max(dp[n][1], max(dp[n][3], dp[n][5]));
    }
};

8 买卖股票的最佳时机 IV

给定数组 prices, 其中第 i 个元素代表某只股票在第 i 天第价格.

你最多可以完成 k 笔交易. 问最大的利润是多少?

输入: k = 2, prices = [4, 4, 6, 1, 1, 4, 2 ,5]
输出: 6
解释: 以 4 买入, 以 6 卖出. 然后再以 1 买入, 以 5 卖出. 利润为 2 + 4 = 6.

如果 k > n/2,相当于任意次买卖,买入价格比下一天低即可买入,与 II 相同。
当 k = 2,与 III 相同。

class Solution {
public:
    /**
     * @param k: An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    int maxProfit(int k, vector<int> &prices) {
        int n = prices.size();
        if (n == 0) return 0;

        if (k > n) {
            int ans = 0;
            for (int i = 0; i < n - 1; i++) {
                if (prices[i + 1] > prices[i]) {
                    ans += prices[i + 1] - prices[i];
                }
            }
            return ans;
        }

        vector<vector<int>> dp(n + 1, vector<int>(2 * k + 2));
        dp[0][1] = 0;
        for (int j = 2; j <= 2 * k + 1; j++) {
            dp[0][j] = INT_MIN;
        }
        for (int i = 1; i <= n; i++) {
            // 阶段 1,3,5,...
            for (int j = 1; j <= 2 * k + 1; j += 2) {
                dp[i][j] = dp[i-1][j];
                if (i > 1 && j > 1 && dp[i-1][j-1] != INT_MIN) {
                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + prices[i-1] - prices[i-2]);
                }
            }
            // 阶段 2,4,...
            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] != INT_MIN) {
                    dp[i][j] = max(dp[i][j], dp[i-1][j] + prices[i-1] - prices[i-2]);
                }
                if (i > 1 && j > 2 && dp[i-1][j-2] != INT_MIN) {
                    dp[i][j] = max(dp[i][j], dp[i-1][j-2] + prices[i-1] - prices[i-2]);
                }
            }
        }

        int mx = 0;
        for (int i = 1; i <= 2 * k + 1; i += 2) {
            if (dp[n][i] > mx) {
                mx = dp[n][i];
            }
        }
        return mx;
    }
};

9 买卖股票的最佳时机 V

给出一个股票n天的价格,每天最多只能进行一次交易,可以选择买入一支股票或卖出一支股票或放弃交易,输出能够达到的最大利润值。

给出 `a = [1,2,10,9]`, 返回 `16`
输入:
[1,2,10,9]
输出:
16

解释:
你可以在第一天和第二天买入股票,第三天和第四天卖出
利润:-1-2+10+9 = 16 

10 比特位计数

序列 + 位操作型动态规划。

给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> dp(n+1);
        dp[0] = 0;
        for (int i = 1; i <= n; i++) {
           dp[i] = dp[i>>1] + (i & 1);
        }
        return dp;
    }
};

参考

https://www.bilibili.com/video/BV1nt4y1Y7n7?p=2
https://www.bilibili.com/video/BV1nt4y1Y7n7?p=3