title: ACM-学习记录-尺取法
tags:

  • ACM
    abbrlink: e3871ff6
    date: 2020-12-04 17:42:00

题目

给定一个数组和一个数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个元素的和,其实现用的是”递推思想“,注意,在编程中”递推“的思想用的特别多,一定要习惯这种思维方式。

  1. sum[0] = x[0];//x为给定的原数组
  2. for(int i = 1; i < n; i++){
  3. sum[i] += sum[i-1];//递推思想
  4. }

然后通过两层循环求解

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

上面的方法当然是可行的,但是复杂度太高,有一个算法可以将其复杂度降为O(n)。这就是”尺取算法“。

尺取法:顾名思义,像尺子一样取一段,借用挑战书上面的话说,尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。之所以需要掌握这个技巧,是因为尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的。

那么,用”尺取法“做上面这道题思路应该是这样的:

其实,这种方法很类似于蚯蚓的蠕动。

1)用一对脚标i, j。最开始都指向第一个元素。

2)如果区间i到j之和比s小,就让j往后挪一位,并把sum的值加上这个新元素。相当于蚯蚓的头向前伸了一下。

3)如果区间i到j之和比s大,就让sum减掉第一个元素。相当于蚯蚓的尾巴向前缩了一下。

4)如果i到j之和刚好等于s,则输入。

实现

  1. #include<iostream>
  2. #include<cstdio>
  3. using namespace std;
  4. void findSUM(int *A, int n, int s){
  5. int i = 0, j = 0;
  6. int sum = A[0];
  7. while(i <= j && j < n){
  8. if(sum >= s){
  9. if(sum == s) printf("%d---%d\n", i, j);
  10. sum -= A[i];
  11. i++;
  12. }
  13. else{
  14. j++;
  15. sum += A[j];
  16. }
  17. }
  18. }
  19. int main(){
  20. std::ios::sync_with_stdio(false);
  21. std::cin.tie(0);
  22. int m;
  23. int x[14] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
  24. cin >> m;
  25. findSUM(x, 14, m);
  26. return 0;
  27. }