日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

八十八、从斐波那契数列和零一背包问题探究动态规划

發布時間:2024/10/8 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 八十八、从斐波那契数列和零一背包问题探究动态规划 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

@Author:Runsen

編程的本質來源于算法,而算法的本質來源于數學,編程只不過將數學題進行代碼化。 ---- Runsen

本人看了vivo,阿里巴巴的校招算法題,可以明確知道絕對有動態規劃。如果沒有,那么出題的面試官真的沒有水平。跌了N次的動態規劃,Runsen最近也拼命搞動態規劃。這篇文章浪費了三天時間。

看了Leetcode公眾號的文章:https://mp.weixin.qq.com/s/rhyUb7d8IL8UW1IosoE34g

極客時間超哥的動態規劃、拉勾教育的算法專欄。Runsen真的不想在動態規劃,死一次又一次。死了N次,學了N次,就是他媽的寫不出來。

動態規劃需要搞定三個系列:三個背包,零錢問題和股票問題。今天,Runsen就開始干掉最重要的背包問題

三個背包問題:01背包,多重背包,完全背包。

動態規劃前置知識

動態規劃的名詞

狀態轉移方程:比如Runsen們一般看到的狀態轉移方程:dp[n] = dp[n-1] + dp[n-2]。

最優子結構:一般由最優子結構,推導出一個狀態轉移方程 f(n),就能很快寫出問題的遞歸實現方法。
把大問題變成幾個小問題,在幾個小問題中求出最佳解。

重疊子問題:比如斐波那契數列中的f(5),算了f(4)和f(3),結果f(4)又給Runsen算了一次f(3)。其實就是將一棵二叉樹進行剪枝操作,方法是備忘錄來存儲在內存上。

自下而上:反過來求解

動態規劃思路

動態規劃是一種求問題最優解的方法。通用的思路:將問題的解轉化成==> 求解子問題,==> 遞推,==>最小子問題為可直接獲得的初始狀態。

詳細的步驟下面所示:

  • 判斷是否可用遞歸來解,可以的話進入步驟 2
  • 分析在遞歸的過程中是否存在大量的重復子問題
  • 采用備忘錄的方式來存子問題的解以避免大量的重復計算(剪枝)
  • 改用自底向上的方式來遞推,即動態規劃

關鍵就是找狀態轉移方程

斐波那契數列和爬樓梯問題

斐波那契數列最早從兔子問題演變過來的,

假設一對初生兔子一個月到成熟期,一對成熟兔子每月生一對兔子,并且一年內沒有發生死亡。那么,由一對初生兔子開始
一年以后可以繁殖多少對兔子?

我們直接看下面的圖

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……

發現以上規律是,每月的兔子對數=上一月的兔子對數+該月新生的兔子對數=上一月的兔子對數+上上月的兔子對數

得到序列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……

這個序列即為斐波那契數列“(Fibonacci sequence)”。斐波那契數列中的任一個數,都叫斐波那契數

斐波那契數列,通常都是用來講解遞歸函數,嘗試用遞歸的思路來解決,但是時間復雜度高達O(2n)O(2^n)O(2n)

def fib(n):if n <= 1:return 1return fib(n-1) + fib(n-2)for i in range(20):print(fib(i), end=' ')

但是,我們發現時間復雜度高達O(2n)O(2^n)O(2n),最主要的原因是存在重復計算。比如fib(3) 會計算 fib(2) + fib(1),
而 fib(2) 又會計算 fib(1) + fib(0)。

這個 fib(1) 就是完全重復的計算,不應該為它再遞歸調用一次,而是應該在第一次求解除它了以后,就把他“記憶”下來。

這就是備忘錄解法,用空間來換取時間的思路。把已經求得的解放在字典Map或者列表list 里,下次直接取,而不去重復結算。

備忘錄解法的代碼和動態規劃的代碼和思路基本一致。

