递推动态规划入门


最近有刷一些算法题来提高自己,想想曾经入门算法竞赛的时候看着大佬写的完全看不懂的公式,不免神往而又不得不感慨:这特么都能想的出来233(虽然到现在我任然没有达到那样的高度),好了废话不多说开始正题。 以下题目均来自leetcode

No1 斐波那契数

没错!就是巨简单无比c语言教材上的斐波那契数!哈哈
在这里插入图片描述
分析: F [ N ] = F [ N − 1 ] + F [ N − 2 ] F\left[N\right]=F[N-1]+F[N-2] F[N]=F[N1]+F[N2] F [ 0 ] = 0 F[0]=0 F[0]=0 F [ 1 ] = 1 F[1]=1 F[1]=1
咳咳!我承认我编不下去了,没啥好分析的直接上代码吧

int fib(int N){
    int f[31] = {0, 1}; //0 <= N <= 30
    for(int i = 2; i <= N; i++)
        f[i] = f[i - 1] + f[i - 2];
    return f[N];
}

优化: 其实我们发现计算 F [ i ] F[i] F[i]仅仅需要 F [ i − 1 ] F[i-1] F[i1] F [ i − 2 ] F[i-2] F[i2],那么其实我们只需要3个变量就可以了

int fib(int N){
    if(N < 2) return N;
    int a = 0, b = 1, c;
    for(int i = 2; i <= N; i++){
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}

附加: 第N个泰波那契数这题和斐波那契一样的

No2 区域和检索-数组不可变

在这里插入图片描述
分析: 可以直接暴力求解,每一次计算i到j的和

#python:用时6884ms 
class NumArray:

    def __init__(self, nums: List[int]):
        self.nums = nums

    def sumRange(self, i: int, j: int) -> int:
        ans = 0
        for k in range(i, j + 1):
            ans += self.nums[k]
        return ans

优化: sumRange会被调用多次,每一次都从i迭代到j会花费大量时间,那么有没有办法可以直接查i到j的值呢?答案是有的,那就是在初始化的时候我们就计算了每一种情况的和,这样的空间复杂度将会是 O ( n 2 ) O(n^2) O(n2)而时间复杂度为 O ( 1 ) O(1) O(1)。那么有没有办法降低空间复杂度呢?想象有两根长度不等的绳子,如果叫你求差值,你只需要用长的减短的就可以了,如果我们用ans[i]来表示从下标0…i的和,那么i到j的值就很好求了
公式: s u m = a n s [ j ] − a n s [ i − 1 ] sum = ans[j] - ans[i-1] sum=ans[j]ans[i1]

class NumArray:

    def __init__(self, nums: List[int]):
        if len(nums) > 0:
            self.ans = [nums[0]]
        for i in range(1, len(nums)):
            self.ans.append(self.ans[i - 1] + nums[i])
        #print(self.ans)

    def sumRange(self, i: int, j: int) -> int:
        if i == 0:
            return self.ans[j]
        return self.ans[j] - self.ans[i - 1]

No3 爬楼梯

在这里插入图片描述
分析: 假设现在台阶只有1阶,那么你只需要一步就可以爬到楼顶;如果有2阶,那么你可以选择直接走两步到楼顶或者从第一阶台阶走一步到楼顶;如果有3阶那么你可以选择从第一阶台阶直接跨两步到楼顶或者从第2阶台阶跨一步到楼顶。OK,那么现在思路就来了假设有n阶台阶,最后一步一定是从第n-1阶台阶走上去的或者是从第n-2阶台阶走上去的 ,那么现在又相当于回到了斐波那契数列,走上第n阶台阶的方法等于走上第n-1阶台阶和第n-2阶台阶的和

公式: F [ N ] = F [ N − 1 ] + F [ N − 2 ] ; F[N] = F[N-1] + F[N - 2]; F[N]=F[N1]+F[N2]; F [ 0 ] = 0 , F[0]=0, F[0]=0, F [ 1 ] = 1 , F[1]=1, F[1]=1, F [ 2 ] = 2 F[2]=2 F[2]=2

int climbStairs(int n){
    int f[100] = {0, 1, 2}; //n没有给条件尽量选个大点的数
    for(int i = 3; i <= n; i++)
        f[i] = f[i - 1] + f[i - 2];
    return f[n];
}

优化: 同样的只需要保留 F [ i − 1 ] F[i-1] F[i1] F [ i − 2 ] F[i-2] F[i2]

int climbStairs(int n){
    if(n < 3) return n;
    int a = 1, b = 2, c;
    for(int i = 3; i <= n; i++){
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}

附加: 三步问题其实这一题和上一题一模一样

No4 使用最小花费爬楼梯

在这里插入图片描述
分析: 其实这题和上面的爬楼梯差不多,只不过加了点条件。题目有说可以选择从索引为0或索引为1的元素开始,这里就给了我们一些陷阱让我们不太容易推导,因为无法确定是从0开始好还是从1开始好。但是有一个点是不变的,那就是终点,无论从0开始还是从1开始我们的目的都是要爬上n阶楼梯,那么我们可以倒过来想:我们要从第n阶台阶走到第0或1阶台阶,要走到第n-2阶台阶可以从第n-1和n阶台阶下来,那么我们是从n上下来呢还是n-1下来好呢?当然是哪个cost小从哪个下来,那么现在思路清晰了

公式: F [ N − 2 ] = c o s t [ N − 2 ] + m i n ( F [ N − 1 ] , F [ N ] ) ; F[N - 2] = cost[N - 2] + min(F[N - 1],F[N]); F[N2]=cost[N2]+min(F[N1],F[N]); F [ N ] = c o s t [ N ] , F[N]=cost[N], F[N]=cost[N], F [ N − 1 ] = c o s t [ N − 1 ] F[N - 1]=cost[N - 1] F[N1]=cost[N1] 最终的答案就是 m i n ( F [ 0 ] , F [ 1 ] ) min(F[0], F[1]) min(F[0],F[1])
用F[N]来表示走到第N阶台阶的花费

int minCostClimbingStairs(int* cost, int costSize){
    int f[1002] = {0};
    f[costSize - 1] = cost[costSize - 1];
    f[costSize - 2] = cost[costSize - 2];
    for(int i = costSize - 3; i >= 0; i--){
        f[i] = cost[i] + (f[i + 1] < f[i + 2] ? f[i + 1] : f[i + 2]);
    }
    return f[0] < f[1] ? f[0] : f[1];
}

优化: 其实可以直接使用cost数组

int minCostClimbingStairs(int* cost, int costSize){
    for(int i = costSize - 3; i >= 0; i--){
        cost[i] += cost[i + 1] < cost[i + 2] ? cost[i + 1] : cost[i + 2];
    }
    return cost[0] < cost[1] ? cost[0] : cost[1];
}

No5 按摩师

在这里插入图片描述

分析: 每个预约有两种状态:接收或不接受。对于第i个预约,如果接受那么第i-1个预约必须是拒绝的;如果不接受那么对于第i-1个预约则没有限制,那么这个时候就必须选择对于第i-1个预约是接受好呢还是不接受好呢,当然是哪个的时间长选哪个。用sum[i][0]表示第i个预约不接受的时候的总时长,用sum[i][1]表示第i个预约接受的时候的总时长。那么现在思路就清晰了

公式: s u m [ i ] [ 0 ] = m a x ( s u m [ i − 1 ] [ 0 ] , s u m [ i − 1 ] [ 1 ] ) sum[i][0] = max(sum[i-1][0], sum[i-1][1]) sum[i][0]=max(sum[i1][0],sum[i1][1])

        s u m [ i ] [ 1 ] = n u m s [ i ] + s u m [ i − 1 ] [ 0 ] , s u m [ 0 ] [ 0 ] = 0 , s u m [ 0 ] [ 1 ] = n u m s [ 0 ] sum[i][1] = nums[i] + sum[i-1][0], sum[0][0]=0,sum[0][1]=nums[0] sum[i][1]=nums[i]+sum[i1][0],sum[0][0]=0,sum[0][1]=nums[0]

int massage(int* nums, int numsSize){
    if(numsSize == 0)return 0;
    int sum[100000][2] = {{0, nums[0]}};
    for(int i = 1; i < numsSize; i++){
        sum[i][0] = sum[i-1][0] > sum[i-1][1] ? sum[i-1][0] : sum[i-1][1];
        sum[i][1] = nums[i] + sum[i-1][0];
    }   
    return sum[numsSize-1][0] > sum[numsSize-1][1] ? sum[numsSize-1][0] : sum[numsSize-1][1];
}

优化: 我们发现每一次迭代用到的其实之后前一个预约的结果,那么我们就只需要保留一个预约的结果就行了,只不过需要借助一个中间变量记录一下上一个预约不接受的结果。

int massage(int* nums, int numsSize){
    if(numsSize == 0)return 0;
    int sum[2] = {0, nums[0]};
    int temp = sum[0];
    for(int i = 1; i < numsSize; i++){
        sum[0] = sum[0] > sum[1] ? sum[0] : sum[1];
        sum[1] = nums[i] + temp;
        temp = sum[0];
    }   
    return sum[0] > sum[1] ? sum[0] : sum[1];
}

附加: 打家劫舍这题其实和按摩师一模一样

No6 买卖股票的最佳时机

在这里插入图片描述
分析: 假设我们在第i天卖出股票,为了使利益最大化那么我们就需要在第i天之前价格最低的时候买入,用f[i]表示第i天卖出的结果,答案就是max(f)。那么现在思路就很清晰了,直接给出公式。

公式: f [ i ] = p r i c e s [ i ] − m i n ( p r i c e s [ 0 ] , p r i c e s [ 1 ] . . . , p r i c e s [ i − 1 ] ) , f [ 0 ] = 0 f[i] = prices[i] - min(prices[0],prices[1]...,prices[i-1]), f[0]=0 f[i]=prices[i]min(prices[0],prices[1]...,prices[i1]),f[0]=0

#python:用时6248ms 结果太磕碜了,但证明了我们思路没错
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        f = [0]
        for i in range(1, len(prices)):
            f.append(max(0, prices[i] - min(prices[0:i])))
        return max(f)

优化: 其实我们可以用一个变量来记录第i天之前的最低价格

#python:用时64ms 速度大大提升
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if prices == []:
            return 0
        f = [0]
        minp = prices[0]
        for i in range(1, len(prices)):
            f.append(prices[i] - minp)
            if minp > prices[i]:
                minp = prices[i]
        return max(f)

其实我们还可以省略f数组,因为我们需要的只是f数组中的最大值,直接用一个变量表示

int maxProfit(int* prices, int pricesSize){
    if(pricesSize == 0) return 0;
    int ans = 0;
    int min = prices[0];
    for(int i = 1; i < pricesSize; i++){
        ans = ans > prices[i] - min ? ans : prices[i] - min;
        if(min > prices[i]) min = prices[i];
    }
    return ans;
}

No7 不同路径

在这里插入图片描述
分析: 其实这一题和上一题的分析过程差不多,我们倒过来想,到终点的方块只有两个,它上方的方块和它左边的方块,其实任选一个方块它的前一个方块一定是它上方或左方的方块,因为机器人只能向右走或向下走,前一题我们用 F [ N ] = F [ N − 1 ] + F [ N − 2 ] F[N] = F[N-1] + F[N - 2] F[N]=F[N1]+F[N2]来表示到终点的方法,那么这一题其实也差不多,只不过是二维的。我们用 a n s [ i ] [ j ] ans[i][j] ans[i][j]来表示走到位置 ( i , j ) (i,j) (i,j)的方法数,那么现在思路就清晰了, a n s [ 0... m ] [ 0 ] = a n s [ 0 ] [ 0... n ] = 1 ans[0...m][0] = ans[0][0...n] = 1 ans[0...m][0]=ans[0][0...n]=1。因为机器人一直往右走和一直往下走都只有一种方法

公式: a n s [ i ] [ j ] = a n s [ i ] [ j − 1 ] + a n s [ i − 1 ] [ j ] ans[i][j]=ans[i][j-1] + ans[i-1][j] ans[i][j]=ans[i][j1]+ans[i1][j]

int uniquePaths(int m, int n){
    int ans[101][101] = {0};
    for(int i = 0; i <n; i++) ans[0][i] = 1;
    for(int j = 0; j < m; j++)ans[j][0] = 1;
    for(int i = 1; i <m; i++)
        for(int j = 1; j < n; j++)
            ans[i][j] = ans[i][j - 1] + ans[i -1][j];
    return ans[m - 1][n - 1];
}

No8 最小路径和

在这里插入图片描述
分析: 这题其实和不同路径还是一样的。只不过加了条件而已,要选一条最小的路径,我们已经知道 ( i , j ) (i,j) (i,j)可以由 ( i − 1 , j ) (i-1,j) (i1,j) ( i , j − 1 ) (i, j -1) (i,j1)到达,那么现在我们只需要选择其中花费较小的一条即可,遍历整个网格那么遍历到 ( m , n ) (m,n) (m,n)的时候就是最优解了,gird[i][j]来表示到达 ( i , j ) (i,j) (i,j)的花费

公式: g r i d [ i ] [ j ] = g r i d [ i ] [ j ] + m i n ( g r i d [ i − 1 ] [ j ] , g r i d [ i ] [ j − 1 ] ) grid[i][j] = grid[i][j] + min(grid[i-1][j], grid[i][j-1]) grid[i][j]=grid[i][j]+min(grid[i1][j],grid[i][j1])

int minPathSum(int** grid, int gridSize, int* gridColSize){
    int m = gridSize;
    int n = *gridColSize;
    for(int i = 1; i < n; i++)
        grid[0][i] += grid[0][i-1];
    for(int i = 1;  i < m; i++)
        grid[i][0] += grid[i - 1][0];
    for(int i = 1; i < m; i++)
        for(int j = 1; j < n; j++)
            grid[i][j] += grid[i - 1][j] > grid[i][j - 1] ? grid[i][j - 1] : grid[i - 1][j];
    return grid[m - 1][n - 1];
}

No9 三角形最小路径和

在这里插入图片描述
分析: 咳咳,不用我说还是一样的,只不过变成了三角形,观察该三角形我们发现最左边和最右边只有一条路可以走,除了两边上的节点外其他的都可以从头上的两个节点到达,那么我们只需要选择一个较小的点即可,遍历整个三角形即可得到最优解

公式: t r i a n g l e [ i ] [ j ] = t r i a n g l e [ i ] [ j ] + m i n ( t r i a n g l e [ i − 1 ] [ j − 1 ] , t r i a n g l e [ i − 1 ] [ j ] ) triangle[i][j] = triangle[i][j] + min(triangle[i-1][j-1],triangle[i-1][j]) triangle[i][j]=triangle[i][j]+min(triangle[i1][j1],triangle[i1][j]) 其它条件就不说了一看代码就明白了。

int minimumTotal(int** triangle, int triangleSize, int* triangleColSize){
    int m = triangleSize;
    for(int i = 1; i < m; i ++){
        triangle[i][0] += triangle[i - 1][0];
        triangle[i][i] += triangle[i - 1][i - 1];
    }
    for(int i = 2; i < m; i++)
        for(int j = 1; j < i; j++)
            triangle[i][j] += triangle[i - 1][j] > triangle[i - 1][j - 1] ? triangle[i - 1][j - 1] : triangle[i - 1][j];
    int ans = triangle[m - 1][0];
    for(int i = 0; i < m; i++)
        if(triangle[m - 1][i] < ans) ans = triangle[m - 1][i];
    return ans;
}

优化: 也许这不算是优化,只是换了种写法。我们可以倒过来写,这样的话到顶点的时候就是答案了。

int minimumTotal(int** triangle, int triangleSize, int* triangleColSize){
    int m = triangleSize;
    for(int i = m - 2; i >= 0; i--)
        for(int j = 0; j <= i; j++)
            triangle[i][j] += triangle[i + 1][j] > triangle[i + 1][j + 1] ? triangle[i + 1][j + 1] : triangle[i + 1][j];
    int ans = triangle[m - 1][0];
    return triangle[0][0];
}

No10 比特位计数

在这里插入图片描述
分析: 这一题一开始我是直接一个个算出来的,根本没网动态规划这方面想,后来发现它是动态规划类型的题目,那么如何利用已经计算好的信息呢?无意中的一瞥我发现temp>>=1其实是已经被计算过了的,那么现在就很好理解了。我们只需要判断一下最低位是不是1,如果是1那么temp中1的个数就等于temp>>1中1的个数加一,否则就为temp>>1中1的个数。

公式: a n s [ i ] = 1 & i ? a n s [ i > > 1 ] : a n s [ i > > 1 ] + 1 ans[i] = 1 \& i ? ans[i>>1]:ans[i>>1] + 1 ans[i]=1&i?ans[i>>1]:ans[i>>1]+1

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> ans = {0};
        int temp;
        for(int i = 1; i <= num; i++){
            temp = i;
            ans.push_back(0);
            while(temp){
                if(1 & temp) ans[i]++;
                temp >>= 1; 
            }
        }
        return ans;
    }
};
class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> ans = {0};
        for(int i = 1; i <= num; i++)
            ans.push_back(i & 1 ? ans[i >> 1] + 1 : ans[i >> 1]);
        return ans;
    }
};
  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值