知識點:數組;前綴和;哨兵;動態規劃;貪心;分治;數組
輸入一個整型數組,數組中的一個或連續多個整數組成一個子數組。求全部子數組的和的最大值。函數
要求時間複雜度爲O(n)。優化
輸入: nums = [-2,1,-3,4,-1,2,1,-5,4] 輸出: 6 解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。
連續子數組 --> 前綴和
從前日後遍歷求前綴和,維持兩個變量,一個是最大子數組和,也就是答案,一個是最小的前綴和,咱們能夠把這個值理解爲哨兵,這個就是咱們用來獲取答案的,由於每次前綴和-這個最小的確定就是最大的。this
class Solution { public int maxSubArray(int[] nums) { int pre = 0; //前綴和; int minPre = 0; //最小的前綴和:哨兵; int maxSum = Integer.MIN_VALUE; for(int i = 0; i < nums.length; i++){ pre += nums[i]; maxSum = Math.max(maxSum, pre-minPre); minPre = Math.min(pre, minPre); } return maxSum; } }
這道題貪心怎麼解?貪什麼呢?想一下在這個過程當中,好比-2 1,咱們須要-2嗎?不須要!由於負數只會拉低咱們最後的和,只起反作用的索性不如不要了。直接從1開始就好了; 貪的就是負數和必定會拉低結果。
因此咱們的貪心選擇策略就是:只選擇和>0的,對於和<=0的均可以捨棄了。code
class Solution { public int maxSubArray(int[] nums) { int maxSum = Integer.MIN_VALUE; int sum = 0; for(int i = 0; i < nums.length; i++){ sum += nums[i]; maxSum = Math.max(sum, maxSum); if(sum <= 0){ sum = 0; //對於<=0的前綴和,已經沒要意義了,從下一位置開始; continue; } } return maxSum; } }
這道題能夠用分治去解。指望去求解一個區間[l,r]內的最大子序和,按照分而治之的思想,能夠將其分爲左區間和右區間。
左區間L:[l, mid]和右區間R:[mid + 1, r].
lSum 表示 [l,r] 內以 l 爲左端點的最大子段和
rSum 表示 [l,r] 內以 r 爲右端點的最大子段和
mSum 表示 [l,r] 內的最大子段和
iSum 表示 [l,r] 的區間和
遞歸地求解出L.mSum以及R.mSum以後求解M.mSum。所以首先在分治的遞歸過程當中須要維護區間最大連續子列和mSum這個信息。
接下來分析如何維護M.mSum。具體來講有3種可能:遞歸
class Solution { public class Status{ public int lSum, rSum, mSum, iSum; // lSum 表示 [l,r] 內以 l 爲左端點的最大子段和 // rSum 表示 [l,r] 內以 r 爲右端點的最大子段和 // mSum 表示 [l,r] 內的最大子段和 // iSum 表示 [l,r] 的區間和 public Status(int lSum, int rSum, int mSum, int iSum){ this.lSum = lSum; this.rSum = rSum; this.mSum = mSum; this.iSum = iSum; } } public Status getInfo(int[] a, int l, int r){ if(l == r) return new Status(a[l], a[l], a[l], a[l]); //終止條件; int mid = l + ((r-l) >> 1); Status lsub = getInfo(a, l, mid); Status rsub = getInfo(a, mid+1, r); return pushUp(lsub, rsub); } //根據兩個子串獲得整個序列結果; public Status pushUp(Status l, Status r){ int iSum = l.iSum + r.iSum; int lSum = Math.max(l.lSum, l.iSum+r.lSum); int rSum = Math.max(r.rSum, r.iSum+l.rSum); int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum+r.lSum); return new Status(lSum, rSum, mSum, iSum); } public int maxSubArray(int[] nums) { return getInfo(nums, 0, nums.length-1).mSum; } }
class Solution { public int maxSubArray(int[] nums) { int len = nums.length; int[] dp = new int[len]; //以i結尾的連續子數組的最大和爲dp[i]; if(nums == null || len <= 1) return nums[0]; dp[0] = nums[0]; for(int i = 1; i < len; i++){ dp[i] = Math.max(dp[i-1]+nums[i], nums[i]); //狀態轉移; } //注意咱們要遍歷一遍返回最大的dp; int maxSum = dp[0]; for(int i = 1; i < len; i++){ maxSum = Math.max(maxSum, dp[i]); } return maxSum; } }
固然上述程序能夠優化,由於咱們的dp[i]其實只和前一狀態i-1有關,因此能夠採用一個滾動變量來記錄,而不用整個數組。get
class Solution { public int maxSubArray(int[] nums) { int pre = 0; //記錄前一狀態; int res = nums[0]; //記錄最後結果的最大值; for (int num : nums) { pre = Math.max(pre + num, num); res = Math.max(res, pre); } return res; } }
這道題目是一道很典型的題目,用到了各類方法和思想。要常看常作,分治是其中比較困難的,可是要會這種思想。這道題目最好的方法仍是哨兵和動態規劃, 其實貪心就是從動態規劃的一個特殊狀況過去的,體會二者的關係;io