【leetcode刷题】动态规划-第1篇

当你超过别人一点点,别人会嫉妒你;当你超过别人一大截,别人就会羡慕你。所以尽可能的超越吧!坚持终会有收获,大家一起加油!

1. 最长回文子串

题目:给你一个字符串 s,找到 s 中最长的回文子串。
在这里插入图片描述

解题思路(参考官网解答):
(两种算法时间复杂度都是 O ( n 2 ) O(n^2) O(n2),中心扩展算法空间复杂的 O ( 1 ) O(1) O(1),动态规划的空间复杂度为 O ( n 2 ) O(n^2) O(n2),复杂度中心扩散相对比较好实现)

  • 动态规划法:dp[i][j]为dp[i+1][j-1]的下一个状态,如果s[i] = s[j],dp[i][j]的回文与否和上一状态有关。
  • 中心扩展算法:所有的字串都是由中心向两边扩散出来的,也就是由1或者2个相同的字串得到。

动态规划法求解:

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.size();
        if (n < 2) {
            return s;
        }

        int maxLen = 1;
        int begin = 0;
        // dp[i][j] 表示 s[i..j] 是否是回文串
        vector<vector<int>> dp(n, vector<int>(n));
        // 初始化:所有长度为 1 的子串都是回文串
        for (int i = 0; i < n; i++) {
            dp[i][i] = true;
        }
        // 递推开始
        // 先枚举子串长度,每种长度都会计算到。
        for (int L = 2; L <= n; L++) {
            // 枚举左边界,左边界的上限设置可以宽松一些
            for (int i = 0; i < n; i++) {
                // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                int j = L + i - 1;
                // 如果右边界越界,就可以退出当前循环
                if (j >= n) {
                    break;
                }

                if (s[i] != s[j]) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {//长度在1、2、3
                        dp[i][j] = true;
                    } else {//长度超过3,则由它的字串决定
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }

                // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLen) {
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substr(begin, maxLen);
    }
};

中心扩展算法:

class Solution {
public:
	//扩散算法
    pair<int, int> expandAroundCenter(const string& s, int left, int right) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            --left;
            ++right;
        }
        return {left + 1, right - 1};//必须用{}
    }

    string longestPalindrome(string s) {
        int start = 0, end = 0;
        for (int i = 0; i < s.size(); ++i) {
        	//类型推导pair,开始扩散,求取最大的回文
            auto [left1, right1] = expandAroundCenter(s, i, i);//必须用[]
            auto [left2, right2] = expandAroundCenter(s, i, i + 1);
            if (right1 - left1 > end - start) {
                start = left1;
                end = right1;
            }
            if (right2 - left2 > end - start) {
                start = left2;
                end = right2;
            }
        }
        return s.substr(start, end - start + 1);
    }
};

2. 接雨水

题目:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
在这里插入图片描述

解题思路:使用双指针指向两端,然后分别向中间移动。

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int left = 0, right = height.size() - 1;//指向两端
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = max(leftMax, height[left]);//左边的最大值
            rightMax = max(rightMax, height[right]);//右边的最大值
            if (height[left] < height[right]) {
                ans += leftMax - height[left];//左侧的高度差
                ++left;
            } else {
                ans += rightMax - height[right];//右侧的高度差
                --right;
            }
        }
        return ans;
    }
};

3. 最大子序和

题目:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

解题思路:判断的前面的序列是否为正(对后续序列是否有贡献),如果为正,则dp[i] = dp[i-1]+a[i]

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int maxSum = nums[0];
        for(int i = 1; i < nums.size(); i++)
        {
            if(nums[i - 1] > 0) nums[i] += nums[i - 1];//判断对后续是否有贡献
            if(nums[i] > maxSum) maxSum = nums[i];
        }
        return maxSum;
    }
};

4. 不同路径

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?

解题思路:

  • 状态转移:dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
  • 如果在边上则dp[i][j] = 1;
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int> > vec(m,vector<int>(n,0)); //初始化row * col二维动态数组,初始化值为0
        for(int i = 0; i < m; i++)
        for(int j = 0; j < n; j++)
        {
            if(i == 0 || j == 0){vec[i][j] = 1;}//在边上的只有一条路径
            else
            {
                vec[i][j] = vec[i - 1][j] + vec[i][j - 1];
            }
        }
        return vec[m - 1][n - 1];
    }
};

5. 不同路径 II

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。
在这里插入图片描述

