Day2||209.长度最小的子数组|59.螺旋矩阵|58.区间和|44.开发商购买土地

 今日重点掌握内容

1.滑动窗口(双指针)的运用  

2.学会用代码模拟螺旋矩阵(充分理解二分法)

3.数组常用解题技巧-前缀和,并熟悉一下acm输入输出模式

209.长度最小的子数组

题目:209. 长度最小的子数组 - 力扣(LeetCode)

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

示例:

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

提示:

  • 1 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

接下来就开始介绍数组操作中另一个重要的方法:滑动窗口

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

 

暴力解法:简单来说是两个for循环的应用,但由于步骤繁琐在 leetcode上是超时的。时间复杂度O(n^2)

窗口滑动法和暴力解法类似,但是用双指针使一个for循环做了两个for循环的事,可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

图示:077ef7a3674f13aecbfdeb71ad0ce9d9.gif

注:红色框的区域就是滑动的窗口 

滑动窗口

class Solution {
public://滑动窗口
    int minSubArrayLen(int target, vector<int>& nums) {
        int result=INT32_MAX;
        int sum=0;
        int i=0;
        int sublenght=0;
      for(int j=0;j<nums.size();j++)
      {
        int sublength=0;
        sum+=nums[j];
        while(sum>=target)//重要代码
        {
         sublength=j-i+1;
         result=result<sublength?result:sublength;
         sum-=nums[i];
         i++;
        }
      }
      return result==INT32_MAX?0:result; //成立就返回0代表没有找到符合条件的数组;
    }
};

 注意:

1.声明一个名为 result 的整型变量,并将其初始化为 INT32_MAXINT32_MAX 是一个宏定义,通常位于 <limits.h> 头文件中,它代表了 32 位有符号整数类型可以表示的最大值。将 INT32_MAX 赋值给 result,意味着 result 被初始化为了一个非常大的数值,几乎相当于将 result 设置为了无穷大。这通常用于初始化变量,以确保在后续的计算中,result 的值能够被更新为更小的数值。这种初始化方式可用于各种场景,比如循环计数器的初始化,以确保循环能够执行预期的次数,或者在某些算法中用来表示无限或极大值。

2.动态调节滑动窗口的实现代码那里写while语句,注意不要写成if。

59.螺旋矩阵II

题目:59. 螺旋矩阵 II - 力扣(LeetCode)

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

示例:

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

2bb37acc3a794e5b9f7ab46e19d8f41d.png

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

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

图示:c027e5bd84af9d519e12e445e3d55a78.png 

 

一定要画图来模拟一下怎么循环的,代码就很好理解了。

就是一圈圈向内填充矩阵。

代码

class Solution {
public://画个图模拟走法就会好理解一点,简单来说就是按螺旋绕圈的方式填充二维数组,没有什么算法
    vector<vector<int>> generateMatrix(int n) {
    vector<vector<int>>res(n,vector<int>(n,0));//定义矩阵
    int loop=n/2;//循环圈数,取决于要走几圈才能剩0个或1个中心位待填充
    int mid=n/2;//矩阵中间的位置,只有n为奇数时才有
    int startx=0,starty=0;
    int i,j;
    int count=1;
    int offset=1;//来控制每一条边遍历的,右边界是开的
    while(loop--)
    {
        i=startx;
        j=starty;
        //下面由每一个for循环来填充每一行来模拟走一圈,都是左闭右开写法
        for(;j<n-offset;j++)//模拟从左到右
        res[i][j]=count++;//赋值后自增

        for(;i<n-offset;i++)//从上到下
        res[i][j]=count++;

        for(;j>startx;j--)//从右到左
        res[i][j]=count++;

        for(;i>starty;i--)//从下到上
        res[i][j]=count++;

        offset++;//控制右边界,往回缩一位
        //下一圈开始时,要更新初始位置
        startx++;
        starty++;

    }
    //如果n为奇数,则需要填充中心位
    if(n%2)
    res[mid][mid]=count;
   
   return res;
    }
};

 58.区间和

题目描述

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

输入描述

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

输出描述

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

