遞推與動態規划


======================= **基礎知識** =======================

1.遞推基礎知識:

  斐波那契(Fibonacii)數列的遞推公式:F(n)  = F(n -1) + F(n - 2);

  70. 爬樓梯: Fibonacci 的最直接體現;

  前置知識: 數學歸納法:

    a: 驗證k0 成立;  (邊界條件)

    b: 證明如果ki 成立,那么Ki+1 也成立;(推導公式)

    c: 聯合a & b, 證明k0 -> kn 成立;

 

2. 遞推問題的步驟:(確定遞推公式時候,不要管前一個結果如何得到)

  1. 確定遞推狀態(狀態方程,學習重點): 一個函數符號f(x), 以及函數符號的含義描述; 確定自變量x(影響結果的因素有哪些), 因變量y;

  2. 確定遞推公式(ki -> ki+1) ,取決於遞推狀態的定義;(這里ki 是第i組對應的滿足題意的結果,不要糾結與它怎么得到的!)

  3. 分析邊界條件;

  4. 程序實現: 遞歸 或者 循環

 

  例子:下面例子中介紹的前兩種思路 是更通用的遞推思路;

   

    比較直接的狀態定義:

            

 

  進一步優化狀態定義: 

    

1. 狀態的定義: f(n, j) : 第一塊塗第0種顏色(這里變成隱變量),最后一塊塗第 j 種顏色個數,
 然后乘上顏色個數既可;因為第一塊塗的不同顏色對應可能個數是一樣的;
2. 那么遞推公式: f(n, j) = ∑ f(n -1, k)  |  k != j; 
3.初值條件: f(1, 0) = 1, f(1, 1) = 0, f(1, 2) = 0;
    下面都可由遞推公式得到:
     f(2, 0) = f(1, 1) + f(1, 2) = 0, f(2, 1) = f(1, 0) + f(1, 2) = 1, f(2, 2) = f(1, 1) + f(1, 0) = 1;
    f(3, 0) = 0(f(n,0)永遠為0), f(3, 1) = f(2, 0) + f(2, 2) = 1, f(3, 2) = f(2, 0) + f(3,1) = 1;
4.最終答案(第一塊顏色有3 種可能):3 * ( f(n - 1, 1) + f(n - 1, 2) ); 
整個過程細節

  進一步對於題意進行分析:

    

與題目本身的聯系比較緊,普世性差點:

1. 狀態定義:f(n) :  n 塊環形牆壁時,滿足條件個數;
2. 遞推公式:第一塊 與 第 n - 1 塊的顏色不一樣情況 
                  第一塊 與 第 n - 1塊顏色一樣,等價與 n - 2 的情況;
3. 初值條件: f(2) = 6, f(3) = 6; //注意f(2)=f(3),必須為初值,因為前后顏色都確定了;
4.最終答案:  f(n) = f(n - 1) + f(n - 2) * 2;
進一步簡化(基於具體題目)

 

