#打卡第二天
感受:今天题目难度较昨天有所增加,也是采用了传统的做题方法:暴力+新方法。在看过Carl哥文章讲解后感受到了非暴力法的高效性。
今日收获:1.双指针法:
数组其实是有序的, 只不过负数平方之后可能成为最大数了。
那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
此时可以考虑双指针法了,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];
。
如动画所示:
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
代码实现:
mine:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* sortedSquares(int* nums, int numsSize, int* returnSize) {
int i =0;
for(;i < numsSize;i++){
nums[i] = nums[i]*nums[i];
}
*returnSize =i;
int temp =0;
for(int j = 0; j < *returnSize; j++){
for (int k =0;k< *returnSize-j-1;k++){
if(nums[k] > nums[k+1]){
temp = nums[k];
nums[k] =nums[k+1];
nums[k+1] =temp;
}
}
}
return nums;
}
由于时间复杂度为O(n^2),甚至超出时间限制
双指针法:
int* sortedSquares(int* nums, int numsSize, int* returnSize){
//返回的数组大小就是原数组大小
*returnSize = numsSize;
//创建两个指针,right指向数组最后一位元素,left指向数组第一位元素
int right = numsSize - 1;
int left = 0;
//最后要返回的结果数组
int* ans = (int*)malloc(sizeof(int) * numsSize);
int index;
for(index = numsSize - 1; index >= 0; index--) {
//左指针指向元素的平方
int lSquare = nums[left] * nums[left];
//右指针指向元素的平方
int rSquare = nums[right] * nums[right];
//若左指针指向元素平方比右指针指向元素平方大,将左指针指向元素平方放入结果数组。左指针右移一位
if(lSquare > rSquare) {
ans[index] = lSquare;
left++;
}
//若右指针指向元素平方比左指针指向元素平方大,将右指针指向元素平方放入结果数组。右指针左移一位
else {
ans[index] = rSquare;
right--;
}
}
//返回结果数组
return ans;
}
时间复杂度仅为O(n)
2.
题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
此题尝试后只有一些不成熟且复杂的想法:定义一个数组存放每次满足条件的元素的个数,定义嵌for循环,外层控制起始点,内层控制终止点,当每次遍历元素的和大于所给数时就break退出循环,同时定义一个变量记录该次元素个数。在找完全部满足条件的情况后,遍历存有每次满足条件的元素个数的数组,返回其中最小值。虽然此想法较复杂,但理论可以实现,下去尝试。
下面为正确的暴力法:
int minSubArrayLen(int target, int* nums, int numsSize){
//初始化最小长度为INT_MAX
int minLength = INT_MAX;
int sum;
int left, right;
for(left = 0; left < numsSize; ++left) {
//每次遍历都清零sum,计算当前位置后和>=target的子数组的长度
sum = 0;
//从left开始,sum中添加元素
for(right = left; right < numsSize; ++right) {
sum += nums[right];
//若加入当前元素后,和大于target,则更新minLength
if(sum >= target) {
int subLength = right - left + 1;
minLength = minLength < subLength ? minLength : subLength;
}
}
}
//若minLength不为INT_MAX,则返回minLnegth
return minLength == INT_MAX ? 0 : minLength;
}
滑动窗口法:
接下来就开始介绍数组操作中另一个重要的方法:滑动窗口。
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。
首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。
如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?
此时难免再次陷入 暴力解法的怪圈。
所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。
那么问题来了, 滑动窗口的起始位置如何移动呢?
这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:
最后找到 4,3 是最短距离。
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
滑动窗口法代码:
int minSubArrayLen(int target, int* nums, int numsSize){
//初始化最小长度为INT_MAX
int minLength = INT_MAX;
int sum = 0;
int left = 0, right = 0;
//右边界向右扩展
for(; right < numsSize; ++right) {
sum += nums[right];
//当sum的值大于等于target时,保存长度,并且收缩左边界
while(sum >= target) {
int subLength = right - left + 1;
minLength = minLength < subLength ? minLength : subLength;
sum -= nums[left++];
}
}
//若minLength不为INT_MAX,则返回minLnegth
return minLength == INT_MAX ? 0 : minLength;
}