這篇文章主要講解了“C++如何實現打氣球游戲”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C++如何實現打氣球游戲”吧!
Example:
Input:
[3,1,5,8]
Output:
167
Explanation:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
Credits:
Special thanks to @peisi for adding this problem and creating all test cases.
這道題提出了一種打氣球的游戲,每個氣球都對應著一個數字,每次打爆一個氣球,得到的金幣數是被打爆的氣球的數字和其兩邊的氣球上的數字相乘,如果旁邊沒有氣球了,則按1算,以此類推,求能得到的最多金幣數。參見題目中給的例子,題意并不難理解。那么大家拿到題后,總是會習慣的先去想一下暴力破解法吧,這道題的暴力搜索將相當的復雜,因為每打爆一個氣球,斷開的地方又重新挨上,所有剩下的氣球又要重新遍歷,這使得分治法不能 work,整個的時間復雜度會相當的高,不要指望可以通過 OJ。而對于像這種求極值問題,一般都要考慮用動態規劃 Dynamic Programming 來做,維護一個二維動態數組 dp,其中 dp[i][j] 表示打爆區間 [i,j] 中的所有氣球能得到的最多金幣。題目中說明了邊界情況,當氣球周圍沒有氣球的時候,旁邊的數字按1算,這樣可以在原數組兩邊各填充一個1,方便于計算。這道題的最難點就是找狀態轉移方程,還是從定義式來看,假如區間只有一個數,比如 dp[i][i],那么計算起來就很簡單,直接乘以周圍兩個數字即可更新。如果區間里有兩個數字,就要算兩次了,先打破第一個再打破了第二個,或者先打破第二個再打破第一個,比較兩種情況,其中較大值就是該區間的 dp 值。假如區間有三個數呢,比如 dp[1][3],怎么更新呢?如果先打破第一個,剩下兩個怎么辦呢,難道還要分別再遍歷算一下嗎?這樣跟暴力搜索的方法有啥區別呢,還要 dp 數組有啥意思。所謂的狀態轉移,就是假設已知了其他狀態,來推導現在的狀態,現在是想知道 dp[1][3] 的值,那么如果先打破了氣球1,剩下了氣球2和3,若之前已經計算了 dp[2][3] 的話,就可以使用其來更新 dp[1][3] 了,就是打破氣球1的得分加上 dp[2][3]。那假如先打破氣球2呢,只要之前計算了 dp[1][1] 和 dp[3][3],那么三者加起來就可以更新 dp[1][3]。同理,先打破氣球3,就用其得分加上 dp[1][2] 來更新 dp[1][3]。說到這里,是不是感覺豁然開朗了 ^.^
那么對于有很多數的區間 [i, j],如何來更新呢?現在是想知道 dp[i][j] 的值,這個區間可能比較大,但是如果知道了所有的小區間的 dp 值,然后聚沙成塔,逐步的就能推出大區間的 dp 值了。還是要遍歷這個區間內的每個氣球,就用k來遍歷吧,k在區間 [i, j] 中,假如第k個氣球最后被打爆,那么此時區間 [i, j] 被分成了三部分,[i, k-1],[k],和 [k+1, j],只要之前更新過了 [i, k-1] 和 [k+1, j] 這兩個子區間的 dp 值,可以直接用 dp[i][k-1] 和 dp[k+1][j],那么最后被打爆的第k個氣球的得分該怎么算呢,你可能會下意識的說,就乘以周圍兩個氣球被 nums[k-1] * nums[k] * nums[k+1],但其實這樣是錯誤的,為啥呢?dp[i][k-1] 的意義是什么呢,是打爆區間 [i, k-1] 內所有的氣球后的最大得分,此時第 k-1 個氣球已經不能用了,同理,第 k+1 個氣球也不能用了,相當于區間 [i, j] 中除了第k個氣球,其他的已經爆了,那么周圍的氣球只能是第 i-1 個,和第 j+1 個了,所以得分應為 nums[i-1] * nums[k] * nums[j+1],分析到這里,狀態轉移方程應該已經躍然紙上了吧,如下所示:
dp[i][j] = max(dp[i][j], nums[i - 1] * nums[k] * nums[j + 1] + dp[i][k - 1] + dp[k + 1][j]) ( i ≤ k ≤ j )
有了狀態轉移方程了,就可以寫代碼,下面就遇到本題的第二大難點了,區間的遍歷順序。一般來說,遍歷所有子區間的順序都是i從0到n,然后j從i到n,然后得到的 [i, j] 就是子區間。但是這道題用這種遍歷順序就不對,在前面的分析中已經說了,這里需要先更新完所有的小區間,然后才能去更新大區間,而用這種一般的遍歷子區間的順序,會在更新完所有小區間之前就更新了大區間,從而不一定能算出正確的dp值,比如拿題目中的那個例子 [3, 1, 5, 8] 來說,一般的遍歷順序是:
[3] -> [3, 1] -> [3, 1, 5] -> [3, 1, 5, 8] -> [1] -> [1, 5] -> [1, 5, 8] -> [5] -> [5, 8] -> [8]
顯然不是我們需要的遍歷順序,正確的順序應該是先遍歷完所有長度為1的區間,再是長度為2的區間,再依次累加長度,直到最后才遍歷整個區間:
[3] -> [1] -> [5] -> [8] -> [3, 1] -> [1, 5] -> [5, 8] -> [3, 1, 5] -> [1, 5, 8] -> [3, 1, 5, 8]
這里其實只是更新了 dp 數組的右上三角區域,最終要返回的值存在 dp[1][n] 中,其中n是兩端添加1之前數組 nums 的個數。參見代碼如下:
解法一:
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
nums.insert(nums.begin(), 1);
nums.push_back(1);
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
for (int len = 1; len <= n; ++len) {
for (int i = 1; i <= n - len + 1; ++i) {
int j = i + len - 1;
for (int k = i; k <= j; ++k) {
dp[i][j] = max(dp[i][j], nums[i - 1] * nums[k] * nums[j + 1] + dp[i][k - 1] + dp[k + 1][j]);
}
}
}
return dp[1][n];
}
};對于題目中的例子[3, 1, 5, 8],得到的dp數組如下:
0 0 0 0 0 0
0 3 30 159 167 0
0 0 15 135 159 0
0 0 0 40 48 0
0 0 0 0 40 0
0 0 0 0 0 0
這題還有遞歸解法,思路都一樣,就是寫法略有不同,參見代碼如下:
解法二:
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
nums.insert(nums.begin(), 1);
nums.push_back(1);
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
return burst(nums, dp, 1 , n);
}
int burst(vector<int>& nums, vector<vector<int>>& dp, int i, int j) {
if (i > j) return 0;
if (dp[i][j] > 0) return dp[i][j];
int res = 0;
for (int k = i; k <= j; ++k) {
res = max(res, nums[i - 1] * nums[k] * nums[j + 1] + burst(nums, dp, i, k - 1) + burst(nums, dp, k + 1, j));
}
dp[i][j] = res;
return res;
}
};感謝各位的閱讀,以上就是“C++如何實現打氣球游戲”的內容了,經過本文的學習后,相信大家對C++如何實現打氣球游戲這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。