1. 程式人生 > >java演算法之簡單的帕斯卡(楊輝三角)Pascal’s Triangle

java演算法之簡單的帕斯卡(楊輝三角)Pascal’s Triangle

轉載自:http://blog.csdn.net/ylyg050518/article/details/48517151

今天這道題目是也是一個經典的問題,列印Pascal’s Triangle,(帕斯卡三角或者說是楊輝三角)。

問題描述

 Given numRows, generate the first numRows of Pascal’s triangle. 
For example, given numRows = 5, 
Return 

  [1], 
  [1,1], 
  [1,2,1], 
  [1,3,3,1], 
  [1,4,6,4,1] 
]

大意: 給定行數numRows,生成帕斯卡三角的前numRows行,例如,給定numRows=5,返回 

  [1], 
  [1,1], 
  [1,2,1], 
  [1,3,3,1], 
  [1,4,6,4,1] 
]

思路分析

  其實只要你能手動寫出帕斯卡三角,你就已經掌握了具體的實現原理,剩下的就是如何用程式碼描述它而已。 
我們總結出帕斯卡三角的幾個規律: 
  1. 第n行,共有n個元素。 
  2. 每一行的邊界元素都是1。 
  3. 從第三行開始,除了邊界元素外,所有的元素值都是在上一行元素中位於當前元素雙肩位置的元素之和。 
  題目要求我們返回帕斯卡三角的集合,我們先不考慮結果儲存的程式碼實現細節,我們先採用列印的方式來控制檯直接輸出結果,只要能夠成功輸出,後期我們只要照貓畫虎,採用合適的儲存方式來生成結果就行了。 
儲存方法:如果僅從列印輸出的角度來看,很容易想到的是,帕斯卡三角的元素儲存方式可以使用二維陣列。由於

Java語言的特性,我們可以使用不規則的動態二維陣列來儲存帕斯卡三角的結果。這裡,我們定義一個整型二維陣列triangle[numRows][],採用行優先的方式儲存,陣列第一維儲存帕斯卡三角的行數numRows,第二維長度根據當前行的長度動態生成。 
實現細節:使用雙層迴圈來,在外層迴圈中對陣列triangle[n][]的第二維置賦值,在內層迴圈中,通過條件判斷分別對每行的邊界元素和中間元素賦值,虛擬碼描述如下

for i← 0 to numRows//外層迴圈
    triangle[i]←new int[i+1]//動態生成第二維
    for j← 0 to i//內層迴圈
        if(i==0||j==0||j==i)
            triangle[i][j]←1//邊界賦值
        else
            trangle[i][j]←(triangle[i - 1][j]+ triangle[i - 1][j - 1])//中間元素賦值
        print trangle[i][j]//打印出陣列元素

以下給出Java實現的程式碼:

/*
 * 控制檯列印方法
 */
