动态规划的算法题型总结

动态规划(DP 即 Dynamic Programming)

理解为将一个大问题拆分为一堆小问题,并且这些小问题不会被重复的计算。

动态规划分为自顶向下top-down(递归时间复杂度以斐波那契数为例n^h-1,空间复杂度为n)和自底向上bottom-up(效率高,最优解,一般一维dp使用滚动数组来优化)。

动态规划的解题步骤:

1:状态定义(定义dp数组)2:转移方程(拆分问题确定dp数组自底向上的转移方程)3:初始状态(确定最开始底部的dp数值):4:返回值(返回dp数组当中的哪一个值为最终结果)

以斐波那锲数为例

剑指 Offer 10- I. 斐波那契数列

如果使用自顶向下的方式去解题,即为递归

code:

def fib(n):
    if n == 1 or n == 2:
        return 1
    return fib(n - 1) + fib(n - 2)

Picture0.png

 图片转自力扣

其因为会重复计算f(n-2)等小问题,导致时间复杂度为二叉树的结点数o(2^h-1) = o(2^n),使用记忆化递归法使空间复杂度为o(n)。

使用自底向上的方法:

第一步状态定义:设置dp[]一维数组,dp[i]代表第几个数

第二步转移方程:dp[n] = dp[n-1]+dp[n-2]代表第n个数的转移方程

第三步初始状态:dp[0] = 0 dp[1] = 1

第四步返回值:dp[n],最后一个数即为最终结果

使用滚动数组的思想去优化 (使用一个常数变量去存储结果,两个常数变量去存储转移方程的另外两个值)

code:

class Solution:
    def fib(self, n: int) -> int:
        a,b = 0,1
        for _ in range(n):
            a,b = b,a+b
        return a%1000000007

同类型的青蛙跳台阶:剑指 Offer 10- II. 青蛙跳台阶问题

code:

class Solution:
    def numWays(self, n: int) -> int:
        a,b = 1,1
        for _ in range(n):
            a,b = b,a+b
        return a%1000000007

只是修改了初始值,该题初始值为斐波那锲数列不包括0,f(0) = 1,f(1) = 1,转移方程一样为f(n) = f(n-1) +f(n-2),因为青蛙跳的时候为一步或两步,如果使用一步则f(n-2)如果两步则f(n-1)

剑指 Offer 63. 股票的最大利润

状态定义:

定义dp[]数组,dp[i]即表示当前第i天的最大利润

转移方程:

dp[i] = max(dp[i-1],prices[i]-min(prices[0:i]))

初始状态:

dp[0] = 0(第一天的利润为0)

返回值:

dp[-1]当前的最大利润

该题是可以进行时间复杂度和空间复杂度的优化的,时间复杂度优化:使用常数变量更新prices[0:i]的最小值缩小o(i)为o(1),空间复杂度优化:前一个dp[i-1](使用结果代替它即可)

code:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:
            return 0
        mincost = prices[0] 
        res = 0
        for i in prices:
            mincost = min(i,mincost)
            res = max(res,i-mincost)
        return res

定义最小值为价格的第一个值,结果初始值为0,之后先获取当前的最小价格,再进行价格对比。

剑指 Offer 42. 连续子数组的最大和

状态定义:

dp[i]表示到nums[i]的子数组最大和,注意其必须包括nums[i]在内(计算nums[i])

转移方程:

当dp[i-1]<0时,其对数组和做负贡献,则直接等于nums[i]

当dp[i-1]>=0时,其可以和当前数进行相加,不用考虑对下一个数的影响。

初始状态:

dp[0]=nums[0]

返回值:

return max(dp),因为在最后的时候有可能对末尾数做负贡献,最大值可能在中间产生。

空间复杂度优化,直接在原有数组当中进行操作,空间复杂度o(1)

code:

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        for i in range(1,len(nums)):
            nums[i] = nums[i]+max(nums[i-1],0)
        return max(nums)

剑指 Offer 47. 礼物的最大价值

状态定义:

dp[][]二维数组dp,记录每一格的礼物最大的价值

转移方程:

dp[i][j] = max(grid[i][j]+dp[i-1][j],grid[i][j]+dp[i][j-1])

初始状态:

dp[0][0] = grid[0][0]

在对dp后续定义时需要注意二维的第一排与第二排

返回值:

dp[-1][-1]

空间复杂度优化,在原grid二维数组中进行操作,优化为o(1)

code:

class Solution:
    def maxValue(self, grid: List[List[int]]) -> int:
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if i==0 and j==0:continue
                if i==0 :
                    grid[i][j] = grid[i][j]+grid[i][j-1]
                elif j ==0:
                    grid[i][j] = grid[i][j]+grid[i-1][j]
                else:
                    grid[i][j] = max(grid[i][j]+grid[i-1][j],grid[i][j]+grid[i][j-1])
        return grid[-1][-1]

剑指 Offer 46. 把数字翻译成字符串

状态定义:

dp[i]为当前字符串i的所有可能数

转移方程:

当int(num[i-1:i+1]) <= 25 and int(num[i-1])!=0时(前两个字符串不超过25,并且前一个字符不为0)dp[i] = dp[i-1] + dp[i-2]

其他情况则dp[i]只等于dp[i-1] (因为最近的两个数只能作为两个字符,不能作为一个字符,)

初始状态:

dp[0] = 1,dp[1]的情况要根据两位数是否大于25决定,大于25则等于1,小于则为2。

返回值:

dp[-1](直接返回dp数组的最后一个值就是结果)

code:

class Solution:
    def translateNum(self, num: int) -> int:
        if num <10:return 1
        num = str(num)
        dp = [None]*len(num)
        dp[0] = 1
        if int(num[:2]) <= 25:
            dp[1] = 2
        else :
            dp[1] = 1
        for i in range(2,len(num)):
            if int(num[i-1:i+1]) <= 25 and int(num[i-1])!=0:
                dp[i] = dp[i-1] + dp[i-2]
            else:
                dp[i] = dp[i-1]
        return dp[-1]

剑指 Offer 48. 最长不含重复字符的子字符串

状态定义:

dp[i]表示当前i的最大不重复字符串

转移方程:

j为当前字符的前一个出现的下标,当i-j>dp[i-1]时dp[i] = dp[i-1]+1

当i-j<=dp[i-1]时dp[i] = i - j

初始状态:

dp[0] = 1

返回值:

max(dp)dp数组的最大值

本题需要使用哈希表的数据结构来存储数组的下标(上面dp思路中的j)

空间复杂度优化:使用滚动数组思想,temp记录当前的dp[i-1],res为数组当中的最大值一直更新,

code:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s: return 0
        res = temp = 0
        hashmap = {}
        for j in range(len(s)):
            i = hashmap.get(s[j],-1)
            temp = temp + 1 if temp<j - i else  j - i
            hashmap[s[j]] = j
            res = max(temp,res)
        return res

本文其他:

常见的时间复杂度:

从小到大依次是o(1)<o(log2n)<o(n)<o(nlog2n)<o(n^2)<o(n^3)<o(2^n)<o(n!)<o(n^n)

log2n的代表性算法为二分折半查找:每次进姓折半为1/2,一共进行了k次运算,因为最终的结果只有一个则有n*(1/2)^k = 1推导出2^k = n 进而得出k为logn

n表示算法为一次循环的线性算法

n^2表示对数组排序的各种简单算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值