小马的力扣日记Day4 数组 #59 螺旋矩阵II #189 轮转数组

#59 螺旋矩阵II


思路

要求从外到内安排进这n*n个数,我首先想到的是迷宫算法。

我想先设置一个大小n*n的布尔型二维数组,将其作为迷宫壁,初始化将整个数组先置为false,代表此路不通,然后将n*n的范围置为true,代表可走。

按照“右下左上”的顺序来走,当判断到一个方向可走,沿途将下一个数填入下一个格子,并将其布尔数组置为false,走到墙壁为止。

但是随着轮数的增加,每次判断的边界条件也会发生变化,再加上C++下标的问题,使得这个想法实现起来比较困难。

于是我用纸(随手抓了几张卫生纸)手动运作了一下填数的过程。

我把一次“右下左上”这套动作看作一个round,那么n=1和2的时候round=1 ,n=3和4的时候round=2,也就是n/2之后向上取整,即round=int(n/2)+(n mod 2)。

通过对每一个round行进下标的变化总结,可以得到round=r的时候的下标范式。(图上应该是round r 而不是round n 笔误)


代码

class Solution {

public:

    vector> generateMatrix(int n) {

        vector> res(n, vector(n, 0)); 

        int round=(int (n/2)+n%2);

        int count,r,i,j=0;

        count=0;

        for (r=1; r<=round ; r++)

        {

            for (j=r-1; j<=n-r ; j++ )

            {

                count++;

                res[r-1][j]=count;

            }

            for (i=r; i<=n-r ; i++ )

            {

                count++;

                res[i][n-r]=count;

            }

            for (j=n-r-1; j>=r-1 ; j-- )

            {

                count++;

                res[n-r][j]=count;

            }

            for (i=n-r-1; i>=r ; i-- )

            {

                count++;

                res[i][r-1]=count;

            }

        }


        return res;

    }

};

反思


其实这个题主要是思考的时间比较久,实现起来倒是挺快的,也没怎么卡BUG。

实际情况中出过两个问题

第一个是vector的向量我不会声明,网上找了一个抄来的。

第二个问题是从右往左走和从下往上走这两个动作是i--和j--,我下意识写成了j++,报错之后才发现的。






#189 轮转数组



思路

这个题的思路比较多。

首先最暴力的解法,就是额外搞一个空数组,先把K及以后的元素依次放入,再将原数组第一个到第K-1个元素放入新数组,这样这个新数组就是所求的数组。

不过上述方法对空间非常浪费,所以我想到第二种方法,作为第一种方法的空间优化版本。就是用一个栈去做这个事情。不用申请一个新的数组,就只使用一个栈,将第K个及之后的元素入栈,然后将原数组的元素,往右移动K位,再将栈内元素出栈,组成数组的前面K个元素,就得到所求数组。这样做的好处就是,在大部分情况下,栈对空间的消耗是小于一个固定的大数组的,这就减少了空间消耗。

写到这里,自然而然地就能想到一种新的方法,就是将K位轮转拆分为K个单次轮转,每个单次轮转都将数组最后一个元素存下来,然后将其他数往右移动一位,最后将保存的元素放回数组第一位,这也就是题目进阶要求中想让我们达到的原地轮转,而不是单独开辟数组或者栈,代码实现的也是这种思路。(实际上这种方法超时了,虽然是n平方的时间复杂度,但是题目数据量太大了)

最后调整了思路,主要是看到有人用reverse这个翻转函数,那么这个题就进入到一个全新的思路里了。

我将移位过程看作两个包团的位置互换,如图k=3的时候,实际上我们要的结果就是,左边四个数组成的包团和右边三个数组成的包团之间的互换。

移位其实就是包团的互换

C++里并没有包团移位这个操作,但可以通过Reverse函数来实现。

如图Reverse就是将目标段落的数据翻转过来。

那么两个包团之间的互换,可以通过三次Reverse翻转来实现:

第一次翻转,翻转整个数组,这样左右两个包团的位置就实现了互换,尽管每个包团内部的顺序反了,但没关系,我们接下来就来把包团内部的顺序翻回来。

第二次翻转,翻转左包团内部,这样左包团内部实际上经历了两次翻转,内部的顺序恢复了正常。

第三次翻转,翻转右包团内部,同样的,右包团内部也经历了两次翻转,属于是顺序翻反了又翻回来。

这样就完成了整个操作。

各个包团内部翻转两次

代码

class Solution {

public:

    void rotate(vector& nums, int k) {

        int i,j,temp=0;

        int numsize=nums.size()-1;

        k=k%nums.size();

        if (k!=0)

        {

            reverse (nums.begin(),nums.end());

            reverse (nums.begin(),nums.begin()+k);

            reverse(nums.begin()+k,nums.end());

        }

    }

};


反思


一开始很快就做出来了,我的代码长这样,大部分情况是没问题的。但是提交的时候就发现第34组测试样例就是特别大的一个样本,就很恶心,我超时了,我心想我n平方的时间复杂度都能超时???????

class Solution {

public:

    void rotate(vector& nums, int k) {

        int i,j,temp=0;

        if (k!=0)

        {

        for (i=1;i

        {

            temp=nums[nums.size()-1];

            for (j=nums.size()-2 ; j>=0; j--)

            {

                nums[j+1]=nums[j];

            }

            nums[0]=temp;

        }

        }

    }

};

后来万佬告诉我,10^5的数据量意味着几乎只有时间复杂度不高于nlogn的算法能通过,10^6的话就几乎只能用n的算法,这是个不成文的经验,我还是太年轻了。

最后用翻转包团的方法解决了问题,其实我觉得这个题可以提炼出一种通用的方法。

即:当我们需要将数组的两个相邻子数组进行位置交换时,我们可以通过使用三次reverse操作来实现。


我又在想,这个包团交换的方法,能不能推广到非相邻的子数组呢,答案是肯定的。

如图,交换两个不相邻的包团,实际上我们可以看做是三个包团,交换不相邻的两个包团,而中间的包团的对外顺序不变,内部顺序也不变。


也就是说我们需要四次Reverse:

第一次,翻转整个数组,这样左右包团的相对顺序交换,而中间包团的对外位置不变。

然后我们分别对三个包团进行单独翻转,将它们的内部顺序改回来,就完成了不相邻子数组的位置交换。

即:当我们需要将数组的两个子数组进行位置交换时,我们可以通过使用三或四次reverse操作来实现。

你可能感兴趣的:(小马的力扣日记Day4 数组 #59 螺旋矩阵II #189 轮转数组)