代码随想录训练营Day2 | 209.长度最小的子数组 | 59.螺旋矩阵II | 58. 区间和

1. 学习滑动窗口

2.学习标准输入输出模式

3.学习文档代码随想录 (programmercarl.com) 数组总结

Leetcode 209.长度最小的子数组

 题目描述:

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 

子数组

 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

解题思路:

1) 暴力解法:两个for循环,来不断的寻找符合条件的子序列,时间复杂度O(n^2),暴力解法是使用了两个for循环,一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环完成不断搜索区间的过程。

完整代码:目前会超时,直接贴的代码随想录中的代码

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

2) 滑动窗口(代码随想录 (programmercarl.com)):所谓滑动窗口就是不断的调节子序列的起始位置和终止位置,从而得出结果,那么滑动窗口是如何用一个for循环来完成这个操作呢?

首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入 暴力解法的怪圈。

所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么——是满足题目要求的总和大于等于target的长度最小的连续子数组;
  • 如何移动窗口的起始位置——如果当前总和大于等于target,起始位置前移(缩小总和);
  • 如何移动窗口的结束位置——这里是for循环中的索引,要遍历数组;

分析:这题需要使用滑动窗口,开始思路想到了,但是忘记了这三点注意内容,重新复习总结!
 

 完整代码:滑动窗口

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        // 当result为最大值时,才能保证不断更新
        int result = INT32_MAX;
        int sum = 0;       // 滑动窗口数值之和
        int i = 0;         // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 满足条件,取子序列的长度
            // 每一次都更新最小的result
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; 
            // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

 Leetcode 59.螺旋矩阵II

本题没有思路!需要多加复习,考察对代码的掌控能力!

解题思路(代码随想录 (programmercarl.com)): 

这道题目可以说在面试中出现频率较高的题目,本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。

要如何画出这个螺旋排列的正方形矩阵呢?

求解本题依然是要坚持循环不变量原则。

模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

由外向内一圈一圈这么画下去。

可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人

这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

 

这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。

这也是坚持了每条边左闭右开的原则。

在画每一条边的时候,一会左开右闭,一会左闭右闭,一会又来左闭右开,岂能不乱。

代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开(左闭又开 最后一个值不填充)。
 

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组 最后结果返回的是一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置(x与y)
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        int count = 1; // 用来给矩阵中每一个空格赋值 初始值为1
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j; j < n - offset; j++) {
                res[i][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};

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

题目描述

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

输入描述

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

输出描述

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

输入示例

5
1
2
3
4
5
0 1
1 3

输出示例

3
9

 使用常规解法,直接去判断这个区间,将这个区间中的数都累加一遍。会超时

 如果查询m次,每次查询的范围都是从0 到 n - 1,那么该算法的时间复杂度是 O(n * m) m 是查询   的次数,如果查询次数非常大的话,这个时间复杂度也是非常大的。

#include <iostream> // 包含标准输入输出流定义
#include <vector>

using namespace std;
int main() {
    int n, a, b;
    // 从标准输入读取一个整数n,表示接下来要输入的整数数量
    cin>>n;
    vector<int>vec(n);
    for(int i = 0; i<n; i++) {
        // 从标准输入读取一个整数并存储在向量vec的第i个位置
        cin>>vec[i];
    }
    // 开始一个while循环,只要能够从标准输入读取两个整数a和b,循环就继续
    while(cin >> a >> b) {
        int sum = 0;
        for(int i=a; i<=b; i++) {
            sum+=vec[i];
        }
        cout<<sum<<endl;
    }
}

解题思路 :前缀和(重复利用之前计算过的子数组之和,降低区间查询需要的累加计算次数)58. 区间和 | 代码随想录 (programmercarl.com)

 假设一个数组,定义一个前缀和数组(每个元素是数组该位置之前所有元素之和),那么去计算[2-5]区间的区间和,就不需要重新遍历,只需要用p[5]-p[1]即可;所以后面每次求区间和的之后 我们只需要 O(1) 的操作;

特别注意在使用前缀和求解的时候,要特别注意 求解区间。                                                   

如下图,如果我们要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。

 完整代码:

#include <iostream>
#include <vector>
using namespace std;
int main() {
    // 声明三个整型变量n、a和b。n用于存储输入的数字个数,a和b用于后续的区间查询。
    int n, a, b;
    // 从标准输入读取一个整数n,表示接下来要输入的数字个数
    cin >> n;
    vector<int> vec(n);
    vector<int> p(n);
    int presum = 0;
    // 循环n次,每次循环读取一个整数并更新前缀和
    for (int i = 0; i < n; i++) {
        // 从标准输入读取一个整数并存储在vec的第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);
    }
}

NOTE:特别注意输入输出的判断,对于输入输出不熟悉!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值