從0打卡leetcode之day 3 -- 最大子序列和

帥地發表於2018-08-13

前言

就有要把leetcode的題刷完,每天一道題,每天進步一點點


從零打卡leetcode之day 3

題目描述:
給定一個int型別的陣列,求最大子序列的和。
也就是說,從這個陣列中擷取一個子陣列,這個子陣列的元素和最大。

例如:
-1 20 -4 14 -4 -2 
這個陣列的最大字序列和為30。即20 -4 14。

解題

1.初級版解法

對於這道題,其實我們可以採取遍歷所有可能的組合,然後再比較哪種組合的和最大。

也就是說,我們可以找出所有子序列,然後逐個比較。程式碼如下。

    public int solve(int[] arrs){

        int max = 0;//用來存放目標子序列的和

        int temp = 0;//用來存每個子序列的和

        for(int i = 0; i < arrs.length; i++){

            for(int j = i; j < arrs.length; j++){

                temp = 0;

                //計運算元序列的和
                for(int k = 0; k < arrs.length; k++){
                    temp += arrs[k];
                }
                //進行比較
                if(temp > max){
                    max = temp;
                }

            }
        }

        return max;
    }`

在這三個迴圈中,外面兩個迴圈列舉出所有子序列,第三個迴圈計運算元序列的和。

看到三個for迴圈,時間複雜度的O(n3)。這速度,實在是太慢了。我們來優化優化。

2.進階版

其實,你仔細看一下里面的那兩層for迴圈,會發現其實可以把它們合併成一個for迴圈的。

也就是說,我們可以在列舉所有子序列的過程中,是可以一邊進行資料處理的。還是直接看程式碼好理解點。如下:

    public int solve2(int[] arrs){

        int max = 0; 

        int temp = 0;

        for(int i = 0; i < arrs.length; i++){

            temp = 0;

            for(int j = i; j < arrs.length; j++){

                //一邊處理資料
                temp += arrs[j];

                //進行比較選擇

                if(max < temp){

                    max = temp;
                }
            }
        }

        return temp;
    }

該方法用了兩個for迴圈,時間複雜度為O(n2),相對來說好了一點。

3.再次優化進階

這次,我們可以使用遞迴的思想來處理。遞迴最重要的就是要找到:

  1. 遞迴的結束條件
  2. 把問題分解成若干個子問題。

對於這道題,其實我們可以把序列分成左右兩部分。那麼,最大子序列和的位置會出現在以下三種情況:

  1. 子序列完全在左半部分。
  2. 子序列完全在右半部分。
  3. 一部分在左,一部分在右。

所以我們只要分別求出左半部分的最大子序列和、右半部分的最大子序列和(注意,問題已經轉化為求左右兩部分的最大子序列和了,也就是說問題被分解成若干子問題了)、以及跨越左右兩部分的最大子序列和。最後比較三者之中哪個比較大就可以了。

如何求解左半部分和右半部分的最大子序列?

其實道理一樣,把左半部分和右半部分再次分解左右兩部分就可以了。

那麼,如何求解跨越左右兩部分的最大子序列呢?

其實只要求出包含左半部分中最右邊元素的子序列的最大和,以及求出包含右半部分中最左邊元素的子序列的最大和,然後讓兩者相加,即可求出跨域左右兩部分的最大子序列和了。

子問題已經分解出來了,那麼遞迴的結束條件是什麼?

我們把陣列分成左右兩部分,其實當左右兩部分只有一個元素時,遞迴結束。

這道題的遞迴思路算是找出來了,不過,程式碼實現?假如你對遞迴不大熟悉的話,可能在實現上還是有那麼點困難的。對於遞迴的學習,大家也可以看我寫的關於遞迴與動態規劃的幾篇文章。

我就直接拋程式碼了。

    //遞迴版本
    public int solve3(int[] arrs, int left, int right){
        int max = 0;

        //表示只有一個元素,無需在分解
        if(left == right){
            //為什麼?因為低於0的數肯定不可以是最大值的
            //大不了最大值為0
            max = arrs[left] >= 0 ? arrs[left]:0;
        }else{

            int center = (left + right)/2;
            //求解左半部分最大子序列
            int leftMax = solve3(arrs, left, center);
            //求解右半部分最大子序列
            int rightMax = solve3(arrs, center+1, right);

            //求解kua跨越左右兩部分的最大子序列
            //1.求包含左部分最右元素的最大和
            int l = 0;
            int l_max = 0;
            for(int i = center; i >= left; i--){
                l += arrs[i];
                if(l > l_max){
                    l_max = l;
                }
            }

            //2.求包含右部分最左元素的最大和
            int r = 0;
            int r_max = 0;
            for(int i = center+1; i <= right; i++){
                r += arrs[i];
                if(r > r_max){
                    r_max = r;
                }
            }
            //跨越左右兩部分的最大子序列
             max = l_max + r_max;

            //取三者最大值
            if(max < leftMax) max = leftMax;
            if(max < rightMax) max = rightMax;
        }

        return max;
    }

遞迴求解方法的時間複雜度為O(nlgn)。這速度,比第一種做法,不知道快了幾個級別….

遞迴解法可以說是很快的了

但是,等等,我還是不滿意…

4.最終版:動態規劃

接下來的最終版,時間複雜度可以縮減到O(n), 雖然說是採用了動態規劃的思想,不過,我覺得你沒學過動態規劃也可以看懂。

假如我給你

1 2 -4 5 6

五個元素,你在計算前面三個元素的時候,即

1 + 2 + -4 = -1

發現前面三個元素的和是小於0的,那麼,這個

1 2 -4

的子序列我們還要嗎?顯然,這個子序列的和都小於0了,我們是可以直接淘汰的。因為如果還要這個子序列的話,它和後面的5一相加,結果變成了4,我們還不如讓我們的目標子序列直接從5開始呢。

先看程式碼吧,可能反而會好理解點

    //基於動態規劃的思想
    public int solve4(int[] arrs){
        int max = 0;//存放目標子序列的最大值
        int temp = 0;//存放子序列的最大值

        for(int i = 0; i < arrs.length; i++){
            temp += arrs[i];
            if(temp > max){
                max = temp;
            }else{
                //如果這個子序列的值小於0,那麼淘汰
                //從後面的子序列開始算起
                if(temp < 0){
                    temp = 0;
                }
             }
        }
        return max;
    }

這道題不是leetcode上的題目,不過我覺得這道題很不錯,所以拿出來分享給大家。

對付的對手

相關文章