LeetCode 977.有序数组的平方
文章讲解:文章讲解--有序数组的平方
视频讲解:视频讲解--有序数组的平方
题目链接:. - 力扣(LeetCode)
题目描述: 给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
第一想法: 直接遍历平方后再对数组进行一次排序即可.
时间复杂度 O(nlogn) 空间复杂度 O(n)
看完代码随想录后的想法: 双指针法, 利用本题数组自带的非递减性使双指针逐步向中间合拢即可得到一个由大到小的新数组, 再倒序存储到新数组即可.
时间复杂度O(n) 空间复杂度O(n)
代码实现:
①暴力:
int* cmp(const void* a, const void* b) {
return(*(int* )a - *(int* )b);
}
int* sortedSquares(int* nums, int numsSize, int* returnSize) {
*returnSize = numsSize;
int* ans = (int* )malloc(sizeof(int) * numsSize);
for(int i = 0; i < numsSize; i++) {
ans[i] = nums[i] * nums[i];
}
qsort(ans, numsSize, sizeof(int), cmp);
return ans;
}
②双指针法:
int* sortedSquares(int* nums, int numsSize, int* returnSize) {
*returnSize = numsSize;
int* ans = (int* )malloc(sizeof(int) * numsSize);
int k = numsSize - 1 , i, j; //倒序构造新数组
for(i = 0, j = numsSize - 1; i <= j; ){
if(nums[i] * nums[i] > nums[j] * nums[j]) { //平方后最左端比最右端还大
ans[k--] = nums[i] * nums[i];
i++; //i向后走
}
else { //反之
ans[k--] = nums[j] * nums[j];
j--; //j向前走
}
}
return ans;
}
思路: 从数组的非递减性可观察到所有元素平方后, 值是由数组两端逐渐递减的(数组的左右两端都有可能是最大值), 双指针逐步向中间合拢即可得到一个由大到小的新数组, 再倒序存储到新数组, 最后返回新数组即可.
收获:
1.为什么是 "i <= j" 呢 ? -- 因为如果是i < j的话, 当i = j时, 循环跳出, 则此时对应的元素没有更新, 也自然没有放在新数组ans里, 等于漏掉了一个元素!
2.在做题时要时时刻刻注意到题目中的数据结构自带的各种特性, 能够帮助拓宽思路.
LeetCode 209.长度最小的子数组(滑动窗口)
文章讲解:文章讲解--长度最小的子数组
视频讲解:视频讲解--长度最小的子数组
题目链接:. - 力扣(LeetCode)
题目描述:给定一个含有 n
个正整数的数组和一个正整数 target
。找出该数组中满足其总和大于等于 target
的长度最小的 连续子数组[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回 0
。
第一想法: 除了双重循环暴力枚举之外,并没有什么好的想法.
看完代码随想录后的想法: 使用双指针法来动态地修改子数组的长度, 对我来说真的很难想的到.
代码实现:
双指针法:
int min(int a, int b) {
return a < b? a : b;
}
int minSubArrayLen(int target, int* nums, int numsSize) {
int i = 0;
int sum = 0;
int ret = numsSize + 1;
for(int j = 0; j < numsSize; j++) {
sum += nums[j]; //统计和
while(sum >= target) {
ret = min(ret, j - i + 1); //取最小值
sum -= nums[i++]; //起始位置要向后走 sum值自然要减掉nums[i]
}
}
return ret == numsSize + 1? 0 : ret; //若不存在最小子数组 则返回0
}
思路: 先让 ret = numsSize + 1 , 使用两个指针 i , j 分别表示子数组的起始位置和终止位置, 先让后者 j 遍历到子数组和能大于target 值的位置, 再移动起始位置 i 逐步逐步地使子数组的和最接近target值, 同时更新子数组的长度 (取较小) . 注意: 当整个数组之和都比 target x小的话, 说明ret就没有更新过, 因此在返回处可以进行判断, 若没有更新过,说明不存在符合题意的最小子数组, 返回0.
收获:
1.为什么 j 比 i 先动呢? -- 假设起始位置先动, 当起始位置到达了符合 sum >= s 的位置之后, 终止位置其实要遍历到起始位置的后方才能获得范围在起始位置到终止位置之内的子数组, 而这和暴力枚举没有什么区别, 效率极低.
2.为什么是while (sum >= s) 而不是 if (sum >= s) 呢? -- 这是因为当终止位置停下时, 起始位置是一个持续移动的过程, 直到子数组的和最接近 s 此时的子数组长度才是最终答案, 若使用 if , 则只执行一次过后便得到 ret , 此时的 ret 并不一定就是最终答案.
3.第一次了解到滑动窗口这类题, 感觉就像是需要我们动态修改一个或多个子数组使其满足题目要求, 感觉难度相当大.
LeetCode 59.螺旋矩阵Ⅱ
文章讲解:文章讲解--螺旋矩阵Ⅱ
视频讲解:视频讲解--螺旋矩阵Ⅱ
题目链接:. - 力扣(LeetCode)
题目描述:给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
第一想法:没什么别的想法, 就是模拟整个构建螺旋矩阵的过程.
看完代码随想录后的想法:和自己的第一想法大差不大, 多注意区间的划分即可.
代码实现:
int** myMalloc(int r, int c, int* returnSize, int** returnColumnSizes) {
int** ret = (int** )malloc(sizeof(int* ) * r);
*returnColumnSizes = (int* )malloc(sizeof(int) * r);
*returnSize = r;
for(int i = 0; i < r; i++) {
ret[i] = (int* )malloc(sizeof(int) * c);
(*returnColumnSizes)[i] = c;
}
return ret;
}
int** generateMatrix(int n, int* returnSize, int** returnColumnSizes) {
int startX = 0; //设置每次循环的起始位置
int startY = 0; //设置每次循环的起始位置
int mid = n / 2; //设置二维数组的中间值,若n为奇数。需要最后在中间填入数字
int loop = n / 2; //循环圈数
int offset = 1; //偏移数
int count = 1; //当前要添加的元素
while(loop) {
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++;
}
offset += 2; //偏移值每次加2
startX++; //遍历起始位置每次+1
startY++; //遍历起始位置每次+1
loop--;
}
if(n % 2)
ans[mid][mid] = count; //若n为奇数需要单独给矩阵中间赋值
return ans;
}
思路: 整个构建螺旋矩阵的思路并没有什么特别的, 只需注意每一圈每个处理区间是逐渐递减的, 所以需要一个偏移量来控制我们的处理区间, 圈数是 n / 2 , 从上侧开始模拟, 从左到右 i 不变, 而 j 只到达 n - 2为止, 保证每个处理的区间是"左闭右开",到达上侧最右端时, 此时从上到下 j 不变, 而 i 也要到达 n - 2为止, 到达最右下端,从右到左赋值, 此时 i 不变, j 应该递减到 startY - 1 , 到达左下端时,从上到下赋值, 此时 j 不变, i 应该递减到startY - 1 , 此时已经完成了第一圈的构建, 起始位置都要+1 , 而偏移量需要+ 2, 圈数 - 1. 若 n 为奇数时, 还需要对矩阵最中间位置 ans[mid][mid] 赋值.
收获:做带有区间划分的题目时要注意始终保证一个原则!