3. 遞推 與 動態規划 之間的關系:

 a. 動態規划是一種求最優化遞推問題,動態規划中涉及到決策過程; 所以動態規划是遞推問題的子問題;

  其中 遞推公式 在動態規划中叫做 狀態轉移方程, 具體步驟與上面遞推是類似的;

 b. 在狀態轉移方程中,主要下面三個重點:

   狀態定義:

   決策過程:從所有可能中選擇最優解;  //如果不涉及決策,大概率就是貪心算法;

   階段依賴:當前階段只依賴上一階段; 對於不同問題中,階段概念是一個很寬泛的定義;

 c. 在實際求解問題過程中,有兩種方向:處理問題中,如果從哪里來的條件判斷比較復雜,那就可以考慮到哪里去的方法;

     從哪里來:當前狀態 = f(前一個狀態 ) ;                   當前這個狀態(被推導) 可以通過其他狀態推導得到;

     到哪里去:每更新當前狀態時 ==> 主動更新可以從它推導出狀態的結果; 當前這個狀態(去推導)可以到達哪些狀態;

    這兩種方向,本質是一樣的,底層是一個圖的結構, 遞推(動歸) 求解順序就是狀態依賴圖的一個拓撲序,對有向圖的一維化序列化的結果;

 

  d. 動歸典型題: 體會從哪里來,到哪里去的精髓;

  120. 三角形最小路徑和 : 同樣的符號,不同的狀態定義,遞推公式就不一樣;決策過程也明確; 當前階段 與 下一階段 區分與依賴關系也很明確;

 1 class Solution {
 2 public:
 3     int minimumTotal(vector<vector<int>>& triangle) {
 4 //從上往下,到哪里去
 5         int size = triangle.size();
 6         vector<vector<int>> nums(size, vector<int>(size, INT_MAX));
 7         nums[0][0] = triangle[0][0];
 8 
 9         for(int i = 0, I = size - 1; i < I; ++i) {
10             for(int j = 0; j <= i; ++j){
11                 nums[i + 1][j] = min(nums[i][j] + triangle[i + 1][j], nums[i + 1][j]);
12                 nums[i + 1][j + 1] = min(nums[i][j] + triangle[i + 1][j + 1], nums[i + 1][j + 1]);
13             }
14         }
15 
16         int ans = INT_MAX;
17         for(auto &x : nums[size -1]) ans = min(ans, x);
18         return ans;
19 
20 
21 //rev1: 從下往上, 從哪里來
22 //rev1        int size = triangle.size();
23 //rev1        vector<vector<int>> nums(2, vector<int>(size, 0));
24 //rev1
25 //rev1        int ind = (size - 1) % 2, pre_ind = 0;
26 //rev1        for(int i = 0; i < size; ++i) nums[ind][i] = triangle[size - 1][i];
27 //rev1
28 //rev1        for(int i = size - 2; i >= 0; --i) {
29 //rev1            ind = i % 2;
30 //rev1            pre_ind = !ind;
31 //rev1            for(int j = 0; j <= i; ++j)  
32 //rev1                nums[ind][j] = min(nums[pre_ind][j], nums[pre_ind][j + 1]) + triangle[i][j];
33 //rev1
34 //rev1        }
35 //rev1
36 //rev1        return  nums[0][0];
37     }
38 };
動歸典型題

  e. 動歸程序實現中優化; 滾動數組 ==>再壓縮,可以順着/倒着刷表; 記憶化數組;

  132. 分割回文串 II :記憶化數組

 1 class Solution {
 2 public:
 3 #define MAX_N 2006
 4     bool mark[MAX_N][MAX_N];
 5 
 6     bool isPalindrome(int l, int r, string s) {
 7         if(s[l] == s[r]) {
 8             if(r - l > 1) mark[l][r] = mark[l + 1][r - 1];
 9             else mark[l][r] = true;
10         }
11         return mark[l][r];
12     }
13 
14     int minCut(string s) {
15         memset(mark, 0, sizeof(mark));
16         int n = s.size();
17         vector<int>dp(n + 1, n); //i 個字符可組成的最少回文個數
18         dp[0] = 0;
19 
20         for(int i = 0, pre = 0; i < n; ++i) {
21             dp[i + 1] = dp[i] + 1;
22             for(int j = pre; j <= i; ++j) {
23                 if(isPalindrome(j, i, s) && dp[j] + 1 < dp[i + 1]) {
24                     dp[i + 1] = dp[j] + 1;
25                     pre = j;
26                 }
27                 pre = max(0, pre - 1);
28             }
29 
30         }
31         return dp[s.size()] - 1;
32     }
33 };
記憶化數組

 

======================= **代碼演示** =======================

 1. 動態規划:  746. 使用最小花費爬樓梯

 1 class Solution {
 2 public:
 3     int minCostClimbingStairs(vector<int>& cost) {
 4 //        int len = cost.size();
 5 //        vector<int> dp(len + 1, 0); 
 6 //        dp[0] = cost[0], dp[1] = cost[1];
 7 //        cost.push_back(0);
 8 //
 9 //        for(int i = 2; i <= len; ++i) {
10 //            dp[i] = min(dp[i - 2], dp[i - 1]) + cost[i];
11 //        }
12 
13         //優化空間使用
14         int dp[3] = {cost[0], cost[1], 0};
15         cost.push_back(0);
16         for(int i = 2, I = cost.size(); i < I; ++i) {
17             int cur = i % 3, pre1 = (i + 3 - 1) % 3, pre2 = (i + 3 - 2) % 3;
18             dp[cur] = min(dp[pre1], dp[pre2]) + cost[i];
19         }
20         return dp[(cost.size() - 1) % 3];
21     }
22 };
動規帶有決策的遞推

