力扣977.有序数组的平方
题目链接:力扣
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
这题用指针的契合点:数组中所有数都平方后,最大的数肯定是从数组的两端开始寻找。
int* sortedSquares(int* nums, int numsSize, int* returnSize){ //这个题目非常不好的地方就是returnsize这个函数参数,没什么用,而且题目描述的不清楚,让人很头疼,不看答案都不知道这个参数要干嘛。returnsize就是一个用来存放要返回的数组的大小的变量。 int *result = (int*)malloc(sizeof(int) * numsSize); *returnSize = numsSize;//这行没啥用,单纯为了能过这题 int k=numsSize-1;//新数组的元素序号(从大到小---题目要求) int i,j; for(i=0,j=numsSize-1;i<=j;) { if(nums[i]*nums[i]>nums[j]*nums[j]) { result[k]=nums[i]*nums[i]; i++; k--; } else { result[k]=nums[j]*nums[j]; j--; k--; } } return result; }
小发现:二分查找中一次查找结束的条件是left>right;这道题里面一次检索结束的条件也是类似的,为i<=j;
力扣209.长度最小的子数组
本题的第一个难点在于对题目的理解,题目要求的是数组中长度最小的但是数组中所有值加起来大于target的子数组。
这题用滑动窗口的思想真的是第一次接触到!
int minSubArrayLen(int target, int* nums, int numsSize){ int result =numsSize+1; int left=0;//左指针 int right=0;//右指针 int sum=0; int lenght=0;//当前计算出的数组长度 for(right =0;right<=numsSize-1;right++) { sum+=nums[right]; while(sum>=target) { lenght=right-left+1; result=result>lenght?lenght:result; sum=sum-nums[left]; left++; } } return result==numsSize+1?0:result;//易错,一定要注意判断结果 }
这题如果不使用滑动窗口,按理是需要以数组中的每一个元素为新数组的起点,然后向后找到一个元素之和刚刚超过target的数组——》接着把分别以每个元素为起点的数组的长度进行比较,找到数组长度最小的数组,即为所求!
滑动窗口法的核心优点:但是如果按照上面最简单的思路的话,其实以每个元素为起点的时候,刚开始形成的那些长度只有1、2、3个元素的数组很大可能sum<target,所以这些情况下面还要去和target进行比较很浪费时间。滑动窗口法很巧妙使”以每个元素为起点的数组在一开始的sum值就能达到非常接近target的状态,这样就省去了很多没有意义的比较
滑动窗口是一种用于处理数组、字符串和链表的算法思想,它通常适用于需要计算连续、固定长度的元素序列的问题。具体来说,滑动窗口思想适用于以下类型的问题:
-
最大子数组/子串问题:寻找给定数组或字符串中元素之和或乘积最大的连续子区间/子串(本题)。
-
字符串匹配问题:在给定文本中查找是否存在与目标字符串匹配的子串--KMP(学过)。
-
数组/字符串分段问题:将给定序列划分成大小固定且不重叠的区间,并对每个区间执行某些操作。
-
滑动窗口代替哈希表:对于某些列举问题,如全排列、最小覆盖子串等问题,我们可能会使用哈希表形式的滑动窗口来解决。
59.螺旋矩阵II
题目链接:力扣
这道题的算法类型叫:模拟行为——模拟创建一个矩阵的行为。
思想:把一个大问题拆解成相似的小问题——》画螺旋矩阵的本质就是画一个个正方形。最后判断中间需不需要加上一个单独的值(非得要加上单独的只是因为loop=n除以2;就算把loop改成loop=n/2+1后,由于for循环里面都是j < startY + n - offset这种大于和小于的判断条件,而不是大于等于or小于等于,所以把loop改成n/2+1后也没有用)
难点:小问题之间的连贯性和转化(也就是解决了外圈矩阵的绘制后,如何把解决外圈的这一套算法放到解决内圈矩阵的绘制中)
int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){ *returnSize = n;//要返回的数组中有多少个数字 *returnColumnSizes = (int*)malloc(sizeof(int) * n); //初始化返回数组为ans;ans是个二维数组 int** ans = (int**)malloc(sizeof(int*) * n); int i; for(i = 0; i < n; i++) { ans[i] = (int*)malloc(sizeof(int) * n); (*returnColumnSizes)[i] = n; } //设置每次循环的起始位置 int startX = 0; int startY = 0; //设置二维数组的中间值,若n为奇数。需要最后在中间填入数字 int mid = n / 2; //循环圈数-->每转一圈到里面一圈时,矩阵的长和宽后会减2,所以圈数=n/2 int loop = n / 2; //偏移数 int offset = 1; //当前要添加的元素 int count = 1; while(loop) {//只要loop不为0就继续循环 int i = startX; int j = startY; //模拟上侧从左到右 for(; j < startY + n - offset; j++) { ans[startX][j] = count++; } //模拟右侧从上到下 for(; i < startX + n - offset; i++) { ans[i][j] = count++; } //模拟下侧从右到左 for(; j > startY; j--) { ans[i][j] = count++; } //模拟左侧从下到上 for(; i > startX; i--) { ans[i][j] = count++; } //偏移值每次加2 offset+=2; //遍历起始位置的横纵坐标每次都+1(也就是起始位置往右下角移动了一格)——巧 startX++; startY++; loop--; } //若n为奇数需要单独给矩阵中间赋值 if(n%2) ans[mid][mid] = count; return ans;
这道题我觉得有个2个难点:
要确定出那么躲需要使用的变量,特别是loop、offset和二维数组的中间值mid
offset的推导公式——我觉得就是在不知道offset应该设置为多少的时候,将第二圈的startY 和n这两个变量的数值带入计算公式startY + n - offset中,然后看offset应该等于多少才能使这个循环正常。然后再把暂时确定下来的offset的值带入到第二圈别的边的计算和第三圈的计算中,看对不对,不对就再调整。
文章讲解:代码随想录
视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili
数组总结
一、数组
1.动态地申请数组空间:
c语言: int*arr=(int*)malloc(sizeof(int)*length); c++: 使用new关键字或者vector
2.二维数组的地址空间也是连续的
二、双指针算法
移除元素、有序数组的平方都用到了双指针的思想。而这些使用双指针的题目都是可以用暴力的方式解决的——》所以以后如果一个数组题目可以用暴力解决,那么就接下去考虑一下用双指针能不能解决。
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
双指针法(快慢指针法)在数组和链表的操作中是非常常见的
三、滑动窗口
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
四、模拟行为
模拟类的题目在数组中很常见,不涉及到什么算法,就是单纯的模拟,十分考察大家对代码的掌控能力。
在这道题目中,我们再一次介绍到了循环不变量原则,其实这也是写程序中的重要原则。
相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的,大家可以在这道题目中体会到这一点。