LeetCode刷题——第一周

LeetCode刷题——第一周

ID :1143,最长公共子序列问题

问题描述如下:问题描述
动态规划问题的思维辅助工具就是列表。在使用列表之前需要首先知道动态规划所要使用的数组的每一个元素所表示的含义,在本题中dp数组的元素dp[i][j]的含义就是text1字符数组的前i个元素和text2的前j个元素的最大公共子序列的长度。则该dp数组的演化示例如下

a b c d e
a 1 1 1 1 1
c 1 1 2 2 2
e 1 1 2 2 3

动态规划思维辅助工具——列表可以帮组理解dp数组的变化过程,从而帮助找到dp的状态转移方程,才能写代码实现。

dp[i][j] dp[i][j+1]
dp[i+1][j] dp[i+1][j+1]

状态转移方程的本质就是确定dp[i+1][j+1]与dp[i][j+1],dp[i+1][j]和dp[i][j]之间的关系。这就需要分类讨论:

  1. 当text1[i] 与text2[j]相等时:
    dp[i+1][j+1] = dp[i][j] + 1
  2. 当text1[i] 与text2[j]不相等时:
    dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1]);
    由此便可得出该问题的状态转移方程。

动态规划代码优化的两个思路

动态规划的数组是可以在两个方面做优化的,对其优化的效果就是空间复杂度具有明显的提高。其具体优化可以分为两个思路。

dp由二维数组降至一维数组

这样的降维将n*m降至m的空间复杂度是有前提条件的。即:dp[i+1][j+1]的生成不依赖与全部依赖与这三个元素dp[i][j],dp[i+1][j]和 dp[i][j+1]时,可以通过题目修改遍历数组的顺序完成降维。

使用滚动数组优化二维数组为2*m的二维数组

当dp[i+1][j+1]的生成依赖与上述三个元素时,则可以使用滚动数组的思路优化dp[N][M]为dp[2][M]。本题就可以使用这种优化,具体代码如下:

int longestCommonSubsequence(string text1, string text2) {
       int length1 = text1.size();
       int length2 = text2.size();
       if(length1 == 0 || length2 == 0){
           return 0;
       }
       vector<vector<int> > dp(2, vector<int>(length1+1, 0));
       int idx = 0;
       int jdx = 1;
       for(int i = 0; i < length2; ++i){
           jdx = (idx%2) == 0? 1 : 0; 
           for(int j = 0; j < length1; ++j){
               if(text1[j] == text2[i]){
                   dp[idx][j+1] = dp[jdx][j] + 1;
               }else{
                   dp[idx][j+1] = max(dp[jdx][j+1], dp[idx][j]);
               }
           }
           idx = (idx%2 == 0? 1 : 0);
       }
       return dp[idx%2 == 0? 1 : 0][length1];
   }

ID 300, 最长上升子序列

问题描述:问题描述
解题过程如下:

  1. 列表辅助思考,找出状态转移方程。
    dp[i]表示以num[i]结尾的最长上升子序列,则dp数组的推演过程如下表所示
i\j 10 9 2 5 3 7 101 18
10 1
9 1
2 1 1
5 1 1 2
3 1 1 2 2
7 1 1 2 3 3
101 2 2 2 3 3 4
18 2 2 2 3 3 4 4

