代码随想录day02
977. 有序数组的平方
思路
暴力法
每个数平方之后,排个序
class Solution {
public int[] sortedSquares(int[] nums) {
int[] result = new int[nums.length];
for(int i = 0; i < nums.length; i++) {
result[i] = nums[i]*nums[i];
}
Arrays.sort(result);
return result;
}
}
双指针
因为原数组升序排列且包含负数,那么每个元素平方后的结果中,更大的值一定出现在数组的两端,不是左边就是右边,所以可以定义首尾两个指针,分别平方之后判断大小,从新数组的最后一位向前填入数组。
class Solution {
public int[] sortedSquares(int[] nums) {
int left = 0;
int right = nums.length - 1;
int[] result = new int[nums.length];
int i = nums.length - 1; //指向result数组下一个需要填入的位置
while(left <= right) { //在循环体内执行的 left++ 和 right--,所以使用<=,处理最后两个元素
if(nums[left] * nums[left] > nums[right] * nums[right]){
result[i] = nums[left] * nums[left];
left++;
i--;
}else{
result[i] = nums[right] * nums[right];
right--;
i--;
}
}
return result;
}
}
总结
包含负数的数组各元素平方之后,看似无需,实则有序,元素呈现两端大,中间小的形式,那么我们就可以考虑使用双指针,从数组两端向中间处理。
209.长度最小的子数组
思路
暴力法
遍历数组的元素,将他们分别作为最小子数组的起始位,然后从这一位向后累加,直到累加和大于等于target,我们就找到了以这一个元素作为起始位的最小子数组,然后计算数组长度,并比较是否是更小的子数组。
显然这种方法需要两个嵌套for循环,时间复杂度为O(n^2)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int sum;
int result = Integer.MAX_VALUE; //最终的结果
int subLength = 0; // 每次循环子序列的长度
for(int i = 0; i < nums.length; i++){ //连续子数组的起始位置
sum = 0;
for(int j = i; j < nums.length; j++){
sum += nums[j];
if(sum >= target){
subLength = j - i + 1;
if(result > subLength){
result = subLength;
break;
}
}
}
}
return result == Integer.MAX_VALUE? 0 : result;
}
}
滑动窗口
仔细思考上面的暴力法,可以发现我们其实重复滑动了很多次指针。
以题目中的[2,3,1,2,4,3]举例,我们以下标为1的元素作为子数组起始位置时,向后遍历: [3],[3,1],[3,1,2],[3,1,2,4],然后发现[3,1,2,4]满足题目条件,然后进入下个循环,这时我们又从1开始遍历累加:[1],[1,2],[1,2,4],发现[1,2,4]满足条件。在上述例子中,可以看到,我们多次遍历累加了重复元素。
其实在找到一个满足条件的子数组后,我们可以考虑循环从累加和中减去这一子数组的起始元素,并向后移动子数组的起始下标,看累加和是否仍大于target,当不满足条件了,直接循环向后移动子数组的结束下标,寻找下一个满足条件的子数组。在这种方法里,滑动窗口的两个下标都只是从原数组头移动到了原数组尾,没有重复移动和计算,所以时间复杂度仅为O(n)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int sum = 0;
int result = Integer.MAX_VALUE;
int subLength = 0;
int i = 0, j = 0; //滑动窗口的起始和结束位置
for(j = 0; j < nums.length; j++){
sum += nums[j]; //先滑动窗口结束位置,直到找到第一个满足条件的子数组
while(sum >= target){ //找到了一个满足的子数组
subLength = j - i + 1; //先计算子数组长度
result = subLength < result? subLength : result;
sum -= nums[i]; //先从sum中减去起始位置的值,再向后滑动窗口起始位置
i++;
}
}
return result == Integer.MAX_VALUE? 0 : result;
}
}
总结
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
虽然for循环中包含一个while,但是这个while只是用于向后移动滑动窗口起始下标,并没有重复滑动。
滑动窗口可以简单理解为:先向后移动窗口的结束下标,直到找到一个满足题目条件的窗口,找到这个窗口后,再向后移动窗口的起始下标,来寻找更小的窗口。直到当前的滑动窗口内的元素已经不满足条件,再次向后移动滑动窗口的结束下标以寻找下一个可以满足条件的窗口。
再简单一点就是:结束下标向后滑动来寻找可以满足条件的窗口,起始下标向后滑动来寻找最小满足条件的窗口。
59. 螺旋矩阵 II
思路
这道题转来转去的转迷糊了,一时没有找出规律,参考LeetCode题解:
初始化一个 n×n 大小的矩阵matrix,模拟整个向内环绕的填入过程:
定义当前左右上下边界 l,r,t,b,初始值 num = 1,迭代终止值 n * n;
当 num <= nn 时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环;
每次填入后执行 num ++,得到下一个需要填入的数字;
更新边界:例如从左到右填完后,上边界 t += 1,相当于上边界向内缩 1。
使用num <= nn而不是l < r || t < b作为迭代条件,是为了解决当n为奇数时,矩阵中心数字无法在迭代过程中被填充的问题。
最终返回 matrix 即可。
作者:Krahets
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int l = 0; //左边界所在的列数
int r = n - 1; //右边界所在的列数
int t = 0; //上边界所在的行数
int b = n - 1; //下边界所在的行数
int num = 1;
int i; //每轮循环改变的下标
while(num <= n*n){
for(i = l; i <= r; i++){
matrix[t][i] = num++;
}
t++;
for(i = t; i <= b; i++){
matrix[i][r] = num++;
}
r--;
for(i = r; i >= l; i--){
matrix[b][i] = num++;
}
b--;
for(i = b; i >= t; i--){
matrix[i][l] = num++;
}
l++;
}
return matrix;
}
}
总结
螺旋矩阵的填充规律比较难找,但是如果把上下左右四条边定义为变量,分别代表上下边的行数、左右边的列数,抓住每次循环中的不变量(比如一开始,是按照上边界从左向右赋值的,而上边界的行数t不变,变的只有列数),一条边赋值完成后,就将这条边对应的行或列收缩。研究清楚规律之后,写代码就很容易了。再次推荐leetcode这位大佬的题解,一目了然:https://leetcode.cn/problems/spiral-matrix-ii/