Studying-刷题补充| 数组:58.区间和、44. 开发商购买土地

目录

58.区间和

44. 开发商购买土地 

总结 


58.区间和

文档讲解:代码随想录58.区间和

题目:58. 区间和(第九期模拟笔试) (kamacoder.com)

学习:本题最直接的做法是,将数组Array保存好后,通过下标遍历数组,以此来计算区间的总和。代码也很直观:保存数组,计算总和即可

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, a, b;
    cin >> n;
    vector<int> vec(n);
    for (int i = 0; i < n; i++) cin >> vec[i];
    while (cin >> a >> b) {
        int sum = 0;
        // 累加区间 a 到 b 的和
        for (int i = a; i <= b; i++) sum += vec[i];
        cout << sum << endl;
    }
} 

但是这种方法,在本题中会存在超时的情况,因为这种解法,每次我们都需要遍历数组上下标,来计算区间的总和。假如存在一个极端的情况,我们查询m次,每次查询的范围都是0到n - 1,则该算法的时间复杂度为O(m * n)其中m为查询的次数,可见时间复杂度是很高的。

因此我们可以采取另一种方法来解决本题:前缀和。

前缀和在涉及计算区间和的问题时非常有用。前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。

其实很好理解,我们通过保存每个下标i(包括i)之前元素的累加和,就能够很容易得得到,某个区间的总和,例如:

此时如果我们想要统计数组下标1,3之间的累加和,我们只需要使用p[3] - p[0]即可。这是因为p[3]包含了下标3之前元素的和,而p[0]包含了下标0之前元素的和。这样一减,剩下的就是下标1到下标3之间的元素的和了。 

又比如下标2和下标5之间,就是p[5] - p[1]:

p[1] = vec[0] + vec[1];

p[5] = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5];

p[5] - p[1] = vec[2] + vec[3] + vec[4] + vec[5]; 

这样的话,我们再处理好前缀和数组后,每一次查找两个下标之间的区间和,时间复杂度就为O(1),极大的降低了时间复杂度。

这里要注意两点:

  1. 我们在进行前缀和减的时候是p[b] - p[a - 1]而不是p[a],这是因为我们需要下标a的值,区间是左闭右闭的。
  2. 又因为上述的原因,当a = 0时,我们需要单独处理情况,不需要减了,p[b]就是我们需要的值。 
#include <iostream>
#include <vector>
using namespace std;
 
 
int main() {
    int n, a, b;
    cin >> n;
    vector<int> Arraysum(n); //记录前缀和
    int ans;
    int sum = 0;
    for(int i = 0; i < n; i++) {
        cin >> ans;
        sum += ans;
        Arraysum[i] = sum;
    }
     
    while(cin >> a >> b) {
        int presum;
        if(a == 0) {
            presum = Arraysum[b];
        }
        else {
            presum = Arraysum[b] - Arraysum[a - 1];
        }
        cout << presum << endl;
    }
    return 0;
}

C++代码,面对大量数据读取输出操作的时候,可以使用scanf 和 printf,耗时会减少很多:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    int n, a, b;
    cin >> n;
    vector<int> vec(n);
    vector<int> p(n);
    int presum = 0;
    for (int i = 0; i < n; i++) {
        scanf("%d", &vec[i]);
        presum += vec[i];
        p[i] = presum;
    }

    while (~scanf("%d%d", &a, &b)) {
        int sum;
        if (a == 0) sum = p[b];
        else sum = p[b] - p[a - 1];
        printf("%d\n", sum);
    }
}

44. 开发商购买土地 

文档讲解:代码随想录44.开发商购买土地

题目: 44. 开发商购买土地(第五期模拟笔试) (kamacoder.com)

学习:本题是一个分割问题,关键是只能纵向或者横向切割,并且根据题意只能切割一刀。本题存在暴力的解法,时间复杂度是O(n^3),使用一个for枚举分割线,嵌套两个for去累加区间里的和进行求解。

但我们可以根据上题的前缀和的思想,来进行时间复杂度的优化。

首先根据本题,我们只能纵向或者横向的切一刀,这就是两种情况,分别对应行分割和列分割。

1.行分割:对于行分割来说,我们可以理解为切一刀后,该位置之前的和就为A公司的区域和 sumA,而B公司的区域和为sumB = sum - sumA,而B公司和A公司的差值就为两者相减:

abs(sum - sumA - sumA)

