动态规划思路和Python解决零钱兑换问题和最大乘积子序列的乘积的问题

动态规划(Dynamic Programming)思路和Python解题示例

动态规划是一种主要用来优化朴素递归的方法,每当输入不同值调用递归函数出现大量重复的(子)输入和调用(返回结果)时,就可以考虑使用动态递归的方式来优化复杂度。

动态规划的主要思想是存储子问题的结果,以便于在接下来可能出现的的重复子问题中直接使用已有的结果,这样子便可以将时间复杂度从指数级别降低到多项式(nlogn…)或线性级别,是一种以空间换时间的算法。

需要提及一点的是,动态规划(Dynamic Programming)中的programming并不是编程的意思,规划也不是指要望向很长远的未来去计划,programming可以理解为递推,也就是问题的解决方法存在F(n)=F(n-1)+F(n-2)这样类似的关系,每一步的结果都与他前面/相邻的一步或几步的结果有关,由这相邻的一步或几步推出而得到当前步的结果

动态规划解法的关键点在于:

  1. 递归+状态记忆,也就是递推
  2. 状态转移(递推/DP)方程的推导与定义
  3. 最优子结构,最优子结构是dp问题被分解为子问题,子问题递归求解时的最优解

以一个斐波那契数列的为示例:
朴素递归写法:

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

复杂度为O(2^n)
在这里插入图片描述
添加了记忆化缓存后的斐波那契解法:

cached = {0: 0,
}def fib_with_cache(n):print("执行次数+1")if n <= 1:cached[n] = nreturn nif cached.get(n) is None:cached[n] = fib_with_cache(n - 1) + fib_with_cache(n - 2)return cached[n]

复杂度为O(n)
这种解法通过储存子结构(例如计算fib(5)时fib(3)就是fib(5)的子结构)的最优解(这里只有唯一解也是最优解),使得在计算较大的问题(例如fib(7))时,能够利用之前较小问题(fib(3))计算过的步骤,从而避免了重复计算带来的复杂度
单独执行一次fib_with_cache(3)需要执行3次该函数

>>> print(fib_with_cache(3))  # 5
执行次数+1
执行次数+1
执行次数+1
执行次数+1
执行次数+1
2

先执行一次n=2再执行一次n=3,fib_with_cache(3)只需要执行3次该函数

>>> print(fib_with_cache(2))    # 3
>>> print(fib_with_cache(3))  # 3
执行次数+1
执行次数+1
执行次数+1
1
执行次数+1
执行次数+1
执行次数+1
2

在这里插入图片描述
这里的递推方程的方式可以写成f(n) = f(n-1) + f(n-2)

F[0] = 0;
F[1] = 1;
for (i=2; i<= n; i++){F[i] = F[i-1] + F[i-2]
}
return F[n];

实例和解题思路

不同路径问题

给定一个m×n的格子,最左上格子为起点,最右下格子为终点,只能向右或者向下走,求解从起点到终点的总路径数
以1个3×3的格子为例
给各个格子命名
给各个格子命名
初始站在起点A,那么路径肯定就只有1条,那么从A→B的路径有1条,从A→D的路径有1条
从A→E的路径有2条:A→B→E和A→D→E
A→E路径数就等于A→B路径数与A→D路径数之和,也就是1+1=2

实际上可以推断出,从起点A到任何一个格子的路径数都等于这个格子的左边的格子的路径数+上方的路径数
在这里插入图片描述
因此可以总结出这个问题的递推方程:
dp[h][v] = dp[h - 1][v] + dp[h][v - 1]其中dp是一个m×n的二维数组,h和v分别为水平和垂直方向的索引
沿着上面的分析思路,最后结果就存放在数组的最右下角dp[m-1][n-1]