public static void generate2(int numRows) {
    int triangle[][] = new int[numRows][];// 建立二維陣列
    // 遍歷二維陣列的第一層
    for (int i = 0; i < triangle.length; i++) {
        triangle[i] = new int[i + 1];// 初始化第二層陣列的大小
        // 遍歷第二層陣列
        for (int j = 0; j <= i; j++) {
            // 將兩側的陣列元素賦值為1
            if (i == 0 || j == 0 || j == i) {
                triangle[i][j] = 1;
            } else {// 其他數值通過公式計算
                triangle[i][j] = triangle[i - 1][j]
                        + triangle[i - 1][j - 1];
            }
            System.out.print(triangle[i][j] + " "); // 輸出陣列元素
        }
        System.out.println(); // 換行
    }
}`

  經過以上程式碼的書寫,我們已經成功地正確的帕斯卡三角結果輸出,接下來就是要考慮採取以何種資料結構來儲存中間結果,並最終將結果集合返回。 
  Java的集合框架給我們提供了很多方式。這裡我們採用List集合儲存最終的結果,在具體的實現中,每一行的結果也得需要List集合來儲存結果,最終的結果List集合實際是儲存了每一行List集合的集合。需要注意的是,Listjava.util包的一個介面,程式碼中需要用它的實現類來建立它的物件。 
下面給出最終的實現程式碼:

    /*
     * Pascal’s Triangle I,空間複雜度O(n²),時間複雜度O(n²)
     */
    public static List<List<Integer>> generate1(int numRows) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        // 不合法的輸入
        if (numRows <= 0)
            return result;
        List<Integer> pre = new ArrayList<Integer>();
        pre.add(1);
        result.add(pre);
        for (int i = 2; i <= numRows; i++) {
            List<Integer> cur = new ArrayList<Integer>();
            cur.add(1); // 開頭元素
            for (int j = 0; j < pre.size() - 1; j++) {
                cur.add(pre.get(j) + pre.get(j + 1)); // 中間位置
            }
            cur.add(1);// 末尾元素
            result.add(cur);
            pre = cur;
        }
        return result;
    }

特別說明:上述程式碼中,我們採用了一箇中間List集合pre,用來儲存初始結果以及用來生成下一行結果。之所以這樣做,是因為集合和陣列畢竟不同,不能預先指定大小,並且在Java中不能通過下標索引直接訪問到集合中的結果,需要通過List集合提供的get(int index)方法來獲取集合當中的元素。 
以上演算法的時間複雜度為O(n²),空間複雜度也是O(n²)。

問題變形

原文描述:

 Given an index k, return the kth row of the Pascal’s triangle.For example, given k = 3,Return [1,3,3,1] . 
Note: Could you optimize your algorithm to use only O ( k ) extra space?

大意:給定一個K值,返回帕斯卡三角的第K行的結果。例如,當k=3時,返回[1,3,3,1].要求空間複雜度O(n).

思路分析

  變形後的題目要求我們返回第K行的結果,並且只能使用O(k)額外的輔助空間。很顯然,這就意味著我們在演算法中只能使用一個List集合,演算法的所有操作必須圍繞這個集合來展開。同樣我們還是必須利用雙層迴圈,外層迴圈用來控制層數,記憶體迴圈用來進行當前層結果集合的更新。但是關鍵的問題就出現了,如何在單箇中進行值的更新。在最初的問題當中,我們是採用了輔助的集合來儲存上一行的結果,但是現在除了結果集合外,不能再使用了輔助空間了,所以我們必須另找思路。假設當前集合中的元素集為[1,3,3,1],下一行的結果[1,4,6,4,1]應該如何產生?很容易想到的是,可以從一箇中間元素3開始,假設當前位置為j+1,依次讀取result[j] (1),result[j+1] (3)結果,然後把兩者之和賦給result[j+1],這樣我們就得到了一次更新結果[1,4,3,1],但是繼續往下這樣做的時候,會出現問題,發現下次更新的時候,result[j]的變化了,變成了4,更新結果變成了[1,4,5,1],這樣顯然是錯誤的,原因就在於我們已經賦值,原來的位置的值被覆蓋掉,那麼可不可以也用臨時變數儲存這個覆蓋之前的值呢?細細分析以下,發現這樣做也是不可行的,因為我們一次更新之後必定會覆蓋掉result[j+1]原本位置的值,程式要維護的不是幾個臨時變數那麼簡單,而是需要整個上一行的集合。 
  轉變思路,如何避免這個值被覆蓋?既然從頭部開始行不通,那麼我們考慮從尾部開始試試,使j初始化為result.size()-2,還是假設當前集合為[1,3,3,1],從最後一個元素1開始,假設當前位置為j+1,依次讀取result[j] (1),result[j+1] (3)結果,然後把兩者之和賦給result[j+1],這樣我們就得到了一次更新結果[1,3,3,4],j遞減,下次更新結果為[1,3,6,4],依次類推,結果為[1,4,6,4],最終將1新增到末尾,即可完成結果集合的更新。 
下面給出程式碼:

/*
 * Pascal’s Triangle II,空間複雜度O(n),時間複雜度O(n²)
*/
    public static List<Integer> getRow1(int rowIndex) {
        List<Integer> result = new ArrayList<Integer>();
        if (rowIndex <= 0)
            return result;
        result.add(1);
        if (rowIndex == 1)
            return result;
        for (int i = 1; i < rowIndex; i++) {
            for (int j = result.size() - 2; j >= 0; j--) {
                // 賦值前
                // System.out.println("賦值前:" + result);
                result.set(j + 1, result.get(j) + result.get(j + 1));// 從右到左
                // 賦值後
                // System.out.println("賦值後:" + result);
            }
            result.add(1);
        }
        return result;
    }

說明:上述演算法的時間複雜度為O(n²),而空間複雜度為O(n)。

友情提示:由於作者水平和經驗所限,博文之中難免有疏漏,歡迎大家批評指正。