输入示例

行1是数组长度,行2-6是数组元素,行7-8是要相加的区间和

1 5

2 1

3 2

4 3

5 4

6 5

7 0 1

8 1 3

输出示例

3
9

 暴力解法是直接累计所给区间的数,但是查询次数越大,时间复杂度越大。

前缀和法,本题就是多创建了一个数组来存储从0-i区间的前缀和,要注意如果计算左区间不是从0开始的区间和,由数组右区间下标对应的数减去左区间下标对应的数的前一个。

如图:d8b5348081ca4986a72ac1e8363e97c2.png

 代码

#include<iostream>
#include<vector>
using namespace std;
int main()
{
    int a,b,n,sum;
    int presum=0;
    cin>>n;
    vector<int>vec(n);//整数数组
    vector<int>pre(n);//一个存储从0到i区间的前缀和数组
    for(int i=0;i<n;i++)
    {
         cin>>vec[i];
         presum+=vec[i];
         pre[i]=presum;
         
    }
    while(cin>>a>>b)
    {
        sum=0;
        if(a==0)sum=pre[b];
        else sum=pre[b]-pre[a-1];
        cout<<sum<<endl;
    }
   
}

一个新的知识点:C++ 代码 面对大量数据 读取 输出操作,最好用scanf 和 printf,耗时会小很多。 

44.开发商购买土地

【题目描述】

在一个城市区域内,被划分成了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。

数据范围:

1 <= n, m <= 100;
n 和 m 不同时为 1。

第一遍看时,什么玩意,根本看不懂(宽面条泪/(ㄒoㄒ)/~~) 

运用前缀和的思路,终于弄懂了。

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

using namespace std;
int main () {
    int n,m;
    cin>>n>>m;
    int sum=0;
    //创建n*m区域的容器
    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++)horizontal[j]+=vec[i][j];
    }
    
    //遍历,计算横向切割时的土地总切割价值的差,并更新result变量
    int result=INT_MAX;
    int horizontalcut=0;
    for(int i=0;i<n;i++)
    {
        horizontalcut+=horizontal[i];
        result=min(result,abs(sum-horizontalcut-horizontalcut));
        //这里减去两个是因为sum本身就含有ho的值,要计算的是两个区域的差值
    }
    
    int verticalcut=0;
    for(int j=0;j<m;j++)
    {
        verticalcut+=vertical[j];
        result=min(result,abs(sum-verticalcut-verticalcut));
    }
    
    cout<<result<<endl;
    
}

​

注意

1.在更新 result 时,代码使用了 abs(sum - horizontalCut - horizontalCut) 和 abs(sum - verticalCut - verticalCut) 的形式。这里的 sum 是整个区域所有区块权值的总和。在计算横向切割时,horizontalCut 是从第一行到当前分割点的权值总和。当计算 result 时,需要从 sum 中减去两次 horizontalCut,这是因为 horizontalCut 已经包含了从第一行到分割点的权值,如果再减去一次,就会重复减去这些权值,导致计算出的差值偏小。因此,为了得到正确的差值,需要减去两次 horizontalCut。同样的逻辑适用于纵向切割。

2.为什么使用头文件<climits>

<climits> 是 C++ 标准库中的一个头文件,提供了与整数类型相关的限制和特性。它定义了一组常量,描述了各种整数类型(如 charintlong 等)的最小值、最大值和其他相关属性。这些常量来自 C 标准库的 <limits.h> 头文件。

如 整型

  • INT_MINint 类型的最小值。(题中用到了)
  • INT_MAXint 类型的最大值。
  • UINT_MAX:无符号 int 类型的最大值。

更优解法后续补上。

最后的最后,写博客真的要花挺长时间,今天写题加博客都花了四个多小时了。os明天看看能不能总结一下数组篇的知识吧。才知道我平时在vscode写的代码就是acm模式的,所以前三题没有耗费太多时间,基本上不用反复看都能自己写出来,虽然第一次刷不能自己写出代码来,但有些题目思路也是对上了。最后一题思考怎么切割那里耗费时间长了点...差点整崩溃了。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值