1. 程式人生 > 實用技巧 >ACM-學習記錄-尺取法

ACM-學習記錄-尺取法

題目

給定一個數組和一個數s,在這個陣列中找一個區間,使得這個區間之和等於s。

例如:給定的陣列int x[14] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};和一個s = 15。那麼,可以找到的區間就應該有0到4, 3到5, 6到7.(注意這裡的下標從0開始)

思路

對於這樣的題,不用任何技巧就可以跑出結果,例如下面這個方法可能是大多數人能夠想出來的:

先用一個數組sum[i]存放前i個元素的和,其實現用的是”遞推思想“,注意,在程式設計中”遞推“的思想用的特別多,一定要習慣這種思維方式。

sum[0] = x[0];//x為給定的原陣列
for(int i = 1; i < n; i++){
   sum[i] += sum[i-1];//遞推思想
}

然後通過兩層迴圈求解

for(int i = 0; i < n; i++)
	for(int j = n-1; j >= 0; j--){
		if(sum[j]-sum[i]==s)	printf("%d---%d\n", i, j);
	}

上面的方法當然是可行的,但是複雜度太高,有一個演算法可以將其複雜度降為O(n)。這就是”尺取演算法“。

尺取法:顧名思義,像尺子一樣取一段,借用挑戰書上面的話說,尺取法通常是對陣列儲存一對下標,即所選取的區間的左右端點,然後根據實際情況不斷地推進區間左右端點以得出答案。之所以需要掌握這個技巧,是因為尺取法比直接暴力列舉區間效率高很多,尤其是資料量大的。

那麼,用”尺取法“做上面這道題思路應該是這樣的:

其實,這種方法很類似於蚯蚓的蠕動。

1)用一對腳標i, j。最開始都指向第一個元素。

2)如果區間i到j之和比s小,就讓j往後挪一位,並把sum的值加上這個新元素。相當於蚯蚓的頭向前伸了一下。

3)如果區間i到j之和比s大,就讓sum減掉第一個元素。相當於蚯蚓的尾巴向前縮了一下。

4)如果i到j之和剛好等於s,則輸入。

實現

#include<iostream>
#include<cstdio>
using namespace std;

void findSUM(int *A, int n, int s){
	int i = 0, j = 0;
	int sum = A[0];
	while(i <= j && j < n){
		if(sum >= s){
			if(sum == s)	printf("%d---%d\n", i, j);
			sum -= A[i];
			i++;
		}
		else{
			j++;
			sum += A[j];
		}
	}
} 

int main(){
	std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    int m;
    int x[14] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
    cin >> m;
    findSUM(x, 14, m);

	return 0;
}