代码随想录算法训练营Day2——数组part 02

        今天的内容是209.长度最小的子数组(滑动窗口)、59.螺旋矩阵II(循环不变量)、区间和(前缀和)、开发商购买土地,拓展题周末解决。

一、长度最小的子数组

        题目:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

        示例:输入:s = 7, nums = [2,3,1,2,4,3]                            输出:2(子数组 [4,3] )

        分析:①暴力解法:两个for循环,然后不断寻找符合条件的子序列,时间复杂度是O(n^2)

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX; // 最终的结果
        int sum = 0; // 子序列的数值之和
        int subLength = 0; // 子序列的长度
        for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
            sum = 0;
            for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
                sum += nums[j];
                if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                    subLength = j - i + 1; // 取子序列的长度
                    result = result < subLength ? result : subLength;
                    break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
                }
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

                 ②滑动窗口:实际上,很多题目需要动笔画画图,有时候多比划几下思路就有了。题目中要求的是“连续”子数组,那么画出一个方框,就往数组里套。所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。动图就显得很明白,引用一下:

        这个滑动窗口就是满足其和 ≥ s 的长度最小的连续子数组。如果当前滑动窗口的值大于等于s了,滑动窗口起始位置就要向前移动了(也就是该缩小了)。如果小于s,那么滑动窗口终止位置就要向前移动,以保证滑动窗口内元素之和大于等于s。终止条件则显然是终止位置到达数组末尾。这里对终止条件的思考在二叉树部分屡见不鲜,不过二叉树那边是递归,这里是循环(笑)。

        因此可以写出如下代码(用while循环比for循环容易理解):

#include <iostream>
#include <vector>

class Solution 
{
    public:
        int length(std::vector<int>& nums, int s)
        {
            int i = 0, j = 0, sum = 0, result = INT_MAX;
            while(j < nums.size())
            {
                sum += nums[j];
                j++;
                while(sum >= s)  //用if则会造成一击脱离的情况,例如[1,1,1,1,1,100]
                    {
                        result = min(result, j - i + 1);
                        sum -= nums[i];
                        i++;
                    }
            }
            return result == INT_MAX ? 0 : result;
        }
};


int main()
{
    int n, s;
    while (std::cin >> n) {                  // 读取数组长度
        std::vector<int> nums(n);
        for (int i = 0; i < n; ++i) {   // 读取数组元素
            std::cin >> nums[i];
        }
        std::cin >> s;                 // 读取目标值
        Solution S;
        std::cout << "长度为: " << S.length(nums, s) << std::endl;  // 输出结果
    }
    return 0;
}

        滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)

二、59螺旋矩阵II

        题目:给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

        示例:输入: 3                            输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]

        这题上来就被吓懵了,后来听讲解发现没有机制,单纯的数值,十分考察对代码的掌控能力。我自觉理解能力不强口齿不清,这里不多写,附上讲解的链接:BV1SL4y1N7mV

三、区间和

        题目:给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。

        输入:第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间,直至文件结束。如下:

5
1
2
3
4
5
0 1
1 3

        输出:每个指定区间内元素的总和。

3
9

         分析:本题最直观的想法是给一个区间,然后把这个区间的和都累加一遍,是一道简单不能再简单的题目。

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

int main() {
    int n, a, b;    //a和b为区间左右端点,n为数组元素数量
    cin >> n;
    vector<int> vec(n);
    for (int i = 0; i < n; i++) 
    {
        cin >> vec[i];
    }
    while (cin >> a >> b)     //这里是指输入两个数,第一个为a,第二个为b
    {
        int sum = 0;
        // 累加区间 a 到 b 的和
        for (int i = a; i <= b; i++) 
        {
            sum += vec[i];
        }
        cout << sum << endl;
    }
} 

        这个解法的时间复杂度是 O(n * m) ,m 是查询的次数。非常有优化的空间。

        接下来我们来引入前缀和,看看前缀和如何解决这个问题。前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。

※前缀和 在涉及计算区间和的问题时非常有用
        用一张图来说明(此处计算从下标2☞5的区间和, p[i] 表示数组下标 0 到 i 的累加和。

 

#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++) {
        cin >> vec[i];
        presum += vec[i];
        p[i] = presum;
    }

    while (cin >> a >> b) {
        int sum;
        if (a == 0) sum = p[b];
        else sum = p[b] - p[a - 1];
        cout << sum << endl;
    }
}

※特别注意: 在使用前缀和求解的时候,要特别注意求解区间。如果我们要求区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。

四、开发商购买土地

        题目:在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。注:区块不可再分。

        示例:第一行输入两个正整数,代表 n 和 m。接下来的 n 行,每行输出 m 个正整数。请输出一个整数,代表两个子区域内土地总价值之间的最小差距。

【输入示例】3 3 1 2 3 2 1 3 1 2 3                                【输出示例】0

【提示信息】如果将区域按照如下方式划分:1 2 | 3 2 1 | 3 1 2 | 3,那么两个子区域内土地总价值之间的最小差距可以达到 0。

        这题第一遍时没有看懂,二刷才发现是一刀切,那么可以用上一个前缀和来解决。

#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;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值