leetcode算法习题----动态规划系列

知道算法很重要,但是就是提不提刷题的劲来,到现在也就刷过leetcode30来道题,从今天开始每天至少刷一道题。坚持。

文章目录

    • 斐波那契数列
      • 斐波那契数列
      • 爬楼梯(leetcode 70)
      • 打家劫舍(leetcode 198)
      • 环形街道抢劫 (leetcode 213)
    • 矩阵路径
      • 最小路径和(leetcode64)
      • 不同路径 (leetcode 62)
    • 数组区间
      • 数组区间和(leetcode 303)
      • 等差数列划分(leetcode 413)
    • 分割整数
      • 整数拆分(leetcode 343)
      • 完全平方数(leetcode 279)
      • 解码方法(leetcode 91)
    • 最长递增子序列
      • 最长上升子序列(leetcode 300 )
      • 最长数对链(leetcode 646)

斐波那契数列

斐波那契数列

1.斐波那契数列

题目描述:
斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
给定 N,计算 F(N)。
示例 1:

输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1.

递归实现:

class Solution {
public:
    int fib(int N) {
        if(N<=0)
            return 0;
        if(N<=2)
            return N;
        return fib(N-1)+fib(N-2);
    }
};

非递归实现

class Solution {
public:
    int fib(int N) {
        if(N<=0)
            return 0;
        if(N<=2)
            return 1;
        int first=1;
        int second=1;
        int result;
        for(int i=3;i<=N;++i)
        {
            result=first+second;
            first=second;
            second=result;
        }
        return result;
    }
};

爬楼梯(leetcode 70)

题目描述:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

解题思路:
定义一个数组dp存储上楼梯的方法树,dp[i]表示走到第i个楼梯的方法数目。
当i1时,dp[1]=1;
当i
2时,dp[2]=2;(两次都上一个台阶和一次上两个台阶)
当i3时,可以从第2个楼梯和第1个楼梯一步到达,dp[3]=dp[1]+dp[2]
当i
4时,可以从第3个楼梯和第2个楼梯一步到达,dp[4]=dp[3]+dp[2]

可以总结出,第i个楼梯可以从第i-1个楼梯和第i-2个楼梯处一步到达。
dp[i]=dp[i-1]+dp[i-2];

代码描述:

class Solution {
public:
    int climbStairs(int n) {
        if(n<0)
            return 0;
        if(n<=2)
            return n;
        else{
            int result;
            int first=1;
            int second=2;
            for(int i=3;i<=n;i++)
            {
                result=first+second;
                first=second;
                second=result;
            }
            return result;
        }
    }
};

打家劫舍(leetcode 198)

题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额

示例:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

解题思路:
创建一个dp数组,每一步都放入当前能偷取的最大金额,即当前房屋和间隔房屋之和与上一个房屋金额相比的最大值

dp[i]=max(dp[i-2]+nums[i],dp[i-1])

代码:

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

环形街道抢劫 (leetcode 213)

题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例:

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

思路:
既然题目说是环形的,可以把它拆开来看成两个队列,分别是nums[0],nums[1],…,nums[n-2]和nums[1],nums[2],nums[n-1].保证最后两个相邻的房屋不能同一天被偷。
代码描述:

用两组dp实现真的很复杂,看一下代码:

class Solution {
public:
//用两组dp来实现
    int rob(vector<int>& nums) {
        if(nums.size()==0)
            return 0;
        if(nums.size()==1)
            return nums[0];
        if(nums.size()==2)
            return max(nums[0],nums[1]);
        vector<int> dp1(nums.size()-1,0);
        vector<int> dp2(nums.size(),0);
        dp1[0]=nums[0];dp1[1]=max(nums[0],nums[1]);
        for(int i=2;i<=nums.size()-2;i++){
            dp1[i]=max(nums[i]+dp1[i-2],dp1[i-1]);
        }
        dp2[0]=0;dp2[1]=nums[1];dp2[2]=max(nums[1],nums[2]);
        for(int i=3;i<=nums.size()-1;i++){
            dp2[i]=max(nums[i]+dp2[i-2],dp2[i-1]);
        }
        return max(dp1[nums.size()-2],dp2[nums.size()-1]);
    }
};

看了其他人的答案,真的要学会创造接口:

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

矩阵路径

最小路径和(leetcode64)

题目描述:
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步

示例:

输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

思路:

数据结构选取:
二维数组 grid
dp数组选取,一维数组,用于记录上一步的最小路径。
具体算法:
遍历二维数组,记录当前的最小路径到dp,dp记录每一行的最小路径。
如果遇到行为0(i0), 只需要当前位置加左边位置的最小路径dp[j]=grid[i][j]+dp[j-1]
如果遇到列为0(j
0),只需要当前位置加上面位置的最小路径dp[j]=grid[i][j]+dp[j]
如果都不为0,就是当前位置加上面位置和左边位置的最小路径,dp[j]=grid[i][j]+min(dp[j],dp[j-1])

