本文接着上一篇文章《LeetCode刷題總結-數組篇(上)》,繼續講第二個常考問題:矩陣問題。html
矩陣也能夠稱爲二維數組。在LeetCode相關習題中,做者總結的考點有:矩陣元素的遍歷、矩陣位置的旋轉、矩陣行或列次序的交換、空間複雜度爲O(1)等。本期共12道題,2道簡單題,8道中等題,2道困難題。面試
題號:119,難度:簡單(可參考 楊輝三角,題號:118,難度:簡單)算法
題目描述:數組
解題思路:spa
依據楊輝三角的規律,當前行的數據和上一行的數據有着遞推關係:dp[i][j] = dp[i-1][j-1] + dp[i-1][j]。依據該遞推公式能夠寫一個較爲清晰的動態規劃解法,其空間複雜度爲O(k)。可是,也能夠採用遞歸的思想來解決。此處提供一個應用遞歸思想來解決該問題的代碼。3d
具體代碼:code
class Solution { public List<Integer> getRow(int rowIndex) { List<Integer> result = new ArrayList<>(); if(rowIndex + 1 <= 0) return result; dfs(result, rowIndex+1); return result; } public void dfs(List<Integer> result, int rowIndex) { if(rowIndex == 1) { result.add(1); return; } dfs(result, rowIndex - 1); int len = result.size(); int temp = 1; for(int i = 1;i < len;i++) { int t = result.get(i); result.set(i, temp+result.get(i)); temp = t; } result.add(1); } }
執行結果:htm
題號:54,難度:中等blog
題目描述:排序
解題思路:
這題只須要不停的往內順時針旋轉訪問便可。可是,在實現代碼時須要注意邊界的問題。另外,依據LeetCode上評論的解答思路,能夠給訪問過的元素作一個標記,或者統計當前已經訪問的元素個數,這樣更加有利於判斷訪問結束的時間節點。
具體代碼:
class Solution { public List<Integer> spiralOrder(int[][] matrix) { if(matrix.length == 0) return new ArrayList<Integer>(); List<Integer> result = new ArrayList<>(); int start_x = 0, start_y = 0; int max_x = matrix.length, max_y = matrix[0].length; int len = matrix.length * matrix[0].length; while(result.size() < len) { int x = start_x, y = start_y; for(;y < max_y && result.size() < len;y++) result.add(matrix[x][y]); // 向右 y = y -1; for(x = x + 1;x < max_x && result.size() < len;x++) result.add(matrix[x][y]); // 向下 x = x - 1; for(y = y -1;y >= start_y && result.size() < len;y--) result.add(matrix[x][y]); // 向左 y = y + 1; for(x = x - 1;x > start_x && result.size() < len;x--) result.add(matrix[x][y]); // 向上 max_x--; max_y--; start_x++; start_y++; } return result; } }
執行結果:
題號:48,難度:中等
題目描述:
解題思路:
這題能夠理解爲是例2的一個演化版。題目意思說明爲n*n的矩陣,因此不用考慮長方形矩陣的情形。下面代碼給出的解答思路爲依據正方形矩陣不停往內進行旋轉轉圈,每次轉動的步數爲邊長長度減去1的大小,每往內旋轉一步,正方形邊長減2。另外,看到LeetCode評論的解答思路,有一個頗有意思:把矩陣翻轉兩次,第一次沿着主對角線翻轉,第二次沿着垂直中線翻轉。
具體代碼:
class Solution { public void rotate(int[][] matrix) { for(int i = 0;i < matrix.length;i++) { int len = matrix.length - i * 2; while(--len > 0) { int temp = matrix[i][i]; // 左移 for(int j = i + 1;j < matrix.length - i;j++) temp = swap(matrix, i, j, temp); // 下移 for(int j = i + 1;j < matrix.length - i;j++) temp = swap(matrix, j, matrix.length - i -1, temp); } // 右移 for(int j = matrix.length - i - 2;j >= i;j--) temp = swap(matrix, matrix.length - i -1, j, temp); // 上移 for(int j = matrix.length - i - 2;j >= i;j--) temp = swap(matrix, j, i, temp); } } } public int swap(int[][] matrix, int i, int j, int temp) { int t = matrix[i][j]; matrix[i][j] = temp; return t; } }
執行結果:
題號:73,難度:中等
題目描述:
解題思路:
先說一下空間複雜度爲O(m+n)的思路(PS:該思路不符合題意的原地算法要求)。申請兩個一維數組,一個表示矩陣行,一個表示矩陣列。而後,遍歷矩陣中全部元素,一旦出現零,把該零對應行和對應列的一維數組的值標記爲常數1。最後,分別按行和按列給原始矩陣賦值零。
如今參考LeetCode上一個評論的思路,空間複雜度爲O(2)。申請兩個布爾變量cow和col,分別記錄原矩陣第0行和第0列中是否存在零,若是存在標記爲True,不然標記爲False。而後,接下來的思路就是上面O(m+n)的解決思路,惟一不一樣的是此時的空間是採用原始矩陣的空間。
具體代碼:
class Solution { public void setZeroes(int[][] matrix) { boolean row = false, col = false; for(int i = 0;i < matrix.length;i++) { if(matrix[i][0] == 0) { row = true; break; } } for(int j = 0;j < matrix[0].length;j++) { if(matrix[0][j] == 0) { col = true; break; } } for(int i = 1;i < matrix.length;i++) { for(int j = 1;j < matrix[0].length;j++) { if(matrix[i][j] == 0) { matrix[i][0] = 0; matrix[0][j] = 0; } } } for(int i = 1;i < matrix.length;i++) { if(matrix[i][0] == 0) { for(int j = 1;j < matrix[0].length;j++) matrix[i][j] = 0; } } for(int j = 1;j < matrix[0].length;j++) { if(matrix[0][j] == 0) { for(int i = 1;i < matrix.length;i++) matrix[i][j] = 0; } } if(row) { for(int i = 0;i < matrix.length;i++) matrix[i][0] = 0; } if(col) { for(int j = 0;j < matrix[0].length;j++) matrix[0][j] = 0; } } }
執行結果:
題號:289,難度:中等
題目描述:
解題思路:
此題在要求採用原地算法,即不能應用額外的空間來更新原始的矩陣元素。此題的解決方案須要使用位運算來解決。
先觀察題目對活細胞和死細胞的定義,而後把它們轉化爲二進制表示。
活細胞:1變換爲二進制01,若是活細胞變爲死細胞,只須要把01變爲11,即1變爲3,其最後一位依然能夠識別爲活細胞。
死細胞:0變換爲二進制00,若是死細胞變爲活細胞,只須要把00變爲10,即0變爲2,其最後一位依然能夠識別爲死細胞。
最後,採用board[i][j]&1進行位運算的法則來求取一個細胞周圍的活細胞數量,並更新當前細胞的狀態。
具體代碼:
class Solution { public void gameOfLife(int[][] board) { // 01表示活細胞,01——>11變爲死細胞,即由1變爲3 // 00表示死細胞,00——>10變爲活細胞,即由0變爲2 for(int i = 0;i < board.length;i++) { for(int j = 0;j < board[0].length;j++) { int count = countLive(board, i, j); if((board[i][j] & 1) == 1) { if(count < 2 || count > 3) board[i][j] = 3; } else { if(count == 3) board[i][j] = 2; } } } for(int i = 0;i < board.length;i++) { for(int j = 0;j < board[0].length;j++) { if(board[i][j] == 3) board[i][j] = 0; if(board[i][j] == 2) board[i][j] = 1; } } } public int countLive(int[][] board, int x, int y) { int[][] step = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}}; int count = 0; for(int i = 0;i < step.length;i++) { int temp_x = x + step[i][0]; int temp_y = y + step[i][1]; if(temp_x >= 0 && temp_x < board.length && temp_y >= 0 && temp_y < board[0].length) { if((board[temp_x][temp_y] & 1) == 1) count++; } } return count; } }
執行結果:
題號:835,難度:中等
題目描述:
解題思路:
此題注意如何處理矩陣的移動,使得移動後兩個矩陣進行匹配。直接採用兩個for循環表示其中一個矩陣移動後的位置。具體變換形式參考代碼。
具體代碼:
class Solution { public int largestOverlap(int[][] A, int[][] B) { int result = 0, len = A.length; for(int i = 0;i < len;i++) { for(int j = 0;j < len;j++) { int count1 = 0, count2 = 0; for(int m = 0;m < len - i;m++) { for(int n = 0;n < len - j;n++) { count1 += (A[m][n] & B[m+i][n+j]); count2 += (B[m][n] & A[m+i][n+j]); } } result = Math.max(result, count1); result = Math.max(result, count2); } } return result; } }
執行結果:
題號:999,難度:簡單
題目描述:
解題思路:
本題較爲簡單,直接遍歷矩陣找到車的位置,而後在車能行走的四個方向依次遍歷便可。(放入此題的意圖,題目比較長,讀懂題意須要耗費一些時間,另外編寫代碼須要注意邊界問題)
具體代碼:
class Solution { public int numRookCaptures(char[][] board) { for(int i = 0;i < board.length;i++) { for(int j = 0;j < board[0].length;j++) { if(board[i][j] == 'R') return getResult(board, i, j); } } return 0; } public int getResult(char[][] board, int x, int y) { int count = 0; int tempX = x, tempY = y; //向上 while(--tempX >= 0) { if(board[tempX][y] == 'B') break; else if(board[tempX][y] == 'p') { count++; break; } } tempX = x; //向下 while(++tempX < board.length) { if(board[tempX][y] == 'B') break; else if(board[tempX][y] == 'p') { count++; break; } } //向左 while(--tempY >= 0) { if(board[x][tempY] == 'B') break; else if(board[x][tempY] == 'p') { count++; break; } } tempY = y; //向右 while(++tempY < board[0].length) { if(board[x][tempY] == 'B') break; else if(board[x][tempY] == 'p') { count++; break; } } return count; } }
執行結果:
題號:1222,難度:中等
題目描述:
解題思路:
此題是例7的一個小小的升級版,重點仍是處理邊界問題,以及快速編寫代碼和一次經過的能力。
具體代碼:
class Solution { private int[][] used; public List<List<Integer>> queensAttacktheKing(int[][] queens, int[] king) { used = new int[8][8]; used[king[0]][king[1]] = 2; for(int i = 0;i < queens.length;i++) used[queens[i][0]][queens[i][1]] = 1; List<List<Integer>> result = new ArrayList<>(); for(int i = 0;i < queens.length;i++) { if(judgeQ(queens[i][0], queens[i][1])) { List<Integer> temp = new ArrayList<>(); temp.add(queens[i][0]); temp.add(queens[i][1]); result.add(temp); } } return result; } public boolean judgeQ(int x, int y) { int x1 = x; while(--x1 >= 0) { if(used[x1][y] == 2) return true; if(used[x1][y] == 1) break; } int x2 = x; while(++x2 < 8) { if(used[x2][y] == 2) return true; if(used[x2][y] == 1) break; } int y1 = y; while(--y1 >= 0) { if(used[x][y1] == 2) return true; if(used[x][y1] == 1) break; } int y2 = y; while(++y2 < 8) { if(used[x][y2] == 2) return true; if(used[x][y2] == 1) break; } int x3 = x, y3 = y; while(--x3 >= 0 && --y3 >= 0) { if(used[x3][y3] == 2) return true; if(used[x3][y3] == 1) break; } int x4 = x, y4 = y; while(++x4 < 8 && ++y4 < 8) { if(used[x4][y4] == 2) return true; if(used[x4][y4] == 1) break; } int x5 = x, y5 = y; while(--x5 >= 0 && ++y5 < 8) { if(used[x5][y5] == 2) return true; if(used[x5][y5] == 1) break; } int x6 = x, y6 = y; while(++x6 < 8 && --y6 >= 0) { if(used[x6][y6] == 2) return true; if(used[x6][y6] == 1) break; } return false; } }
執行結果:
題號:74,難度:中等
題目描述:
解題思路:
正常的想法是把矩陣想象成一個一維數組,而後採用二分查找的思路來實現。可是,這種方法可能須要處理(1,m)和(m,1)這兩種特殊的二維矩陣邊界問題(PS:在LeetCode上看到過直接應用二分查找很快解決的代碼)。由於矩陣按照行是有序的,不妨採用每行最後一個值做爲邊界與目標值進行比較大小,每次增長一行或者減小一列的數據,具體實現可參考代碼。
具體代碼:
class Solution { /* 二分查找的代碼 public boolean searchMatrix(int[][] matrix, int target) { if (matrix.length == 0 || matrix[0].length == 0) return false; int begin, mid, end; begin = mid = 0; int len1 = matrix.length, len2 = matrix[0].length; end = len1 * len2 - 1; while (begin < end) { mid = (begin + end) / 2; if (matrix[mid / len2][mid % len2] < target) begin = mid + 1; else end = mid; } return matrix[begin / len2][begin % len2] == target; } */ public boolean searchMatrix(int[][] matrix, int target) { if(matrix.length == 0) return false; int row = 0, col = matrix[0].length-1; while(row < matrix.length && col >= 0){ if(matrix[row][col] < target) row++; else if(matrix[row][col] > target) col--; else return true; } return false; } }
執行結果:
題號:64,難度:中等
題目描述:
解題思路:
此題最直接的思路是採用遞歸進行深度搜索遍從來解決,可是提交代碼後發現會出現超時的問題。此時,須要考慮應用動態規劃的思想來解題。動態規劃的轉換方程:dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j],另外須要考慮矩陣邊界的問題。
具體代碼:
class Solution { public int minPathSum(int[][] grid) { int[][] dp = new int[grid.length + 1][grid[0].length + 1]; for(int i = 1;i < dp.length;i++) { for(int j = 1;j < dp[0].length;j++) { if(i == 1) dp[i][j] = dp[i][j-1] + grid[i-1][j-1]; else if(j == 1) dp[i][j] = dp[i-1][j] + grid[i-1][j-1]; else dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i-1][j-1]; } } return dp[grid.length][grid[0].length]; } }
執行結果:
題號:1074,難度:困難
題目描述:
解題思路:
這道題實際上是《和爲K的子數組,題號:560,難度:中等》的一個升級版,560題是一維數組,本題是二維數組,其和變成了一個子矩陣的形式。此處咱們能夠採用把矩陣每行的元素變換成從該行開始道當前元素的和,另外單獨選兩列求矩陣和,這樣就把二維數組變成了列形式的一維數組求和。此時,解題思路就和560題同樣。
具體代碼:
class Solution { public int numSubmatrixSumTarget(int[][] matrix, int target) { for(int i = 0;i < matrix.length;i++) for(int j = 1;j < matrix[0].length;j++) matrix[i][j] += matrix[i][j-1]; int result = 0; for(int j1 = 0;j1 < matrix[0].length;j1++) { for(int j2 = j1;j2 < matrix[0].length;j2++) { Map<Integer, Integer> map = new HashMap<>(); map.put(0,1); int pre = 0; for(int i = 0;i < matrix.length;i++) { int val = pre + (j1 == 0 ? matrix[i][j2] : matrix[i][j2] - matrix[i][j1-1]); result += map.getOrDefault(val - target, 0); map.put(val, map.getOrDefault(val, 0)+1); pre = val; } } } return result; } }
執行結果:
題號:782,難度:困難
題目描述:
解題思路:
本題分析一下01出現的規律,可知若是n爲偶數,那麼每行每列中0和1的個數必然相等;若是n爲奇數,那麼0和1個數差的絕對值爲1。因爲矩陣只能交換行和列,結果要出現0和1不斷相間排列,那麼全部行中只能出現兩種模式的01排列方式,而且這兩種排列方式互補。例如,n=4, 第一行排序:1001,那麼其餘行要不等於1001,要麼等於0110,不然就不可能轉換爲棋盤,直接輸出-1便可。對於列的狀況,和行同樣。(PS:此題須要處理最小變換次數的邊界問題,分奇偶討論行或者列的最小交換次數)
具體代碼:
class Solution { public int movesToChessboard(int[][] board) { if(check(board)) { int row = 0, start = board[0][0]; for(int i = 1;i < board.length;i++) { if(board[i][0] == start) row++; start = 1 - start; } int col = 0; start = board[0][0]; for(int j = 1;j < board[0].length;j++) { if(board[0][j] == start) col++; start = 1 - start; } if(board.length % 2 == 0) { // 分奇數偶數討論行和列最小交換次數 row = Math.min(board.length-row, row); col = Math.min(board.length-col, col); } else { if(row % 2 == 1) row = board.length-row; if(col % 2 == 1) col = board.length-col; } return row / 2 + col / 2; } return -1; } public boolean judgeEqual(int[][] board, int i, int j) { for(int k = 0;k < board[0].length;k++) { if(board[i][k] != board[j][k]) return false; } return true; } public boolean judgeNotEqual(int[][] board, int i, int j) { for(int k = 0;k < board[0].length;k++) { if(board[i][k] == board[j][k]) return false; } return true; } public boolean check(int[][] board) { int row_0 = 0, row_1 = 0, col_0 = 0, col_1 = 0; for(int i = 0;i < board[0].length;i++) { if(board[0][i] == 0) row_0++; else if(board[0][i] == 1) row_1++; if(board[i][0] == 0) col_0++; else if(board[i][0] == 1) col_1++; } if(Math.abs(row_0 - row_1) > 1 || row_0+row_1 != board[0].length) return false; if(Math.abs(col_0 - col_1) > 1 || col_0+col_1 != board.length) return false; row_0 = 0; row_1 = 0; for(int j = 1;j < board[0].length;j++) { if(judgeEqual(board, 0, j)) row_0++; else if(judgeNotEqual(board, 0, j)) row_1++; else return false; } return true; } }
執行結果: