Java中將矩陣元素設定為零的三種演算法方法

banq發表於2024-04-14

矩陣是各種電腦科學、數學和工程領域中使用的基本資料結構。在某些情況下,我們可能需要根據特定條件或要求將某些矩陣元素設定為零。在本教程中,我們將討論在 Java 中有效完成此任務的各種方法。

理解問題
給定一個矩陣,我們的目標是將矩陣中每個零元素的整行和整列設定為零。此操作有效地將包含至少一個零元素的行和列“清零”。

例如,考慮以下矩陣:

[1, 2, 3]
[4, 0, 6]
[7, 8, 9]

應用變換後,矩陣變為:

[1, 0, 3]
[0, 0, 0]
[7, 0, 9]

簡單的解決方案
獲得所需結果的常用策略是利用簡單的問題解決方法,通常不強調最佳化或效率考慮。它通常是解決問題最明顯的方法,但在效能或資源使用方面可能不是最有效或最高效的。

為了使用簡單的方法解決我們的問題,我們可以首先複製輸入矩陣以保留原始值,然後遍歷它以檢測零元素。

當遇到零元素時,我們將複製矩陣中的整個相應行和列清零。最後,我們用從副本獲得的修改值更新原始矩陣:

public class SetMatrixToZero {
    static void setZeroesByNaiveApproach(int[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;
        int [][] result = new int[row][col];
        for (int i = 0; i<row; i++) {
            for (int j = 0; j < col; j++) {
                result[i][j] = matrix[i][j];
            }
        }
        
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (matrix[i][j] == 0) {
                    for (int k = 0; k < col; k++) {
                        result[i][k] = 0;
                    }
                    for (int k = 0; k < row; k++) {
                        result[k][j] = 0;
                    }
                }
            }
        }
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                matrix[i][j] = result[i][j];
            }
        }
    }
}

雖然這種方法很簡單,但時間複雜度為O (( mn ) ∗ ( m + n )),其中 m 是行數,n 是列數。其空間複雜度為O(m*n)。對於大型矩陣,這種方法可能效率不高。

讓我們透過執行以下測試來測試這種方法:

@Test
void givenMatrix_whenUsingSetZeroesByNaiveApproach_thenSetZeroes() {
    int[][] matrix = {
        {1, 2, 3},
        {4, 0, 6},
        {7, 8, 9}
    };
    int[][] expected = {
        {1, 0, 3},
        {0, 0, 0},
        {7, 0, 9}
    };
    SetMatrixToZero.setZeroesByNaiveApproach(matrix);
    assertArrayEquals(expected, matrix);
}

時間最佳化方法
該方法的重點是透過使用額外的空間來儲存矩陣中零元素的索引來提高解決方案的時間複雜度。

它首先遍歷矩陣來識別零個元素,並將它們的行索引和列索引儲存在單獨的HashSet中。然後,它再次遍歷矩陣並根據儲存的索引將相應的行和列設定為零:

static void setZeroesByTimeOptimizedApproach(int[][] matrix) {
    int rows = matrix.length;
    int cols = matrix[0].length;
    Set<Integer> rowSet = new HashSet<>();
    Set<Integer> colSet = new HashSet<>();
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (matrix[i][j] == 0) {
                rowSet.add(i);
                colSet.add(j);
            }
        }
    }
    for (int row: rowSet) {
        for (int j = 0; j < cols; j++) {
            matrix[row][j] = 0;
        }
    }
    for (int col: colSet) {
        for (int i = 0; i < rows; i++) {
            matrix[i][col] = 0;
        }
    }
}

此方法的時間複雜度為 O(m * n),並且需要 O(m + n) 的額外空間來儲存索引,這對於大型矩陣來說效率較低。

我們可以透過下面的測試來驗證上述方法:

@Test
void givenMatrix_whenUsingSetZeroesByTimeOptimizedApproach_thenSetZeroes() {
    int[][] matrix = {
        {1, 2, 3},
        {4, 0, 6},
        {7, 8, 9}
    };
    int[][] expected = {
        {1, 0, 3},
        {0, 0, 0},
        {7, 0, 9}
    };
    SetMatrixToZero.setZeroesByTimeOptimizedApproach(matrix);
    assertArrayEquals(expected, matrix);
}

最佳方法
這種方法透過在不使用額外空間的情況下修改原始矩陣來最佳化空間複雜度。

它利用矩陣的第一行和第一列來儲存有關零個元素的資訊。該演算法首先檢查第一行和第一列是否包含任何零。

