簡體   English   中英

3SUM - O(n^2 * log n) 比 O(n^2) 慢?

[英]3SUM - O(n^2 * log n) slower than O(n^2)?

在我呈現給您的場景中,我的解決方案應該代表 O(n^2 * log n),而我認為是解決“3SUM”問題的最快方法的“指針”解決方案代表 O(n ^2 * 1); 留下的問題是 O(1) 比 O(log n) 快,用我的代碼舉例。
有人可以解釋為什么會這樣嗎? 請。 我的邏輯告訴我 O(log n) 應該和 O(1) 一樣快,如果不是更快的話。
我希望我對我的解決方案代碼的評論是清楚的。

編輯:我知道這聽起來不是很聰明...... log(n) 計算輸入 (n -> ∞),而 1...... 只是 1。但是,在這種情況下,為了找到一個數字,如何做加法和減法而不是使用二進制搜索(log n)應該更快? 它只是沒有進入我的腦海。


LeetCode 3SUM 問題描述


O(n^2 * log n)

對於 3,000 個值的輸入:

  • 迭代次數: 1,722,085 (比“指針解決方案”少 61%)
  • 運行時間: ~92 毫秒(比典型的 O(n^2) 解決方案慢 270%)
public IList<IList<int>> MySolution(int[] nums)
{
    IList<IList<int>> triplets = new List<IList<int>>();

    Array.Sort(nums);

    for (int i = 0; i < nums.Length; i++)
    {
        // Avoid duplicating results.
        if (i > 0 && nums[i] == nums[i - 1])
            continue;

        for (int j = i+1; j < nums.Length - 1; j++)
        {
            // Avoid duplicating results.
            if (j > (i+1) && nums[j] == nums[j - 1])
                continue;

            // The solution for this triplet.
            int numK = -(nums[i] + nums[j]);

            // * This is the problem.
            // Search for 'k' index in the array.
            int kSearch = Array.BinarySearch(nums, j + 1, nums.Length - (j + 1), numK);

            // 'numK' exists in the array.
            if (kSearch > 0)
            {
                triplets.Add(new List<int>() { nums[i], nums[j], numK });
            }
            // 'numK' is too small, break this loop since its value is just going to increase.
            else if (~kSearch == (j + 1))
            {
                break;
            }
        }
    }

    return triplets;
}

O(n^2)

對於 3,000 個值的相同輸入:

  • 迭代次數: 4.458.579
  • 運行時間: ~34 毫秒
public IList<IList<int>> PointersSolution(int[] nums)
{
    IList<IList<int>> triplets = new List<IList<int>>();

    Array.Sort(nums);

    for (int i = 0; i < nums.Length; i++)
    {
        if (i > 0 && nums[i] == nums[i - 1])
            continue;

        int l = i + 1, r = nums.Length - 1;

        while (l < r)
        {
            int sum = nums[i] + nums[l] + nums[r];

            if (sum < 0)
            {
                l++;
            }
            else if (sum > 0)
            {
                r--;
            }
            else
            {
                triplets.Add(new List<int>() { nums[i], nums[l], nums[r] });

                do
                {
                    l++;
                }
                while (l < r && nums[l] == nums[l - 1]);
            }
        }
    }

    return triplets;
}

似乎您的概念誤解來自這樣一個事實,即您錯過了Array.BinarySearch也進行了一些迭代(這由您現在已更改的問題中的初始迭代計數表明)。

因此,雖然假設二分搜索應該比通過集合的簡單迭代更快是非常有效的 - 你錯過了二分搜索基本上是一個額外的循環,所以你不應該比較這兩者,而是比較第二個for循環+第一個二分搜索針對第二個循環的解決方案。

聚苯乙烯

要至少在一定程度上確定基於運行時的時間復雜度,您至少需要使用不同數量的元素(如 100、1000、10000、100000 ...)執行多個測試,並查看運行時如何變化。 還建議對相同數量的元素使用不同的輸入,因為理論上您可以為一種算法找到一些最佳情況,而對於另一種算法可能是最壞的情況。

快速感嘆——不確定你的第二個解決方案( pointers )是O(n^2)它有第三個內部循環。 (見下面 Stron 的回復)

我花了一點時間使用通用 .NET 分析器分析您的代碼,並且:

在此處輸入圖像描述

應該這樣做吧? ;)

檢查實現后,我發現BinarySearch內部使用CompareTo ,我認為這並不理想(但是,作為非托管類型的泛型,它不應該那么糟糕......)

為了“改進”它,我拖着BinarySearch ,又踢又叫,並將CompareTo替換為實際的比較運算符。 我將此基准命名為MyImproved結果如下:

火焰圖

Benchmark.NET 結果:

有趣的是,Benchmark.NET 無視常識並將MyImproved置於Pointers之上。 這可能是由於分析器關閉了一些優化。

方法 復雜 意思 錯誤 標准偏差 代碼大小
指針 O(n^2)??? 76.76 毫秒 1.465 毫秒 1.628 毫秒 1,781 乙
我的 O(n^2 * log n) 93.08 毫秒 1.831 毫秒 3.980 毫秒 1,999 乙
MyImproved O(n^2 * log n) 62.53 毫秒 1.234 毫秒 2.226 毫秒 1,980 乙

長話短說:

.CompareTo()似乎阻礙了.BinarySearch()的實施。 刪除它並使用實際的 integer 比較似乎有很大幫助。 要么,要么是一些我不准備研究的時髦界面東西:)

兩個提示:

  1. 使用 sharplab.io 查看您降低的代碼,它可能會揭示一些東西( 鏈接

  2. 嘗試通過 do.netBenchmark nuget package 運行這些單獨的測試,它會給你更准確的計時,如果 memory 在一種情況下的使用或分配相當高,那可能就是你的答案。

無論如何,您是在調試模式還是發布模式下運行這些測試? 我只是有一個想法,我最近沒有測試過,但我相信調試器開銷會顯着影響二進制搜索的性能。

給它一個 go,讓我知道

暫無
暫無

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

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