算法题总结之动态规划

本文详细总结了动态规划在各种算法题中的应用,包括斐波那契数列、强盗抢劫问题、矩阵路径问题、数组区间和、分割整数、最长递增子序列等。通过实例解析,展示了动态规划在解决复杂问题时的有效性和优化空间,帮助读者深入理解动态规划的思路和技巧。
摘要由CSDN通过智能技术生成

动态规划

斐波那契数列

爬楼梯

70. Climbing Stairs (Easy)

题目描述:有 N 阶楼梯,每次可以上一阶或者两阶,求有多少种上楼梯的方法。

定义一个数组 dp 存储上楼梯的方法数(为了方便讨论,数组下标从 1 开始),dp[i] 表示走到第 i 个楼梯的方法数目。第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。

img

考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。

class Solution {
public:
    int climbStairs(int n) {
        
        if(n<3)
            return n:
        int fibn;
        int fibMinus1=2;
        int fibMinus2=1;
        for(int i=3;i<=n;++i)
        {
            fibn=fibMinus1+fibMinus2;           
            fibMinus2=fibMinus1;//注意赋值顺序
            fibMinus1=fibn;
        }
        return fibn;        
    }
};
强盗抢劫

198. House Robber (Easy)

题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。

这道题的本质相当于在一列数组中取出一个或多个不相邻数,使其和最大。那么我们对于这类求极值的问题首先考虑动态规划Dynamic Programming来解,我们维护一个一位数组dp,其中dp[i]表示到i位置时不相邻数能形成的最大和,那么状态转移方程怎么写呢,我们先拿一个简单的例子来分析一下,比如说nums为{3, 2, 1, 5},那么我们来看我们的dp数组应该是什么样的,首先dp[0]=3没啥疑问,再看dp[1]是多少呢,由于3比2大,所以我们抢第一个房子的3,当前房子的2不抢,所以dp[1]=3,那么再来看dp[2],由于不能抢相邻的,所以我们可以用再前面的一个的dp值加上当前的房间值,和当前房间的前面一个dp值比较,取较大值当做当前dp值,所以我们可以得到状态转移方程dp[i] = max(num[i] + dp[i - 2], dp[i - 1]), 由此看出我们需要初始化dp[0]和dp[1],其中dp[0]即为num[0],dp[1]此时应该为max(num[0], num[1]),代码如下:

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

优化空间复杂度后的代码:

class Solution {
public:
    int rob(vector<int>& nums) {
        int pre1=0;
        int pre2=0;
        int cur=0;
        for(int i=0;i<nums.size();++i)
        {
            cur=max(pre1,pre2+nums[i]);
            pre2=pre1;
            pre1=cur;
        }
        return cur;        
    }
};
强盗在环形街区抢劫

213. House Robber II (Medium)

这道题是之前那道 House Robber 的拓展,现在房子排成了一个圆圈,则如果抢了第一家,就不能抢最后一家,因为首尾相连了,所以第一家和最后一家只能抢其中的一家,或者都不抢,那我们这里变通一下,如果我们把第一家和最后一家分别去掉,各算一遍能抢的最大值,然后比较两个值取其中较大的一个即为所求。那我们只需参考之前的 House Robber 中的解题方法,然后调用两边取较大值,代码如下:

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size()<2)
            return nums.empty()? 0:nums[0];
        return max(core(nums,0,nums.size()-1),core(nums,1,nums.size()));
    }
    int core(vector<int>& nums,int start,int end)
    {
        int pre1=0;
        int pre2=0;
        int cur;
        for(int i=start;i<end;++i)
        {
            cur=max(pre1,pre2+nums[i]);
            pre2=pre1;
            pre1=cur;
        }
        return cur;
    }
};
信件错排

题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。

定义一个数组 dp 存储错误方式数量,dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:

(1)i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)*dp[i-2] 种错误装信方式。

(2)i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (i-1)*dp[i-1] 种错误装信方式。综上所述,错误装信数量方式数量为:

img

母牛生产

题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。

第 i 年成熟的牛的数量为:img

矩阵路径

矩阵的最小路径和

64. Minimum Path Sum (Medium)

题目描述:求从矩阵的左上角到右下角的最小路径和,每次只能向右和向下移动。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int rows=grid.size();
        int cols=grid[0].size();
        int dp[rows][cols];
        dp[0][0]=grid[0][0];
        for(int i=1;i<rows;++i)
            dp[i][0]=grid[i][0]+dp[i-1][0];
        for(int j=1;j<cols;++j)
            dp[0][j]=grid[0][j]+dp[0][j-1];
        for(int i=1;i<rows;++i)
            for(int j=1;j<cols;++j)
                dp[i][j]=grid[i][j]+min(dp[i-1][j],dp[i][j-1]);
        return dp[rows-1][cols-1];
        
    }
};
矩阵的总路径数

62. Unique Paths (Medium)

题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向右或者向下移动。

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

数组区间

数组区间和

303. Range Sum Query - Immutable (Easy)

求区间 i ~ j 的和,可以转换为 sum[j + 1] - sum[i],其中 sum[i] 为 0 ~ i - 1 的和。

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

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * int param_1 = obj.sumRange(i,j);
 */

优化后:

class NumArray {
public:
    NumArray(vector<int> &nums) {
        accu.push_back(0);
        for (int num : nums)
            accu.push_back(accu.back() + num);
    }

    int sumRange(int i, int j) {
        return accu[j + 1] - accu[i];
    }
private:
    vector<int> accu;
};
数组中等差递增子区间的个数

413. Arithmetic Slices (Medium)

A = [1, 2, 3, 4]
return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.

dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。

在 A[i] - A[i - 1] == A[i - 1] - A[i - 2] 的条件下,{A[i - 2], A[i - 1], A[i]} 是一个等差递增子区间。如果 {A[i - 3], A[i - 2], A[i - 1]} 是一个等差递增子区间,那么 {A[i - 3], A[i - 2], A[i - 1], A[i]} 也是等差递增子区间,dp[i] = dp[i-1] + 1。

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

进一步优化空间

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

    }
};

分割整数

分割整数的最大乘积

343. Integer Break (Medim)

题目描述:For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).

动态规划做法:</

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值