剑指offer-面试题 20:顺时针打印矩阵

# 面试题 20:顺时针打印矩阵
题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。


1. 思路

  题目理解很简单。重要的是如何建模呢?容易想到,要做到从外往里打印,每当执行一次打印操作,必须将某个参数减1(我们先不用考虑具体的代码细节,只需要有这个思路即可)。对于具体的参数设计,我们在后面会有详细的解释。我们通过对题目分析,除了代码的实现,得出了两个比较重要的问题:
  (1)代码如何终止?
  (2)而对于极端情况(例如只有1行或者1列,或者只有一个数)该怎么做?
以下是我的详细思路:

1.1 建模


打印矩阵

  如图是一个二维矩阵的简单示意图。如果我们要顺时针打印一个矩阵,那么通常会分成四部分,先左右横,后上下竖,后右左横,最后下上竖,一个圈完成,之后类推。
实现顺时针转动的话,每部分完成后需要将某个参数减1(我们先不去关注具体是哪个参数),之后执行下一部分。
根据上述思路,我设置了四个轴,分别为: up轴right轴down轴left轴
完成一圈的过程是:
(1)左右横, up轴 向下移动一行;
(2)上下竖, right轴 向左移动一行;
(3)右左横, down轴 向上移动一行;
(4)下上竖, left轴 向右一行。
之后up轴从第二行开始(假设初始值是第1行)从左往右遍历,right轴从倒数第二列开始向下遍历(初始值最后一列),down轴从倒数第二行开始从右往左遍历(初始值为倒数第一行),left轴从第二列从下往上遍历(初始值为第一列)……

1.2 对结束条件的判断

那么如何判断执行到头了呢?我们可以从简单的情况开始思考:
(1)当二维数组只有一个数字时,行和列数均为1,那么只要打印出这个数就可以了。此时四个轴的值是什么呢?up,down,right和left均为1;
(2)当二维数组只有一行时,up = down ,left = 1,right = 列数(这里是列数)。
打印操作执行为打印这一行。用代码实现就是:

    for(int i = 0; i < rowLen - 1; i++) {  //列数不为1,逐个打印
        System.out.println(a[0][i] + " ");  //只有一行
    }

(3)当二维数组只有一列时,left = right ,up = 1,down = 行数(这里是行数)。
打印操作执行为打印这一列,代码跟上面类似。
(4)更复杂的情形:
多行多列时,我们发现,打印的操作都是一个for循环,那么怎么确定到底是打印数组的哪条边呢?
  我的想法是:打印有四个方向,每次从左往右横向打印完了,之后会打印从上往下。下次横向再打印时,方向肯定是从右往左;
  每次从上往下纵向打印完了,之后横向从左往右打印。下次纵向再打印时,方向总是从下往上。
  所以想法是设定两个方向判断变量rDir和cDir:其中rDir为行的方向变量,为1时从左往右,为0从右往左打印;cDir为列的方向变量,为1从上往下,为0从下往上打印。在代码中会对这两个变量进行赋值,用它们来判断打印的方向,下面是一个简单的情形:

    int rDir = 1;  // 行的方向变量,为1从左往右,为0从右往左打印
    int cDir = 1;  // 列的方向变量,为1从上往下,为0从下往上打印
    ……
    if(rDir == 1) {
        printLR(a, left, right, up, down);
        up++;
        rDir = 0;
    }else {
        printRL(a, left, right, up, down);
        down--;
        rDir = 1;
    }   
    ……
    if(up <= down) {
        if(cDir == 1) {
            printUD(a, left, right, up, down);
            right--;
            cDir = 0;
        }else {
            printDU(a, left, right, up, down);
            left++;
            cDir = 1;           
        }   

rDir默认为1,也就是从左到右打印,打印完后rDir赋值为0;为0时,代表从右到左打印,打印完后rDir变成0。
cDir默认为1,也就是从下到上打印,打印完后cDir赋值为0;为0时,代表从上到下打印,打印完后cDir变成0。
其中四个print方法是封装for循环的按方向打印的函数,具体实现会在后续代码中给出。
以上是我们对打印方向的确定和简单实现,那么回到我们最初提出的问题:
如何判断结束呢?其实就是判断up和down,left和right的大小。这部分比较简单,我们直接看代码:


2. 代码

package swordOffer;
/**面试题 20:顺时针打印矩阵
 * 题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
 * 
 * @author Stephen Huge
 * @date 17/04/22
 */
public class Ex20PrintMatrixClockwisely {

    public static void main(String[] args) {
//      int[][] array = new int[3][0];
//      int[][] array = {{1,2,3,4,5}};
//      int[][] array = {{1},{2},{3},{4},{5}};
//      int[][] array = {{1,2,3},{2,3,4}};      
        int[][] array = {{1,2},{2,3},{3,4},{4,5},{5,6}};        
//      int[][] array = {{1,2,3},{2,3,4},{3,4,5}};
//      int[][] array={{1,2,3,4,5},{5,6,7,8,9},{9,10,11,12,13},{13,14,15,16,17},{17,18,19,20,21},{21,22,23,24,25}};
        Ex20PrintMatrixClockwisely pmcw = new Ex20PrintMatrixClockwisely();
        pmcw.printMatrixClociwisely(array);
    }
    public void printMatrixClociwisely(int[][] a) {
        if(a == null || a.length == 0 || (a.length > 0 && a[0].length == 0)) {
            System.out.println("Length error!");
            return;
        }

        int up = 0;
        int down = a.length - 1;    //每列的行数
        int left = 0;
        int right = a[0].length - 1;//每行的列数

        int rDir = 1;   // 行的方向变量,为1从左往右,为0从右往左打印
        int cDir = 1;   // 列的方向变量,为1从上往下,为0从下往上打印

        while(true) {
            if(left <= right) {
                if(rDir == 1) {
                    printLR(a, left, right, up, down);
                    up++;
                    rDir = 0;
                }else {
                    printRL(a, left, right, up, down);
                    down--;
                    rDir = 1;
                }   
            }else {
                System.out.println("left > right");
                return;
            }
            if(up <= down) {
                if(cDir == 1) {
                    printUD(a, left, right, up, down);
                    right--;
                    cDir = 0;
                }else {
                    printDU(a, left, right, up, down);
                    left++;
                    cDir = 1;           
                }   
            }else {
                System.out.println("up < down");
                return;
            }   
        }
    }
    private void printLR(int[][] a, int left, int right, int up, int down) {    //从左到右打印
        for(int i = left; i <= right; i++) {
            System.out.print(a[up][i] + " ");
        }
        System.out.print(". ");
    }
    private void printRL(int[][] a, int left, int right, int up, int down) {    //从右到左打印
        for(int i = right; i >= left; i--) {
            System.out.print(a[down][i] + " ");
        }
        System.out.print(". ");
    }
    private void printUD(int[][] a, int left, int right, int up, int down) {    //从上到下打印
        for(int i = up; i <= down; i++) {
            System.out.print(a[i][right] + " ");
        }
        System.out.print(". ");
    }
    private void printDU(int[][] a, int left, int right, int up, int down) {    //从下到上打印
        for(int l = down;l >= up; l--) {
        System.out.print(a[l][left] + " ");
    }
        System.out.print(". ");
    }
}

运行结果:

1 2 . 3 4 5 6 . 5 . 4 3 2 . 

在一个无限循环中,不断的判断up和down,left和right的大小。如果up > down 或者left > right ,循环结束,打印完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值