簡體   English   中英

在排序數組中找到[i] = i的最有效方法是什么?

[英]What would be the most efficient way to find a[i] = i in a sorted array?

給定的陣列a[]這將是最有效的方式,以確定是否至少一個元件i滿足條件a[i] == i

數組中的所有元素都是排序和不同的,但它們不一定是整數類型(即它們可能是浮點類型)。

有幾個人聲稱“排序”,“不同”和“不一定是整數”的相關性。 實際上,正確選擇有效的算法來解決這個問題取決於這些特性。 如果我們知道數組中的值既是不同的又是積分的,那么更有效的算法是可能的,而如果值可能是非不同的,則需要效率較低的算法,無論它們是否是整數。 當然,如果數組尚未排序,您可以先對其進行排序(平均復雜度為O(n log n)),然后使用更有效的預排序算法(即對於排序的數組),但在未排序的情況下例如,簡單地保持數組未排序並直接比較線性時間(O(n))中的值會更有效。 請注意,無論選擇何種算法,最佳情況都是O(1)(當檢查的第一個元素包含其索引值時); 在執行任何算法的任何時候,我們可能會遇到一個元素,其中a[i] == i ,此時我們返回true; 在這個問題的算法性能方面真正重要的是我們可以多快地排除所有元素並聲明沒有這樣的元素a[i]其中a[i] == i

問題沒有說明a[]的排序順序,這是一個非常重要的缺失信息。 如果它是遞增的,最壞情況下的復雜性將始終為O(n),我們無法做任何事情來使最壞情況的復雜性更好。 但是如果排序順序是下降的,即使是最壞情況下的復雜度也是O(log n):因為數組中的值是不同的並且是降序的,所以只有一個可能的索引,其中a[i]可以等於i ,基本上所有你要做的是二元搜索以找到交叉點(如果有這樣的交叉,則升序索引值越過降序元素值),並確定交叉點索引值處的a[c] == c c 由於這非常簡單,我將繼續假設排序順序為升序。 有趣的是,如果元素是整數,即使在升序的情況下也存在類似的“交叉”情況(盡管在升序的情況下可能存在多個a[i] == i匹配),所以如果元素是整數二進制搜索也適用於升序情況,在這種情況下,即使是最差情況下的性能也是O(log n)(參見訪談問題 - 在排序數組X中搜索索引i,使得X [i] = i )。 但是在這個版本的問題上我們沒有給予豪華。

以下是我們如何解決此問題:

從第一個元素開始, a[0] 如果它的值是== 0 ,你找到了一個滿足a[i] == i的元素,所以返回true。 如果其值< 1 ,則下一個元素( a[1] )可能包含值1 ,因此您將繼續執行下一個索引。 但是,如果a[0] >= 1 ,則您知道(因為值不同)條件a[1] == 1不可能為真,因此您可以安全地跳過索引1 但你甚至可以做得更好:例如,如果a[0] == 12 ,你知道(因為值是按升序排序的),那么不可能有任何元素滿足a[i] == i prior元素a[13] 因為數組中的值可以是非整數的,所以我們不能在此處進行任何進一步的假設,因此我們可以安全地直接跳到的下一個元素是a[13] (例如a[1]a[12]可能全部包含12.000...13.000...之間的值13.000...這樣a[13]仍然可以正好等於13 ,所以我們必須檢查它)。

繼續該過程產生如下算法:

// Algorithm 1
bool algorithm1(double* a, size_t len)
{
    for (size_t i=0; i<len; ++i) // worst case is O(n)
    {
        if (a[i] == i)
            return true; // of course we could also return i here (as an int)...
        if (a[i] > i)
            i = static_cast<size_t>(std::floor(a[i]));
    }
    return false; // ......in which case we’d want to return -1 here (an int)
}

如果a[]中的許多值大於它們的索引值,那么它具有相當好的性能,並且如果a[]中的所有值都大於n(在僅一次迭代后它返回false),則具有優異的性能,但它具有如果所有值都小於其索引值,則表現不佳(在n次迭代后它將返回false)。 所以我們回到繪圖板......但我們需要的是一個輕微的調整。 考慮到算法可能已被編寫為從n向下掃描到0,就像它可以從0向前掃描一樣容易。 如果我們將迭代的邏輯從兩端組合到中間,我們得到如下算法:

// Algorithm 2
bool algorithm2(double* a, size_t len)
{
    for (size_t i=0, j=len-1; i<j; ++i,--j) // worst case is still O(n)
    {
        if (a[i]==i || a[j]==j)
            return true;
        if (a[i] > i)
            i = static_cast<size_t>(std::floor(a[i]));
        if (a[j] < j)
            j = static_cast<size_t>(std::ceil(a[j]));
    }
    return false;
}

這在兩種極端情況下都具有出色的性能(所有值都小於0或大於n),並且幾乎任何其他值的分布都具有相當好的性能。 最糟糕的情況是,如果數組下半部分的所有值都小於它們的索引,並且上半部分中的所有值都大於它們的索引,在這種情況下,性能會降低到最壞情況下的O( N)。 最好的情況(或者極端情況)是O(1),而平均情況可能是O(log n),但是我推遲到有數學專業的人來確定。