当遍历到数组的最后一个是,dp数组演化结束,dp数组中最大的值即为结果。
并不是所有的问题结果都是dp数组的最后一个元素,这样dp的设定属性有关,本题dp[i]的含义就是以num[i]结尾的最长上升子序列,而dp的最后一个元素的含义就是以num数组最后一个元素结尾的最长上升子序列,这并不一定是整个数组的最长上升子序列。
2. 求出状态转移方程
d p [ i ] = m a x ( d p [ j ] + 1 , d p [ i ] )     0 < = j < i ,   w h e n   n u m [ j ] < n u m [ i ] dp[i] = max(dp[j]+1, dp[i]) \space \space \space 0 <= j < i, \space when \space num[j] < num[i] dp[i]=max(dp[j]+1,dp[i])   0<=j<i, when num[j]<num[i]
3. 写代码实现

    int lengthOfLIS(vector<int>& nums) {
        int length = nums.size();
        if(length == 0)
            return 0;
        vector<int> dp(length, 1);

        for(int i = 1; i < length; ++i){
            for(int j = 0; j < i; ++j){
                if(nums[i] > nums[j]){
                    dp[i] = max(dp[j] + 1, dp[i]);
                }
            }
        }
        return *max_element(dp.begin(), dp.end());
    }

ID 120, 三角形最小路径和

题目描述:题目描述
dp[i][j]的含义:i层时的,第j个元素对应的三角形路径和。
示例
2 2 2 3    4 3 \space \space 4 3  4 6    7    5 6 \space \space 7 \space \space 5 6  7  5 4    1    8    3 4\space \space1\space \space8\space \space3 4  1  8  3
动态规划表如下:

所在层\当前数组元素下标 0 1 2 3
0 2 0 0 0
1 5 6 0 0
2 11 10 13 0
3 15 11 21 16

可以看出dp[i+1][j+1]=min(dp[i][j], dp[i][j+1])+num[i+1][j+1]。与dp[i+1][j]无关,所以,可以通过修改遍历顺序将dp有二维数组降至一维数组。由于dp[i+1][j+1]的生成与dp[i][j]有关,所以在求dp[i+1][j+1]之前不可以修改dp[i][j]所以需要倒序遍历
代码如下:

    int minimumTotal(vector<vector<int>>& triangle) {
        int length = triangle.size();
        if(length == 0)
            return 0;

        vector<int> dp(length , 0);
        dp[0] = triangle[0][0];
        for(int i = 1; i < length; ++i){
            for(int j = i; j >= 0; --j){
                if(j == i){
                    dp[j] = dp[j-1];
                }else if(j != 0 ){
                    dp[j] = min(dp[j-1], dp[j]);
                }
                dp[j] += triangle[i][j];
            }
        }
        return *min_element(dp.begin(), dp.end());
    }

ID 53, 最大子序和

题目描述:题目描述
dp[i]:以num[i]结尾的最大子序和。
以[-2,1,-3,4,-1,2,1,-5,4]为例,动态表如下:

-2 1 -3 4 -1 2 1 -5 4
0 -2
1 1
2 -2
3 4
4 3
5 5
6 6
7 1
8 5

动态方程:dp[i+1] = max(dp[i] + num[i+1], num[i]);
优化:有动态方程可以看出,dp[i+1]仅与dp[i]有关,所以,可以将dp数组使用两个变量表示,pre和max_ele,分别记录dp[i]的值求出dp[i+1],max_ele记录dp中最大的数。
代码如下:

    int maxSubArray(vector<int>& nums) {
        int length = nums.size();
        if(length == 0)
            return 0;
        int pre = nums[0];
        int max_ele = pre;
        for(int i = 1; i < length; ++i){
            pre = max(nums[i] + pre , nums[i]);
            if(max_ele < pre)
                max_ele = pre;
        }
        return max_ele;
    }

本周总结:
动态规划的思考步骤:

  1. 确定dp数组的含义
  2. 构造动态规划表,寻找状态转移方程
  3. 状态转移方程
  4. 对dp数组优化
  5. 写代码
    其中第四步对dp数组优化思考的核心在于dp[i+1][j+1]与dp[i+1][j],dp[i][j+1]和dp[i][j]的哪些有关系
    ① 仅与其中的一个元素有关时:参考ID53的解题过程
    ②仅与其中的两个元素有关时:参考ID120的解题过程
    ③与这三个元素都有关时:参考ID1143的解题过程

你可能感兴趣的:(笔试习题集)