算法演練2:山脈數組的峯頂

這個leetcode題目是這樣的:
題目算法

這個數組的特色是會造成一個山峯,而題目要求返回這個山峯的索引。數組

通常的解法

若是按題意來想,很快就想到一個解決辦法:把a[i]跟a[i+1]做比較,若是a[i+1]比a[i]小了,那就是到山峯了,返回i便可。這個算法須要遍歷數組,一直找到開始變小的狀況。性能

對於這個算法,能夠這樣實現(由於山峯必定不會在最後出現,因此沒必要擔憂A[i+1]會越界):設計

int peakIndexInMountainArray(int* A, int ASize) {
    int i;
    for (i = 0; i < ASize; i ++) {
        if (A[i] > A[i+1]) {
            break;
        }
    }   
    return i;
}

把這個實現提交到leetcode,反饋是這樣的:
提交0code

從反饋來看,這並非一個速度很快的算法設計,但這不表明它不可行,實際上,不少狀況,咱們寧願簡單一點的實現也不要複雜但性能很佳的設計,由於有時簡單就是最重要的。可是,這裏的訓練就是要追求更好的設計,而不是考慮實際狀況。blog

通過設計的解法

小程以前已經提過:通常天然的想法,都不是高效的算法設計。索引

因此,除了理解題意外,還須要有獨立的時間來進行算法設計(而不是簡單地按着題意來解答),這對於其它場景也是同樣,使用什麼套路,有時候是很關鍵的。leetcode

另外,高效是相對的,檢測是否是高效有兩個辦法,一個辦法是運行起來看執行速度,通常須要對比其它解決辦法,另外一個辦法是分析時間複雜度,這兩個都不是小程重點講解的內容,讀者不該該陷入時間複雜度的分析當中,而應該把時間放在經典的算法套路的理解上。io

對於這個題目,若是換一個角度,那實際就是求最大值的索引,依然能夠套用小程以前反覆提到的「分治」與「重用」的套路。class

假如一開始就決定使用分治與重用的套路,那思考的過程多是這樣的:

1. 這裏的分治,只須要簡單的分,從中間一分爲二便可。
2. 而後就是重用,也就是標準做業,就是本身,即返回最大值的索引。

總體的思路是這樣的:把數列分爲兩部分,而後重用本身,求得兩部分的最大值的索引,而後對這兩個索引對應的值再比較一次,最終返回總體的最大值的索引。

分到何時結束呢?分到只有一個元素時結束,或者,在程序實現時再考慮均可以。在算法設計上,能夠不考慮細節。

這個算法的實現是這樣的:

int maxindex(int* A, int b, int e) {
    if (b < e) {
        int m = (b+e)>>1;
        int l = maxindex(A, b, m);
        int r = maxindex(A, m+1, e);
        int ret = l;
        if (A[l] < A[r]) {
            ret = r;
        }
        return ret;
    }
    return b;
}

int peakIndexInMountainArray(int* A, int ASize) {
    return maxindex(A, 0, ASize-1);
}

把這個代碼提交到leetcode,反饋以下(可見速度大幅提高了):
提交1

以上是同時使用「分治」與「重用」,並且「重用」的標準做業是自身,所設計出來的算法。

實際上,還有其它套路能夠優雅地解決這個問題,好比「排除」的套路,不斷地排除掉不符合答案的區域,最終定位到答案。對於這個「排除」套路,能夠用折半查找的辦法來實現。

對於「排除」套路,小程另做介紹,這裏只給出對應的代碼實現並結束本文的主要內容:

public class Solution {
    public int PeakIndexInMountainArray(int[] A) {
        int ret = 0;
        int l = 0, r = A.Length - 1;
        while (l < r) {
            int m = (l+r) >> 1;
            if (A[m] < A[m + 1]) l = m;
            else if (A[m-1] > A[m]) r = m;
            else {
                ret = m;
                break;
            }
        }
        return ret;
    }
}

簡單來講。分治,就是大事化小,難點在於,怎麼分,跟分到什麼程度爲止。重用,就是反覆使用標準做業,可能要先假設有某種能力,再使用它,並真的達到某種能力,須要有空手套白狼的勇氣。你本身領悟一下。

總結一下,本文再次介紹「分治」與「重用」套路的使用,分治能使問題規模變小並最終獲得解決,重用能簡化思考的複雜度並讓思路簡潔,這兩個套路常常出如今算法設計當中。


hello

相關文章
相關標籤/搜索