2. 二維動態規划: 1143. 最長公共子序列

 1 class Solution {
 2 public:
 3     int longestCommonSubsequence(string text1, string text2) {
 4         int len1 = text1.size(), len2 = text2.size();
 5         vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
 6 
 7         for(int i = 1; i <= len1; i++) {
 8             for(int j = 1; j <= len2; j++) {
 9                 dp[i][j] = max(dp[i - 1][j - 1] + (text1[i - 1] == text2[j - 1]),
10                             max(dp[i][j - 1], dp[i - 1][j]));
11             }
12         }
13         return dp[len1][len2];
14     }
15 };
16 //狀態方程: dp[i][j] 代表text1 長度為i, text2長度為j 的最長公共子序列長度;i 不一定要等於 j
17 //狀態轉移方程:
18 //      dp[i][j] = max(dp[i][j - 1], 
19 //                     dp[i -1][j],
20 //                     dp[i - 1][j - 1] + (text1[i] == text2[j]));  //這一組只有在text1[i] == text2[j] 才有必要
21 //
二維動歸典型題

 ======================= **經典問題** =======================

1. 相鄰互斥,動態過程最優解;

   劍指 Offer II 091. 粉刷房子

 1 class Solution {
 2 public:
 3     int minCost(vector<vector<int>>& costs) {
 4         vector<vector<int>> dp(2, costs[0]);
 5 
 6         for(int i = 1, I = costs.size(); i < I; ++i) {
 7             int cur = i % 2, pre = !cur;
 8             dp[cur][0] = min(dp[pre][1], dp[pre][2]) + costs[i][0];
 9             dp[cur][1] = min(dp[pre][0], dp[pre][2]) + costs[i][1];
10             dp[cur][2] = min(dp[pre][0], dp[pre][1]) + costs[i][2];
11         }
12 
13         int idx = (costs.size() - 1) % 2;
14         return min(dp[idx][0], min(dp[idx][1], dp[idx][2]));
15     }
16 };
動歸/滾動數組

   198. 打家劫舍

 1 class Solution {
 2 public:
 3     int rob(vector<int>& nums) {
 4         int len = nums.size();
 5         vector<vector<int>> dp(2, vector<int>(2)); //偷/不偷 對應的最大值
 6         dp[0][0] = nums[0];
 7         for(int i = 1; i < len; ++i) {
 8             int cur = i % 2, pre = !cur;
 9             dp[cur][0] = nums[i] + dp[pre][1];
10             dp[cur][1] = max(dp[pre][0], dp[pre][1]);
11         }
12         len -= 1;
13         return max(dp[len % 2][0], dp[len % 2][1]);
14     }
15 };
動歸

  213. 打家劫舍 II

 1 class Solution {
 2 public:
 3     int rob(vector<int>& nums) {
 4         int n = nums.size();
 5         if(n == 1) return nums[0];
 6 
 7         int dp1[2][2] = {nums[0]}, dp2[2][2] = {0};
 8 
 9         //第一家一定偷
10         dp1[0][0] = dp1[0][1] = dp1[1][0] = dp1[1][1] = nums[0];
11         for(int i = 2; i < n; ++i) {
12             int idx = i % 2, pre = !idx;
13             dp1[idx][0] = dp1[pre][1] + nums[i];   //偷;
14             dp1[idx][1] = max(dp1[pre][0], dp1[pre][1]); //不偷;
15         }
16         //第一家一定不偷
17         for(int i = 1; i < n; ++i) {
18             int idx = i % 2, pre = !idx;
19             dp2[idx][0] = dp2[pre][1] + nums[i]; //
20             dp2[idx][1] = max(dp2[pre][0], dp2[pre][1]); //不偷
21         }
22 
23         n--;
24         return max(dp1[n % 2][1], max(dp2[n % 2][0], dp2[n % 2][1]));
25     }
26 };
27 
28 //original
29 //
30 class Solution {
31 public:
32     int rob(vector<int>& nums) {
33 //rev2: 存儲空間優化方案
34         int size = nums.size();
35         if(size == 1) return nums[0];
36        vector<vector<int>> rob(2, vector<int>(2, 0));
37        int ret = 0;
38     //最后一家一定不搶
39         rob[0][0] = 0;
40         rob[0][1] = nums[0];
41        for(int i = 1, I = size - 1; i < I; ++i) {
42            int ind = i % 2, pre_ind = !ind;
43            rob[ind][0] = max(rob[pre_ind][0], rob[pre_ind][1]);
44            rob[ind][1] = rob[pre_ind][0] + nums[i];
45        }
46        int last_ind = (size - 2) % 2;
47        ret = max(rob[last_ind][0], rob[last_ind][1]);
48 
49     //第一家一定不搶
50         rob[0][0] = rob[0][1] = rob[1][0] = rob[1][1] = 0;
51         for(int i = 1; i < size; ++i) {
52             int ind = i % 2, pre_ind = !ind;
53             rob[ind][0] = max(rob[pre_ind][0], rob[pre_ind][1]);
54             rob[ind][1] = rob[pre_ind][0] + nums[i];
55         }
56         last_ind = (size - 1) % 2;
57         ret = max(ret, max(rob[last_ind][0], rob[last_ind][1]));
58 
59 //rev1: 無任何優化方式
60 //        int size = nums.size();
61 //        if(size == 1) return nums[0];
62 //        vector<vector<int>> rob(size, vector<int>(2, 0));
63 //        int ret = 0;
64 //// 第一家隨意,最后一家一定不搶
65 //        rob[0][0] = 0;
66 //        rob[0][1] = nums[0];
67 //        for(int i = 1, I = size - 1; i < I; ++i) {
68 //            rob[i][0] = max(rob[i - 1][0], rob[i -1][1]);
69 //            rob[i][1] = rob[i -1][0] + nums[i];
70 //        }
71 //        ret = max(rob[size -2][0], rob[size -2][1]); 
72 ////第一家不搶, 最后一家隨意
73 //        rob[0][1] = 0; 
74 //        for(int i = 1; i < size; ++i) {
75 //            rob[i][0] = rob[i][1] = 0;
76 //            rob[i][0] = max(rob[i - 1][0], rob[i - 1][1]);
77 //            rob[i][1] = rob[i -1][0] + nums[i];
78 //        }
79 //
80 //        ret = max(ret, max(rob[size -1][0], rob[size -1][1]));
81         return ret;
82     }
83 };
狀態定義/轉移

   152. 乘積最大子數組

 1 class Solution {
 2 public:
 3     int maxProduct(vector<int>& nums) {
 4         int ans = INT_MIN, max_val = 1, min_val = 1;
 5 
 6         for(auto &x : nums) {
 7             if(x < 0) swap(max_val, min_val);
 8             max_val = max(max_val * x, x);
 9             min_val = min(min_val * x, x);
10             ans = max(ans, max_val);
11         }
12         return ans;
13     }
14 };
15 
16 //f(n) 以當前位置結尾的最大/最小值
狀態定義

 

