一、题目打卡
1.1 有序数组的平方
这个题目虽然有提示是使用双指针对问题进行求解,但是拿上手的时候没有具体想到双指针怎么使用,所以算是使用了暴力的解法吧,采用的快速排序的方法对计算出来的结果直接进行排序,也当做是对之前快速排序的方法进行复习:
class Solution {
private:
int partation(vector<int> &input,int l, int r){
int tmp = input[l]; // 哨兵
int i = l, j = r;
if(i >= j) return i;
while(i < j){
while(i < j && input[j] >= tmp) j--;
while(i < j && input[i] <= tmp) i++;
swap(input[i],input[j]);
}
swap(input[l],input[i]);
return i;
}
void fastSort(vector<int> &input, int l, int r){
if(l >= r) return;
int res = partation(input, l, r);
fastSort(input, l, res - 1);
fastSort(input, res + 1, r);
}
public:
vector<int> sortedSquares(vector<int>& nums) {
// 检测算法是否正确
// vector<int> input{5,4,9,2,1,3,7};
// fastSort(input, 0, 6);
// for(auto &it : input){
// cout << it << " ";
// }
// return input;
for(int i = 0; i < nums.size();i++){
nums[i] = nums[i] * nums[i];
}
fastSort(nums,0,nums.size()-1);
return nums;
}
};
接着看了答案,对双指针有了更进一步的理解,就目前的认知来看,双指针的使用貌似就是从两头向中间,要不就是设置快慢指针,看了答案解析以后,自己写的,并且犯了一个小错:
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
//双指针的思想
int i = 0, j = nums.size() - 1, ind = nums.size() - 1;
vector<int> res(nums.size(),0); // 提前创建好方便进行索引
//这里的关键思想是最大值只有可能在两端产生
while(i < j){
if(nums[i] * nums[i] >= nums[j] * nums[j]){
res[ind] = nums[i] * nums[i];
ind--;
i++;
}else{
res[ind] = nums[j] * nums[j];
j--;
ind--;
}
}
// 处理 i == j 的最后一个数
/* debug
cout << "i=" << i << "j=" << j << "nums[i]=" << nums[i] * nums[i];
cout <<endl;
for(auto &it : nums){
cout << it << " ";
}
*/
// 手滑写成判断语句了
// res[ind] == nums[i] * nums[i];
res[ind] = nums[i] * nums[i];
return res;
}
};
感想:
这里的感想首先是对于 while 循环的条件,如果很多时候没有办法准确判断 i 和 j 的关系,那么就思考一下,等于的时候是否是两者需要结束的时候,这里以快速排序为例,当等于的时候,也就相当于所有和哨兵比较的数都已经交换完毕,那么就已经可以结束循环了,而对于平方最大值来说,其实都可以,如果写成 i < j 的话,需要自己在最后进行单独数据的一个处理。
1.2 长度最小的子数组
这个题目在一开始看到的时候,提醒使用滑动窗口进行求解,接着自己思考写了下面的代码,在写的过程还是存在一些问题,基本都是通过 debug 找到问题所在:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
// 这里是比小,因而min_len需要初始化一个很大的数
int min_len = INT_MAX, i = 0, j = 0, sum = 0;
while(true){
if(sum < target) sum += nums[j++];
else{
min_len = min_len < j - i ? min_len : j - i;
sum -= nums[i++];
}
if(j >= nums.size() && sum < target) break;
}
return min_len == INT_MAX ? 0 : min_len;
}
};
当往滑动窗口进行思考的时候,我将其分为了三个过程,分别维护了四个变量,min_len用于动态更新数组比较过程中的最小长度,i , j 分别代表滑动窗口的区间,这里我计算的时候是将其作为闭区间处理的,最后 sum 用于动态更新统计出来的滑动窗口内数的大小,接着思路如下:
1)当滑动窗口的和比target小的时候,就不断增加滑动窗口的右端,并动态更新 sum;
2)当滑动窗口的和不小于target的时候,这时候就可以开始统计窗口的长度了,这里有一个小坑一开始自己犯的,因为比较的过程是比小,因而滑动窗口的和的数值一开始应该更新为无限大,在更新完 min_len 以后,将左端向右边移动,并减去左端的值。
3)如此一直更新到 j 到达整个数组的右端时,此时的关键是观测 sum 是否比 target 小,如果否,那么就一直缩小左端,此时有一个关键,自己 debug 时候发现的,就是 sum < target,这个条件在一开始我思考到了,但是发现这个条件不能带等号,因为带等号也就说明,此时区间还可以进一步缩小,这里就和此时 j 的索引始终是比 nums.size() 大有关,也就是相当于是开区间。
看了随想录的解析,和自己思路差不多,这里就直接把随想录的代码贴在这里:
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;
}
};
1.3 生成螺旋矩阵
这个题自己写的过程挺折磨的,很久之前自己做过,然后凭借记忆重新写了一遍,但是遇到了很多细节上的问题,关键是,为什么这个 vscode 不能 debug 啊?代码有问题的时候,vscode 的 debug 也会出错,导致电脑还炸了几次,暂时没有找到为什么,关键还是代码不能写错(但是我真的想吐槽这个 debug,debug 不就是为了找错误吗,代码对了还 debug 干啥)。
这是代码其中的一个版本:
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<int> tmp(n,0);
vector<vector<int>> res(n,tmp);
// 模拟四个天花板 left, right, top, bottom, 想象是一个盒子进行包裹
int l = 0, r = n, t = 0, b = n;
int i = 1;
while(t < b && l < r){
int it = l;
while(l < r && it < r) res[t][it++] = i++;
// 第一个出现错误的地方,我一开始把t++,写成了t--,下面的l++也是
t++;
it = t;
// 第二个错误,我把 it < b 这个条件写成了 it > b
while(t < b && it < b) res[it++][r - 1] = i++;
r--;
// 第三个错误的地方,我把这里赋值的语句忘记减1了,但实际由于右侧是开区间,这里还需要减一个1
it = r - 1;
// 最后就是 it >= l 这里,因为 l 和 t 是闭区间,因而这里写终止条件的时候,需要加等号
while(l < r && it >= l) res[b - 1][it--] = i++;
b--;
it = b - 1;
while(t < b && it >= t) res[it--][l] = i++;
l++;
}
// 用来检查对不对的
// for(auto &i : res){
// for(auto &j : i){
// cout << j << " ";
// }
// cout << endl;
// }
return res;
}
};
int main(int argc, char *argv[])
{
int n = 3;
Solution s;
s.generateMatrix(n);
return 0;
}
整体的思路没有问题,就是设置四个边界不断缩小来进行输出,但是这里面我在一开始的这些地方出现了错误,在代码中都标识出来了。
感想:
这里主要是对开区间和闭区间的后终止条件进行了进一步学习,有了更深入的认识,其实本身来说,这个题用双闭区间会好一点,如果右边是开区间,那就需要注意后面判断条件的使用,并且在动态更新 it 的时候,也会好处理一点,还有一个需要注意的小细节就是对于 it 以及各个天花板的的变化,方向不要搞错了,不然会导致越界。