补day2,时间有点长,因为发现排序的东西有点忘了,所以中间抽空又去补了一下,排序那块东西还是需要整理的,等抽空再整理一下。本篇代码为C语言。
一、有序数组平方(快排和双指针思想)
1、力扣题目
题目描述:给你一个按 非递减顺序 排序的整数数组
nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。示例 :
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
2、初次解题
看完该题第一个想到的就是先平方,之后排序即可,使用的是快排(回顾快排的时候看的这篇文章快速排序(快排) (C语言实现)_c语言快速排序-CSDN博客,忘记的可以看看)。这里简单进行一个说明,该方法的主要思想就是,找到一个数作为基准值,经过操作后使得该数左边都比它小,右边都比它大。
最先开始的是hoare法,该方法有几点需要注意:
①快排找的一定是左边都比它小,右边都比它大,所以判断必须有等于号。
②当left=right时,一定会发生交换。如果nums[left]是最小值时此时与其他任何数交换都会出错,假如left在前,根据判断条件的等于号,left一定会+1,那么导致必定与其他数发生了交换。如果是right在前,最终会指向当前基准值,此时交换便没有影响。
③该基准值必须不等于left和right,否则死循环。
该方法代码如下:
void swap(int *a,int *b){
int temp =*a;
*a=*b;
*b=temp;
}
void quicksort(int* a, int begin, int end)
{
if (begin >= end)return;//终止
//一趟的实现
int left = begin;
int right = end;
int key = left;//即通常所见的pivot的下标
while (left < right)
{
while (left < right && a[key] <= a[right])//右边,注意等于号
{
right--;
}
while (left < right && a[left] <= a[key])//左边
{
left++;
}
swap(&a[left], &a[right]);
}
swap(&(a[key]), &(a[left]));
key = left;
quicksort(a, begin, key - 1);//左边
quicksort(a, key+1, end);//右边
}
int* sortedSquares(int* nums, int numsSize, int* returnSize) {
int *n=(int *)malloc(sizeof(int)*numsSize);
*returnSize=numsSize;
for(int i=0;i<numsSize;i++){
n[i]=nums[i]*nums[i];
}//平方
quicksort(n,0,numsSize-1);//快排
return n;
}
3、视频学习
视频链接:双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili
视频用的双指针方法,因为原数组是有顺序的,平方之后,最小值一定是在中间,最大值一定在两边,所以可设置双指针,从两边向中间推进。获得数都是从大到小,那么新数组从后往前填充即可。
int* sortedSquares(int* nums, int numsSize, int* returnSize) {
int *n=(int *)malloc(sizeof(int)*numsSize);
*returnSize=numsSize;
int last=numsSize-1;
for(int i=0,j=numsSize-1;i<=j;){
if(nums[i]*nums[i]>nums[j]*nums[j]){
n[last]=nums[i]*nums[i];
i++;
}
else{
n[last]=nums[j]*nums[j];
j--;
}
last--;
}
return n;
}
4、个人总结
看完视频真的感慨,双指针的思想在数组中灵活运用能够省很多事情。在遇到数组问题例如排序或者查找的时候最好能够尝试转化成双指针。
二、长度最小的子数组
1、力扣题目
题目描述:给定一个含有
n
个正整数的数组和一个正整数target
。找出该数组中满足其总和大于等于
target
的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回0
。示例 :
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组
[4,3]
是该条件下的长度最小的子数组。
2、初次解题
看到该题,脑子第一反应是用动态规划求最小长度,但是写了半天用例一直报错,后来才发现题目中是满足条件的连续子数组,所以思路错了,耗费了我半天时间,所以一定要仔细读题。
3、视频学习
视频链接:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili
因为求的是连续子数组,所以此时用滑动窗口会很方便。
开始时,起始指针和终止指针都在nums[0]处,for循环处移动的是终止指针,不然跟暴力破解,枚举取最小没区别(该题暴力会超时)。
[ 起始指针 , 终止指针 ] 区间和 >= target 时,终止指针停止,起始指针向右移动,来找最小区间。(①注意该处为while,因为起始指针满足条件可以一直进行移动。
②移动时所计算的区间和要记得减去刚开始起始指针所指向的值)
代码如下:
int min(int a,int b){
return a<b?a:b;
}
int minSubArrayLen(int target, int* nums, int numsSize) {
int sum=0;
int i=0;//起始指针
int short_line=0,result=numsSize;
for(int i=0;i<numsSize;i++){
sum=sum+nums[i];
}//计算总和大小
if(sum<target){
return 0;//总和无法满足条件,返回0
}
else{
sum=0;
for(int j=0;j<numsSize;j++){//j代表终止指针
sum+=nums[j];//计算和
while(sum>=target){//当前开始和终止区间的和大于target
//注意是while而不是if,因为起始指针要进行多次移动,直到最小区间
short_line=j-i+1;//计算长度,注意+1
result=min(short_line,result);//取最小值
//起始指针右移,sum减去该起始值后进行i+1
sum=sum-nums[i];
i++;
}
}
return result;
}
}
4、个人总结
通过该题掌握滑动窗口的思想,滑动窗口适用于解决连续的子数组问题,在进行滑动窗口解题时,需注意先循环移动的是右边界,之后再循环移动左边界。右边界在外层,左边界在内层。
三、螺旋矩阵
1、力扣题目
题目描述:给你一个正整数
n
,生成一个包含1
到n2
所有元素,且元素按顺时针顺序螺旋排列的n x n
正方形矩阵matrix
。示例:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
2、初次解题
写该题的时候给绕进去了,就不说了
3、视频学习
视频链接:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili
在进行循环的时候,一定要注意循环不变量,可以理解为要找的是一个通解,处理的情况得类似,以该题为例,处理时,最好不要出现第一次包含两边的拐点,之后只包含一个拐点的情况,写题时容易弄混。
如下图,黄色为拐点
容易迷的思路 循环不变量思路
思路解析:
①每次处理一圈,首先看能够处理多少圈,每次处理n/2圈(以偶数为准,奇数比偶数多一个中心值,处理完之后单独处理就行,因为最后一格位置是可求的)
②每一圈分成四个部分,上下左右
③每一条边包括一个顶点和边长为n-1的长(结合上面右边的图)
④思考边长和顶点的变化,每次正方形向内缩减时,边长-2,顶点坐标x和y各向内移动一步(也就是斜着移动一格)
⑤注意奇数时对最中间一格的处理。
代码如下:
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int** generateMatrix(int n, int* returnSize, int** returnColumnSizes) {
//初始化返回数组大小
*returnSize=n;
*returnColumnSizes = (int*)malloc(n * sizeof(int));
//初始化螺旋矩阵
int **matrix=(int **)malloc(sizeof(int *)*n);
for(int i=0;i<n;i++){
matrix[i]=(int *)malloc(sizeof(int)*n);
(*returnColumnSizes)[i] = n;
}
int start=0,end=n-1;
int count=1;
int m=n/2;//相当于要处理的小正方形的个数
int every_long=n-1;
while(m){
//按大圈来,可以理解为有多少个正方形
//每正方形的处理条件就是 一边一顶点
//处理边长时,第一个正方形初始长度为n-1,其余每次递减2
//四个顶点分别是(start,start)(start,end)
// (start,end) (end,start)
//顶点每次向内缩减一层,每次递减1
for(int j=0;j<every_long;j++){//上
matrix[start][start+j]=count++;
}
for(int j=0;j<every_long;j++){//右
matrix[start+j][end]=count++;
}
for(int j=0;j<every_long;j++){//下
matrix[end][end-j]=count++;
}
for(int j=0;j<every_long;j++){//左
matrix[end-j][start]=count++;
}
every_long=every_long-2;
start++;
end--;
m--;
}
if(n%2==1){//奇数最中间的值单独处理
int a=(n-1)/2;
matrix[a][a]=count++;
}
return matrix;
}
4、总结
循环不变量在进行多个循环处理的时候十分重要,否则容易迷,哪怕最后解出来也可能是稀里糊涂跑过的,在应用该思想能够减少出现绕进去的情况,写题的时候能够帮助快速整理出思路。