双指针法、滑动窗口法、螺旋矩阵

发布时间 2023-09-21 22:53:09作者: 你好,周周女侠

1.双指针法解有序数组的平方

1.1题目要求

LeetCode977有序数组的平方
题目内容:给你一个按非递减顺序排序的整数数组 nums,返回 每个数字的平方组成的新数组,要求也按非递减顺序排序。

示例 1:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 2:

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

1.2题目思路

1.2.1暴力破解法

  声明:暴力破解法放到Leetcode会超时,因此本题主要还是用双指针法,并给出双指针法的源代码,暴力破解法的思路如下:
1.首先用一个for循环,计算数组平方后的数字
2.利用快速排序,对数组进行重新排序,按照题目要求从小到大排序

1.2.2双指针法

1.2.2.1 为什么会想到滑动窗口法呢?

  首先我们来观察一下这道题,题目本来就是按照由小到大的顺序排列的,在平方之后,会因为负数的增大而改变顺序,如果是这样的话,那么平方后最大的数字必然是在数组的两边,并且由两边到中间逐渐递减。

1.2.2.2 本题思路介绍

  双指针法解本道题的主要思路如下:
1.首先分别设立两个指针i,j;
2.i指针从数组的前面开始遍历,而j指针从数组的后面开始遍历,计算并比较nums[i]×nums[i]和nums[j]×nums[j],取较大的放在result数组(返回结果的数组)的最后
3.所指元素已经放到result数组中的指针向中间移动,继续第2步操作,直到把所有的序列排好(i与j相遇)

源代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        vector<int> result(nums.size(),0);
        int n=nums.size();
        int t=n-1; //t指针用于更新result数组的下标
        int i=0,j=n-1; //i指针从最前面开始遍历,j指针从最后面开始遍历
        while(i<=j) //注意这里要带等号,因为所遍历的最后一个元素也要放到result数组中
        {
            if(nums[i]*nums[i]>nums[j]*nums[j])
            {
                result[t]=nums[i]*nums[i];
                i++;
                t--;
            }
            else
            {
                result[t]=nums[j]*nums[j];
                j--;
                t--;
            }
        }
        return result;

    }
};

2.滑动窗口法解长度最小的子数组

2.1题目要求

LeetCode209长度最小的子数组
题目内容:给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

2.2题目思路

2.2.1.暴力破解法

  用两层for循环,从头开始遍历,第一层for循环(i)从第一个元素遍历到最后一个,第二个for循环从第一个for循环的元素(i)开始遍历,遍历到符合题意,即sum>=target为止,源代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n=nums.size();
        int min=10000;
        for(int i=0;i<n;i++)
        {
            int sum=0;
            int t=0;
            //这里忘记加j<n导致溢出了
            for(int j=i;sum<target&&j<n;j++)
            {
                sum += nums[j];
                t++;
            }
            //这里不能直接写成t<min
            if(sum>=target&&t<min)
            min=t;
        }
        if(min==10000)
        min=0;
        return min;

    }
};

2.2.2.1 易错点

  如你所见,我把我写的过程中犯的错误标注到代码里了,下面我将一一解释我所犯的错误,以及得出的结果,希望你不要再犯。
错误一:第二层for循环忘记少加了一个j的限制条件,即j<n,导致数组越界。
  这个错误在LeetCode中的表现是其下一行代码sum+=nums[j]报错,并且告诉你int数据类型越界,这个错误一时间不容易想到是for循环的错,正常人的思路是,先去查看target的取值范围,发现好像没有越界,我后来是看到了其显示的是两个负数相加,才知道是数组越界。

错误二:直接写成了t<min,就把t赋值给min,而没有考虑循环结束后是否满足我们所需要的条件。

2.2.2 滑动窗口法

  在讲解滑动窗口法之前,我们首先来思考本题为什么可以用滑动窗口法:
  观察题干,要求我们找到长度最小的连续子数组,而滑动窗口法的前一个指针正是往后滑动,去减少数组的长度,每次滑动都是等i指向了在该for循环的j下的最大值后,j才会向后滑动,只要j向后滑动,这里的长度就会变大,大于现在的min,所以不用考虑在这个j下i往前挪可能也有符合题意的子数组。(这一段有点讲不清楚啊啊啊,有没有大佬可以概括一下)