有幾個人建議采用“分而治之”的方法解決問題,但沒有具體說明如何划分問題以及如何處理遞歸划分的子問題。 當然,這樣一個不完整的答案可能不會滿足面試官。 上述算法2的朴素線性算法和最壞情況性能都是O(n),而算法2通過跳過(不檢查)元素,將平均情況性能提高到(可能)O(log n)。 如果在一般情況下,它在某種程度上能夠跳過比算法2可以跳過的更多元素,那么分而治之的方法只能勝過算法2。 讓我們假設我們通過將數組分成兩個(幾乎)相等的連續半部來遞歸地划分問題,並決定是否由於產生的子問題,我們可能能夠跳過比算法2可以跳過更多的元素,尤其是算法2的最壞情況。 對於本討論的其余部分,讓我們假設一個輸入對於算法2來說是最壞的情況。在第一次分割之后,我們可以檢查兩個半部分的頂部和底部元素,以獲得導致O(1)性能的相同極端情況。算法2,但結果是兩個半部的O(n)性能。 如果下半部分中的所有元素都小於0並且上半部分中的所有元素都大於n-1,則會出現這種情況。 在這些情況下,對於我們可以排除的任何一半,我們可以立即將O(1)性能排除在底部和/或上半部分之外。 當然,在進一步遞歸之后仍然需要確定該測試不能排除的任何一半的性能,再將該半除以半直到我們找到其頂部或底部元素包含其索引值的任何段。 與算法2相比,這是一個相當不錯的性能提升,但它僅出現在算法2最壞情況的某些特殊情況下。 我們用分而治之的方式做的就是減少(略微)引起最壞情況行為的問題空間的比例。 對於分而治之,仍然存在最壞情況,它們完全匹配大多數問題空間,這會引發算法2的最壞情況行為。

因此,鑒於分而治之的算法具有較少的最壞情況,繼續使用分而治之的方法是不是有意義?

總之,沒有。 也許會。 如果您事先知道大約一半的數據小於0且一半大於n,那么這種特殊情況通常會采用分而治之的方法。 或者,如果您的系統是多核的並且您的'n'很大,那么在所有核心之間平均分配問題可能會有所幫助,但是一旦它們在它們之間分開,我認為每個核心上的子問題可能是最好的用上面的算法2解決,避免進一步划分問題,當然避免遞歸,正如我在下面論述....

在遞歸的分治方法的每個遞歸級別,算法需要某種方式來記住問題的尚未解決的后半部分,同時它會遞歸到上半部分。 通常,這是通過讓算法首先為一半遞歸調用自身,然后為另一半,一種在運行時堆棧上隱式維護此信息的設計來完成的。 另一種實現可以通過在顯式堆棧上保持基本相同的信息來避免遞歸函數調用。 在空間增長方面,算法2是O(1),但任何遞歸實現都不可避免地是O(log n),因為必須在某種堆棧上維護這些信息。 但是除了空間問題之外,遞歸實現還有額外的運行時開銷,即記住尚未遞歸到子問題的一半的狀態,直到可以遞歸到它們為止。 這種運行時開銷並不是免費的,並且考慮到上面算法2的實現的簡單性,我認為這種開銷是成比例的。 因此,我建議上面的算法2將對絕大多數情況下的任何遞歸實現進行全面打擊。

在最壞的情況下,你不能做任何比檢查每個元素更好的事情。 (想象一下像a[i] = i + uniform_random(-.25, .25) 。)你需要一些關於你的輸入看起來像什么的信息。

實際上我會從最后一個元素開始,並進行基本檢查(例如,如果你有1000個元素,但最高為100,你知道你只需要檢查0..100)。 在最糟糕的情況下,您仍然需要檢查每個元素,但找到可能的區域應該更快。 如果它如上所述(a [i] = i + [-0.25..0.25]),你是f($!ed並且需要搜索每一個元素。

對於排序數組,您可以執行插值搜索。 二分搜索類似,但假設值均勻分布,可以更快。

我認為這里的主要問題是你的沖突陳述:

a [i] == i

數組中的所有元素都是有序且不同的,它們不必總是整數

如果數組的值等於其訪問下標,則表示它是一個整數。 如果它不是一個整數,並且他們說... char ,什么被認為是“排序”? ASCII值( A < B < C )?

如果它是一系列字符,我們會考慮:

a[i] == i

如果是真的

我== 65 10 && a [i] =='A'

如果我在這次采訪中,我會在回答問題之前給面試官提供跟進問題。 那說......

如果我們所知道的只是你所說的,我們可以安全地說我們可以在O(n)中找到值,因為這是完成數組一次完整傳遞的時間。 有了更多細節,我們可以通過二進制搜索數組將其限制為O(log(n))。

注意到數組中的所有元素都是排序和不同的 ,所以如果我們用b [i] = a [i] -i構造一個新數組b,數組b中的元素也會被排序 ,我們需要找到的就是查找數組中的零。 我認為二分搜索可以解決問題! 這是一個用於計算已排序數組中出現次數的鏈接 您也可以在原始陣列上執行類似的Divide&Conquer技術,而無需構建輔助陣列! 時間復雜度是O(Logn)!

Take this as an example:
a=[0,1,2,4,8]
b=[0,0,0,1,4]
What we need to find is exactly index 0,1,2

希望能幫助到你!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM