[2021校招必看之Java版《剑指offer》-19] 顺时针打印矩阵

1、题目描述

  【JZ19】输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
  例如,如果输入如下4 X 4矩阵:
  1 2 3 4
  5 6 7 8
  9 10 11 12
  13 14 15 16
  则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
  知识点:矩阵
  难度:☆

2、解题思路

2.1 递归法

  假设遍历到的数字为 temp,上一个遍历到的数字为 last,下一个遍历到的数字为 next。

  于是,每遍历到一个 temp ,都存在两个问题:
  1、last -> temp 的方向是什么;
  2、temp -> next 的方向又是什么。

  比如,以题目的矩阵为例子,当遍历到数字 3 时,last -> temp 为从左向右;temp -> next 也是从左向右。当遍历到数字 13 时,last -> temp 为从右向左,temp -> next 为从下向上。

  因此,我们可以用递归的方式解决顺时针打印矩阵的问题。

  我们能够事先直到 last -> temp 的方向,然后根据情况分析处 temp -> next 的方向即可。比如一开始第一个元素肯定是从左向右(顺时针)。

  定义一个大小和输入矩阵一样大小的二维矩阵 mark[][] 用于标记位置走过的痕迹,如果位置走过,则值为 1,否则为 0。当 mark 都标记为 1 ,说明已经顺时针遍历完毕。

  以前行方向分别记上下左右为u、d、l、r

  以u为例,当 last -> temp 为u时,说明现在是往上走的趋势,那么下一步可能往上走,也可能往右走,也可能无路可走。
  往上走的情况:当前没有到达上边界,往上走一个的位置没走过;
  往右走的情况:当前在上边界,往右走一个的位置没走过;
  无路可走:当前在上边界,右边走一个的位置已经走过了。

  其他三个情况以此类推进行分析。

  有路可走则继续递归,无路可走则直接 return;

2.2 层遍历法

  其实,顺时针遍历矩阵,可以看成是从外往里一层一层遍历。先顺时针遍历最外层,然后顺时针遍历第二层,直到最后一层。

  每一层分成下图四步:
在这里插入图片描述
  我们可以用矩阵的左上角和右下角唯一表示一个矩阵。设左上角坐标为 (lx,ly) , 右下角坐标为 (rx,ry)。

3、解题代码

3.1 递归法

package pers.klb.jzoffer.hard;

import java.util.ArrayList;

/**
 * @program: JzOffer2021
 * @description: 顺时针打印矩阵(递归法)
 * @author: Meumax
 * @create: 2020-08-11 19:32
 **/
