問題描述:
輸入一組整數,求出這組數字子串行和中最大值。也就是只要求出最大子串行的和,不必求出最大的那個序列。例如:
序列:-2 11 -4 13 -5 -2,則最大子串行和為20。
序列:-6 2 4 -7 5 3 2 -1 6 -9 10 -2,則最大子串行和為16。
演算法一:
//窮舉法,複雜度o(n^3) 最容易想到也是最簡單的演算法
[cpp]
< span style="font-size: 14px">long maxsubsum1(const vector&a)
if (thissum> maxsum)
maxsum= thissum;
} }
return maxsum; }
long maxsubsum1(const vector&a)
if (thissum> maxsum)
maxsum= thissum;}}
return maxsum;
}這是乙個o(n^3) 的演算法,演算法本身很容易理解,而且很直觀的感覺做了很多無用操作。例如:i = 0, j = 3時,會計算a[0] +a[1] +…+ a[3];而當i = 0, j = 4時候又會計算a[0]+ a[1] +…a[4]。
演算法二:
通過撤出乙個for迴圈來避免三次時間。
//也是窮舉法,不過減去了上面的一些不必要操作o(n^2)
[cpp]
< span style="font-size: 14px">long maxsubsum2(const vector&a)
} return maxsum; }
long maxsubsum2(const vector&a)
}return maxsum;
}這是乙個非常直觀的窮舉法(比上面的分析還有簡單些),而且沒有多餘重複的操作,複雜度為o(n^2) 。其中,thissum表示a[i] + a[i+1] + … + a[j-1]。
演算法三:
對這個問題,有乙個相對複雜的o(nlogn)的解法,就是使用遞迴。如果要是求出序列的位置的話,這將是最好的演算法了(因為我們後面還會有個o(n)的演算法,但是不能求出最大子串行的位置)。該方法我們採用「分治策略」(divide-and-conquer)。
在我們例子中,最大子串行可能在三個地方出現,或者在左半部,或者在右半部,或者跨越輸入資料的中部而佔據左右兩部分。前兩種情況遞迴求解,第三種情況的最大和可以通過求出前半部分最大和(包含前半部分最後乙個元素)以及後半部分最大和(包含後半部分的第乙個元素)相加而得到。
//遞迴法,複雜度是o(nlogn)
[cpp]
< span style="font-size: 14px">long maxsumrec(const vector&a, int left, int right)
int center= (left + right) / 2;
long maxleftsum= maxsumrec(a, left, center);
long maxrightsum= maxsumrec(a, center+1, right);
//求出以左邊對後乙個數字結尾的序列最大值
long maxleftbordersum= 0, leftbordersum = 0;
for (int i= center; i >= left; i--)
//求出以右邊對後乙個數字結尾的序列最大值
long maxrightbordersum= 0, rightbordersum = 0;
for (int j= center+1; j <= right; j++)
return max3(maxleftsum,maxrightsum,
maxleftbordersum+ maxrightbordersum);
} long maxsubsum3(const vector&a)
另外max3(long,long,long)表示求三個long中的最大值:
//求出三個long中的最大值
long max3(long a, long b, long c)
if (a> c)
return a;
else
return c; }
long maxsumrec(const vector&a, int left, int right)
int center= (left + right) / 2;
long maxleftsum= maxsumrec(a, left, center);
long maxrightsum= maxsumrec(a, center+1, right);
//求出以左邊對後乙個數字結尾的序列最大值
long maxleftbordersum= 0, leftbordersum = 0;
for (int i= center; i >= left; i--)
//求出以右邊對後乙個數字結尾的序列最大值
long maxrightbordersum= 0, rightbordersum = 0;
for (int j= center+1; j <= right; j++)
return max3(maxleftsum,maxrightsum,
maxleftbordersum+ maxrightbordersum);
}long maxsubsum3(const vector&a)
另外max3(long,long,long)表示求三個long中的最大值:
//求出三個long中的最大值
long max3(long a, long b, long c)
if (a> c)
return a;
else
return c;
}對這個演算法進行分析:
t(1) = 1
t(n) = 2t(n/2) +o(n)
最後得出演算法的複雜度為:o(nlogn) 。
演算法四:
下面介紹乙個線性的演算法,這個演算法是許多聰明演算法的典型:執行時間是明顯的,但是正確性則很不明顯(不容易理解)。
//線性的演算法o(n)
[cpp]
< span style="font-size: 14px">long maxsubsum4(const vector&a)
return maxsum; }
long maxsubsum4(const vector&a)
return maxsum;
}很容易理解時間界o(n) 是正確的,但是要是弄明白為什麼正確就比較費力了。其實這個是演算法二的乙個改進。分析的時候也是i代表當前序列的起點,j代表當前序列的終點。如果我們不需要知道最佳子串行的位置,那麼i就可以優化掉。
重點的乙個思想是:如果a[i]是負數那麼它不可能代表最有序列的起點,因為任何包含a[i]的作為起點的子串行都可以通過用a[i+1]作為起點來改進。類似的有,任何的負的子串行不可能是最優子串行的字首。例如說,迴圈中我們檢測到從a[i]到a[j]的子串行是負數,那麼我們就可以推進i。關鍵的結論是我們不僅可以把i推進到i+1,而且我們實際可以把它一直推進到j+1。
舉例來說,令p是i+1到j之間的任何乙個下標,由於前面假設了a[i]+…+a[j]是負數,則開始於下標p的任意子序列都不會大於在下標i並且包含從a[i]到a[p-1]的子串行對應的子串行(j是使得從下標i開始成為負數的第乙個下標)。因此,把i推進到j+1是安全的,不會錯過最優解。注意的是:雖然,如果有以a[j]結尾的某序列和是負數就表明了這個序列中的任何乙個數不可能是與a[j]後面的數形成的最大子串行的開頭,但是並不表明a[j]前面的某個序列就不是最大序列,也就是說不能確定最大子串行在a[j]前還是a[j]後,即最大子串行位置不能求出。但是能確保maxsum的值是當前最大的子串行和。這個演算法還有乙個有點就是,它只對資料進行一次掃瞄,一旦a[j]被讀入處理就不需要再記憶。它是乙個聯機演算法。聯機演算法:在任意時刻演算法都能夠對它已讀入的資料給出當前資料的解。
最大連續子串行和
最大連續子串行和問題是個很老的面試題了,最佳的解法是o n 複雜度,當然其中的一些小的地方還是有些值得注意的地方的。這裡還是總結三種常見的解法,重點關注最後一種o n 的解法即可。需要注意的是有些題目中的最大連續子串行和如果為負,則返回0 而本題目中的最大連續子串行和並不返回0,如果是全為負數,則返...
最大連續子串行和
求最大連續子串行和 分析 用乙個陣列存入輸入的數字。用乙個變數temp從0開始往後加,存放累計的和,用sum變數存放出現過的最大和。當temp遇到負數會減小,但不能初始化為0重新累計,因為後面還有可能出現正數,和會比前面sum大的情況。只有當temp遇到負數減到小於0時,temp初始化為0重新開始加...
最大連續子串行和
include include include include include include include include include include using namespace std typedef long long ll define pi 3.1415926535897932 ...