1. 程式人生 > >一維、二維 最大連續子陣列和

一維、二維 最大連續子陣列和

    演算法導論中的一個題目,上次面試題中被擴充套件到了二維和二維環形陣列,記錄下以供參考。

一、    一維連續子陣列 最大和

    問題描述:給定一個一維陣列,求其中連續子陣列和的最大值。
    樣例輸入:{ 1, 5, -3, 6, -7}
    樣例輸出:9  // 1 + 5 + -3 + 6

    方法1:分治法,也是演算法導論中介紹的一種方法。選取陣列的中間元素a[n/2],那麼子陣列要麼經過中間元素,要麼在a[0]和中間元素之間,要麼在中間元素和a[n]之間。如果經過中間元素,從中間元素開始向兩側尋找最大和子陣列,兩個數組合並即可得到跨中間元素的最大和子陣列。遞迴求解兩側最大子陣列情況。


    時間複雜度分析:T( n ) = 2*T( n/2 ) + Θ(n) ------->  Θ(n logn)

    這種演算法時間複雜度為n logn,雖然相比蠻力法更快,但依然不是最優。因為每次遞迴求解子陣列和時,都會產生重複求解的問題。

    下列方法2、3使用動態規劃對此問題進行改進,時間複雜度達到 O( n )。

    方法2:動態規劃。一維陣列的子陣列兩端都是不固定的,我們可以先假設一端已經確定位置,那麼只需確定另一端的位置即可。用輔助陣列 c[r] 儲存 a[0] -> a[r] 之間最大連續子陣列和,其中 a[r] 是子陣列的一個端點。當填充完c[0] -> c[n-1]後,c陣列中的最大值即是我們要求的結果。

    狀態轉移方程:c[r] = max( 0, c[r-1] ) + a[r]    // a[r]為一個端點,如果c[r]非0,那麼最大值應為 c[r] + a[r]

    這裡使用c陣列來儲存狀態,實際程式中只需要一個變數即可。因為c的後一個狀態只與前一個狀態有關。

#include <stdio.h>

int getMaxSubArraySum( int *a, int n)
{
	int max_sum=0, c=0;
	for( int i=0; i<n; i++) {
		c = (c>0?c:0) + a[i];     // 狀態轉移方程
		max_sum = c>max_sum ?c :max_sum;
	}
	
	return max_sum;
}

int main()
{
	int a[] = { 1,  5, -3, 6, -7};
	printf( "%d", getMaxSubArraySum( a, 5));
}

    方法3:動態規劃。這種方法與上一種方法類似,假設子陣列一端已經確定位置,然後進行另一端位置的確定,不過這裡使用的狀態轉移方程不同。用輔助陣列s[i]記錄 a[0] -> a[r] 元素之和,min記錄 s[0] 到 s[r-1] 的最小值。那麼 c[r] = s[r] - min 即以 a[r] 結束的子陣列的最小值。最後求得c陣列中的最大值即結果。

    同方法二,s和c只需要取兩個變數即可,沒有必要設定為兩個陣列。

int getMaxSubArraySum( int *a, int n)
{
	int max_sum=0, s=0, min=0;
	for( int i=0; i<n; i++) {
		s += a[i];            // sum of a[0...i]
		min = s<min ?s :min;
		max_sum = s-min>max_sum ?s-min :max_sum;
	}
	
	return max_sum;
}

    因為至少要遍歷陣列一遍,因此方法2、3的時間複雜度已達到最低 O( n ) 要求。

二、    一維環形子陣列 最大和