class Solution:def uniquePaths(self, m: int, n: int) -> int:# 初始化格子数组,以1填补值,稍后会做计算并覆盖dp = [[1 for _ in range(n)] for _ in range(m)]# 从dp[1][1]开始遍历(最左边的一列和最上边的一行的值一定全为1)# 每一个格子的路径数都等于它左一和上一个格子的路径数之和for h in range(1, m):for v in range(1, n):dp[h][v] = dp[h-1][v] + dp[h][v-1]return dp[m-1][n-1]S = Solution()
print(S.uniquePaths(3, 3))
print(S.uniquePaths(3, 7))

输出

6
28

不同路径2

在一个m×n的格子中,最左上的一个格子为起点,最右下的一个格子为终点,其中值为X的格子代表障碍,不能经过,从起点出发只能往右或者往下前进,求从起点到终点的路径总数
在这里插入图片描述

相比于上一个问题,这里定义dp方程时需要考虑两种情况

if dp[i][j] == "空地":opt[i][j] = opt[i-1][j] + opt[i][j-1]
else: // 障碍opt[i][j] = 0

其中opt[i][j]表示在(i, j)位置时的(最少要走的)路径数,也就是最优解/最优子结构
这里以反向递推的方式,从终点到起点来递推路径的条数(实际上使用递归的解法时从终点往起点回推的方式更符合我们的逻辑)

import numpy as nparr = np.ones((5, 4)).astype(int)   # 5行4列的数组# 设置网格中的的障碍
zeros = [(1, 1),(3, 0),(2, 2),
]
for i, j in zeros:arr[i][j] = 0def count_the_paths(grid):width = len(grid[0])depth = len(grid)# 最下和最右边的格子不会变化for d in range(depth-2, -1, -1):for w in range(width-2, -1, -1):if grid[d][w] != 0:grid[d][w] = grid[d+1][w] + grid[d][w+1]return grid[0][0]if __name__ == "__main__":print(count_the_paths(arr))

解法和上一题基本一样,就是多了一个判断障碍的情况,还有这里用的是反向递推的思路,会更容易理解
输出

5

再列举出DP、回溯和贪心算法的特性和区别,加深对DP的理解:
回溯(递归):穷举式的重复计算
贪心:每次都选择当前遇到部分的问题中的局部最优解
DP:找出并记录(部分)子结构最优解,然后用动态转移方程推导出下一个状态(位置)的最优解
回溯+记录局部最优解避免重复计算就是DP,也就是说DP是回溯+贪心的组合

习题部分

