五、59.螺旋矩阵II

五、59.螺旋矩阵II

题解参考:代码随想录 (programmercarl.com)

题目链接:59. 螺旋矩阵 II - 力扣(LeetCode)

1. 题目描述

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

示例 1:

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

输入:n = 1
输出:[[1]]

提示:

  • 1 <= n <= 20

2. 思路

本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。我们在这篇文章一、704 二分查找中讲解了二分法,提到如果要写出正确的二分法一定要坚持循环不变量原则。对于本题依然要坚持这个原则,否则很容易被各个边界条件弄混,不仅费时,而且还有很大的概率会出错。

模拟顺时针画正方形矩阵步骤如下:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上

由外向内一圈一圈这么画下去。可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是很容易被自己绕糊涂。

本题我们坚持使用左闭右开原则,保证每一行(列)的最后一个元素交给下一列(行)来处理,这样就能保证循环不变量原则,也就是统一的处理规则。

这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

那么我按照左闭右开的原则,来画一圈,大家看一下:

image-20230412201308239

这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。这样就坚持了每条边左闭右开的原则。

3. C语言版本

大致实现步骤如下所示:

  • 开辟返回结果数组,注意返回的是二维数组,在C语言中二维数组本质上是由多个一维数组构成。(每一个一维数的大小必须相同),因此我们在开辟数组空间的时候,returnColumnSizes需要开辟int*类型的空间
  • 定义循环的起始位置startX、startY、循环圈数loop、偏移量offset、中间位置mid、空格值count
  • 根据n的奇偶性填充数组
    • n为偶数:直接按照上面的左闭右开原则给数组赋值,每次循环结束,起始位置和偏移量都需要+1,循环圈数-1,空格值+1,
    • n为奇数:如果n为奇数,那么在填充完外围的数组后,最后会剩下中间的位置需要单独填充

代码如下所示

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 * 时间复杂度:O(n^2)
 * 空间复杂度:O(1)
 */
int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){
    // 初始化返回结果数组的大小,n行
    *returnSize = n; 
    // 每行元素都是一个一维数组,各行的数组大小需要用一个数组来存储
    *returnColumnSizes = (int*)malloc(sizeof(int) * n); 
    // 初始化返回结果数组,n个一维数组
    int** ans = (int**)malloc(sizeof(int*) * n);
    int i, j;
    for (i = 0; i < n; i++) {
        // 给每一行元素开辟空间
        ans[i] = (int*)malloc(sizeof(int) * n); 
        // 第i行的数组大小为n
        (*returnColumnSizes)[i] = n; 
    }
    // 设置每次循环的起始位置
    int startX = 0;
    int startY = 0;
    // 设置循环圈数,例如:n = 3, 循环1圈,n = 4,循环2圈
    int loop = n / 2;
    // 设置矩阵中间的位置,当n为奇数时,需要给中间位置单独赋值
    // 例如:n = 3, 中间位置(1,1); n = 5, 中间位置(2,2)
    int mid = n / 2;
    // 设置偏移量
    int offset = 1;
    // 设置每一个空格的值
    int count = 1;
    while (loop--) {
        i = startX;
        j = startY;
        // 下面4个for循环就是模拟转了1圈
        // 上行从左往右填充(左闭右开区间)
        for (j = startY; j < n - offset; j++) {
            ans[startX][j] = count++;
        }
        // 右列从上到下填充(左闭右开区间)
        for (i = startX; i < n - offset; i++) {
            // 此时元素在最右边1列,所以为j
            ans[i][j] = count++; 
        }
        // 下行从右往左填充(左闭右开区间)
        for (; j > startY; j--) {
            ans[i][j] = count++;
        }
        // 左列从下到上填充(左闭右开区间)
        for (; i > startX; i--) {
            ans[i][j] = count++;
        }
        // 下一圈开始时,startX和startY都需要+1
        // 例如:第二圈,x和y起始位置都变为1
        startX++;
        startY++;
        // 偏移量也+1
        offset++;
    }
    // 如果n为奇数,单独填充中间位置的元素值
    if (n % 2 == 1) {
        ans[mid][mid] = count;
    }
    return ans;
}
  • 时间复杂度:O(n^2),模拟遍历二维矩阵的时间。

  • 空间复杂度:O(1),除了返回的矩阵以外,空间复杂度是常数。

4. Java版本

4. 1 版本一

这个版本和上面C语言的版本的实现方法一样,只是换用Java编写了上述代码

代码如下所示

class Solution {
    public int[][] generateMatrix(int n) {
        // 定义返回数组
        int[][] ans = new int[n][n];
        // 设置每次循环的起始位置
        int startX = 0;
        int startY = 0;
        // 设置循环圈数,例如:n = 3, 循环1圈,n = 4,循环2圈
        int loop = n / 2;
        // 设置矩阵中间的位置,当n为奇数时,需要给中间位置单独赋值
        // 例如:n = 3, 中间位置(1,1); n = 5, 中间位置(2,2)
        int mid = n / 2;
        // 设置偏移量
        int offset = 1;
        // 设置每一个空格的值
        int count = 1;
        int i, j;
        while (loop-- > 0) {
            i = startX;
            j = startY;
            // 下面4个for循环就是模拟转了1圈
            // 上行从左往右填充(左闭右开区间)
            for (j = startY; j < n - offset; j++) {
                ans[startX][j] = count++;
            }
            // 右列从上到下填充(左闭右开区间)
            for (i = startX; i < n - offset; i++) {
                // 此时元素在最右边1列,所以为j
                ans[i][j] = count++; 
            }
            // 下行从右往左填充(左闭右开区间)
            for (; j > startY; j--) {
                ans[i][j] = count++;
            }
            // 左列从下到上填充(左闭右开区间)
            for (; i > startX; i--) {
                ans[i][j] = count++;
            }
            // 下一圈开始时,startX和startY都需要+1
            // 例如:第二圈,x和y起始位置都变为1
            startX++;
            startY++;
            // 偏移量也+1
            offset++;
        }
        // 如果n为奇数,单独填充中间位置的元素值
        if (n % 2 == 1) {
            ans[mid][mid] = count;
        }
        return ans;
    }
}
  • 时间复杂度:O(n^2),模拟遍历二维矩阵的时间。

  • 空间复杂度:O(1),除了返回的矩阵以外,空间复杂度是常数。

4.2 版本二

这个版本思路一样,仍然坚持左闭右开原则,实现方式略有改变,大致思路如下所示:

  • 这里的循环条件loop从0开始计数,用loop代表当前循环的次数,同时也代表偏移量,即每次循环后loop++,表示当前循环次数和偏移量都+1,同样能保证循环不变量
  • 填充下行和左列时for循环终止条件时>=
  • 这里的起始位置只用一个start表示,每次循环结束后+1
  • 其他实现逻辑和上面C语言版本的一致

代码如下所示

class Solution {
    public int[][] generateMatrix(int n) {
        // 定义返回数组
        int[][] ans = new int[n][n];
        // 设置循环起始位置
        int start = 0;
        // 循环总圈数,例如:n = 3, 循环1圈,n = 4,循环2圈
        int allLoop = n / 2;
        // 当前处于循环第几圈
        int loop = 0;
        // 设置矩阵中间的位置,当n为奇数时,需要给中间位置单独赋值
        // 例如:n = 3, 中间位置(1,1); n = 5, 中间位置(2,2)
        int mid = n / 2;
        // 设置每一个空格的值
        int count = 1;
        int i, j;
        // 判断边界之后,loop从1开始计数
        while (loop++ < n / 2) {
            // 下面4个for循环就是模拟转了1圈
            // 上行从左往右填充(左闭右开区间)
            for (j = start; j < n - loop; j++) {
                ans[start][j] = count++;
            }
            // 右列从上到下填充(左闭右开区间)
            for (i = start; i < n - loop; i++) {
                // 此时元素在最右边1列,所以为j
                ans[i][j] = count++; 
            }
            // 下行从右往左填充(左闭右开区间)
            for (; j >= loop; j--) {
                ans[i][j] = count++;
            }
            // 左列从下到上填充(左闭右开区间)
            for (; i >= loop; i--) {
                ans[i][j] = count++;
            }
            // 下一圈开始时,start+1
            // 例如:第二圈,x和y起始位置都变为1
            start++;
        }
        // 如果n为奇数,单独填充中间位置的元素值
        if (n % 2 == 1) {
            ans[mid][mid] = count;
        }
        return ans;
    }
}
  • 时间复杂度:O(n^2),模拟遍历二维矩阵的时间。

  • 空间复杂度:O(1),除了返回的矩阵以外,空间复杂度是常数。

5. 总结

这道题目之所以一直写不好,代码越写越乱,就是因为在画每一条边的时候,一会左开右闭,一会又来左闭右开,没有坚持使用同一个填充规则,岂能不乱。因此我们在判断边界条件的时候,一定要坚持循环不变量原则,即坚持使用同一个填充规则,例如这题我们一开始使用左闭右开原则,那么后面要保持一致,这样方便理清逻辑,不仅省力,而且不容易把自己绕糊涂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值