Given a matrix of M x N elements (M rows, N columns), return all elements of the matrix in diagonal order as shown in the below image.
Example:
Input: [ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ] Output: [1,2,4,7,5,3,6,8,9] Explanation:
Note:
- The total number of elements of the given matrix will not exceed 10,000.
這道題給了我們一個mxn大小的數組,讓我們進行對角線遍歷,先向右上,然后左下,再右上,以此類推直至遍歷完整個數組,題目中的例子和圖示也能很好的幫我們理解。由於移動的方向不再是水平或豎直方向,而是對角線方向,那么每移動一次,橫縱坐標都要變化,向右上移動的話要坐標加上[-1, 1],向左下移動的話要坐標加上[1, -1],那么難點在於我們如何處理越界情況,越界后遍歷的方向怎么變換。向右上和左下兩個對角線方向遍歷的時候都會有越界的可能,但是除了左下角和右上角的位置越界需要改變兩個坐標之外,其余的越界只需要改變一個。那么我們就先判斷要同時改變兩個坐標的越界情況,即在右上角和左下角的位置。如果在右上角位置還要往右上走時,那么要移動到它下面的位置的,那么如果col超過了n-1的范圍,那么col重置為n-1,並且row自增2,然后改變遍歷的方向。同理如果row超過了m-1的范圍,那么row重置為m-1,並且col自增2,然后改變遍歷的方向。然后我們再來判斷一般的越界情況,如果row小於0,那么row重置0,然后改變遍歷的方向。同理如果col小於0,那么col重置0,然后改變遍歷的方向。參見代碼如下:
解法一:
class Solution { public: vector<int> findDiagonalOrder(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty()) return {}; int m = matrix.size(), n = matrix[0].size(), r = 0, c = 0, k = 0; vector<int> res(m * n); vector<vector<int>> dirs{{-1,1}, {1,-1}}; for (int i = 0; i < m * n; ++i) { res[i] = matrix[r][c]; r += dirs[k][0]; c += dirs[k][1]; if (r >= m) {r = m - 1; c += 2; k = 1 - k;} if (c >= n) {c = n - 1; r += 2; k = 1 - k;} if (r < 0) {r = 0; k = 1 - k;} if (c < 0) {c = 0; k = 1 - k;} } return res; } };
下面這種方法跟上面的方法思路相同,不過寫法有些不同,這里根據橫縱左邊之和的奇偶性來判斷遍歷的方向,然后對於越界情況再單獨處理即可,參見代碼如下:
解法二:
class Solution { public: vector<int> findDiagonalOrder(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty()) return {}; int m = matrix.size(), n = matrix[0].size(), r = 0, c = 0; vector<int> res(m * n); for (int i = 0; i < m * n; ++i) { res[i] = matrix[r][c]; if ((r + c) % 2 == 0) { if (c == n - 1) {++r;} else if (r == 0) {++c;} else {--r; ++c;} } else { if (r == m - 1) {++c;} else if (c == 0) {++r;} else {++r; --c;} } } return res; } };
下面這種方法是按遍歷方向來按規律往結果res中添加數字的,比如題目中的那個例子,那么添加的順序如下:
[0,0] -> [0,1],[1,0] -> [2,0],[1,1],[0,2] -> [1,2],[2,1] -> [2,2]
根據遍歷的方向不同共分為五層,關鍵就是確定每一層的坐標范圍,其中下邊界low = max(0, i - n + 1),這樣可以保證下邊界不會小於0,而上邊界high = min(i, m - 1),這樣也保證了上邊界不會大於m-1,如果是偶數層,則從上邊界往下邊界遍歷,反之如果是奇數層,則從下邊界往上邊界遍歷,注意從matrix中取數字的坐標,,參見代碼如下:
解法三:
class Solution { public: vector<int> findDiagonalOrder(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty()) return {}; int m = matrix.size(), n = matrix[0].size(), k = 0; vector<int> res(m * n); for (int i = 0; i < m + n - 1; ++i) { int low = max(0, i - n + 1), high = min(i, m - 1); if (i % 2 == 0) { for (int j = high; j >= low; --j) { res[k++] = matrix[j][i - j]; } } else { for (int j = low; j <= high; ++j) { res[k++] = matrix[j][i - j]; } } } return res; } };
下面這種方法就有一點暴力搜索的感覺,不像上面一種精確計算每一層的坐標范圍,這種方法是利用對角線上的數字的橫縱坐標之和恆定這一特性來搜索的,然后把和為特定值的數字加入結果res中,參見代碼如下:
解法四:
class Solution { public: vector<int> findDiagonalOrder(vector<vector<int>>& matrix) { if (matrix.empty() || matrix[0].empty()) return {}; int m = matrix.size(), n = matrix[0].size(), k = 0; vector<int> res; for (int k = 0; k < m + n - 1; ++k) { int delta = 1 - 2 * (k % 2 == 0); int ii = (m - 1) * (k % 2 == 0); int jj = (n - 1) * (k % 2 == 0); for (int i = ii; i >= 0 && i < m; i += delta) { for (int j = jj; j >= 0 && j < n; j += delta) { if (i + j == k) { res.push_back(matrix[i][j]); } } } } return res; } };
參考資料:
https://discuss.leetcode.com/topic/77866/short-bf-solution
https://discuss.leetcode.com/topic/77865/concise-java-solution/2
https://discuss.leetcode.com/topic/77862/my-8ms-short-solution-9line
https://discuss.leetcode.com/topic/77937/java-15-lines-without-using-boolean