今日重点掌握内容
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)。
图示:
注:红色框的区域就是滑动的窗口
滑动窗口
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_MAX
。INT32_MAX
是一个宏定义,通常位于 <limits.h>
头文件中,它代表了 32 位有符号整数类型可以表示的最大值。将 INT32_MAX
赋值给 result
,意味着 result
被初始化为了一个非常大的数值,几乎相当于将 result
设置为了无穷大。这通常用于初始化变量,以确保在后续的计算中,result
的值能够被更新为更小的数值。这种初始化方式可用于各种场景,比如循环计数器的初始化,以确保循环能够执行预期的次数,或者在某些算法中用来表示无限或极大值。
2.动态调节滑动窗口的实现代码那里写while语句,注意不要写成if。
59.螺旋矩阵II
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]
模拟顺时针画矩阵的过程:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
图示:
一定要画图来模拟一下怎么循环的,代码就很好理解了。
就是一圈圈向内填充矩阵。
代码
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开始的区间和,由数组右区间下标对应的数减去左区间下标对应的数的前一个。
如图:
代码
#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++ 标准库中的一个头文件,提供了与整数类型相关的限制和特性。它定义了一组常量,描述了各种整数类型(如 char
、int
、long
等)的最小值、最大值和其他相关属性。这些常量来自 C 标准库的 <limits.h>
头文件。
如 整型
INT_MIN
:int
类型的最小值。(题中用到了)INT_MAX
:int
类型的最大值。UINT_MAX
:无符号int
类型的最大值。
更优解法后续补上。
最后的最后,写博客真的要花挺长时间,今天写题加博客都花了四个多小时了。os明天看看能不能总结一下数组篇的知识吧。才知道我平时在vscode写的代码就是acm模式的,所以前三题没有耗费太多时间,基本上不用反复看都能自己写出来,虽然第一次刷不能自己写出代码来,但有些题目思路也是对上了。最后一题思考怎么切割那里耗费时间长了点...差点整崩溃了。