這裡說的一維環形子樹組是指子陣列可以跨越a[0] 與 a[n-1]形成子陣列。例如輸入為{ -4, 3, 6, -9, 5},那麼輸出應為10,即子陣列為{ 5,-4,3,6}。

    解決方法:分治+動態規劃。一維環形子陣列相比於前面只是增加了環形而已,因此我們將此問題分解為兩個子問題考慮。第1種情況是子陣列不跨越 a[0]、a[n-1],這種情況是我們上面解決過的;第2種情況是跨越 a[0]、a[n-1],我們可以先求解 a[0] -> a[n-1] 最小和子陣列min_sum,使用陣列總和sum - min_sum即可得到第二種情況的最大值。 然後返回1、2情況較大的值即可。

    下面程式碼沿用一的方法2,增加狀態轉移方程 s[r] = min( 0, s[r-1] ) + a[r],s[r] 記錄以 a[r] 結束的最小和子陣列。增加min_sum記錄 子陣列最小和,sum記錄陣列總的和。

#include <stdio.h>

int getMaxSubArraySum( int *a, int n)
{
	int max_sum=0, c=0, min_sum=0, s=0, sum=0;
	for( int i=0; i<n; i++) {
		c = (c>0?c:0) + a[i];     // 最大和狀態轉移方程
		max_sum = c>max_sum ?c :max_sum;
		
		s = (s<0?s:0) + a[i];     // 最小和狀態轉移方程
		min_sum = s<min_sum ?s :min_sum;
		
		sum+=a[i];
	}
	
	if( max_sum>sum-min_sum) return max_sum;
	return sum-min_sum;
}

int main()
{
	int a[] = { -4, 3, 6, -9, 5};
	printf( "%d", getMaxSubArraySum( a, 5));
}

三、    二維連續子陣列 最大和

    問題描述:給定一個二維陣列,求其中連續子陣列和的最大值。

    樣例輸入: 1, 5, -3,   6, -7
                      3, 5, -9, -4,  6
                     -8, 4, 0, 12, -3
                      3,-1, 5, -5,   8

    樣例輸出:20   //  4, 0, 12, -3
                            // -1, 5, -5,  8

    解決方法:暴力降維法。這種方法時間複雜度為O(n^3),暫時沒有找到時間複雜度更低的方法(理論時間複雜度下限為O(n^2) ),若有時間複雜度更低的方法,還望不吝賜教。

    二維子陣列的四面都可以進行擴充套件,如果使用暴力法的話需要 O( n^4) 時間複雜度,因此同上,我們選擇固定其兩端不變化,然後使用 一維陣列 的解決方案進行求解。見下圖:


#include <stdio.h>
#define max(A,B) ( (A)>(B) ?(A) :(B) )

int getMaxSubArraySum( int *a, int n)
{
	int max_sum=0, c=0, min_sum=0, s=0, sum=0;
	for( int i=0; i<n; i++) {
		c = (c>0?c:0) + a[i];     // 最大和狀態轉移方程
		max_sum = max( c, max_sum);
	}
	
	return max_sum;
}

int getMaxSubMatrixSum( int *a, int m, int n)
{
	int max_sum=0, sum[m];
	
	for( int i=0; i<n; i++) {
		for( int j=i; j<n; j++) {
			for( int z=0; z<m; z++) {
				if( j==i) sum[z]=0;   // j=i 重新計算 sum of a[i..j]
				sum[z] += a[z*n+j];   // 使用偏移, 轉化為二維陣列
			}
			max_sum = max( getMaxSubArraySum( sum, m), max_sum);
		}
	}
	return max_sum;
}

int main()
{
	int a[4][5] = { {  1, 5, -3,  6, -7},
		            {  3, 5, -9, -4,  6},
				    { -8, 4,  0, 12, -3},
				    {  3,-1,  5, -5,  8} };
	printf( "%d", getMaxSubMatrixSum( (int*)a, 4, 5));
}

四、    二維環形子陣列 最大和

相比於三問題增加了環形,其實我們僅需對上方程式碼做如下改進即可:j的取值不做≥i的限制,j小於i代表子陣列跨越 a[n-1] 與 a[0] ,這樣實現了列方向環形。使用問題二中的 getMaxSubArraySum 函式作為求解一維最大環形子陣列,實現了行方向上的環形。