代码描述:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int rows=grid.size();
        int cols=grid[0].size();
        //设置dp数组
        vector<int> dp(cols,0);
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(j==0){
                    dp[j]=grid[i][j]+dp[j];
                }
                else if(i==0){
                    dp[j]=grid[i][j]+dp[j-1];
                }
                else{
                    dp[j]=grid[i][j]+min(dp[j-1],dp[j]);
                }
            }
        }
        return dp[cols-1];
    }
};

不同路径 (leetcode 62)

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

示例:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向右 -> 向下
  2. 向右 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向右

思路:
不管是一直向右还是一直向下都是一条路径,只有当向右再向下或者向下再向右走的时候路径数会增加。
采用动态规划dp,一维数组实现。这个一维数组表示走到每一行中的位置有多少路径。
既然,向右或者向下走是一条,那么当i0,j0时,此位置都为1.
都不为0时,dp[j]=dp[j-1]+dp[j]
解释一下这个方程:dp[j]表示本行当前位置的路径数,dp[j-1]表示这个位置左面的位置的路径数,第二个dp[j]表示和当前位置同一列的上面位置的路径数。因为是一维数组迭代,所以我们最后求出的dp是最后一行所有位置的路径数。

代码实现:

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

数组区间

数组区间和(leetcode 303)

题目描述:
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

示例:

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

思路:

使用一维数组建立dp,记录原数组每一个位置当前的累加和。给出i,j之后,只需要用j位置累加和减去i-1位置累加和。
建立原数组的dp数组[-2,0,1,-4,-2,-3] i=2,j=5, -3-0=-3.

代码实现:

class NumArray {
public:
    NumArray(vector<int>& nums) {
        if(nums.size()==0) return;
        else{
                dp.resize(nums.size(),0);
                dp[0]=nums[0];
                for(int i=1;i<nums.size();i++){
                    dp[i]=dp[i-1]+nums[i];
                 }
        }
    }
    
    int sumRange(int i, int j) {
        if(i==0){
            return dp[j];
        }
        else{
            return dp[j]-dp[i-1];
        }
    }
    private:
    vector<int> dp;
};

等差数列划分(leetcode 413)

题目描述:
如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。

示例:

A = [1, 2, 3, 4]

返回: 3, A 中有三个子等差数组: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。
代码实现:

思路:

因为元素不少于3个,所以判断当前元素A[i]和上个元素A[i-1],与等差数列中的差值相等即可(即A[i]-A[i-1]=A[i-1]-A[i-2]).
构造dp一维数组,用于记录当前元素下等差子数列有多少个。如果前面相等,那么新区间中等差数列的个数即为 1+dp[i-1]。sum同时也要加上这个值来更新全局的等差数列总数。

举例:
1,2,3是一个等差数列,构造dp[0,0,1]在3位置对应出现一个等差数列。
1,2,3,4是一个等差数列,现在dp变为[0,0,1,2],4位置对应两个等差子数列,{1,2,3},{2,3,4}.

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

时间复杂度O(n),空间复杂度O(n).

常数空间动态规划:

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) {
        if(A.size()<=2)
            return 0;
        int dp=0;
        int sum=0;
        for(int i=2;i<A.size();i++){
            if(A[i]-A[i-1]==A[i-1]-A[i-2]){
                dp=dp+1;
                sum+=dp;
            }else{
                dp=0;
            }
        }
        return sum;
    }
};

分割整数

整数拆分(leetcode 343)

题目描述:
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

思路:

dp建立从1到n每一位整数的最大拆分乘积数组。
1的最大乘积是1,从2开始进行拆分,拆分为两对加数a,b(如10拆分为1,9、2,8…),那么求最大乘积就有四种情况,a,b都不拆分,a,b都拆分,a拆分b不拆分,b拆分a不拆分。逐一比较,求出最大拆分乘积,放入dp数组中。

代码:

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

再简写一下,这道题其实也可以只拆分一个加数,然后和原数比较即可,我试过也能过。而且用的时间更少。

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

时间复杂度O(n2),空间复杂度O(n)

完全平方数(leetcode 279)

题目描述:
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例:

输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.

思路:

dp用于存储每一位数的完全平方数相加的最小个数。
过程:n=10,从1开始计算完全平方数的最小个数,将当前这个数(比如5),切分成两种情况,第一种[5-i1,4],第二种[5-22,1],那么我们只需要看dp[1]位置和dp[2]位置的数谁小,比如dp[1]算出来为1,dp[2]算出来为2,然后取最小的1,再将这个数加一即可。
5完全平方数就应该切分为,5=1+4.

另外一种思路:

1,对于正整数N, 所有的解都是 N = 一个整数的平方 + 另一个整数; 直白点, N = AxA + B
2, 而B又是由 “一个整数的平方 + 另一个整数” 组成的; 那么, B = CxC + D
3,总结下就是:N = IxI + N’ 而 N’ = IxI + N’’