可见其实我们只需要遍历A公司的区域和就可。因此我们先预处理数组,将其每一行的和求出来,再遍历行和数组,来确定A公司的区域和,以此来找到两个公司最小的差值。

result = min(result, abs(sum - sumA - sumA))

2.列分割:列分割同理,我们同样预处理数组,计算每一列的和,然后再遍历列和数组,来计算最小差值。

最终可以得到代码:时间复杂度为O(n^2)

#include <iostream>
#include <vector>
#include <climits>

using namespace std;
int main () {
    int n, m;
    cin >> n >> m;
    int sum = 0;
    vector<vector<int>> vec(n, vector<int>(m, 0)) ;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> vec[i][j];
            sum += vec[i][j];
        }
    }
    // 统计横向
    vector<int> horizontal(n, 0);
    for (int i = 0; i < n; i++) {
        for (int j = 0 ; j < m; j++) {
            horizontal[i] += vec[i][j];
        }
    }
    // 统计纵向
    vector<int> vertical(m , 0);
    for (int j = 0; j < m; j++) {
        for (int i = 0 ; i < n; i++) {
            vertical[j] += vec[i][j];
        }
    }
    int result = INT_MAX;
    int horizontalCut = 0;
    for (int i = 0 ; i < n; i++) {
        horizontalCut += horizontal[i];
        result = min(result, abs(sum - horizontalCut - horizontalCut));
    }
    int verticalCut = 0;
    for (int j = 0; j < m; j++) {
        verticalCut += vertical[j];
        result = min(result, abs(sum - verticalCut - verticalCut));
    }
    cout << result << endl;
}

我们也可以进行一些优化: 实际上超过sum/2后,计算一次即可,后面差值只会越来越大

#include <iostream>
#include <vector>
#include <climits>
using namespace std;

int main() {

    int n, m;
    cin >> n >> m;
    
    vector<vector<int>> vec(n, vector<int>(m, 0));
    int sum = 0; //统计总和
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            cin >> vec[i][j];
            sum += vec[i][j];
        }
    }
    
    //统计行和
    vector<int> linesum(n, 0);
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            linesum[i] += vec[i][j];
        }
    }
    //统计列和
    vector<int> colusum(m, 0);
    for(int j = 0; j < m; j++) {
        for(int i = 0; i < n; i++) {
            colusum[j] += vec[i][j];
        }
    }
    
    int result = INT_MAX;
    int horisum = 0;
    for(int i = 0; i < n; i++) {
        horisum += linesum[i];
        result = min(result, abs(sum - horisum - horisum));
        if(horisum > sum/2) { //优化,实际上超过sum/2后,计算一次即可,后面差值只会越来越大
            break;
        }
    }
    
    int versum = 0;
    for(int j = 0; j < m; j++) {
        versum += colusum[j];
        result = min(result, abs(sum - versum - versum));
        if(versum > sum/2) { //优化,实际上超过sum/2后,计算一次即可,后面差值只会越来越大
            break;
        }
    }
    cout << result << endl;
    return 0;
}

本题也可以在暴力求解的基础上,优化一下,就不用前缀和了,在行向遍历的时候,遇到行末尾就统一一下, 在列向遍历的时候,遇到列末尾就统计一下。时间复杂度也是O(n^2),空间复杂度稍微降低了一些。

#include <iostream>
#include <vector>
#include <climits>

using namespace std;
int main () {
    int n, m;
    cin >> n >> m;
    int sum = 0;
    vector<vector<int>> vec(n, vector<int>(m, 0)) ;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> vec[i][j];
            sum += vec[i][j];
        }
    }

    int result = INT_MAX;
    int count = 0; // 统计遍历过的行
    for (int i = 0; i < n; i++) {
        for (int j = 0 ; j < m; j++) {
            count += vec[i][j];
            // 遍历到行末尾时候开始统计
            if (j == m - 1) result = min (result, abs(sum - count - count));

        }
    }

    count = 0; // 统计遍历过的列
    for (int j = 0; j < m; j++) {
        for (int i = 0 ; i < n; i++) {
            count += vec[i][j];
            // 遍历到列末尾的时候开始统计
            if (i == n - 1) result = min (result, abs(sum - count - count));
        }
    }
    cout << result << endl;
}

总结 

数组的一些补充题型,前缀和的使用。实际上前缀和在后续动态规划中,同样也会被使用。 

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值