2.最長上升子序列:

   300. 最長遞增子序列

 1 class Solution {
 2 public:
 3     int bs_01(vector<int>& arr, int num) {
 4         int l = 0, r = arr.size(), mid;
 5         while(l < r) {
 6             mid = (l + r) >> 1;
 7             if(arr[mid] < num) l = mid + 1;
 8             else r = mid;
 9         }
10         return l;
11     }
12 
13     int lengthOfLIS(vector<int>& nums) {
14 //優化解法
15         vector<int> arr;
16         for(auto &x : nums) {
17             if(arr.empty() || arr.back() < x) arr.push_back(x);
18             else arr[bs_01(arr, x)] = x;
19         }
20         return arr.size();
21 
22 //基礎解法dp
23 //        vector<int> dp(nums.size(), 0);
24 //        map<int,int> m;
25 //        int ans = 0;
26 //        for(int i = 0, I = nums.size(); i < I; ++i) {
27 //            m[nums[i]] = i;
28 //            auto it = m.find(nums[i]);
29 //            while(it != m.begin()) dp[i] = max(dp[i], dp[(--it)->second]);
30 //            dp[i] += 1;
31 //            ans = max(ans, dp[i]);
32 //        }
33 //        return ans;
34 //dp(n) 以n 為結尾的子數組長度;
35     }
36 };
狀態定義/優化

  714. 買賣股票的最佳時機含手續費

 1 class Solution {
 2 public:
 3     int maxProfit(vector<int>& prices, int fee) {
 4         int n = prices.size();
 5         vector<vector<int>> dp(2, vector<int>(2, 0));  //第i 天 持有/不持有
 6         dp[0][0] = -prices[0], dp[0][1] = 0;
 7         for(int i = 1; i < n; ++i) {
 8             int idx = i % 2, pre = !idx;
 9             dp[idx][0] =  max(dp[pre][0], dp[pre][1] - prices[i]);//持有
10             dp[idx][1] =  max(dp[pre][0] + prices[i] - fee, dp[pre][1]);//不持有
11         }
12 
13         return dp[!(n % 2)][1];
14     }
15 };
動歸

 