解题思路:先遍历首行首列,然后遍历中间。细节就是要判断上一状态和当前状态是否有障碍物。

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size();
        int n = obstacleGrid[0].size();
        if(obstacleGrid[m - 1][n - 1] == 1) return 0;
        vector<vector<int>> vec(m, vector<int>(n, 0));
        for(int i = 0; i < m; i++)//遍历边上
        {
            if(obstacleGrid[i][0] == 1) 
            {
                while(i < m){
                    vec[i][0] = 0;
                    i++;
                }
                break;
            }
            vec[i][0] = 1;
        }
        for(int j = 0; j < n; j++)//遍历边上
        {
            if(obstacleGrid[0][j] == 1) 
            {
                while(j < n){
                    vec[0][j] = 0;
                    j++;
                }
                break;
            }
            vec[0][j] = 1;
        }
        for(int i = 0; i < m; i++)
        for(int j = 0; j < n; j++)
        {
            if(i == 0 || j == 0)continue;//当前位置有障碍物
            else if(obstacleGrid[i - 1][j] == 1 && obstacleGrid[i][j - 1] == 1)
            {
                vec[i][j] = 0;
            }
            else if(obstacleGrid[i - 1][j] == 1)
            {
                vec[i][j] = vec[i][j - 1];
            }
            else if(obstacleGrid[i][j - 1] == 1)
            {
                vec[i][j] = vec[i - 1][j];
            }
            else
            {
                vec[i][j] = vec[i - 1][j] + vec[i][j - 1];
            }
        }
        return vec[m - 1][n - 1];
    }
};

6. 最小路径和

题目:给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

解题思路:

  • 对于非首行首列dp[i][j] = min(dp[i-1][j], dp[i][j-1]);
  • 首行首列则只有一条路。
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if(grid.size() < 0) return 0;
        int m = grid.size();
        int n = grid[0].size();
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
            {
                if(i == 0 && j == 0)continue;//第一个数字
                else if(i == 0) grid[i][j] += grid[i][j - 1];//首行
                else if(j == 0) grid[i][j] += grid[i - 1][j];//首列
                else grid[i][j] += min(grid[i - 1][j], grid[i][j - 1]);//中间
            }
        return grid[m - 1][n - 1];
    }
};

7. 爬楼梯

题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

解题思路:dp[i] = dp[i - 1] + dp[i - 2];

class Solution {
public:
    int climbStairs(int n) {
        if(n < 3) return n;//第1,2个元素
        int a = 1;
        int b = 2;
        for(int i = 3; i <= n; i++)//从第3个元素开始
        {
            int tmp = a + b;
            a = b;
            b = tmp;
        }
        return b;
    }
};

8. 编辑距离

题目:给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

解题思路:总体下来只有两种操作,一种是插入,一种是修改。(因为删除操作,也可以是对另一个字符串进行插入),所以动态规划的转移方程为:

  • 如果word1[i] == word2[j],也就是最后一个字符相同,则dp[i][j] = dp[i - 1][j - 1].
  • 如果不相同,则为三种情况的最小值+1:
  1. 知道"abcd"变成"fgh"多少步(假设X步),那么从"abcde"到"fgh"就是"abcde"->“abcd”->“fgh”。(一次删除,加X步,总共X+1步)->dp[i][j] = dp[i-1][j] + 1
  2. 知道"abcde"变成“fg”多少步(假设Y步),那么从"abcde"到"fgh"就是"abcde"->“fg”->“fgh”。(先Y步,再一次添加,加X步,总共Y+1步) -> dp[i][j] = dp[i][j-1]+1
  3. 知道"abcd"变成“fg”多少步(假设Z步),那么从"abcde"到"fgh"就是"abcde"->“fge”->“fgh”。->dp[i][j] = dp[i-1][j-1] + 1
class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.length();
        int m = word2.length();

        // 有一个字符串为空串
        if (n * m == 0) return n + m;

        // DP 数组
        int D[n + 1][m + 1];

        // 边界状态初始化
        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 值,所有元素都从1开始
        for (int i = 1; i < n + 1; i++) {
            for (int j = 1; j < m + 1; j++) {
                int left = D[i - 1][j] + 1;//从左边过来
                int up = D[i][j - 1] + 1;//从上面过来
                int left_up = D[i - 1][j - 1];//左上角
                if (word1[i - 1] != word2[j - 1]) left_up += 1;
                D[i][j] = min(left, min(up, left_up));
            }
        }
        return D[n][m];
    }
};

9. 买卖股票的最佳时机

题目:给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

解题思路:保留之前的最低点cost,并计算当前利润profit = price - cost;

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int cost = INT_MAX;
        int profit = 0;
        for(auto price : prices)
        {
            cost = min(cost, price);
            profit = max(profit, price - cost);
        }
        return profit;
    }
};

10 买卖股票的最佳时机 III

题目:给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。

  • 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

解题思路:定义买卖的四种状态,对应4种类状态:

  1. 只进行过一次买操作;-> buy1
  2. 进行了一次买操作和一次卖操作,即完成了一笔交易;-> buy1&sell1
  3. 在完成了一笔交易的前提下,进行了第二次买操作;-> buy1&sell1&buy2
  4. 完成了全部两笔交易。buy1&sell1&buy2&sell2
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        int buy1 = -prices[0], sell1 = 0;
        int buy2 = -prices[0], sell2 = 0;
        for (int i = 1; i < n; ++i) {
            buy1 = max(buy1, -prices[i]);//第一种情况
            sell1 = max(sell1, buy1 + prices[i]);//第二种情况
            buy2 = max(buy2, sell1 - prices[i]);//第三种情况
            sell2 = max(sell2, buy2 + prices[i]);//第四种情况
        }
        return sell2;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非晚非晚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值