一、零钱兑换问题(leetcode #322)

给定不同面额的硬币coins,例如[1, 2, 5],和一个总金额amount,例如3,计算满足总金额amount所需的最少的硬币数(给的例子要返回2,因为最少为1+2,2个硬币),如果无法满足就返回-1
注:硬币的数量没有限制,硬币面额最小为1

经过分析,解决这个问题需要注意以下几点:

  • 优先选择最大的面额不一定是最优解,如coins = [1, 3, 8, 9],amount = 11,11 = 8+3→2个和11 = 9+1+1→3个,所以优先选择最大面额不一定是最优解(贪心解法,不一定会得到最终最优解)

解题的关键,找出状态关系和列出状态转移方程
组成amount的最少硬币数(最终最优解)等于amount减去面额列表conis中某个面额得到的前一个总金额(状态)amount - coins[x]的最优解+1,这个1就是减去的那个面额对应的1个硬币,x可能是conins中的任意一个面额
也就得到了如下的递归思路
在这里插入图片描述

然后继续思考,假设面额amount为x,例如amount = 11,那么不论何种情况下它最多需要x个硬币(那么这个例子里为11),因为硬币面额最小为1,从反向递推的思路出发,从面额最小,即amount = min(coins)递推到总金额amount,求解所需的最小硬币数

from typing import Listclass Solution:def coinChange(self, coins: List[int], amount: int) -> int:ceiling = amount + 1mem_arr = [0] + [ceiling] * amountfor coin in coins:for i in range(coin, ceiling):mem_arr[i] = min(mem_arr[i], mem_arr[i-coin] + 1)   # key:return mem_arr[amount] if mem_arr[amount] < ceiling else -1S = Solution()
print(S.coinChange([0], 0))
print(S.coinChange([0], 1))
print(S.coinChange([1, 2], 1))
print(S.coinChange([1, 2], 2))
print(S.coinChange([1, 2], 3))
print(S.coinChange([3, 2, 1, 0, 5], 6))

输出

0
-1
1
1
2
2
二、求最大乘积子序列(的乘积)

题目要求:
在一个整数序列中寻找最大乘积的子序列的乘积,例如
arr = [5, 0, -4, 2, -3] result = 24因为-4×2×(-3) = 24
arr = [2, -1, 1, 1] result = 2
arr = [3] result = 3

根据前面递推的思路
遍历nums所有元素,到当前元素nums[j]为止的最大连乘积可能等于

  1. 当前元素nums[j]为正数时,前一步为止的最大正数乘积*nums[j]
  2. 当前元素nums[j]为负数时,前一步为止的最小负数乘积*nums[j]
  3. 当前元素为0,最大连乘积结果可能为0或者前一步为止的最大正数乘积*nums[j]

总之,到当前元素nums[j]为止的最大连乘积为1,2,3的情况的最大值

遍历时需要找到到达当前步数为止的最大(正数)乘积和最小(负数)乘积
设以nums[i]结尾的最大连续子串的乘积为max_curr,以nums[i]结尾的最小连续子串的乘积为min_curr
那么到当前步数j时的
最大连续子串乘积max_curr = max(max_curr*nums[j], nums[j], min_curr*nums[j])
最小连续子串乘积min_curr = min(max_curr*nums[j], nums[j], min_curr*nums[j])
也就是我们的状态转移方程
初始状态:
max_curr = min_curr = nums[0], max_li = [nums[0]]
将每一步的max_curr放入到max_li中,最终的结果为max(max_li)

def max_product(nums):max_li = [nums[0]]max_curr = min_curr = nums[0]for j in range(1, len(nums)):max_curr, min_curr = max(max_curr * nums[j], nums[j], min_curr * nums[j]), min(max_curr * nums[j], nums[j],min_curr * nums[j])max_li.append(max_curr)res = max(max_li) if max_li else max_currreturn resif __name__ == '__main__':li = [5, 0, -4, 2, -3]  # -4*2*(-3) = 24print(max_product(li))

输出

24

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/469433.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Pytorch神经网络实战案例】06 逻辑回归拟合二维数据

1 逻辑回归与拟合过程 1.1 准备数据-code_01_moons.py&#xff08;第1部分&#xff09; import sklearn.datasets import torch import numpy as np import matplotlib.pyplot as plt from LogicNet_fun import LogicNet,plot_losses,predict,plot_decision_boundary# 1.1 准…

将Win10包含中文的用户名改为英文的,同时解决Anaconda navigator无法运行jupyter的问题

Win10用户名包含中文字符导致无法在Anaconda navigator直接运行jupyter的问题 本篇文章内容包含&#xff1a; WIN10如何修改"C:\Users\用户名"中的用户名执行1后&#xff0c;也就是用户名修改为英文名后&#xff0c;在Anaconda navigator启动之前无法启动的jupyter…

linux dd使用记录

dd if/dev/sda of/dev/sdb bs10M Linux下显示dd命令的进度&#xff1a;dd if/dev/zero of/tmp/zero.img bs10M count100000想要查看上面的dd命令的执行进度&#xff0c;可以使用下面几种方法&#xff1a; 比如&#xff1a;每5秒输出dd的进度 方法一&#xff1a;watch -n 5 pkil…

【Pytorch神经网络理论篇】 01 Pytorch快速上手(一)概述+张量

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

【Pytorch神经网络理论篇】 02 Pytorch快速上手(二)GPU与CPU张量切换+具有随机值的张量+张量的数学运算

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

解读Android 4.0 Camera原生应用程序的设计思路

解读Android 4.0 Camera原生应用程序的设计思路 一篇很不错的文章&#xff1a; http://my.oschina.net/jerikc/blog/907911. 设置摄像头方向 2. 打开线程与预览线程 3. 设置参数 4. Camera外设按键 5. 自动对焦与触摸对焦 6. 拍照 7. 人脸检测 8. 位置管理 9. 旋转管理 10. 变…

【Pytorch神经网络理论篇】 03 Pytorch快速上手(三)张量的数据操作

1 张量的数据操作 1.1 torch.reshape()实现数据维度变化 import torch a torch.tensor([[1,2],[3,4]]) print(torch.reshape(a,(1,-1))) # 将其转化为只有1行数据的张量,参数-1表示自动计算 # tensor([[1, 2, 3, 4]]) print(a.reshape((1,-1))) # # 将其转化为只有1行数据的…

写一个包含多个事件四则运算的留存SQL ——impala hive

在实现一个留存业务需求时&#xff0c;碰到了一个难题&#xff0c;我需要提供展示一个按照如下图格式的数据&#xff0c; day 1 ~ day n的第一行是留存用户数量&#xff0c;第二行是一个由多个事件组合执行四则算术运算得到的复合数值&#xff0c;这里碰到的难点主要是第二行的…

V4L2用户空间和kernel层driver的交互过程

这篇文章详细分析了V4L2用户空间和kernel层driver的交互过程&#xff0c;目的只有一个&#xff1a;更清晰的理解V4L2视频驱动程序的系统结构&#xff0c;驱动编程方法&#xff0c;为以后开发视频驱动打好基础既然从用户层出发探究驱动层&#xff0c;这里先贴出应用层code&#…

【Pytorch神经网络理论篇】 04 Variable类型与自动微分模块剖析

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

php 对象赋值后改变成员变量影响赋值对象

话不多说看代码 <?php class obj {}$obj1new obj();//实例化对象 $obj2$obj1;//赋值新对象 $obj1->name"test";//改变老对象的成员变量属性 var_dump($obj1); var_dump($obj2); $obj2->name"name";//改变新对象的成员变量属性 var_dump($obj1); …

Android Camera 通过V4L2与kernel driver的完整交互过程

原文地址&#xff1a;Android Camera 通过V4L2与kernel driver的完整交互过程 作者&#xff1a;xinyuwuxian Android Camera 通过V4L2与kernel driver的完整交互过程之前在 Android Camera 的执行流程http://blog.chinaunix.net/uid-26765074-id-3499537.html这篇文章中已经详细…

【Pytorch神经网络理论篇】 05 Module类的使用方法+参数Parameters类+定义训练模型的步骤与方法

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

BZOJ 2822: [AHOI2012]树屋阶梯 [Catalan数 高精度]

2822: [AHOI2012]树屋阶梯 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 779 Solved: 453[Submit][Status][Discuss]Description 暑假期间&#xff0c;小龙报名了一个模拟野外生存作战训练班来锻炼体魄&#xff0c;训练的第一个晚上&#xff0c;教官就给他们出了个难题。由…

【Pytorch神经网络理论篇】 06 神经元+神经网络模型+全连接网络模型

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

面试题:N皇后问题,思路和python解题笔记

n皇后问题算法思路和python解法 问题描述 n皇后问题&#xff0c;在nn的棋盘上&#xff0c;解出n个皇后所有不能互相攻击的摆法&#xff0c; 皇后在数组中用“Q”表示&#xff0c;空地用“.”表示 返回的数据结构格式要求&#xff1a;[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…

【Pytorch神经网络理论篇】 07 激活函数+Sigmoid+tanh+ReLU+Swish+Mish+GELU

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

【Pytorch神经网络理论篇】 08 Softmax函数(处理分类问题)

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

Python套接字编程Socket Progaming——1

本篇文章是Network And Web Programing-Socket Programing分类中的第一篇文章&#xff0c;内容主要包含 Socket概念理解Socket programing介绍一个简单的TCP协议的server-client程序支持同时处理多个客户端简单server-client连接程序socket的常用选项使用 理解socket概念 一…

【Pytorch神经网络理论篇】 09 神经网络模块中的损失函数

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…