4, 本题要解的问题:正整数N最少由多个平方数相加;
5, 那么,N的最优解 = 1 + (N’的最优解)。而N’肯定小于N。
6, 所以本题的思路就是,对每一个N,观察1到N-1中,谁的解最小,那么N的解就是它+1.

7, 但是我们没必要1到N+1中的每一个数都去观察,因为有些组合不满足N = IxI + N’,譬如12 = 2+N’是不需要的,因为2不是某个数的平方。所以我们观察的范围要大大减小。

拿12举例,我们只能观察:
12 = 1 + 11
12 = 4 + 8
12 = 9 + 3
我们要得出3,8,11中谁的解最优,那么12的解就是它+1。

8, 我们从1到N计算, 2的解从1里找,3的解从[2,1]里找,4的解从[3,2,1]里找,依次类推,最后算到N的解即可。

leetcode算法习题----动态规划系列_第1张图片
代码描述:

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1,0);
        for(int i=1;i<=n;i++){
            int minum=INT_MAX;
            for(int j=1;j*j<=i;j++){
                minum=min(minum,dp[i-j*j]);
            }
            dp[i]=minum+1;
        }
        return dp[n];
    }
};

时间复杂度O(n*sqrt(n)),空间复杂度O(n)

解码方法(leetcode 91)

题目描述:
一条包含字母 A-Z 的消息通过以下方式进行了编码:
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。

示例:

输入: “12”
输出: 2
解释: 它可以解码为 “AB”(1 2)或者 “L”(12)。

思路:

先初始化一个v数组,v[i]记录对应到s[i]位置的解码方法的总数。
当s[i-1]不是“1”或“2”的时候,证明s[i]不可以和s[i-1]联合解码
当s[i]是 “0”的时候,s[i]无法解码,返回0
当s[i]不是“0”的时候,s[i]位置独立解码,此时v[i]=v[i-1]
当s[i-1]是“1”或“2”的时候,证明s[i]可以和s[i-1]联合解码
当s[i]是 “0”的时候,s[i]一定要与s[i-1]一起解码,此时v[i]=v[i-2]
当s[i]不是“0”的时候
如果s[i-1]s[i]组成的数字小于等于26,则可以联合解码,v[i]=v[i-1]+v[i-2]
否则,还是不可以联合解码,s[i]独立解码,v[i]=v[i-1]

代码:

class Solution {
public:
    int numDecodings(string s) {
        if(s[0]=='0') return 0;
        if(s.size()==1) return 1;
        vector<int> dp(s.size(),0);
        dp[0]=1;
        if(s.substr(0,2) <="26"){
            dp[1]=(s[1]=='0')?1:2;
        }
        else{
            dp[1]=(s[1]=='0')?0:1;
        }
        for(int i=2;i<s.size();i++){
            if(s[i-1]!='1'&&s[i-1]!='2'){
                if(s[i]=='0') return 0;
                else
                    dp[i]=dp[i-1];
            }
            else{
                if(s[i]=='0')
                    dp[i]=dp[i-2];
                else{
                    dp[i]=(s.substr(i-1,2)<="26")?(dp[i-1]+dp[i-2]):(dp[i-1]);
                }
            }
        }
        return dp[s.size()-1];
    }
};

最长递增子序列

最长上升子序列(leetcode 300 )

题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

解题思路:O(n^2)
可以使用dp数组来求出每一个位置的最大上升子序列长度,当我们要插入num[i]时,即在dp[0,…,i-1]的位置后面再加上一个nums[i],那么要求出nums[i]位置的最长子序列长度,其状态就必须从它前面的数字中的dp[j]位置转移过来,即当nums[j]

代码描述:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if(nums.size()==0) return 0;
        vector<int> dp(nums.size(),0);
        for(int i=0;i<nums.size();i++){
            dp[i]=1;
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        return *max_element(dp.begin(),dp.end());
    }
};

最长数对链(leetcode 646)

题目描述
给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。
现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。
给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。

示例 :
输入: [[1,2], [2,3], [3,4]]
输出: 2
解释: 最长的数对链是 [1,2] -> [3,4]

思路:先对原数组pairs进行排序,把所有的数对排成有序的,然后再进行动态规划。使用dp数组进行每一组最长链的记录,当我们动态规划dp[i]时,应该在前面已经规划好的位置中找到满足条件且最大链dp[j]进行加1操作。
满足的条件为pairs[j][1]

代码描述:

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        if(pairs.empty()) return 0;
        sort(pairs.begin(),pairs.end(),[](const vector<int>&a,const vector<int>& b){
            return (a[0]==b[0]&&a[1]<b[1])||(a[0]<b[0]);
        });
        int n=pairs.size(),result=0;
        vector<int> dp(n,1);
        for(int i=0;i<n;i++){
            for(int j=0;j<i;j++){
                if(pairs[j][1]<pairs[i][0]){
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
            result=max(result,dp[i]);
        }
        return result;
    }
};

你可能感兴趣的:(leetcode刷题系列)