leetcode刷题动态规划-第1篇

Posted 非晚非晚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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以上是关于leetcode刷题动态规划-第1篇的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode 动态规划刷题总结

LeetCode刷题笔记-动态规划-day1

LeetCode刷题笔记-动态规划-day1

LeetCode刷题笔记-动态规划-day1

leetcode刷题- 动态规划

LeetCode刷题笔记-动态规划-day7