力扣刷题笔记:动态规划(13)

第一步骤:定义数组元素的含义,上面说了,我们会用一个数组,来保存历史数组,假设用一维数组 dp[] 吧。这个时候有一个非常非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思?
第二步骤:找出数组元素之间的关系式,我觉得动态规划,还是有一点类似于我们高中学习时的归纳法的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2]……dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步,后面我会讲几种类型的题来说。
第三步骤:找出初始值。学过数学归纳法的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但是,我们得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我们必须要能够直接获得 dp[2] 和 dp[1] 的值,而这,就是所谓的初始值。

42. 接雨水(两次遍历+两组一维动规)

雨水量=min(左边高度,右边高度)-当前高度
先从左边扫一遍每个点左边的最大高度,再从右边扫一遍得到每个点右边的最大高度
求这两个数组的最小值,再减去每个位置的高度求和就是雨水总量

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if (n == 0) 
            return 0;
        //从左边扫一遍动规
        vector<int> leftMax(n);
        leftMax[0] = height[0];
        for (int i = 1; i < n; ++i) 
            leftMax[i] = max(leftMax[i - 1], height[i]);
        //从右边扫一遍动规
        vector<int> rightMax(n);
        rightMax[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; --i) 
            rightMax[i] = max(rightMax[i + 1], height[i]);
        int ans = 0;
        for (int i = 0; i < n; ++i) 
            ans += min(leftMax[i], rightMax[i]) - height[i];
        return ans;
    }
};

62. 不同路径(矩阵+二维动规)

设置二维数组dp为当前点的路径和,该值等于x-1或者y-1走来的点路径相加

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> f(m, vector<int>(n));
        for (int i = 0; i < m; ++i) 
            f[i][0] = 1;
        for (int j = 0; j < n; ++j) 
            f[0][j] = 1;
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) 
                f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
        return f[m - 1][n - 1];
    }
};

64. 最小路径和(矩阵+二维动规)

用二维数组dp记录当前最小路径,当前dp等于上面来的左边来的最小值加当前值

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if (grid.size() == 0 || grid[0].size() == 0) 
            return 0;
        int rows = grid.size(), columns = grid[0].size();
        auto dp = vector < vector <int> > (rows, vector <int> (columns));
        dp[0][0] = grid[0][0];
        for (int i = 1; i < rows; i++) 
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        for (int j = 1; j < columns; j++) 
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        for (int i = 1; i < rows; i++) 
            for (int j = 1; j < columns; j++) 
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
        return dp[rows - 1][columns - 1];
    }
};

70. 爬楼梯(一次遍历+一维动规)

1、本题不能用递归,楼梯过多会超出时间限制,用动态规划
2、用first和second代替动规状态数组,存储前两步楼梯的各自方案数之和
3、当前等于first+second,再更新first和second

   int climbStairs(int n) {
        if(n==0) return 1;
        else if (n==1) return 1;
        else if (n==2) return 2;
        int first=1,second=2,now=3,sum=0;
        while(now<=n) {
            sum=first+second;
            first=second;
            second=sum;
            now++;
        }
        return sum;
    }

72. 编辑距离

1、有三种编辑方法,在a字符串加一个字符,在b字符串加一个字符,在a或b字符串修改一个字符
2、二维dp初始化为ab中有一个为0时,另一个字符串只能增加对应长度的字符
3、dp[i][j]等于a加一个,b加一个,若相等则不用改,不相等则加一个,这三种情况中最小的一个

class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.length(),m = word2.length(),D[n + 1][m + 1];
        if (n * m == 0) return n + m;   // 有一个字符串为空串
        // 边界状态初始化
        for (int i = 0; i < n + 1; i++) 
            D[i][0] = i;
        for (int j = 0; j < m + 1; j++) 
            D[0][j] = j;
        // 计算所有 DP 值
        for (int i = 1; i < n + 1; i++) {
            for (int j = 1; j < m + 1; j++) {
                int left = D[i - 1][j] + 1;
                int down = D[i][j - 1] + 1;
                int left_down = D[i - 1][j - 1];
                if (word1[i - 1] != word2[j - 1]) left_down += 1;
                D[i][j] = min(left, min(down, left_down));
            }
        }
        return D[n][m];
    }
};

96. 不同的二叉搜索树

个数从小到大构建,每次构建等于以不同数为根节点,其他分到左右两边

class Solution {
public:
    int numTrees(int n) {
        vector<int> G(n + 1, 0);
        G[0] = 1;
        G[1] = 1;
        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= i; ++j) {
                G[i] += G[j - 1] * G[i - j];
            }
        }
        return G[n];
    }
};

152. 乘积最大子数组(一次遍历+双一维动规)

