算法 - 矩阵问题

馋奶兔 提交于 2019-12-05 17:35:55

矩阵问题

问题一:转圈打印矩阵

给定一个整型矩阵matrix,请按照转圈的方式打印它。

要求:额外空间复杂度为O(1)。

例如:
[ 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

本题可以在 LeetCode 54 螺旋矩阵测试结果。

思路

定义一个方法,以左上角的点和右下角的点开始,从边界开始打印矩形。打印一圈后循环遍历内圈的矩形,直到整个矩形打印完毕。有一些特殊的情况,比如打印的是一条竖线,或者横线,或者一个点,都没有关系,注意代码的边界即可。

实现

public static void spiralOrderPrint(int[][] matrix, int tR, int tC, int dR, int dC) {
    //三种情况:横线,竖线,矩形
    if (tR == dR) {
        for (int i = tC; i <= dC; i++) {
            System.out.printf("%3d", matrix[tR][i]);
        }
    } else if (tC == dC) {
        for (int i = tR; i <= dR; i++) {
            System.out.printf("%3d", matrix[i][tC]);
        }
    } else {
        int tmpC = tC;
        int tmpR = tR;
        while (tmpC < dC) {
            System.out.printf("%3d", matrix[tR][tmpC++]);
        }
        while (tmpR < dR) {
            System.out.printf("%3d", matrix[tmpR++][dC]);
        }
        while (tC < tmpC) {
            System.out.printf("%3d", matrix[dR][tmpC--]);
        }
        while (tR < tmpR) {
            System.out.printf("%3d", matrix[tmpR--][tC]);
        }
    }
}

public static void printMatrix(int[][] matrix) {
    System.out.println("-----------PrintMatrixInSpiralOrder.printMatrix-----------");
    int tR = 0;
    int tC = 0;
    int dR = matrix.length - 1;
    int dC = matrix[0].length - 1;
    //横线,竖线等特殊形式都囊括在内
    while (tR <= dR && tC <= dC) {
        spiralOrderPrint(matrix, tR++, tC++, dR--, dC--);
    }
}

测试

public static void main(String[] args) {
    int[][] matrix = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
        {13, 14, 15, 16}
    };

    for (int[] arr : matrix) {
        for (int n : arr) {
            System.out.printf("%3d", n);
        }
        System.out.println();
    }

    printMatrix(matrix);
}
output:
  1  2  3  4
  5  6  7  8
  9 10 11 12
 13 14 15 16
-----------PrintMatrixInSpiralOrder.printMatrix-----------
  1  2  3  4  8 12 16 15 14 13  9  5  6  7 11 10

问题二:旋转正方形矩阵

给定一个整型正方形矩阵 matrix,请把该矩阵调整成逆时针旋转 90 度的样子。

要求:额外空间复杂度为O(1)。

例如:
[ 1  2  3
  4  5  6
  7  8  9 ]
旋转:
[ 3  6  9
  2  5  8
  1  4  7 ]

思路

先从外圈开始旋转,然后依次对内一圈一圈转,直到转完为止。

实现

public static void rotateMatrix(int[][] matrix) {
    int tR = 0;
    int tC = 0;
    int dR = matrix.length - 1;
    int dC = matrix[0].length - 1;
    //当两个点重合时,旋转就结束了
    while (tR < dR) {
        rotate(matrix, tR++, tC++, dR--, dC--);
    }
}

public static void rotate(int[][] matrix, int tR, int tC, int dR, int dC) {
    int times = dR - tR;
    for (int i = 0; i < times; i++) {
        //四个点变换位置
        int tmp = matrix[tR + i][tC];
        matrix[tR + i][tC] = matrix[tR][dC - i];
        matrix[tR][dC - i] = matrix[dR - i][dC];
        matrix[dR - i][dC] = matrix[dR][tC + i];
        matrix[dR][tC + i] = tmp;
    }
}

测试

public static void main(String[] args) {
    //int[][] matrix = {
    //        {1, 2, 3},
    //        {4, 5, 6},
    //        {7, 8, 9},
    //};

    int[][] matrix = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15},
        {16, 17, 18, 19, 20},
        {21, 22, 23, 24, 25}
    };

    printMatrix(matrix);
    rotateMatrix(matrix);
    printMatrix(matrix);

}

public static void printMatrix(int[][] matrix) {
    System.out.println("------------RotateMatrix.printMatrix------------");
    for (int[] arr : matrix) {
        for (int n : arr) {
            System.out.printf("%3d", n);
        }
        System.out.println();
    }
    System.out.println();
}

使用了两组数做测试,结果完成旋转,输出如下:

------------RotateMatrix.printMatrix------------
  1  2  3  4  5
  6  7  8  9 10
 11 12 13 14 15
 16 17 18 19 20
 21 22 23 24 25

------------RotateMatrix.printMatrix------------
  5 10 15 20 25
  4  9 14 19 24
  3  8 13 18 23
  2  7 12 17 22
  1  6 11 16 21

小总结

问题一和问题二都是矩阵的调整打印问题,采用的思路都很相似,从外围出发,一圈一圈向中心进攻。不要一直着眼在细节处,从宏观的角度浓缩出规律,再在细节处下功夫,如坐标的变换也是重点。这是一种比较通用的方法,把复杂的坐标变换浓缩成规律的活动,多训练这种思考方式,才能更便捷的解决问题。

问题三:“之”字形打印矩阵

给定一个矩阵matrix,按照“之”字形的方式打印这个矩阵,例如:

[ 1  2  3  4 
  5  6  7  8 
  9 10 11 12 ]

“之”字形打印的结果为:1 2 5 9 6 3 4 7 10 11 8 12

要求:额外空间复杂度为O(1)。

思路

从左上角 matrix[0][0] 位置引出两点 A、B,点 A 运动规则是向右走,遇到边界向下走,点 B 运动规则是向下走,遇到边界向右走。每走一步,两点形成的路径就是“之”字的某一条路径。再设定一个 flag 变量,来表示从 A -> B 的路径和从 B -> A 的路径。

实现

public static void zigPrint(int[][] matrix) {
    int row = matrix.length - 1;
    int col = matrix[0].length - 1;
    // A
    int tR = 0;
    int tC = 0;
    // B
    int dR = 0;
    int dC = 0;
    boolean flag = false; //true A -> B false B -> A
    while (tR != row + 1) {
        print(matrix, tR, tC, dR, dC, flag);
        //点 A 判断条件是 tC,所以 tC 放在 tR 后,防止 tC 值的改变对 tR 值的判断造成影响
        tR = tC == col ? tR + 1 : tR;
        tC = tC == col ? tC : tC + 1;
        //点 B 判断条件是 dR,所以 dR 放在 dC 后,防止 dR 值的改变对 dC 值的判断造成影响
        dC = dR == row ? dC + 1 : dC;
        dR = dR == row ? dR : dR + 1;
        flag = !flag;
    }
}

private static void print(int[][] matrix, int tR, int tC, int dR, int dC, boolean flag) {
    if (flag) {
        // true A -> B
        while (tR != dR + 1) {
            System.out.printf("%3d", matrix[tR++][tC--]);
        }
    } else {
        // false B -> A
        while (dR != tR - 1) {
            System.out.printf("%3d", matrix[dR--][dC++]);
        }
    }
}

测试

public static void main(String[] args) {
    int[][] matrix = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
    };
    zigPrint(matrix);
}
output:
1  2  5  9  6  3  4  7 10 11  8 12

小总结

在解题过程中,能使用基础数据类型一定要使用,因为其传值的特性有助于限制变量的作用域。当其作用域可控,算法就更容易理解。

在运算过程中,这个部分需要注意,前后顺序不同结果会受到影响,注意运算的逻辑:

//点 A 判断条件是 tC,所以 tC 放在 tR 后,防止 tC 值的改变对 tR 值的判断造成影响
tR = tC == col ? tR + 1 : tR;
tC = tC == col ? tC : tC + 1;
//点 B 判断条件是 dR,所以 dR 放在 dC 后,防止 dR 值的改变对 dC 值的判断造成影响
dC = dR == row ? dC + 1 : dC;
dR = dR == row ? dR : dR + 1;

问题四:在行列都排好序的矩阵中找数

给定一个有 N * M 的整型矩阵 matrix 和一个整数 K,matrix 的每一行和每一 列都是排好序的。实现一个函数,判断 K 是否在 matrix 中。

例如:

[ 0 1 2 5
  2 3 4 7
  4 5 7 9 ]

如果 K 为7,返回 true;如果 K 为6,返回 false。
要求:时间复杂度为 O(N + M),额外空间复杂度为 O(1)。

思路

选择右上角的数 a 比较
    > a 排除 a 左侧的数,往下寻找
    < a 排除 a 下方的数,往左寻找
按照这个策略比较,最多只需要遍历 N + M 个数就可以知道是否找到数

实现

public static boolean findNumber(int[][] matrix, int number) {
    int dR = matrix.length - 1;
    int dC = 0;
    int tR = 0;
    int tC = matrix[0].length - 1;
    while (tR <= dR && tC >= dC) {
        if (matrix[tR][tC] == number) {
            return true;
        }
        if (matrix[tR][tC] < number) {
            tR++;
        } else {
            tC--;
        }
    }
    return false;
}

测试

public static void main(String[] args) {
    int[][] matrix = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15},
        {16, 17, 18, 19, 20},
        {21, 22, 23, 24, 25}
    };

    for (int i = 0; i < 10000; i++) {
        int number = (int) (Math.random() * 100);
        System.out.println("number = " + number);
        System.out.println(findNumber(matrix, number));
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!