public class ClockwisePrint {
    public ArrayList<Integer> printMatrix(int[][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if (matrix == null) return list;
        int row = matrix.length;
        int col = matrix[0].length;
        int[][] mark = new int[row][col];
        int i = 0;
        int j = 0;
        setMark(0, 0, list, matrix, mark, 'r');
        return list;
    }

    /**
     * @param x         当前横坐标
     * @param y         当前纵坐标
     * @param list      接收链表
     * @param array     原数组
     * @param mark      标记数组
     * @param direction 上一个坐标走到这个左边走的方向,u d l r 分别表示上、下、左、右
     */
    private void setMark(int x, int y, ArrayList<Integer> list, int[][] array, int[][] mark, char direction) {
        list.add(array[x][y]);
        mark[x][y] = 1;   // 这个位置已经走过了

        switch (direction) {
            case 'u': // ⬆ 上
                if (mark[x - 1][y] == 0) { // 可以接着往上走
                    setMark(x - 1, y, list, array, mark, 'u');
                } else if (mark[x][y + 1] == 0) {  // 不能往上走,所以往右走
                    setMark(x, y + 1, list, array, mark, 'r');
                } else {  // 可能走的路全不能走
                    return;
                }
                break;
            case 'd': // ⬇ 下
                if (x == array.length - 1) {  // 走到下边界了,直接往左走
                    if (y == 0) {   // 即是下边界,也是左边界,无路可走
                        return;
                    } else {
                        setMark(x, y - 1, list, array, mark, 'l');
                    }
                } else if (mark[x + 1][y] == 0) {    // 没有走到边界,还可以接着往下走
                    setMark(x + 1, y, list, array, mark, 'd');
                } else if (mark[x][y - 1] == 0) { // 即没到边界,又不能往下走,只能往左走
                    setMark(x, y - 1, list, array, mark, 'l');
                } else {  // 可能走的路全不能走
                    return;
                }
                break;
            case 'l': // <-- 左
                if (y == 0) {  // 已经在左边界
                    if (mark[x - 1][y] == 0) {  // 往上一步的位置没走过
                        setMark(x - 1, y, list, array, mark, 'u');
                    } else { // 往上一步的位置已走过,则现在无法走了
                        return;
                    }
                } else if (mark[x][y - 1] == 0) {    // 没有到边界,左边又没走过,可以往左走
                    setMark(x, y - 1, list, array, mark, 'l');
                } else if (mark[x - 1][y] == 0) {
                    setMark(x - 1, y, list, array, mark, 'u');
                } else {  // 可能走的路全不能走
                    return;
                }
                break;
            case 'r': // --> 右
                if (y == array[0].length - 1) {   // 到了右边界
                    if (x == array.length - 1) { // 也到了下边界
                        return; // 到了右边界,也到了下边界,无路可走
                    } else {    // 到了右边界,没到下边界,往下走
                        setMark(x + 1, y, list, array, mark, 'd');
                    }
                } else if (mark[x][y + 1] == 0) {    // 没有到右边界,右边没走过,则继续往右走
                    setMark(x, y + 1, list, array, mark, 'r');
                } else if (x == array.length - 1) {   // 当前已经是下边界,无路可走
                    return;
                } else if (mark[x + 1][y] == 0) {    // 没有到右边界和下边界,右边又不能走,只能往下走
                    setMark(x + 1, y, list, array, mark, 'd');
                } else {
                    return;
                }
                break;
        }
    }
}

  时间复杂度:O(NM),遍历一次矩阵;
  空间复杂度:O(NM),每递归一次就创建多一次虚拟机栈。

3.2 层遍历法

package pers.klb.jzoffer.hard;

import java.util.ArrayList;

/**
 * @program: JzOffer2021
 * @description: 顺时针打印矩阵(层遍历法)
 * @author: Meumax
 * @create: 2020-08-11 19:32
 **/
public class ClockwisePrint {
    public ArrayList<Integer> printMatrix(int[][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if (matrix == null) return list;
        int lx = 0; // 行坐标
        int ly = 0; // 列坐标
        int rx = matrix.length - 1;    // 右下角行坐标
        int ry = matrix[0].length - 1; // 右下角列坐标

        while (lx <= rx && ly <= ry) {
            printCircle(lx++, ly++, rx--, ry--, matrix, list);
        }

        return list;
    }

    private void printCircle(int lx, int ly, int rx, int ry, int[][] matrix, ArrayList<Integer> list) {
        for (int i = ly; i <= ry; i++) {    // --> 右
            list.add(matrix[lx][i]);
        }
        for (int j = lx + 1; j <= rx; j++) {    // ⬇ 下
            list.add(matrix[j][ry]);
        }
        if (rx - lx >= 1 && ry - ly >= 1) { // <-- 左
            for (int k = ry - 1; k >= ly; k--) {
                list.add(matrix[rx][k]);
            }
        }
        if (ry - ly >= 1 && rx - lx - 1 >= 1) { // ⬆ 上
            for (int m = rx - 1; m >= lx + 1; m--) {
                list.add(matrix[m][ly]);
            }
        }
    }
}

  时间复杂度:O(mn), 矩阵中每个元素遍历一次
  空间复杂度:O(mn), 每个元素需要存下来

4、解题心得

  递归法是直接用直觉的方式进行编程,有时候可以跳出直觉,把顺时针遍历看成一层一层往里遍历,反而更加简单。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值