面試 15:順時針從外往裏打印數字(劍指 Offer 第 20 題)

面試 15:順時針從外往裏打印數字

題目:輸入一個矩陣,按照從外向裏以順時針的順序依次打印每個數字。例如輸入: {{1,2,3}, {4,5,6}, {7,8,9}} 則依次打印數字爲 一、二、三、六、九、八、七、四、5java

這是昨天最後給你們留下的題目,相信你們也有去思考如何處理這道題目了。web

初看這個題目,比較容易理解,也無需牽扯到數據結構或者高級的算法,看起來問題比較簡單,但實際上解決起來且並無想象中的容易。面試

你們極有可能想到循環嵌套的方式,套用幾個 for 循環就能夠啦。算法

  1. 首先打印第 1 行,而後第一個 for 循環從第一列打印到最後一列。
  2. 到最後一列,開始向下打印,爲了防止重複打印第一行最後一列的數字,因此應該從第二行開始打印;
  3. 上面步驟 2 到底的時候,再在最後一行從倒數第二列開始往前打印一直到第一列;
  4. 用步驟 3 到最後一行第一列的時候再往上打印,第一行第一列因爲步驟 1 已經打印過,因此此次只須要從倒數第二行第一列開始打印到順數第二行第一列便可;
  5. 而後裏面實際上是同樣的,不難看出裏面其實就是對一個更小的矩陣重複上面的步驟 1 到步驟 4;
  6. 因爲以前說了必定注意邊界值,因此咱們再步驟 1 以前嚴格注意一下傳入矩陣爲 null 的狀況。

思路想好了,因此開始下筆寫起代碼:數組

public class Test15 { private static void print(int[][] nums) { if (nums == null) return; int rows = nums.length; int columns = nums[0].length; // 由於一次循環後 裏面的矩陣會少 2 行,因此咱們步長應該設置爲 2 // 由於一次循環後 裏面的矩陣會少 2 行,因此咱們步長應該設置爲 2 for (int i = 0; i * 2 < rows || i * 2 < columns; i++) { // 向右打印,i 表明第 i 行,用 j 表明列,從 0 到 列數-1-2*i for (int j = i; j < columns - 2 * i; j++) { System.out.print(nums[i][j] + ","); } // 向下打印,j 表明行,列固定爲最後一列-i*2 for (int j = i + 1; j < rows - 2 * i; j++) { System.out.print(nums[j][rows - 1 - 2 * i] + ","); } // 向左打印,j 表明列,行固定爲最後一列-i*2 for (int j = rows - 2 - 2 * i; j >= 2 * i; j--) { System.out.print(nums[rows - 1 - 2 * i][j] + ","); } // 向上打印,j 表明行,列固定爲第一列 +i*2 for (int j = rows - 2 - 2 * i; j > 2 * i; j++) { System.out.print(nums[j][2 * i] + ","); } } } public static void main(String[] args) { int[][] nums = {{1, 2, 3}, {4,5,6}, {7,8,9}}; print(nums); } 複製代碼

上面的代碼可能你們會以爲看的很繞,實際上我也很暈,在這種很暈的狀況下一般是極易出現問題的。不信?不妨咱們分析來看看。數據結構

  1. 首先咱們作了 null 的輸入值判斷,挺好的,這沒問題;
  2. 而後咱們作了一個循環,輸出當作一個環一個環的輸出,由於輸出完成一個環後總會少 2 行和 2 列,最後一次輸出例外,因此咱們給出步長爲 2 ,而且中間的判斷採用 || 而不是 &&,這裏也沒啥問題;
  3. 咱們直接代入題幹中的例子試一試。
  4. rows = 3,columns = 3,最外層循環會進行 2 次,符合條件;
  5. 進入第一次循環,第一次打印向右,j 從 0 一直遞增到 2 循環 3 次,打印出 1, 2, 3,沒問題;
  6. 進入第二次循環,本次循環咱們但願打印 6,9;咱們從 i + 1 列開始,一直到最後一列,正確,沒問題;
  7. 進入第三次循環,測試沒問題,能夠正常打印 8,7;
  8. 進入第四次循環,測試沒問題,能夠正常打印 4;
  9. 最外層循環進入第二次,此時 i = 1, i < 1,出現錯誤。額,這裏循環結束條件應該 i <= columns - 2 * i
  10. ....

不知道小夥伴有沒有被繞暈,反正我已經雲裏霧裏了,我是誰?我在哪?測試

各類試,會發現坑還很多,其實上面貼的這個代碼已是通過上面這樣走流程走了好幾回修正的,但特別無奈,這個坑始終填不滿。spa

有時候,不得不說,其實能有上面這般思考的小夥伴已經很優秀了,但在算法上仍是欠了點火候。在面試中,咱們固然但願不遺餘力完成健壯性很棒,又能實現功能的代碼,但不得不說,人都有思惟愚鈍的時候,有時候就是怎麼也弄不出來。code

咱們在解題前,其實不妨經過畫圖或者其餘的方式先和麪試官交流本身的思路,雖然他不會告訴你這樣作對與否。但這其實就造成了一種很是好的溝通方式,固然也是展示你溝通能力的一種體現!orm

前面的思路其實沒毛病,只是即便咱們獲得了正解,但這樣的一連串代碼,別說面試官,你本身可能都看的頭大。

咱們確實能夠用這樣先打印矩陣最外層環,打印完後把裏面的再當作一個環,重複外面的狀況打印。環的打印次數上面也提了,限制結束的條件就是環數 <= 行數的二分之一 && 環數 <= 列數的 二分之一。

因此咱們極易獲得這樣的代碼:

private static void print(int[][] nums) { if (nums == null) return; int rows = nums.length; int columns = nums[0].length; for (int i = 0; i * 2 < rows && i * 2 < columns; i++) { printRing(nums, i, rows, columns); } } 複製代碼

咱們着重是須要編寫 printRing(nums,i) 的代碼。

仔細分析,咱們打印一圈實際上就分爲四步:

  1. 從左到右打印一行;
  2. 從上到下打印一列;
  3. 從右到左打印一行;
  4. 從下到上打印一列;

不過值得注意的是,最後一圈有可能退化爲只有一行,只有一列,甚至只有 1 個數字,所以這樣的打印並不須要 4 步。下圖是幾個退化的例子,他們打印一圈分別只須要 3 步、2 步 甚至 1 步。

 

劍指 Offer

 

所以咱們須要仔細分析打印時每一步的前提條件。

  • 第一步老是須要的,無論你是一個數字,仍是隻有一行。
  • 若是隻有一行,那就不用第二步了,因此第二步能進去的條件是終止的行號大於起始的行號;
  • 若是剛剛兩行而且大於兩列,則可進行第三步打印;
  • 要想進行第四步的話,除了終止列號大於起始行號之外,還得至少有三行。

此外,依然得額外地注意:數組的下標是從 0 開始的,因此尾座標老是得減 1 ,而且每進行一次循環,尾列和尾行的座標老是得減去 1。

因此,完整的代碼就奉上了:

public class Test15 { private static void print(int[][] nums) { if (nums == null) return; int rows = nums.length; int columns = nums[0].length; for (int i = 0; i * 2 < rows && i * 2 < columns; i++) { printRing(nums, i, rows, columns); } } private static void printRing(int[][] nums, int start, int rows, int columns) { // 設置兩個變量,endRow 表明當前環尾行座標;endCol 表明當前環尾列座標; int endRow = rows - 1 - start; int endCol = columns - 1 - start; // 第一步:打印第一行,行不變列變,列從起到尾 for (int i = start; i <= endCol; i++) { System.out.print(nums[start][i] + ","); } // 假設有多行才須要打印第二步 if (endRow > start) { // 第二步,打印尾列,行變列不變,須要注意的是尾列第一行已經打印過 for (int i = start + 1; i <= endRow; i++) { System.out.print(nums[i][endCol] + ","); } } // 至少兩行而且 2 列纔會有第三步逆序打印 if (endCol > start && endRow > start) { // 第三步,打印尾行,行不變,列變。須要注意尾行最後一列第二步已經打印 for (int i = endCol - 1; i >= start; i--) { System.out.print(nums[endRow][i] + ","); } } // 至少大於 2 行 而且大於等於 2 列纔會有第四步打印 if (endRow > start && endCol - 1 > start) { // 第四步,打印首列,行變,列不變。須要注意尾行和首行的都打印過 for (int i = endRow - 1; i >= start + 1; i--) { System.out.print(nums[i][start] + ","); } } } public static void main(String[] args) { int[][] nums = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; print(nums); } } 複製代碼

用本身準備的測試用例輸入測試,沒有問題,經過。

上面的代碼中用兩個變量 endRowendCol 以及畫圖完美地解決了咱們思路混亂而且代碼難以看明白的問題。「其實不用吐槽判斷方法有重複的狀況,咱們都是爲了看起來思路更加清晰。

只看不練,很明顯這樣的題是容易被繞進去的,思路其實咱們很好想到,但實現出來徹底是另一回事,因此你們不妨再去動手試試吧~

緊張之餘,仍是要留下明天的習題,記得提早思考和動手練習喲~

面試題:輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷二個序列是否爲該棧的彈出順序。假設壓入棧的全部數字均不相等。例如:壓入序列爲{1,2,3,4,5},那{4,5,3,2,1} 就是該棧的彈出順序,而{4,3,5,1,2} 明顯就不符合要求;

相關文章
相關標籤/搜索