斐波那契數列在Leetcode也有一題類似的,這是Leetcode第70題. 爬樓梯,每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是一個正整數。

輸入: 2 輸出: 2 解釋: 有兩種方法可以爬到樓頂。 1. 1 階 + 1 階 2. 2 階

斐波那契數列和爬樓梯問題的狀態轉移方程都是:dp[i] = dp[i-1] +dp[i-2]。但是需要初始化dp,不然回報list assignment index out of range的錯誤。

下面就是斐波那契數列問題 爬樓梯的解決代碼,也是Leetcode70題的解決代碼。

class Solution:def Fibonacci(self, n):if n == 0:return 1if n == 1:return 1if n > 1:dp = [0] * (n+1)dp[0] = 1 dp[1]= 1for i in range(2,n+1):dp[i] = dp[i-1] +dp[i-2]return dp[n]

Leetcode53 最大子序和

最大子序和,Runsen記得很清楚是Leetcode的53題。

輸入: [-2,1,-3,4,-1,2,1,-5,4], 輸出: 6 解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6

聲明兩個變量, currentSum: 之前連續幾個值相加的和, maxSum: 當前最大的子序列和。最大子序和狀態轉移方程 f(i) = max(f(i), f(i)+nums[i+1])

def maxSubArray(nums) :'''查找連續子數組的最大和Args:nums: 整數數組Returns:返回整數數組的最大子序和'''# 比較當前子序和,最大子序和,返回最大值# 定義當前子序和以及最大子序和為第一個元素cursum = maxsum = nums[0]for i in range(1, len(nums)):cursum = max(nums[i], cursum + nums[i])print(cursum)# 比較當前值和定義的最大子序和值,將最大值重置賦值給 max_summaxsum = max(cursum, maxsum)print(maxsum)return maxsumprint(maxSubArray([-2,1,-3,4,-1,2,1,-5,4]))

前面只是動態規劃的熱身,Runsen先進入三個背包問題的強化系列,01背包問題才是動態規劃的入門階段。

01背包問題

對應的題目:https://www.acwing.com/problem/content/2/

01背包問題就是物品只有一件。

輸入格式 : 第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積。接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i 件物品的體積和價值。 輸出格式 : 輸出一個整數,表示最大價值。 數據范圍 : 0<N,V≤1000 ;0<vi,wi≤1000

輸入樣例

4 5 1 2 2 4 3 4 4 6

輸出樣例:

8 # 4+4 2+6

在解決這類問題先,dp怎么定義和狀態轉移方程怎么搞就是重要,搞定了就是半分鐘的事情。搞不定了可能半小時的事情。

很多人和Runsen一樣,都會把狀態定義二維數組:dp[i][v]dp[i][v]dp[i][v] 為前 iii 「個」 物品中,體積恰好為vvv 時的最大價值。

狀態轉移方程也是順便搞定:dp[i][j]=max(dp[i?1][j],dp[i?1][j?weight[i]]+value[i])dp[i][j] = max(dp[i-1][j],dp[i - 1][j - weight[i]] + value[i])dp[i][j]=max(dp[i?1][j],dp[i?1][j?weight[i]]+value[i])

如果 「不選第 i 個物品」,那么前 i 個背包的最大價值就是前 i-1 個物品的價值,即 dp[i][j] = dp[i-1][j];

如果 「選擇了第 i 個物品」,前 i-1 個物品的體積就是j - weight[i],狀態方程為 dp[i - 1][j - weight[i]] + value[i],注意這時的價值是前i-1個物品的價值,因此少了 weight[i]]的空間,所以 dp[i - 1][j - weight[i]] + value[i]。

''' @Author: Runsen @WeChat:RunsenLiu @微信公眾號: Python之王 @CSDN: https://blog.csdn.net/weixin_44510615 @Github: https://github.com/MaoliRUNsen @Date: 2020/9/10 ''' # n是個數 v是體積 # 4 5 n, v = map(int, input().split()) goods = [] for i in range(n):goods.append([int(i) for i in input().split()])# 初始化,先全部賦值為0,這樣至少體積為0或者不選任何物品的時候是滿足要求 # 因為for 循環先遍歷個數,所以將體積寫在里面 dp = [[0 for i in range(v+1)] for j in range(n+1)] print(goods) # [[1, 2], [2, 3], [3, 4], [4, 5]] # 0 可以無視掉 for i in range(1, n+1):for j in range(1,v+1):# 判斷背包容量是不是大于第i件物品的體積if j>=goods[i-1][0]:# 在選和不選的情況中選出最大值dp[i][j] = max(dp[i-1][j], dp[i - 1][j - goods[i - 1][0]] + goods[i - 1][1])else:# 第i個物品不選dp[i][j] = dp[i-1][j] print(dp) print(dp[-1][-1])# 測試數據 5 10 1 2 2 3 3 4 4 5 5 6 [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]] [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], [0, 2, 3, 5, 5, 5, 5, 5, 5, 5, 5], [0, 2, 3, 5, 6, 7, 9, 9, 9, 9, 9], [0, 2, 3, 5, 6, 7, 9, 10, 11, 12, 14], [0, 2, 3, 5, 6, 7, 9, 10, 11, 12, 14]] 14 # 2+3+4+5

上面代碼,如果知道了dp怎么定義和狀態轉移方程,那么和Runsen寫的一樣快,其實那時Runsen寫得挺慢得,說不定你比Runsen還厲害。

上面的代碼是狀態定義二維數組,有的大佬竟然可以把狀態定義一維數組,這樣空間就節省了。Runsen都百思不知其解。只能說Runsen真的挺菜的。只好勤能補拙!

一維數組就是去掉了狀態iii,且jjj的遍歷方式改為 「倒序」 遍歷到 c[i]。

因此,Runsen們可以將求解空間進行優化,將二維數組壓縮成一維數組,此時,轉移方程變為:

dp(j)=max(dp(j),dp(i?wi)+vi)dp(j) = max(dp(j),dp(i - wi) + vi)dp(j)=max(dp(j),dp(i?wi)+vi)

''' @Author: Runsen @WeChat:RunsenLiu @微信公眾號: Python之王 @CSDN: https://blog.csdn.net/weixin_44510615 @Github: https://github.com/MaoliRUNsen @Date: 2020/9/10 ''' n, v = map(int, input().split()) goods = [] for i in range(n):goods.append([int(i) for i in input().split()]) print(goods) # [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]] dp = [0 for i in range(v + 1)] for i in range(n):# 由于要放入物品,所以從空間v開始遍歷到0for j in range(v, -1, -1):# 判斷背包容量是不是大于第i件物品的體積if j >= goods[i][0]:# 更新j的狀態,即當前容量放入物品之后的狀態dp[j] = max(dp[j], dp[j - goods[i][0]] + goods[i][1]) print(dp) print(dp[-1])5 10 1 2 2 3 3 4 4 5 5 6 [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]] [0, 2, 3, 5, 6, 7, 9, 10, 11, 12, 14] 14

上面就是01背包的最終解決方法,由于文章有限,多重背包,完全背包將在之后的博客進行書寫!!!

不知不覺現在寫了幾天,代碼反復寫,寫完寫博客,真心累!誰叫自己的算法比較弱!

希望以后遇到01背包的問題,就是在恐怖的算法面試中遇見了Runsen的愛情!

如果你想跟博主建立親密關系,可以關注博主,或者關注博主公眾號“Python之王”,了解一個非本科程序員是如何成長的。博主ID:潤森(weixin_44510615),希望大家點贊、評論、收藏

本文已收錄 GitHub,傳送門~ ,里面更有大廠面試完整考點,歡迎 Star。



總結

以上是生活随笔為你收集整理的八十八、从斐波那契数列和零一背包问题探究动态规划的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。