代码随想录day2|977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II
#977.有序数组的平方
977.有序数组的平方
第一次尝试
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
//因为是vector 所以觉得可以用sort
//感觉也可以用一个单调队列 又有点麻烦
for(int i=0;i<nums.size();i++)
{
int temp=nums[i];
nums[i]=temp*temp;
}
sort(nums.begin(),nums.end());
return nums;
}
};
但是这个肯定不是想让我用sort函数
顺便复习了一下sort
// 使用sort函数对vec进行排序
std::sort(vec.begin(), vec.end());
并且sort默认是升序排序 刚好是对的 如果想要降序排序 需要自定义boot compare函数或者用lambda函数(不会)((〃>皿<))
// 自定义比较函数,用于降序排序
bool compare(int a, int b) {
return a > b;
}
sort(vec.begin(),vec.end(),compare);
//lambda函数
sort(vec.begin(),vec.end(),[](int a,int b){return a>b;}
其实就是把自定义函数的类型和名称用[]代替
然后其他的都相同
不用sort函数怎么写呢
不用sort的话就是自己写一个排序算法吧 快速排序或者冒泡排序(这俩也不咋熟悉 冒泡好像是一次把一个最大的沉底 两层for循环 快速排序是选最左边的值 把比他大的放右边 比他小的放左边 用一个双指针实现 大概知道是啥 但是不熟悉写)
看carl思路代码随想录我那种原来是暴力算法
这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)。
数组其实是有序的, 只不过负数平方之后可能成为最大数了。
那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
如果A[i] * A[i] < A[j] * A[j] 那么result[k–] = A[j] * A[j]; 。
如果A[i] * A[i] >= A[j] * A[j] 那么result[k–] = A[i] * A[i]; 。
喔喔喔 好神奇
区别在于题中给的条件是原来的数组就是有序的如果用sort的话原来的数组是否有序 sort都能变成升序的 没有利用到这个条件
第二次尝试
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
//因为是vector 所以觉得可以用sort
//感觉也可以用一个单调队列 又有点麻烦
/*for(int i=0;i<nums.size();i++)
{
int temp=nums[i];
nums[i]=temp*temp;
}
sort(nums.begin(),nums.end());
return nums;*/
int leftIndex=0;
int rightIndex=nums.size()-1;
vector<int> n(nums.size(),0);
int i=nums.size()-1;
while(leftIndex<rightIndex&&i>=0){
if(abs(nums[leftIndex])<abs(nums[rightIndex])){
n[i]=nums[rightIndex]*nums[rightIndex];
i--;
rightIndex--;
}
else if(abs(nums[leftIndex])>abs(nums[rightIndex])){
n[i]=nums[leftIndex]*nums[leftIndex];
i--;
leftIndex++;
}
else{
n[i--]=nums[rightIndex]*nums[rightIndex];
n[i--]=nums[leftIndex]*nums[leftIndex];
rightIndex--;
leftIndex++;
}
}
if(leftIndex==rightIndex) n[0]=nums[leftIndex]*nums[leftIndex];
return n;
}
};
写了一大堆
vector n(nums.size(),0); 注意初始化方法
if(leftIndex==rightIndex) n[0]=nums[leftIndex]*nums[leftIndex];
这里是发现奇数数组会最后可能两者指到一起
ps: c++中没有平方函数
for()中第一项不能初始化两个 可以把初始化放在外面然后里面for(;巴拉巴拉;巴拉巴拉)啊 原来可以((〃>皿<))文心一言骗我for (int i = 0, j = A.size() - 1; i <= j;)
carl的比我的简略很多
class Solution {
public:
vector<int> sortedSquares(vector<int>& A) {
int k = A.size() - 1;
vector<int> result(A.size(), 0);
for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
if (A[i] * A[i] < A[j] * A[j]) {
result[k--] = A[j] * A[j];
j--;
}
else {
result[k--] = A[i] * A[i];
i++;
}
}
return result;
}
};
哦哦哦 差别是不用单独考虑两个相等的情况
因为只有把这个平方数放到数组里之后 这个指针才会移动 所以不用担心会少保存某一边
209.长度最小的子数组
209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的
子数组
[numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
第一次尝试
没理解题中的子数组 是要求连续的数
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//直观上来看就是在一个排好序的数组中找到一部分加起来大于target 并且使这部分最短
//该描述很像滑动窗口
//但是我不是很熟练怎么实现
int leftIndex=0;
int rightIndex=-1;
int sum=0;
int n=INT_MAX;
sort(nums.begin(),nums.end());
if(nums[leftIndex]>=target) return 1;
while(rightIndex<nums.size()-1){
rightIndex++;
sum+=nums[rightIndex];
while(sum>=target){
n=min((rightIndex-leftIndex+1),n);
sum-=nums[leftIndex];
leftIndex++;
}
}
if(n==INT_MAX) return 0;
return n;
}
};
第二次尝试
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//直观上来看就是在一个排好序的(错误的)数组中找到一部分加起来大于target 并且使这部分最短
//该描述很像滑动窗口
//但是我不是很熟练怎么实现
int leftIndex=0;
int rightIndex=-1;
int sum=0;
int n=INT_MAX;
//sort(nums.begin(),nums.end());
if(nums[leftIndex]>=target) return 1;
while(rightIndex<nums.size()){
if(rightIndex!=nums.size()-1){
rightIndex++;
sum+=nums[rightIndex];
}
while(sum>=target){
n=min((rightIndex-leftIndex+1),n);
sum-=nums[leftIndex];
leftIndex++;
}
}
if(n==INT_MAX) return 0;
return n;
}
};
也不对 在走到最后一个位置的时候没法判断 也没法终止
试着用文心一言
#include <vector>
#include <climits>
#include <algorithm> // 虽然在这个修正版本中不需要,但通常是个好习惯
class Solution {
public:
int minSubArrayLen(int target, std::vector<int>& nums) {
int leftIndex = 0;
int rightIndex = 0;
int sum = 0;
int n = INT_MAX;
// 无需排序
// 检查单个元素的情况
if (!nums.empty() && nums[0] >= target) return 1;
while (rightIndex < nums.size()) {
sum += nums[rightIndex];
while (sum >= target) {
n = std::min(n, rightIndex - leftIndex + 1);
sum -= nums[leftIndex];
leftIndex++;
}
rightIndex++;
}
// 如果没有找到满足条件的子数组
if (n == INT_MAX) return 0;
return n;
}
};
好神奇
他把我的右指针的增加放到最后就实现了能判断最后一个数
并且右指针能从0开始判断了
但是这个检查单个元素应该写的不对 哦哦也对我没仔细看
// 检查单个元素的情况
if (!nums.empty() && nums[0] >= target) return 1;
实际测试中没有这句也行
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//直观上来看就是在一个排好序的(错误的)数组中找到一部分加起来大于target 并且使这部分最短
//该描述很像滑动窗口
//但是我不是很熟练怎么实现
int leftIndex=0;
int rightIndex=0;
int sum=0;
int n=INT_MAX;
//sort(nums.begin(),nums.end());
//if(nums[leftIndex]>=target) return 1;
while(rightIndex<nums.size()){
sum+=nums[rightIndex];
while(sum>=target){
n=min((rightIndex-leftIndex+1),n);
sum-=nums[leftIndex];
leftIndex++;
}
rightIndex++;
}
if(n==INT_MAX) return 0;
return n;
}
};
代码随想录carl的
暴力解法是两个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;
}
};
滑动窗口
接下来就开始介绍数组操作中另一个重要的方法:滑动窗口。
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。
首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。
如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?
此时难免再次陷入 暴力解法的怪圈。
所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。
那么问题来了, 滑动窗口的起始位置如何移动呢?
这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:(见链接里的图)
最后找到 4,3 是最短距离。
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:(见链接里的图)
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
唔 和我的思路一样的 就是对于暴力解法的思路不清晰 导致在写滑动窗口如何结束的时候也有点晕(这个精髓部分我思路也是对的)
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
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 < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};
好吧 因为他用的是for 所以就是其实我的while难在了实现一个for的时候++的位置不对(小无语)
错误总结:
- 以后滑动窗口用for,并且for的是结束位置
时间复杂度:O(n)
空间复杂度:O(1)
一些录友会疑惑为什么时间复杂度是O(n)。
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。(大概理解一下)
59.螺旋矩阵II
59.螺旋矩阵II
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例 1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入:n = 1
输出:[[1]]
提示:
1 <= n <= 20
看起来好难 之前做的有的是用数学技巧对折对折
这个我猜可能也是
但是也不知道 准备直接看答案 快到点回去了(急)
我有点不明白画出这个矩阵了之后怎么一行行把他输出出来 或者说怎么在画的同时把他填充进去 没啥思路
添加链接描述
(QAQ这题是咋和二分数组整上关系的)
大家还记得我们在这篇文章数组:每次遇到二分法,都是一看就会,一写就废 (opens new window)中讲解了二分法,提到如果要写出正确的二分法一定要坚持循环不变量原则。
而求解本题依然是要坚持循环不变量原则。
模拟顺时针画矩阵的过程:
填充上行从左到右
填充右列从上到下
填充下行从右到左
填充左列从下到上
由外向内一圈一圈这么画下去。
可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是一进循环深似海,从此offer是路人。
这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
那么我按照左闭右开的原则,来画一圈,大家看一下:
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。
这也是坚持了每条边左闭右开的原则。
一些同学做这道题目之所以一直写不好,代码越写越乱。
就是因为在画每一条边的时候,一会左开右闭,一会左闭右闭,一会又来左闭右开,岂能不乱。
代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。
整体C++代码如下:
(为啥画出来就能把他放到数组里了呢 在没看代码之前还是不明白)
(揭晓谜底(乌拉拉))
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
int count = 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;
}
};
时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
空间复杂度 O(1)
定义二维数组的方式:
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
哦吼 原来因为你先创建了一个二维数组 然后你根据ij下标的位置就能精准定位你想填进去的位置了 所以在画的过程中就能把这个数组填满了 就是答案了
(卡住的点很微妙)
然后这里奇数数组的额外要加的辅助还挺多的 感觉挺难
for (j; j < n - offset; j++)
这是啥写法 第一次见到 文心一言说
- 第一个循环 for (j; j < n - offset; j++):
这个循环假设 j 在循环之前已经被定义并可能已经被初始化了。如果 j 没有被初始化,那么这段代码的行为将是未定义的。
循环的条件是 j < n - offset,并且在每次迭代结束时,j 会通过 j++ 增加 1 - 第二个循环 for (; j < n - offset; j++):
这个循环的初始化部分是空的(即没有执行任何初始化操作)。它同样假设 j 在这个 for 循环之前已经被定义并可能已经被初始化了。
循环的条件和迭代表达式与第一个循环相同。
好神奇 没自己写一遍 但是看懂了(困了)