1. 程式人生 > >【資料結構與演算法】小於等於k的最大連續子序列和

【資料結構與演算法】小於等於k的最大連續子序列和

使用Kadane演算法可以在On內得到最大連續子序列和。如果要求得到和不超過k,那麼該如何解決?

首先要看能不能繼續使用Kadane演算法。答案是不能。回顧Kadane,Kadane中使用dp[i]存最後一個元素是array[i]的最大和,然後所有dp[i]的最大值。

那麼首先一個想法是,找出dp[i]中小於等於k的值中的最大值。這是不正確的,因為正常來講,要求小於等於k的最大值,我們應該求出所有的小於等於k的最大值,再取其中的最大值,也就是最基本的子問題就必須滿足小於等於k的條件,如果子問題不滿足,那麼子問題求出的解可能過大,這樣最後就會被過濾掉,而實際上,可能去掉一個或兩個,某些子問題就可以使用了。

舉個例子,2,2,-1,k=0。

按照沒有k的方法求出dp, dp[0] = 2, dp[1] = 4, dp[2] = 3,再從其中篩選出小於等於0的,那麼沒有滿足的子序列。事實上,子序列(2,2)。即-1是滿足的,應該返回-1.所以不能直接在沒有k的問題的基礎上修改。

那麼有人會問,直接定義dp[i]就是以array[i]結尾的小於等於k的最大值不就解決了嗎?也不行。因為,當我們計算dp[i]時,要用到dp[i - 1],如果array[i] 是負數,那麼相加以後值變小,此時可能讓dp[i - 1]值變大也可行。所以dp的定義還是不能滿足這種需求,所以這個思路也不行。

舉個例子,2, 2, -1, k = 3

如果dp[i]是以array[i]結尾的小於等於k的最大值。

那麼dp[0] = 2,dp[1] = 2, dp[2]=1,但是結果是3。當計算dp[2]時,想到利用前面的,因為是-1,所以此時就算把第一個元素加入,也滿足,所以不能使用dp[i-1]來得到dp[i]。

總之直接使用一維陣列的kadane演算法不可行。

一個直觀的辦法就是計算所有的子序列和,可行,不過複雜度是On2. 程式碼如下:

public int maxSumNoLargerThan1(int[] array, int k){
		int r = Integer.MIN_VALUE;
		for(int i = 0; i < array.length; i++){
			int sum = 0;
			for(int j = i; j < array.length; j++){
				sum += array[j];
				if(sum <= k)
					r = Math.max(r, sum);
			}
		}
		return r;
	}


不過後來在網上看到一個更吊的思路,優化了查詢過程。

要求sum(i,j)即從i到j的和,可以sum(0,j) - sum(0,i-1)。所以sum(0,j) - sum(0,i-1)<=k。

這樣的話,我們用一個遍歷,求出sum(0,i),並且把所有的值存入set,然後每一次都求出一個下界,即sum(0,i)-k,然後在之前的結果中找大於等於這個界的最小值,如果存在,就是一個候選值,然後再把sum(0,i)也放入set。

如果不使用一定的資料結構,以上演算法還是On2的。但是如果set的查詢使用了特殊資料結構比如平衡二叉搜尋樹這種的,那麼查詢後繼或者前驅就是logn級別了。這就是優化的一個方法。在java中,使用Treeset結構即可。所以下面就是一個nlogn的演算法:

	public int maxSumNoLargerThan(int[] array, int k){
		int sum = 0, max = Integer.MIN_VALUE;
		TreeSet<Integer> set = new TreeSet<>();
		set.add(0);
		for(int i = 0; i < array.length; i++){
			sum += array[i];
			Integer min = set.ceiling(sum - k);
			if(min != null)
				max = Math.max(max, sum - min);
			set.add(sum);
		}
		return max;
	}
以上都是假定不存在返回Integer.MIN_VALUE的。