在上篇文章「初探動態規劃」裏面,我提到要寫好動態規劃須要先寫好遞歸,今天就再談談怎麼寫好遞歸。算法
爲何要寫好遞歸?寫遞歸符合動態規劃的原理,同時也容易被人理解。那麼該怎麼寫好這個遞歸呢?大量的實戰練習,直接上例題。數組
- 最大子序和
給定一個整數數組 nums ,找到一個具備最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
題幹給的是一個整數數組,那麼就有正有負,這點很關鍵,若是沒有負數,這道題就根本不用想,直接數組求和。bash
直接思路就是遍歷搜索,用一個變量存儲最大值,須要兩個索引,一個指向子數組的開頭,一個指向子數組的結尾,算出子數組的值和最大值比較而且決定是否更新。post
第一波思路就這麼簡單粗暴,隨後再優化。學習
public static int maxSubArray(int[] nums) {
return solve(0, 0, nums);
}
public static int solve(int start, int end, int[] nums) {
if (start > end) {
// 這裏是爲了方便表示,實際不能這樣寫,若是返回加了一個負數,會直接越界變成一個超級大的正數
return Integer.MIN_VALUE;
}
if (start > nums.length - 1) {
return Integer.MIN_VALUE;
}
if (end > nums.length - 1) {
return Integer.MIN_VALUE;
}
int total = 0;
// 計算子數組和
for (int i = start; i <= end; i++) {
total += nums[i];
}
//end 朝後移動一位
int b = solve(start, end + 1, nums);
//start 朝後移動一位
int a = solve(start + 1, end, nums);
// 取三者最大值返回
total = Math.max(total, a);
total = Math.max(total, b);
return total;
}
複製代碼
雖然這個時間複雜度不敢恭維,但思路簡單明瞭易於理解,這種算法是確定不行得,在 LeetCode 上面會超時。測試
即便加一個 dp[start][end] 的輔助空間用記憶搜索也仍是會超時,由於在 LeetCode 會給你一個超級大的數組求和,數組大到我 QQ 消息一次都發不完的那種。然而正向遞推(for)初始條件那塊,一直沒有弄好,因此不能靠這種方式經過。優化
這個時候應該想一想遞歸還能不能優化,這種方式的遞歸太簡單粗暴了,能不能換一種柔和一點的遞歸,好比一個索引的遞歸?迴歸問題自己,子數組最大的和這個問題。ui
剛纔兩個索引的方案已經被放棄了,那麼一個索引應該怎麼存儲呢?劃重點:若是當前值比以前累加的值大,那麼以前的累加值能夠直接放棄,從新開始累加。好好體會這句話,這句話就能夠把索引從兩個變成一個。spa
public static int max = 0;
public static int maxSubArray(int[] nums) {
max = nums[nums.length - 1];
solve(nums.length - 1, nums[nums.length - 1], nums);
return max;
}
public static int solve(int index, int total, int[] nums) {
if (index < 0) {
return -100000;
}
// 比較當前值和以前的積累
total = Math.max(nums[index] + solve(index - 1, total, nums), nums[index]);
// 始終存儲最大的那個值
max = Math.max(max, total);
return total;
}
複製代碼
這個版本的遞歸確定仍是過不了測試,但這樣子的記憶搜索已經能夠過全部測試用例了。code
回顧整個過程,一樣一道題,不一樣的思考方式帶來的不一樣遞歸方式,雖然都能算出答案,但這個過程明顯不同,而這就是動態規劃須要學習的地方,優化,優化,再優化。
這兩篇自我感受寫得很沒有水準,我的緣由佔大部分,我對動態規劃遠遠沒有到駕輕就熟的地步,思惟出現了斷層,不成體系。