1. 程式人生 > 實用技巧 >ddt資料驅動常見的用法【多測師_王sir】

ddt資料驅動常見的用法【多測師_王sir】

參考:https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-zhi-bo-yi-wen-ti

問題:

給定一堆石子的得分。A和B兩個人進行如下游戲,

輪流,從石堆的兩邊選擇一個石子,最終獲得得分最大的人獲勝。

由A先開始,求最終A是否能獲勝。獲勝則返回true,否則返回false。

Example 1:
Input: piles = [5,3,4,5]
Output: true
Explanation: 
Alex starts first, and can only take the first 5 or the last 5.
Say he takes the first 5, so that the row becomes [3, 4, 5].
If Lee takes 3, then the board is [4, 5], and Alex takes 5 to win with 10 points.
If Lee takes the last 5, then the board is [3, 4], and Alex takes 4 to win with 9 points.
This demonstrated that taking the first 5 was a winning move for Alex, so we return true.
 

Constraints:
2 <= piles.length <= 500
piles.length is even.
1 <= piles[i] <= 500
sum(piles) is odd.

  

解法:DP(動態規劃)

1.確定【狀態】:石頭堆 [ i ~ j ] 的

  • 最左邊石子:piles[i]
  • 最右邊石子:piles[j]

2.確定【選擇】:對於先下的選手dp[i][j].first,分兩種情況,取其中的最大值

  • 選擇最左邊的石子:piles[i]
    • 現在的得分piles[i] + 去掉這個石子後剩下的石頭堆 [ i+1 ~ j ] 中作為後下選手的最大得分 dp[i+1][j].second
    • 這時,對於後下的選手 dp[i][j].second =:
      • 只能從去掉這個石子後剩下的石頭堆[ i+1 ~ j ] 中,作為先下選手的最大得分dp[i+1][j].first
  • 選擇最右邊的石子:piles[j]
    • 現在的得分piles[j] + 去掉這個石子後剩下的石頭堆 [ i ~ j-1 ] 中作為後下選手的最大得分 dp[i][j-1].second
    • 這時,對於後下的選手 dp[i][j].second =:
      • 只能從去掉這個石子後剩下的石頭堆[ i ~ j-1 ] 中,作為先下選手的最大得分dp[i][j-1].first

3. dp[i][j]的含義:

將第 i 個石子到第 j 個石子最為物件石頭堆,在這其中,所能獲得的最大得分。

  • dp[i][j].first:先下選手的最大得分。
  • dp[i][j].second:後下選手的最大得分。

4. 狀態轉移:

dp[i][j].first = max {

  • piles[i] +dp[i+1][j].second:選左邊石子
    • 這時,dp[i][j].second = dp[i+1][j].first
  • piles[j] +dp[i][j-1].second:選右邊石子}
    • 這時,dp[i][j].second = dp[i][j-1].first

5. base case:

  • dp[i][i]:單石子堆
    • .first=piles[i]:先下選手得分就是該石子分值piles[i]。
    • .second=0:後下選手得分為0。(唯一石子已被先下選手拿走)

6. 遍歷順序:

根據狀態轉移公式,在求得dp[i][j]之前,需先求得dp[i+1][j],dp[i][j-1]

因此需要:i:大->小,j:小->大 遍歷

程式碼參考:

 1 class Solution {
 2 public:
 3     //dp[i][j].first : among piles[i~j], the max points that the 1st player can get.
 4     //dp[i][j].second: among piles[i~j], the max points that the 2nd player can get.
 5     //for the 1st player:  dp[i][j].first = ?
 6     // case_1: choose piles[i]: = piles[i] + dp[i+1][j].second
 7     //       thus for the 2nd player can only choose from the left piles[i+1~j]
 8     //       dp[i][j].second = dp[i+1][j].first
 9     // case_2: choose piles[j]: = piles[j] + dp[i][j-1].second
10     //       thus for the 2nd player can only choose from the left piles[i~j-1]
11     //       dp[i][j].second = dp[i][j-1].first
12     // base case:
13     //       there is only one stone left.
14     // dp[i][i].first = piles[i]
15     // dp[i][i].second = 0
16     // target:
17     //       dp[0][size-1].first > dp[0][size-1].second
18     bool stoneGame(vector<int>& piles) {
19         int n = piles.size();
20         vector<vector<pair<int, int>>> dp(n, vector<pair<int, int>>(n, pair<int,int>(0,0)));
21         for(int i=0; i<n; i++) {
22             dp[i][i].first = piles[i];
23         }
24         for(int i=n-2; i>=0; i--) {
25             for(int j=i+1; j<n; j++) {
26                 int left = piles[i]+dp[i+1][j].second, right = piles[j]+dp[i][j-1].second;
27                 if(left >= right) {
28                     dp[i][j].first = left;
29                     dp[i][j].second = dp[i+1][j].first;
30                 } else {
31                     dp[i][j].first = right;
32                     dp[i][j].second = dp[i][j-1].first;
33                 }
34             }
35         }
36         return dp[0][n-1].first > dp[0][n-1].second;
37     }
38 };

♻️ 優化:

空間複雜度:2維->1維

去掉 i

壓縮所有行到一行。

左下角dp[i+1][j-1]會被上面的dp[i][j-1]覆蓋,因此引入變數pre,在更新dp[i][j-1]之前,儲存dp[i+1][j-1]

比原先的這種賦值方式,少了黃色的方塊區域,剛好不會存在需要的黃色區域被上方的綠色區域覆蓋的情況。

因此直接去掉 i 即可。

⚠️ 注意:

1. 在初始化的時候,最開始的一行,和每一行(遍歷 i)時的第一個元素dp[i],需要首先初始化。(如下述程式碼L7~L11

2. 本問題的first和second兩個值的賦值先後問題需注意。(不要被覆蓋:如下述程式碼L15~L18

程式碼參考:

 1 class Solution {
 2 public:
 3     bool stoneGame(vector<int>& piles) {
 4         int n = piles.size();
 5         vector<pair<int, int>> dp(n, pair<int,int>(0,0));
 6         
 7         dp[n-1].first = piles[n-1];//initialize 1st row.
 8         dp[n-1].second = 0;
 9         for(int i=n-2; i>=0; i--) {
10             dp[i].first = piles[i];//initialize 1st item of a row.
11             dp[i].second = 0;
12             for(int j=i+1; j<n; j++) {
13                 int left = piles[i]+dp[j].second, right = piles[j]+dp[j-1].second;
14                 if(left >= right) {
15                     dp[j].second = dp[j].first;
16                     //here *.first will be overwritten,
17                     //so pay attention to the updating sequence of the pair.
18                     dp[j].first = left;
19                 } else {
20                     dp[j].first = right;
21                     dp[j].second = dp[j-1].first;
22                 }
23             }
24         }
25         return dp[n-1].first > dp[n-1].second;
26     }
27 };