然後,它透過將相應元素設定為零來標記第一行和第一列中的零元素。接下來,它遍歷矩陣(不包括第一行和第一列)以標記第一行和第一列中的零個元素。

最後,如果需要,它會遍歷第一行和第一列,將整個行或列更新為零。

我們將把上述步驟分解為單獨的輔助方法。下面的程式碼檢查矩陣的第一行中是否至少有一個零:

static boolean hasZeroInFirstRow(int[][] matrix, int cols) {
    for (int j = 0; j < cols; j++) {
        if (matrix[0][j] == 0) {
            return true;
        }
    }
    return false;
}

類似地,我們檢查矩陣的第一列中是否至少有一個零:

static boolean hasZeroInFirstCol(int[][] matrix, int rows) {
    for (int i = 0; i < rows; i++) {
        if (matrix[i][0] == 0) {
            return true;
        }
    }
    return false;
}

我們透過將第一行和第一列中的相應元素設定為零來標記矩陣中存在零的位置:

static void markZeroesInMatrix(int[][] matrix, int rows, int cols) {
    for (int i = 1; i < rows; i++) {
        for (int j = 1; j < cols; j++) {
            if (matrix[i][j] == 0) {
                matrix[i][0] = 0;
                matrix[0][j] = 0;
            }
        }
    }
}

接下來,我們根據矩陣第一列中的標記在行中設定零:

static void setZeroesInRows(int[][] matrix, int rows, int cols) {
    for (int i = 1; i < rows; i++) {
        if (matrix[i][0] == 0) {
            for (int j = 1; j < cols; j++) {
                matrix[i][j] = 0;
            }
        }
    }
}

類似地,我們根據矩陣第一行中的標記在列中設定零:

static void setZeroesInCols(int[][] matrix, int rows, int cols) {
    for (int j = 1; j < cols; j++) {
        if (matrix[0][j] == 0) {
            for (int i = 1; i < rows; i++) {
                matrix[i][j] = 0;
            }
        }
    }
}

現在,我們將第一行中的所有元素設定為零:

static void setZeroesInFirstRow(int[][] matrix, int cols) {
    for (int j = 0; j < cols; j++) {
        matrix[0][j] = 0;
    }
}

我們對第一列採用類似的方法:

static void setZeroesInFirstCol(int[][] matrix, int rows) {
    for (int i = 0; i < rows; i++) {
        matrix[i][0] = 0;
    }
}

我們合併所有前面的步驟並在此方法中執行它們:

static void setZeroesByOptimalApproach(int[][] matrix) {
    int rows = matrix.length;
    int cols = matrix[0].length;
    
    boolean firstRowZero = hasZeroInFirstRow(matrix, cols);
    boolean firstColZero = hasZeroInFirstCol(matrix, rows);
    
    markZeroesInMatrix(matrix, rows, cols);
    
    setZeroesInRows(matrix, rows, cols);
    setZeroesInCols(matrix, rows, cols);
    
    if (firstRowZero) {
        setZeroesInFirstRow(matrix, cols);
    }
    if (firstColZero) {
        setZeroesInFirstCol(matrix, rows);
    }
}

這種方法消除了對額外空間的需求,導致額外的空間複雜度為O(1)。整體空間複雜度仍然是 O(m * n)。

它在現有矩陣內實現預期結果,並保持 O(m * n) 的時間複雜度,其中 n 表示行數,n 表示列數。這種效率使其適合處理大型矩陣。

讓我們編寫測試來評估這種方法:

@Test
void givenMatrix_whenUsingSetZeroesByOptimalApproach_thenSetZeroes() {
    int[][] matrix = {
        {1, 2, 3},
        {4, 0, 6},
        {7, 8, 9}
    };
    int[][] expected = {
        {1, 0, 3},
        {0, 0, 0},
        {7, 0, 9}
    };
    SetMatrixToZero.setZeroesByOptimalApproach(matrix);
    assertArrayEquals(expected, matrix);
}

結論
在本教程中,我們探索了將矩陣中的元素設定為零的各種方法。天真的方法旨在實現所需的結果,而不優先考慮最佳化或效能。相反,時間最佳化方法透過利用額外的空間來降低時間複雜度。

然而,最佳方法既提高了時間複雜度,又不需要任何額外的空間,使其成為上述所有方法中更好的選擇。重要的是要承認這個問題可能還有其他幾種最佳解決方案。

相關文章