剑指Offer-21-顺时针打印矩阵

题目

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
剑指Offer-21-顺时针打印矩阵_第1张图片
则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

解析

预备知识

我们需要对顺时针打印矩阵进行建模,也就是可以用一种规律描述这个问题。还是画图来吧,假设有4 X 4矩阵,顺时针的轨迹为:
剑指Offer-21-顺时针打印矩阵_第2张图片
假设有4 X 3矩阵,顺时针的轨迹为:
剑指Offer-21-顺时针打印矩阵_第3张图片
假设有3 X 4矩阵,顺时针的轨迹为:
剑指Offer-21-顺时针打印矩阵_第4张图片
假设有1 X 4矩阵,顺时针轨迹为:
这里写图片描述
假设有4 X 1矩阵,顺时针轨迹为:
剑指Offer-21-顺时针打印矩阵_第5张图片
通过以上我们可以发现其实矩阵的顺时针轨迹可以划分为几个圈。也就说我们可以从外到内一圈一圈的打印。所以现在问题转化为给定一个矩阵,如何判断出该打印几圈,通过以上画图,我们发现圈数的确定取决于矩阵的最短边。且每一圈的左上角顶点必为上一圈的顶点的坐标各加一。想象一下就明白了,我们是从外圈到内圈打,左上角坐标必然比上一多1嘛。
知道了以上,我们得出圈数为(最短边 + 1) / 2。因为圈的左上角顶点走到中间结束,加1的目的是为了处理奇数情况,奇数情况最后一圈为1行或1列。

因为从外圈到内圈结束有2种情况,当最短边为偶数时,最后一圈为2行n列或n行2列;当最短边为奇数时,最后一圈为1行n列或者n行1列。

思路一

我们通过预备知识已经知道了如何确定圈数,那么接下来着重考虑如何打印每一圈。
我们可以按各边的顺序依次打印,每一圈的走上角和右下角的坐标都知道了,那么这个圈也就确定了。分4个边打印即可。

左上角的坐标为(i, i), 我们是从外圈到内圈打,左上角坐标必然比上一多1嘛。
相反,右下角的坐标为(行总数-i,列总数-i)。我们是从外圈到内圈打,右上角坐标必然比上一少1嘛。
i为圈数。

但是需要注意两点:
1. 当打印下边的时候,要看看右下角的横坐标是否大于左上角,避免圈是一行的时候,重复打印。
2. 当打印左边的时候,要看看右下角的纵坐标是否大于左上角,避免圈是一列的时候,重复打印。

    /**
     * 1.确定圈数
     * 2.打印每一圈
     * @param matrix
     * @return
     */
    public static ArrayList printMatrix(int [][] matrix) {
        if(matrix == null) {
            return null;
        }
        ArrayList result = new ArrayList<>();
        int row = matrix.length;
        int col = matrix[0].length;
        int countOfCircle = (Math.min(row, col) + 1) / 2;
        for(int i = 0; i < countOfCircle; i++) {
            int endX = row - i - 1;
            int endY = col - i - 1;
            //打印上边
            for(int j = i; j <= endY; j++) {
                result.add(matrix[i][j]);
            }
            //打印右边
            for(int j = i + 1; j <= endX; j++) {
                result.add(matrix[j][endY]);
            }
            //打印下边
            if(endX > i) {
                for (int j = endY - 1; j >= i; j--) {
                    result.add(matrix[endX][j]);
                }
            }
            //打印左边
            if(endY > i) {
                for (int j = endX - 1; j > i; j--) {
                    result.add(matrix[j][i]);
                }
            }
        }
        return result;
    }

思路二

模拟魔方逆时针旋转,我们每次打印矩阵第一行,删除第一行所有元素,然后逆时针旋转矩阵,直到矩阵最后的行为1为止,这时说明已到达最后一个元素。画图解释:假设给定4 X 3矩阵
1. 打印第一行
剑指Offer-21-顺时针打印矩阵_第6张图片
2. 删除第一行,并逆时针旋转
剑指Offer-21-顺时针打印矩阵_第7张图片
3. 删除第一行,继续逆时针旋转
剑指Offer-21-顺时针打印矩阵_第8张图片
4. 删除第一行,继续逆时针旋转
这里写图片描述

  1. 删除第一行,继续逆时针旋转
    这里写图片描述
  2. 删除第一行,已是最后一个元素,结束
    这里写图片描述
    /**
     * 魔方旋转打印
     * @param matrix
     * @return
     */
    public static ArrayList printMatrix2(int [][] matrix) {
        if(matrix == null) {
            return null;
        }
        ArrayList result = new ArrayList<>();
        int row = matrix.length;
        while(row != 0) {
            for(int i = 0; i < matrix[0].length; i++) {
                result.add(matrix[0][i]);
            }
            //当row == 1时,说明已是最后一个元素
            if(row == 1) {
                break;
            }
            matrix = anticlockwiseReverse(matrix);
            row = matrix.length;
        }
        return result;
    }

    /**
     * 逆时针旋转矩阵
     * @param matrix
     * @return
     */
    public static int[][] anticlockwiseReverse(int[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;
        //删除已打印的第一行
        int[][] newMatrix = new int[col][row - 1];
        for(int i = 0; i < col; i++) {
            for(int j = 0; j < row - 1; j++) {
               newMatrix[i][j] = matrix[j + 1][col - 1 - i];
            }
        }
        return newMatrix;
    }

总结

多画图,复杂问题分步解。

你可能感兴趣的:(不刷题心里难受,剑指Offer)