跳转至

动态规划

基本要素

转移方程+边界条件

爬楼梯 70

题目分析

  1. 转移方程:\(f(x)=f(x - 1)+f(x + 1)\)

  2. 边界条件: \(f(0)=1,f(1)=1\) 元方案要求唯一性

由于\(f(x)\)只和\(f(x - 1),f(x - 2)\)有关,故使用滚动数组优化空间复杂度

gif

代码实现

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//滚动数组
int climbStairs(int n) {
    int p = 0, q = 0, r = 1;
    for (int i = 1; i <= n; i ++) {
        p = q;
        q = r;
        r = p + q;
    }
    return r;
}
//dp数组
class Solution {
public:
    int climbStairs(int n) {
        if (n == 1) return 1;
        int* dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i ++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

使用最小花费爬楼梯

题目分析

cost数组长度len代表了路径中的楼梯数, 则len个阶梯分别对应了数组下标中0到\(n - 1\)的部分, 本题即求到达下标为n的楼梯所需要的最小花费

与上一道题类似, 这道题也是将爬n个台阶的问题, 转化为了爬\((n-1)+1\)个台阶和\((n-2)+2\)个台阶的问题, 由此得到该问题的状态转移方程

\(dp[n] = min(dp[n-1] + cost[n-1], dp[n-2] + cost[n-2])\)

有点贪心的思想, 每一步都取到最小值的话, 最终结果一定最小 考虑边界条件, 由题设我们知道, 起点可以在下标为0或1的楼梯中选取, 所以到达这两个楼梯的前驱花费一定为0

Text Only
1
2
dp[0] = 0
dp[1] = 0
有了状态转移方程与边界条件, 就可以着手开始解题了

代码实现

利用动态数组节省空间的代码如下

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int len = cost.size();
        int prev, curr, next;
        prev = 0; curr = 0;
        for (int i = 0; i <= len; i ++) {
            int func1 = prev + cost[i - 1];
            int func2 = prev + cost[i - 2];
            next = func1 > func2? func1 : func2;
            prev = curr;
            curr = next;
        }
        return curr;
    }
};

删除并获得点数

C++哈希表实现

C
1
2
3
4
5
#include <unordered_map>

unordered_map<int, int> hashtable;
hashtable[key] = value; //insert操作
cout << hashtable[key1] << endl; //按键索值

思路与代码

选取数是任意的,如果每取一个值便要执行删除操作,1是无法进行回溯寻求最优解,2是时间复杂度大,故对vector数组进行排序:

C
1
sort(nums.begin(), nums.end());
又因为一旦一个值被选取,则与他相同的值均可以被选取,故使用hash表对每个值与其出现的次数进行存储,能优化时间与空间复杂度

接下来考虑状态转移方程,此时问题已经转化为了:在一个有序单调数组中顺序取数,在取了一个数num后,则不能选取num + 1(因为取数操作从无序数组转为有序数组,故而考虑num - 1与考虑num + 1性质相同),而为了获得最大点数,我们必须权衡选取num与选取num + 1两个操作,究竟哪个利润最大

由此得到对于第i个数据对象的状态转移方程

Text Only
1
2
3
4
if dp[i] - dp[i - 1] == 1
dp[i] = max(dp[i - 1], dp[i] * N + dp[i - 2])
else 
dp[i] = dp[i] * N + dp[i - 2]
由于要考虑选和不选的问题,我们使用一个变量last存储当前的dp[i],也就是下一轮循环的前驱了

对于边界条件,我们考虑到value初始值为0的情况,为了避免对首元素的讨论,我们让dp数组的首元素取0即可

代码如下:

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public:
    int deleteAndEarn(vector<int>& nums) {
        unordered_map<int, int> m; //记录每一个值出现了多少次
        sort(nums.begin(), nums.end());
        vector<int> dp = {0, nums[0]}; //起始两个数
        m[nums[0]] = 1;
        for (int i = 1; i < nums.size(); i ++) {
            m[nums[i]] ++;
            if (nums[i] != dp.back())
                dp.push_back(nums[i]);
        }
        int last = dp[1];
        dp[1] = dp[1] * m[dp[1]];
        for (int i = 2; i < dp.size(); i ++) {
            if (dp[i] - last == 1) {
                last = d[i];
                dp[i] = max(dp[i - 1], dp[i - 2] + dp[i] * m[dp[i]]);
            } else {
                last = dp[i];
                dp[i] = dp[i - 1] + dp[i] * m[dp[i]];
            }
        }
        return dp[dp.size() - 1];
    }
};

最长有效括号 32

问题分析

求最长括号字串长度, 考虑动态规划

在动态规划问题中, 注重的是由小到大, 由浅入深的问题累积过程. 在本题中, 我们设下标i代表当前括号字符串s的第i个元素, dp[i]表示以当前元素结尾并包含当前元素在内的最长有效括号字串, 下面的讨论以上述条件为依托展开

  • 状态转移方程 由于dp[i]表示的是最长有效括号字串, 故要使其值不等于零, 则须满足s[i] \(=\) ')' , 对s[i - 1]进行讨论:
    Text Only
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    1. s[i - 1] == '(' 
        此时s[i - 1]与s[i]构成一对有效括号, dp[i]至少为2
        赋值操作: dp[i] = 2
        若在此基础上, s[i - 2]存在且dp[i - 2] != 0
        说明s[i - 2]是一条有效括号字串的结尾
        dp[i] = dp[i] + dp[i - 2]
    2. s[i - 1] == ')'
        若dp[i - 1] == 0, 说明s[i - 1]为一条有效括号字串的结尾
        为了知道s[i]是否为一条有效括号字串的结尾
        我们需要考虑下标i - dp[i - 1] - 1的存在性
        若其不存在 则s[i]不是有效结尾
        若其存在,且s[i - dp[i - 1] - 1] == '('
        说明s[i]是有效结尾
        dp[i]至少为dp[i - 1] + 2
        赋值操作: dp[i] = dp[i - 1] + 2;
        若再此基础上, s[i - dp[i - 1] - 2]存在
        且dp[i - dp[i - 1] - 2] != 0
        说明s[i - dp[i - 1] - 2]是一条有效括号的结尾
        dp[i] = dp[i] + dp[i - dp[i - 1] + 2]
    
  • 边界条件 我们知道, dp[0] == 0, 因为单个括号不可能有效

在代码实现中, 只需要考虑下标的非负性即可

代码实现

C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public:
    int longestValidParentheses(string s) {
        int size = s.length();
        vector(int) dp(size, 0);
        int maxval = 0;
        for (int i = 1; i < size; i ++) {
            if (s[i] == '(') {
                dp[i] = 0;
            } else if (s[i] == ')') {
                if (s[i - 1] == '(') {
                    dp[i] = 2;
                    if (i - 2 >= 0) {
                        dp[i] += dp[i - 2];
                    }
                } else if (dp[i - 1] > 0 ) {
                    if (i - dp[i - 1] - 1 >= 0 && dp[i - dp[i - 1] - 1] == '(') {
                        dp[i] = dp[i - 1] + 2;
                        if (i - dp[i - 1] - 2 >= 0) {
                            dp[i] += dp[i - dp[i - 1] + 2];
                        }
                    }
                }
            }
            maxval = max(dp[i], maxval);
        }
        return maxval;
    }
}

最长回文字串 5