最小子陣列和——分治思想
首先什麼是最大和子陣列:
即從陣列某一位置開始的連續陣列的所有元素的和要保證最大
例如13,-25,20,-3,-16,-23,18,20,-7,12,最大子陣列即為18,20,-7,12,和為43,且是連續的
在陣列中找到最大和子陣列有三種情況:
1.最大子陣列在源陣列的中間位置的左側 2.最大子陣列在原陣列中間位置的右側 3.最大子陣列橫跨中間位置,既存在於左側又存在於右側
首先來說第三種情況圖b
因為子陣列橫跨中間位置mid,所以這證明i至mid與mid+1至j的所有元素相加為陣列中最大的。為了保證這個條件,i至mid就得是mid左側最大的子陣列,同理,mid+1至j也得是mid右側的最大子陣列,否則前面的條件無法成立。所以任務就是分別找到mid兩側的最大子陣列,然後把他們的和加到一起就是橫跨mid的情況的最大子陣列的和。
1 //尋找橫跨middle的最大子陣列始末位置以及最大值 2 tuple<int, int, int> findMaxCrossSubArray(vector<int>& nums, int left, int middle, int right) { 3 int leftMaxSum = INT_MIN, rightMaxSum = INT_MIN; 4 int sum = 0; 5 int leftIndex = 0, rightIndex = 0; 6 7 //兩個迴圈,分別去找middle左側和右側的最大和以及生成最大和時的索引位置8 for (int i = middle; i >= left; i--) { 9 sum += nums[i]; 10 if (sum >= leftMaxSum) { 11 leftMaxSum = sum; 12 leftIndex = i; 13 } 14 } 15 sum = 0; 16 for (int i = middle + 1; i <= right; i++) { 17 sum += nums[i]; 18 if(sum >= rightMaxSum) { 19 rightMaxSum = sum; 20 rightIndex = i; 21 } 22 } 23 //然後將兩個最大和相加,兩邊索引值也返回 24 tuple<int, int, int> t(leftIndex, rightIndex, leftMaxSum + rightMaxSum); 25 return t; 26 }
函式用一個元組去儲存最大子陣列的始末位置和最大值,middle為mid,先從右往左從middle遍歷至left,每個元素累加,找到最大值leftSumMax和出現最大值時的索引leftIndex。右側同理,只不過範圍變成了middle + 1至right。最後將左右索引和兩端的最大值相加,存在元組裡返回。
之後就是前兩個步驟,要判斷左右兩部分的子陣列首先就要遞迴去拆分,拆到只剩一個元素的時候停止,元素兩兩合併,合併的同時尋找三個累加中最大的一個作為自己的最大和。
如上圖所示,當陣列遞迴到只有一個元素的時候,開始層層返回。返回的過程中要判斷出三個資料(我寫在了每個部分的下面)來計算每個部分的和。因為只存在於一個元素的時候,子陣列的最大和就是本身,所以底層只需要返回自己的索引和值。合併之後,左側的最大值就是之前遞迴返回的,右側的最大值就是右側返回的,之後要計算的就是橫跨mid的最大值,將這三者比較,返回最大值以及它的始末位置。
首先看最左下角,13和-25,左側最大值是13,右側則是-25,計算橫跨mid最大值則是-12,所以13,-25這部分的最大子陣列就是13本身,返回0,0,13。
接著來到13,-25,20,右側因為只有20,所以右側是20,左側已經算出來是上面的13,cross(mid)則是8(-12+20),經過比較返回右側,即2,2,20。
然後是13,-25,20,-3,-16,左側最大值經由上面計算為20,右邊則是別的遞迴層,經過計算(和上面步驟相同)最大值是-3,所以整個陣列左半部分最大子陣列和就是2,2,20。
右側和左側計算方法相同,得出結果是橫跨了一次mid的子陣列6,9,43。
計算源陣列的Cross,經過計算得出21,43 > 21 > 20,所以返回右側元組6,9,43,結果就出來了
1 tuple<int, int, int> findMaxSubArray(vector<int>& nums, int left, int right) { 2 //base case 3 if (left >= right) { 4 tuple<int, int, int> ans(left, right, nums[left]); //就一個元素,返回本身 5 return ans; 6 } 7 else { 8 int middle = (left + right) / 2; //分割 9 int leftLeft, leftRight, rightLeft, rightRight, crossLeft, crossRight, leftSum, rightSum, crossSum; 10 tuple<int, int, int> tl = findMaxSubArray(nums, left, middle); //middle左側的最大子陣列 11 tuple<int, int, int> tr = findMaxSubArray(nums, middle + 1, right); //middle右側的最大子陣列 12 leftLeft = get<0>(tl), leftRight = get<1>(tl), leftSum = get<2>(tl); 13 rightLeft = get<0>(tr), rightRight = get<1>(tr), rightSum = get<2>(tr); 14 tuple<int, int, int> tc = findMaxCrossSubArray(nums, left, middle, right); //橫跨middle的最大子陣列 15 crossLeft = get<0>(tc), crossRight = get<1>(tc), crossSum = get<2>(tc); 16 17 //三個子陣列誰和大返回誰的始末位置和最大值 18 if ((leftSum >= rightSum) && (leftSum >= crossSum)) { 19 return tl; 20 }else if ((rightSum >= leftSum) && (rightSum >= crossSum)) { 21 return tr; 22 }else { 23 return tc; 24 } 25 } 26 }
最終程式碼:
1 vector<int> findMax(vector<int>& nums) { 2 //tuple三位:0起始索引,1結束索引,2最大值 3 tuple<int, int, int> t = findMaxSubArray(nums, 0, nums.size() - 1); 4 //根據元組前兩位去找開始到結束的索引 5 vector<int> ans(nums.begin() + get<0>(t), nums.begin() + get<1>(t) + 1); 6 return ans; 7 } 8 9 tuple<int, int, int> findMaxSubArray(vector<int>& nums, int left, int right) { 10 //base case 11 if (left >= right) { 12 tuple<int, int, int> ans(left, right, nums[left]); //就一個元素,返回本身 13 return ans; 14 } 15 else { 16 int middle = (left + right) / 2; //分割 17 int leftLeft, leftRight, rightLeft, rightRight, crossLeft, crossRight, leftSum, rightSum, crossSum; 18 tuple<int, int, int> tl = findMaxSubArray(nums, left, middle); //middle左側的最大子陣列 19 tuple<int, int, int> tr = findMaxSubArray(nums, middle + 1, right); //middle右側的最大子陣列 20 leftLeft = get<0>(tl), leftRight = get<1>(tl), leftSum = get<2>(tl); 21 rightLeft = get<0>(tr), rightRight = get<1>(tr), rightSum = get<2>(tr); 22 tuple<int, int, int> tc = findMaxCrossSubArray(nums, left, middle, right); //橫跨middle的最大子陣列 23 crossLeft = get<0>(tc), crossRight = get<1>(tc), crossSum = get<2>(tc); 24 25 //三個子陣列誰和大返回誰的始末位置和最大值 26 if ((leftSum >= rightSum) && (leftSum >= crossSum)) { 27 return tl; 28 }else if ((rightSum >= leftSum) && (rightSum >= crossSum)) { 29 return tr; 30 }else { 31 return tc; 32 } 33 } 34 } 35 //尋找橫跨middle的最大子陣列始末位置以及最大值 36 tuple<int, int, int> findMaxCrossSubArray(vector<int>& nums, int left, int middle, int right) { 37 int leftMaxSum = INT_MIN, rightMaxSum = INT_MIN; 38 int sum = 0; 39 int leftIndex = 0, rightIndex = 0; 40 41 //兩個迴圈,分別去找middle左側和右側的最大和以及生成最大和時的索引位置 42 for (int i = middle; i >= left; i--) { 43 sum += nums[i]; 44 if (sum >= leftMaxSum) { 45 leftMaxSum = sum; 46 leftIndex = i; 47 } 48 } 49 sum = 0; 50 for (int i = middle + 1; i <= right; i++) { 51 sum += nums[i]; 52 if (sum >= rightMaxSum) { 53 rightMaxSum = sum; 54 rightIndex = i; 55 } 56 } 57 //然後將兩個最大和相加,兩邊索引值也返回 58 tuple<int, int, int> t(leftIndex, rightIndex, leftMaxSum + rightMaxSum); 59 return t; 60 }C++
採用元組是因為涉及到要返回出最大子陣列的始末位置,如果只需要求得最大子陣列的最大和,則只需要一個int去儲存結果就可以了。