#include <stdio.h>
#define max(A,B) ( (A)>(B) ?(A) :(B) )

int getMaxSubArraySum( int *a, int n)
{
	int max_sum=0, c=0, min_sum=0, s=0, sum=0;
	for( int i=0; i<n; i++) {
		c = (c>0?c:0) + a[i];     // 最大和狀態轉移方程
		max_sum = c>max_sum ?c :max_sum;
		
		s = (s<0?s:0) + a[i];     // 最小和狀態轉移方程
		min_sum = s<min_sum ?s :min_sum;
		
		sum+=a[i];
	}
	
	if( max_sum>sum-min_sum) return max_sum;
	return sum-min_sum;
}

int getMaxSubMatrixSum( int *a, int m, int n)
{
	int max_sum=0, sum[m];
	
	for( int i=0; i<n; i++) {
		for( int j=i; j<n+i; j++) {           // j = j % n
			for( int z=0; z<m; z++) {
				if( j==i) sum[z]=0;   // j=i 重新計算 sum of a[i..j]
				sum[z] += a[z*n+j%n];   // 使用偏移, 轉化為二維陣列
			}
			max_sum = max( getMaxSubArraySum( sum, m), max_sum);
		}
	}
	return max_sum;
}

int main()
{
	int a[4][5] = { {  1, 5, -3,  6, -7},
		            {  3, 5, -9, -4,  6},
				    { -8, 4,  0, 12, -3},
				    {  3,-1,  5, -5,  8} };
	printf( "%d", getMaxSubMatrixSum( (int*)a, 4, 5));
}

// output: 25
//  6, -7,  1, 5
// -4,  6,  3, 5
// 12, -3, -8, 4
// -5,  8, 3, -1

相關推薦

連續陣列

    演算法導論中的一個題目,上次面試題中被擴充套件到了二維和二維環形陣列,記錄下以供參考。一、    一維連續子陣列 最大和    問題描述:給定一個一維陣列,求其中連續子陣列和的最大值。    樣

53 連續陣列

給定一個整數陣列 nums ,找到一個具有最大和的連續子陣列(子陣列最少包含一個元素),返回其最大和。 示例: 輸入: [-2,1,-3,4,-1,2,1,-5,4], 輸出: 6 解釋: 連續子陣列 [4,-1,2,1] 的和最大,為 

演算法之求連續陣列

假設給定一陣列{1,4,2,-3,-1,2,5,6,-8,9},我們要求的是最大的連續子序列的和,如果採用暴力破解法的話,那就是遍歷所有的連續子序列了,時間複雜度就是O(N^3)程式碼如下: private int max = Integer.MIN_VALUE; public int ma

連續陣列