本题存在正负,不能只考虑前一个的最大值,还要考虑前一个的最小值,因为负负得正,通过渐进dp减小时间复杂度

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int maxF = nums[0], minF = nums[0], ans = nums[0];
        for (int i = 1; i < nums.size(); ++i) {
            int mx = maxF, mn = minF;
            maxF = max(mx * nums[i], max(nums[i], mn * nums[i]));
            minF = min(mn * nums[i], min(nums[i], mx * nums[i]));
            ans = max(maxF, ans);
        }
        return ans;
    }
};

198. 打家劫舍(一次遍历+一维动规)

当前取得的最大值取决于前两个,要么偷前一家不偷当前家,要么偷前两家加上当前家

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.empty()) 
            return 0;
        int size = nums.size();
        if (size == 1) 
            return nums[0];
        vector<int> dp = vector<int>(size, 0);
        dp[0] = nums[0];
        dp[1] = max(nums[0], nums[1]);
        for (int i = 2; i < size; i++) 
            dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
        return dp[size - 1];
    }
};

213. 打家劫舍 II(两次遍历+一维动规)

这题加了一个限制条件:首尾相连,所以第一个和最后一个只能偷一个,所以进行两次打家劫舍,要么偷第一家,要么偷最后一家,取两个的最大值

class Solution {
public:
    int robRange(vector<int>& nums, int start, int end) {
        int first = nums[start], second = max(nums[start], nums[start + 1]);
        for (int i = start + 2; i <= end; i++) {
            int temp = second;
            second = max(first + nums[i], second);
            first = temp;
        }
        return second;
    }
    int rob(vector<int>& nums) {
        int length = nums.size();
        if (length == 1) 
            return nums[0];
        else if (length == 2) 
            return max(nums[0], nums[1]);
        return max(robRange(nums, 0, length - 2), robRange(nums, 1, length - 1));
    }
};

337. 打家劫舍 III(哈希表+递归+动规)

用两个哈希表存节点的两个状态,选择和不选择,如果选择,子节点必须不选择,如果不选择,则选子节点中选择和不选择的较大者

class Solution {
public:
    unordered_map <TreeNode*, int> f, g;
    void dfs(TreeNode* node) {
        if (!node) 
            return;
        dfs(node->left);
        dfs(node->right);
        f[node] = node->val + g[node->left] + g[node->right];
        g[node] = max(f[node->left], g[node->left]) + max(f[node->right], g[node->right]);
    }
    int rob(TreeNode* root) {
        dfs(root);
        return max(f[root], g[root]);
    }
};

221. 最大正方形(双重遍历+二维动规)

1、二维动规数字dp表示以该点为右下角的最大正方形边长
2、最上面和最左边的点的dp值只能为1
3、dp值取决于它上面左面和左上方三个点的最小值加1,因为他想构成正方形必须保证这三个点起码都有

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if (matrix.size() == 0 || matrix[0].size() == 0) 
            return 0;
        int maxSide = 0,rows = matrix.size(), columns = matrix[0].size();
        vector<vector<int>> dp(rows, vector<int>(columns));
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                if (matrix[i][j] == '1') {
                    if (i == 0 || j == 0) 
                        dp[i][j] = 1;
                    else 
                        dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                    maxSide = max(maxSide, dp[i][j]);
                }
            }
        }
        int maxSquare = maxSide * maxSide;
        return maxSquare;
    }
};

322. 零钱兑换(双重遍历+一维动规)

设置dp为当前价格的最少方案,一层遍历价格,一层遍历硬币面值,从小到大,看哪一个面值硬币方案最少,保存为当前dp

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

518. 零钱兑换 II(双重遍历+一维动规)

上一题求的是硬币个数,这一题求的是方案总数,上一题外层遍历方案内层遍历硬币,这一题外层遍历硬币内层遍历方案

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

354. 俄罗斯套娃信封问题

1、按照一维升序二维降序的规则对信封排序
2、对二维查找最长子序列,一维dp存当前信封可以套几个

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        if (envelopes.empty()) 
            return 0;
        int n = envelopes.size();
        sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
            return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
        });
        vector<int> f(n, 1);
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (envelopes[j][1] < envelopes[i][1]) {
                    f[i] = max(f[i], f[j] + 1);
                }
            }
        }
        return *max_element(f.begin(), f.end());
    }
};

509. 斐波那契数(一次遍历+一维动规)

一次遍历,不断更新第一个值和第二个值,使用滚动数组暂存前两个数

class Solution {
public:
    int fib(int n) {
        if (n < 2) {
            return n;
        }
        int p = 0, q = 0, r = 1;
        for (int i = 2; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值