1. 程式人生 > >《劍指offer》算法題第三天

《劍指offer》算法題第三天

rotate 小數 條件 ted 同學會 amp number cover 最小數

今日題目:

  1. 斐波那契數列
  2. 青蛙跳臺階問題(及其變種:變態跳臺階)
  3. 矩形覆蓋
  4. 旋轉數組的最小數字
  5. 矩陣中的路徑
  6. 機器人的運動範圍

細心的同學會發現,第1,2,3題其實對應的是《劍指》書上的同一道題目,即第10題斐波那契數列,這類問題屬於遞歸問題,雖然思路比較簡單,但卻是屬於那種不看答案想不出來,看了答案恍然大悟的題目,因此在平時同學們和博主都應該多練練這一類型的題目,培養這種遞歸的思維。有趣的是,博主在做題的時候發現這三道題目是可以用動態規劃的思路來解決的,而且往往動態規劃的所用的時間是要低於遞歸的。在後文中會以矩形覆蓋為例子來說明。

第5,6兩題是回溯法的題目,在leetcode上有不少這一類型的題,只要多做練習,這一類型的題目還算是比較容易上手的。本文只說明第6題。

3.矩行覆蓋

題目描述:
我們可以用2*1的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?

思路:
以n=8為例,我們先把2*8矩形的覆蓋方法記為f(8)。當用一個2*1的小矩形取覆蓋大矩形的最左邊時有兩種選擇:豎著放或橫著放。如果選擇豎著放,那麽右邊還剩下2*7的區域,也就是f(7);如果橫著放在左上角,左下角必須也放置一個矩形,那麽右邊還剩下2*6的區域,也就是f(6)。所以我們有f(8)=f(7)+f(6)。從這邊我們可以看出來,其實這就是一個斐波那契數列。

理清思路後,接下來我們分別用遞歸以及動態規劃的方法來實現。

遞歸代碼如下:

1 public class Solution {
2     public int RectCover(int target) {
3         if(target == 0) return 0;
4         if(target == 1) return 1;
5         if(target == 2) return 2;
6         return RectCover(target-1)+RectCover(target-2);
7     }
8 }

代碼比較簡單、清晰,但是效率非常的低,他的時間復雜度不難推出來是成指數的。在牛客網上的運行時間為447ms。

接下來我們看一下動態規劃是如何解決的,代碼如下:

 1 public class Solution {
 2     public int RectCover(int target) {
 3         if(target == 0) return 0;
 4         int[] dp = new int[target+1];
 5         dp[0] = 1;
 6         dp[1] = 1;
 7         for(int i = 2; i <= target; i++){
 8             dp[i] = dp[i-1] + dp[i-2];
 9         }
10         return dp[target];
11     }
12 }

很明顯,這段代碼的時間復雜度僅為O(n),當然空間復雜度也為O(n),所以,你也可以這麽做:

 1 public class Solution {
 2     public int RectCover(int target) {
 3         if(target == 0) return 0;
 4         int num1 = 1;
 5         int num2 = 1;
 6         int res = 1;
 7         for(int i = 2; i <= target; i++){
 8             res = num1 + num2;
 9             num1 = num2;
10             num2 = res;
11         }
12         return res;
13     }
14 }

這樣空間復雜度也就僅為常數啦。動態規劃在同樣的條件下運行時間僅為12ms。

4.旋轉數組的最小數字

題目描述:
把一個數組最開始的若幹個元素搬到數組的末尾,我們稱之為數組的旋轉。 輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。 NOTE:給出的所有元素都大於0,若數組大小為0,請返回0。

這題在leetcode上面是有原題的,當時博主在做的時候並沒有太多的想法,直接從後往前遍歷數組,如果某個數比它前面的數字要小的話,那麽這個數就是最小的,時間復雜度為O(n)。代碼如下:

 1 public class Solution {
 2     public int minNumberInRotateArray(int [] array) {
 3         if(array.length ==0) return 0;
 4         for(int i = array.length-1; i >0; i--){
 5             if(array[i-1] > array[i])
 6                 return array[i];
 7         }
 8         return array[0];
 9     }
10 }

但是在《劍指》上,這題有一個時間復雜度為O(logn)的思路:利用二分查找的思路來尋找最小數字,具體的過程闡述起來過於復雜,感興趣的同學可以閱讀書上第83頁,這邊只貼出代碼:

 1 public class Solution {
 2     public int minNumberInRotateArray(int [] array) {
 3         if(array.length ==0) return 0;
 4         int s = 0;
 5         int e = array.length - 1;
 6         if(array[s] < array[e])//表示旋轉了0個數字到數組後面
 7             return array[s];
 8         while(s != e){
 9             if(e - s == 1)
10                 return array[e];
11             int mid = (s+e)/2;
12             if(array[mid] == array[s] && array[mid] == array[e])//處理特殊情況
13                 return orderFind(array,s,e);
14             if(array[mid] >= array[s])
15                 s = mid;
16             else if(array[mid] <= array[e])
17                 e = mid;
18         }
19         return array[s];
20     }
21     public int orderFind(int[] array,int start,int end){
22         int res = array[start];
23         for(int n:array){
24             res = (n < res)?n:res;
25         }
26         return res;
27     }
28 }

6. 機器人的運動範圍

題目描述:
地上有一個m行和n列的方格。一個機器人從坐標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行坐標和列坐標的數位之和大於k的格子。 例如,當k為18時,機器人能夠進入方格(35,37),因為3+5+3+7 = 18。但是,它不能進入方格(35,38),因為3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

思路:
這是比較直接的回溯法的題目,首先使用一個數組來標記機器人已經走過的放歌,防止重復,然後使用回溯法讓機器人運動,每次運動到一個方格都判斷一下是否合法,如果合法,計數器加一接著往下走;否則返回0;

代碼如下:

 1 public class Solution {
 2     public int movingCount(int threshold, int rows, int cols)
 3     {
 4         boolean[][] visited = new boolean[rows][cols];
 5         return movingCount(threshold,visited,rows,cols,0,0);
 6     }
 7     
 8     public int movingCount(int k,boolean[][] visited,int rows,int cols,
 9                           int row,int col)
10     {
11         if(row < 0 || row >= rows||
12           col < 0 || col >= cols || visited[row][col])
13             return 0;
14         visited[row][col] = true;
15         int sum = 0;
16         int row_tmp = row;
17         int col_tmp = col;
18         do{
19             sum += (row%10);
20             row /= 10;
21         }while(row > 0);
22         
23         do{
24             sum += (col%10);
25             col /= 10;
26         }while(col > 0);
27         row = row_tmp;
28         col = col_tmp;
29         if(sum > k)
30             return 0;
31         else{
32             return movingCount(k,visited,rows,cols,row+1,col)+
33                 movingCount(k,visited,rows,cols,row-1,col)+
34                 movingCount(k,visited,rows,cols,row,col+1)+
35                 movingCount(k,visited,rows,cols,row,col-1)+1;
36         }
37     }
38 }

《劍指offer》算法題第三天