【算法速刷(3/100)】LeetCode —— 54.螺旋矩阵

目录

层次遍历法

1. 缩圈法进行层次遍历

2. 循环的边界

3.  上右遍历后是否要进行下左遍历 

模拟法

        遍历的步数

        遍历的方向


关于这一类矩阵题都可以通过模拟的方法进行解答,只是代码复杂的高低不同

首先是与Leetcode官方的题解一样的方法

层次遍历法

层次遍历法最重要的点有三个地方

1. 缩圈法进行层次遍历

        整体的解题思路,从外围向内围进行顺时针遍历,外围遍历完就进行缩圈,对内层进行遍历,从而达成螺旋顺时针的遍历顺序

2. 循环的边界

        第二个重要的是循环边界,关于遍历行列时的取值,如下中的while循环语句

        首先要说的是这些while循环语句可以改成for循环,这样就可以在循环声明语句中进行创建变量和赋初值

        其次是为什么是p <=right而不是p<right,很容易想到的一个点,就是如果left == right,那么这个while就不会被执行,因此需要结结实实的循环到right上,才能保证在双方左右或者上下边界重合时,正确的进行遍历对应的行列。

也正因如此,才需要在下一次初值时使用 p = upper + 1来避免重复遍历已遍历过的点

//以上层和右层遍历为例子
int p = left;
while(p <= right)                        
   res.push_back(matrix[upper][p++]);    //遍历上层
p = upper + 1;
while(p <= bottom)
   res.push_back(matrix[p++][right]);    //遍历右层

3.  上右遍历后是否要进行下左遍历 

        第三个重要的是在上右两次遍历后,在开启下左遍历前,要先经过判断在遍历

为什么?

        通过if的条件很容易就可以看出来,这是为了保证只有在四个边界没有左右或者上下重合时才进行遍历。

        原因也很简单,当出现重合时,如果继续遍历,就意味着将同一个序列,正着遍历一次(上右遍历)后,又反着遍历(下左)了一次,这是不正确的

vector<int> spiralOrder(vector<vector<int>>& matrix) {
        //按层模拟,先模拟外层,再层层递推到下一层
        vector<int> res;

        int m = matrix.size(), n = matrix[0].size();
        int upper = 0, bottom = m - 1, left = 0, right = n - 1;

        while(upper <= bottom && left <= right)
        {
            int p = left;
            while(p <= right)                        
                res.push_back(matrix[upper][p++]);    //遍历上层
            p = upper + 1;
            while(p <= bottom)
                res.push_back(matrix[p++][right]);    //遍历右层
            
            if(left < right && upper < bottom)
            {
                p = right - 1;
                while(p >= left)
                    res.push_back(matrix[bottom][p--]);   //遍历下层
                p = bottom - 1;
                while(p > upper)
                    res.push_back(matrix[p--][left]);     //遍历左层
            }

            left++;
            right--;
            upper++;
            bottom--;
        }

        return res;
    }

上面是官方的解法和我对中间一些细节的思考

之后还有一个是使用模拟法来进行解决,这个方法需要画图来进行充分的理解,也是我一开始想的解决办法,全网唯一,童叟无欺()

模拟法

模拟法就比较复杂了

模拟法主要源于我对遍历过程的规律发现

        遍历的步数

                设m为矩阵长,n为矩阵宽,则模拟法走的步长分别是

                 n-1 -> m-1 -> n-1 -> m-2 -> n-2 -> m-3......步,依次类推

                可发现,排除第一次后,每走一轮(一行加一列),步数就会减一

                因此使用一个 变量 z来记录这个减掉的值,使用step来记录步数

                同时将初值赋值为 n - 1来特殊处理

        遍历的方向

                和步数不同,方向不存在首次特殊情况,方向在遍历完一个行列后(上层和右层),

         会向反方向再遍历一个行列(下层和左层),整体方向呈现的规律是

                正 -> 正 -> 负 -> 负 -> 正 -> 正 ->...

        因此,直接使用一个dir来记录这个变量即可

        基于以上规律事实,可以直接使用模拟法来进行模拟

vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;
       int m = matrix.size();       //矩阵长
       int n = matrix[0].size();    //矩阵宽
       int dir = 1;                 //遍历方向(1为正向,-1为反向)
       bool isM = false;            //表示是否在列上进行遍历
       int x = 0, y= 0, z = 0;      //x, y表示在矩阵中的位置,z记录了当前遍历了几轮(一轮代表一次行列)
       int step = n - 1;            //本次遍历的剩余步数
       
       res.push_back(matrix[0][0]); //先将第一个数放入(遍历时处理不到该元素)
       
       while(res.size() < m * n )   //如果结果数组长度等于矩阵大小,说明遍历完成
       {
           if(step <= 0)            //剩余步数小于等于0,说明该重置变量了
           {
               if(isM)              //当前在列上进行遍历
               {
                   step = n - z;    //步数重置
                   dir *= -1;       //方向取反
               }
               else                 //当前在行上进行遍历
               {
                   z++;             //轮次加1
                   step = m - z;    //步数重置(比上次少一步)
               }

               isM = !isM;          //更新当前在列上还是在行上
           }
           
           if(isM)                  //根据在列上还是在行上以及方向对坐标进行加减
               x += dir;
           else
               y += dir;
           
           res.push_back(matrix[x][y]);     //推入结果数组
           step--;                          //计步器减一
       }
       
       return res;
    }

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值