源代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n=nums.size();
        //还是不能直接给min赋值为100000
        int i=0,j=0,min=INT32_MAX;
        int sum=0;
        for(j=0;j<n;j++)
        {
            sum+=nums[j];
            while(sum>=target)
            {
                if(min>(j-i+1))
                min=j-i+1;
                sum-=nums[i++];
            }
        }
	//主要是这里的问题
        if(min==INT32_MAX)
        min=0;
        return min;

    }
};

2.2.2.1易错点

  暴力破解法的时候,我直接给min赋值了100000,也就是最大的target,因为暴力破解法在LeetCode中也跑不出来,所以我也就没有意识到问题的错误,但是当用滑动窗口法可以跑出来之后,我发现有一个测试用例,min正好等于100000的时候符合题意,也就是正好取到它的最大值,然后我试图改进,把if(min= =100000)这个判断语句改成if(min==100000)&&target!=sum),结果发现还是无法通过那个测试样例,我返回去看了一眼代码,才发现当sum>=target,也就是说,当sum=target的情况下,进入while循环依旧会改变sum的值(这里写成sum>=target是因为题目说的是大于等于)。
  所以这里还是写成min= = INT32_MAX,因为加最后一个if判断语句的目的就是看看是否有整个数组都加进去但是仍然小于target的情况。

3.螺旋矩阵II

3.1题目要求

LeetCode59螺旋矩阵II
题目内容:给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

3.2 解题思路

本题没有什么算法技巧,纯逻辑题,但是刚做这道题时容易处理不好边界问题,卡哥的建议是,四个方向的便利都选择相同的区间,即左闭右开,也就是说,把最后一个放到下一个for循环中处理,下面简要介绍一下本题定义的参数:

loop:要循环的次数
target:循环了第几圈,主要是用来计算这次循环的矩形长度(n-target)
startx:循环开始的行数
starty:循环开始的列数

值得注意的是:当n为奇数时,循环完之后还要在最中间的小方格加上最大的数字

源代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> a(n, vector<int>(n, 0));
        int startx=0,starty=0,target=1;
        int num=1;
        int loop=n/2;
        while(loop--)
        {
            int j=starty;
	//横向循环上面一行,本行最后一个(也就是右上角)不在这层循环里
            for(;j<n-target;j++)
            {
                a[startx][j]=num++;
            }
	//纵向循环最后一列,右上角在这层循环里,但是这一列的最后一个(右下角)不在这层循环里
            int i=startx;
            for(;i<n-target;i++)
            {
                a[i][j]=num++;
            }
	//横向循环最下面一行,右下角在这个循环里,左下角不在
            for(;j>starty;j--)
            {
                a[i][j]=num++;
            }
	//纵向循环最前面一列,左下角在这个循环里,但右上角不在
            for(;i>startx;i--)
            {
                a[i][j]=num++;
            }
            target++;
            startx++;
            starty++;
        }
        if(n%2==1)
        a[n/2][n/2]=num++;
        return a;

    }
};

总结反思

1.在找子数组的最大或最小长度时,可以考虑滑动窗口法
2.要为某个变量设最大值时,最好不要瞎赋值,而是写成INT32_MAX这种
3.像第三个题目这种,最好四个循环保持区间端点的一致性,不然会很难调这个边界值,奇数和偶数的不同不一定非要分开写两个程序,有时候在后面加个判断语句就可以了
4.注意一定不要让数组越界
5.当要赋值min这种数值时,除了要判断其大小,还要判断是否满足条件

写给自己的话

  不要过分对未来焦虑,不要过分考虑未来,不要考虑失败后怎么样,先把自己手头的事情做好,一点一点的来,进步是需要慢慢积累的,加油呀!
  以及,我好喜欢学习算法时思考的感觉和做出一道题的成就感!