3. 典型的0/1 背包問題; 資源有限的情況下收益最大化問題; 對於當前值,就兩種可能,要么選,要么不選;

  416. 分割等和子集 :可達數組類型, 倒着刷表

 1 class Solution {
 2 public:
 3 
 4     bool canPartition(vector<int>& nums) {
 5         int total = 0;
 6         for(auto &x : nums) total += x;
 7         if(total % 2) return false;
 8         total /= 2;
 9 
10         //dp[i][j] : 前i個數字,能否湊出j值;
11         //dp[i][j] : dp[i - 1][j] | dp[i - 1][j - nums[i]
12         //優化:當前i狀態 只與前面所有可能到達狀態相關, 
13         //      從后往前,保證當前i狀態 由 所有前面的狀態決定
14         vector<bool> dp(total + 1, false); 
15         dp[0] = true;
16 
17         for(auto &x : nums){
18             for(int i = total - x; i >= 0; --i) {
19                 if(dp[i]) dp[i + x] = true;
20             }
21             if(dp[total]) return true;
22         }
23         return false;
24     }
25 };
優化

   474. 一和零

 1 class Solution {
 2 public:
 3     int findMaxForm(vector<string>& strs, int m, int n) {
 4         //dp[i][m][n] : 第i 個str, 取m 個 0, n 個 1 的最多子集樹;
 5         //dp[i][m][n] = max(dp[i - 1][m][n] , dp[i - 1][n - cnt0][n - cnt1]) //選 , 不選對應可能
 6         //優化:當前i 結果只與 前面所有可能狀態相關; 注意方向,保證是取 i 前面可能狀態;
 7         
 8         vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));  //i 個0, j 個1 的有可能取到最多子集數;
 9 
10         for(int i = 0, I = strs.size(); i < I; ++i) {
11             int idx = 0, cnt0 = 0, cnt1 = 0;
12             while(strs[i][idx]) cnt0 += (strs[i][idx++] == '0');
13             cnt1 = strs[i].size() - cnt0;
14 
15             for(int x = m; x >= cnt0; --x) {
16                 for(int y = n; y >= cnt1; --y) {
17                     dp[x][y] = max(dp[x][y], dp[x - cnt0][y - cnt1] + 1);
18                 }
19             }
20         }
21         return dp[m][n];
22     }
23 };
優化

   494. 目標和 // 到哪里去

 1 class Solution {
 2 public:
 3     int findTargetSumWays(vector<int>& nums, int target) {
 4 //Rev2: 滾動數組/可達數組/偏移量
 5         int sum = 0;
 6         for(auto &x : nums) sum += x;
 7         if(target > sum || target < -sum) return 0;
 8         //偏移量,滾動數組
 9         int buff[2][2 * sum + 5], *f[2] = {buff[0] + sum + 2, buff[1] + sum + 2};
10         memset(buff, 0, sizeof(buff));
11         f[1][0] = 1;
12         sum = 0;
13 
14         for(int i = 0, I = nums.size(); i < I; ++i) {
15             int cur = i % 2, pre = !cur;
16             memset(buff[cur], 0, sizeof(buff[cur]));
17             for(int j = -sum; j <= sum; ++j) {
18                 f[cur][j + nums[i]] += f[pre][j];
19                 f[cur][j - nums[i]] += f[pre][j];
20             }
21             sum += nums[i];
22         }
23 
24         int idx = !(nums.size() % 2);
25         return f[idx][target];
26         
27 //Rev1: 滾動數組/可達數組
28 //        vector<unordered_map<int,int>> dp(2);
29 //        //i位置可以組成的和為nums的個數
30 //        dp[0][nums[0]] += 1, dp[0][-nums[0]] += 1;
31 //        for(int i = 1, I = nums.size(); i < I; ++i) {
32 //            int idx = i % 2, pre = !idx;
33 //            dp[idx].clear();
34 //            for(auto &x : dp[pre]) {
35 //                //我到哪里去
36 //                dp[idx][x.first + nums[i]] += x.second;
37 //                dp[idx][x.first - nums[i]] += x.second;
38 //            }
39 //        }
40 //
41 //        int idx = !(nums.size() % 2);
42 //        return dp[idx][target];
43     }
44 };
滾動數組/偏移量

  2218. 從棧中取出 K 個硬幣的最大面值和 :典型的分組背包問題;

 1 class Solution {
 2 public:
 3     int maxValueOfCoins(vector<vector<int>>& piles, int k) {
 4         //k為背包容量,piles 處理成每堆有那些重量,價值多少的物品,且每堆只能取一件物品
 5         //dp[i][j] : 前i 堆中 取容量為j 的最大值;
 6         int len = piles.size(), dp[len + 1][k + 1];
 7         memset(dp, 0, sizeof(dp));
 8         vector<vector<int>> val(len,vector<int>(1, 0));
 9         for(int i = 0; i < len; ++i) {
10             for(int j = 0, J = piles[i].size(); j < J; j++) {
11                 val[i].push_back(val[i].back() + piles[i][j]);
12             }
13         }
14 
15         for(int i = 0; i < len; ++i) {
16             for(int j = 1; j <= k; ++j) {
17                 int n_i = i + 1, x = 0;
18                 while(x <= j && x < val[i].size()) {
19                     dp[n_i][j] = max(dp[n_i][j], dp[i][j - x] + val[i][x]);
20                     x++;
21                 }
22             }
23         }
24         return dp[len][k];
25     }
26 };
分組背包

   還有多重背包,完全背包;

 

