暴力递归转动态规划----以货币数问题展开
接著上道題,這道題也是用來感受暴力遞歸優化成動態規劃的套路。先想暴力嘗試的方法,然后優化成動態規劃。首先是原問題的暴力嘗試方法。只有想出嘗試方法是最難、最重要的。
我們首先來看一下題目:
給你一個數組arr,arr中所有的值都為正數且不重復,和一個整數aim。如果可以任意選擇arr中的數字,每個數字只能選一次,能不能累加得到aim,返回true或者false。
思路類似字符串的子序列問題:令f(i,sum)中的i表示數組下標,sum表示數組最開始元素到當前元素的和,以arr={3,2,7,13},aim=9為例,則遞歸過程如下圖所示:(PS:這是一張只有本人看得懂的圖,o(╯□╰)o)
所以解決步驟如下:
1.寫出嘗試(遞歸)版本
public static boolean money1(int[] arr, int aim) {return process1(arr, 0, 0, aim);}public static boolean process1(int[] arr, int i, int sum, int aim) {if (i == arr.length) {return sum == aim;}//繼續往下執行,兩種情況:要么選要么不選return process1(arr, i + 1, sum, aim) || process1(arr, i + 1, sum + arr[i], aim);}2. 分析是否滿足轉動態規劃的條件:
? ? ?(1).存在大量重復計算:上述例子不夠清楚,可以換成{3,2,5,13},則到第三步將會出現兩個f(3,5),一個是由f(2,5)+0,一個是由f(2,0)+5所得,而f(3,5)的后續返回值肯定相同,所以存在重復計算。
? ? ?(2).該問題屬于“無后效性”問題:同1,不管哪條路徑到達的f(3,5),得到的返回值都一樣,則表示為“無后效性”。
綜述所述,可以轉為動態規劃。
3.?分析可變參數,哪幾個可變參數的值能代表返回狀態,幾個可變參數,以及它們的變化范圍,即可構造幾維dp表。
數組和aim不可變,i,sum可變。
4.看base case,列出不依賴的位置。(以數組{3, 2, 5 },aim=7為例,減少工作量)
二維表行代表i,列代表sum即全部元素的和(aim原則上不會超過這個數,如果超過了很顯然返回false)。
可知base case為最后一行,只有當sum==aim的時候才是T,其它都是F。所以可得下表內容:
| I \ SUM | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 0 | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
| 1 | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
| 2 | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? |
| 3 | F | F | F | F | F | F | F | T | F | F | F |
?
5.分析一個普遍位置的依賴。
return process1(arr, i + 1, sum, aim) || process1(arr, i + 1, sum + arr[i], aim);可知,要知道一個普遍位置的值,就得知道它的下一行的值和下一行并向右加arr[i]列的值。例如f(2,0),根據上述代碼可知,依賴于f(3,0)和f(3,5),所以f(2,0)為F(因為f(3,0)和f(3,5)都為F)。
綜述所述,最后一行知道了,反過來就能填完了整張表。
| I \ SUM | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 0 | T | F | T | F | T | T | F | T | F | F | F |
| 1 | T | F | T | F | F | T | F | T | F | F | F |
| 2 | F | F | T | F | F | F | F | T | F | F | F |
| 3 | F | F | F | F | F | F | F | T | F | F | F |
整體代碼如下:
package com.gxu.dawnlab_algorithm8;/*** 換錢問題* * @author junbin** 2019年7月12日*/ public class Money_Problem {public static boolean money1(int[] arr, int aim) {return process1(arr, 0, 0, aim);}public static boolean process1(int[] arr, int i, int sum, int aim) {if (sum == aim) { //如果遍歷過程中滿足該條件,則停止后續遍歷return true;}// sum != aimif (i == arr.length) { //如果已經到了數組末尾,則直接輸出return false;}//繼續往下執行,兩種情況:要么選要么不選return process1(arr, i + 1, sum, aim) || process1(arr, i + 1, sum + arr[i], aim);}//動態規劃public static boolean money2(int[] arr, int aim) {boolean[][] dp = new boolean[arr.length + 1][aim + 1];//列改為aim+1可以更節省空間for (int i = 0; i < dp.length; i++) {dp[i][aim] = true;}for (int i = arr.length - 1; i >= 0; i--) {for (int j = aim - 1; j >= 0; j--) {dp[i][j] = dp[i + 1][j];if (j + arr[i] <= aim) {dp[i][j] = dp[i][j] || dp[i + 1][j + arr[i]];}}}return dp[0][0];}public static void main(String[] args) {int[] arr = { 1, 4, 8 };int aim = 12;System.out.println(money1(arr, aim));System.out.println(money2(arr, aim));} }總結:到現在已經體驗了兩道暴力遞歸轉動態規劃的經典題目了,可以感覺到高度的套路化,但是仍然覺得很不熟練,特別是最難的寫出嘗試版本和畫出dp表階段,繼續加油!
總結
以上是生活随笔為你收集整理的暴力递归转动态规划----以货币数问题展开的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab 画思维图像,「4」图像思维
- 下一篇: macOS 上安装 PECL