題目描述:給定一個數組A[0,....,n-1],求A的連續子陣列,使得該子陣列的和最大。eg:  1,-2,3,10,-4,7,2,-5的最大子陣列為3,10,-4,7,2演算法分析:記S[i]為以A[i]結尾的陣列中和最大的子陣列,則S[i+1]=max(S[i]+A[i

陣列連續陣列

#include "stdafx.h" #include "stdio.h" #include "stdlib.h" int _tmain(int argc, _TCHAR* argv[]) { int x[3][3] = {0}; int sum = 0; int max = 0; int

返回一個整數陣列中的陣列

一。題目:                   1、輸入一個二維整形陣列,數組裡有正數有負數。                 &nbs

動態規劃(三)長遞增序列LIS連續序列連續序列乘積

最長遞增子序列LIS 問題 給定一個長度為N的陣列,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)。例如:給定一個長度為6的陣列A{5, 6, 7, 1, 2, 8},則其最長的單調遞增子序列為{5,6,7,8},長度為4. 最長遞增子序列

連續序列遞增序列”公共序列”長公共串”問題總結

一、最大連續子序列和(最大子序列) 最大子序列是要找出由陣列成的一維陣列中和最大的連續子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,達到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。 思路:只要前i項的和還沒有小

連續序列,以及開始結束下標(Java)

對一個有n個元素的陣列,求最大的連續子陣列的和,並求其開始、結束下標。 陣列的元素必然有正數也有負數才有意義,如果全是正數,那最大的子陣列就是本身;如果全部為負數,那最大子陣列就是空陣列。 例如下面的陣列,其最大子陣列序列和為187,子陣列為X[2,..,6

(4)C語言——求連續序列

log spa clas 最大連續子序列和 alloc 最大 code max 連續 題目: 輸入一組整數,求出這組數字子序列和中最大值。也就是只要求出最大子序列的和,不必求出最大的那個序列。例如: 序列:-2 11 -4 13 -5 -2,則最大子序列和為20。 序列:-

連續序列

運行時 介紹 最大連續子序列和 () vector 運行 n) else 連續子序列和 下面介紹一個線性的算法,這個算法是許多聰明算法的典型:運行時間是明顯的,但是正確性則很不明顯(不容易理解)。 //線性的算法O(N) long maxSubSum4(const vec

連續

img ans define open space sed 表示 max get 最大連續子段和sum表示以當前數a[i]結尾的最大子段和,如果sum<0,那麽它對後面就沒有積極作用,不如拋棄。所以sum+=a[i]維護最大值sum=max(sum,0) 1 #

【bzoj5089】連續 分塊+單調棧

我們 如果 一條直線 時間復雜度 支持 led 包括 每一個 a + b 題目描述 給出一個長度為 n 的序列,要求支持如下兩種操作: A l r x :將 [l,r] 區間內的所有數加上 x ; Q l r : 詢問 [l,r] 區間的最大連續子段和。

python求連續陣列

尋找最大子陣列問題: 給定陣列A:尋找A中的和最大的非空連續子陣列。我們稱這樣的連續子陣列為最大子陣列(maximum subarray) 1、暴力求解:兩個迴圈,時間複雜度為O(n^2) 2、用分治策略的求解方法: 假定我們要尋找子陣列A[low...high]的最大子陣列。使用分

golang 求陣列連續陣列

直接上程式碼了 //x.1 求數組裡最長連續字陣列 O(n)內 func MaxSerialArr(arr []int) []int { //總長 var max = 0 //集合點 var jointIndex = -1 //結果集

LeetCode周賽#105 Q2 Maximum Sum Circular Subarray (連續變形題)

題目來源:https://leetcode.com/contest/weekly-contest-105/problems/maximum-sum-circular-subarray/ 問題描述 918. Maximum Sum Circular Subarray Given a&nbs

連續序列/長不下降序列/長公共序列/長迴文

//最大連續子序列和 #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 10010; int A[maxn],dp[maxn]; int main(){ int

動態規劃——連續序列

最大連續子序列和問題如下:       下面介紹動態規劃的做法,複雜度為 O(n)。   步驟 1:令狀態 dp[i] 表示以 A[i] 作為末尾的連續序列的最大和(這裡是說 A[i] 必須作為連續序列的末尾)。   步驟

hdoj1003+codeup2086:Max Sum連續序列問題解法總結

目錄 hdoj 1003求解方法 暴力求解O(n^3)/O(n^2)(不推薦,很可能會超時) 分治法(比較複雜,掌握思想即可) 遍歷求和法O(n) dp動態規劃(強推) codeup2086的求解方法 dp求解 hdoj 1003求解方法 暴力求解

連續序列可能的優解

問題描述: 給定一個整數序列,a0, a1, a2, …… , an(項可以為負數),求其中最大的子序列和。如果所有整數都是負數,那麼最大子序列和為0; 解決這個問題的演算法有很多種,比如兩重迴圈的暴力破解,或者利用分治的思想,但是還有一種線性時間複雜度的演算法:線上處理,可以比較好的解決這