4. 湊硬幣類型:

  322. 零錢兌換: 正着刷表   

 1 class Solution {
 2 public:
 3     int coinChange(vector<int>& coins, int amount) {
 4 //優化版本
 5         vector<int> dp(amount + 5, -1); //總數為i時候,需要的硬幣總數
 6         dp[0] = 0;  
 7         for(auto &x : coins) { //使用前n 種硬幣可以組成的各個金額總數
 8             for(int i = x; i <= amount; ++i) {
 9                 if(-1 == dp[i - x]) continue;
10                 if(-1 == dp[i] || dp[i] > dp[i - x] + 1) dp[i] = dp[i - x] + 1;
11             }
12         }
13         return dp[amount];
14 
15 //初始版本, 而且下面兩個循環之間互換,有些情況下影響很大,例如518題
16 //        sort(coins.begin(), coins.end());
17 //        vector<int> nums;
18 //        for(auto &x : coins){   //減枝過程,可優化
19 //            if(x > amount) break;
20 //            nums.push_back(x);
21 //        }
22 //
23 //        for(int i = 0; i <= amount; i++) {
24 //            if(dp[i] == -1) continue;
25 //            for(auto &x : nums) {
26 //                int val = x + i;
27 //                if(val > amount) break;  
28 //                if(dp[val] == -1) dp[val] = dp[i] + 1; //可優化
29 //                else dp[val] = min(dp[val], dp[i] + 1);
30 //            }
31 //        }
32 //        return dp[amount];
33     }
34 };
正着刷表

  518. 零錢兌換 II : 遞推/正向刷表;

 1 class Solution {
 2 public:
 3     int change(int amount, vector<int>& coins) {
 4         //dp[i][j] = dp[i - 1][j] + dp[i][j - x], 可優化成下面正向刷表;
 5         vector<int> dp(amount + 1, 0);  //i 金額對應的硬幣組合個數
 6         dp[0] = 1;  
 7 
 8         for(auto &x : coins) { //使用前 x種硬幣時候,各個金額可能的組合
 9             for(int i = x; i <= amount; ++i) {
10                 dp[i] += dp[i - x];
11             }
12         }
13         return dp[amount];
14     }
15 };
遞推/正向刷表

   377. 組合總和 Ⅳ : 這題與322, 518 三題綜合起來看,包含了最佳,不計順序,計算順序的不同要求對應的不同操作;

 1 class Solution {
 2 public:
 3     int combinationSum4(vector<int>& nums, int target) {
 4         vector<unsigned int> dp(target + 1, 0);
 5         sort(nums.begin(), nums.end());
 6 
 7         dp[0] = 1;
 8         //到哪里去
 9         for(int i = 0; i <= target; ++i) {  //當前金額可以實現哪些新的金額
10             if(!dp[i]) continue;
11             for(auto &x : nums) {
12                 int val = i + x;
13                 if(val > target) break;
14                 dp[val] += dp[i];
15             }
16         }
17         return dp[target];
18     }
19 };
20 
21 //original
22 class Solution {
23 public:
24     int combinationSum4(vector<int>& nums, int target) {
25         vector<unsigned int> cnt(target + 1, 0);
26         cnt[0] = 1; 
27         sort(nums.begin(), nums.end());
28         //從哪里來
29         for(int i = 1; i <= target; ++i) {  //當前金額,可以由哪些金額組成
30             for(auto &x : nums) {
31                 if(x > i) break; 
32                 cnt[i] += cnt[i - x];
33             }
34         }
35 
36         return cnt[target];
37     }
38 };
從哪里來/到哪里去

 
======================= **應用場景** =======================


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM