[英]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)應該更快? 它只是沒有進入我的腦海。
對於 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;
}
對於 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 ...)執行多個測試,並查看運行時如何變化。 還建議對相同數量的元素使用不同的輸入,因為理論上您可以為一種算法找到一些最佳情況,而對於另一種算法可能是最壞的情況。
快速感嘆——不確定你的第二個解決方案( (見下面 Stron 的回復)pointers
)是O(n^2)
它有第三個內部循環。
我花了一點時間使用通用 .NET 分析器分析您的代碼,並且:
應該這樣做吧? ;)
檢查實現后,我發現BinarySearch
內部使用CompareTo
,我認為這並不理想(但是,作為非托管類型的泛型,它不應該那么糟糕......)
為了“改進”它,我拖着BinarySearch
,又踢又叫,並將CompareTo
替換為實際的比較運算符。 我將此基准命名為MyImproved
結果如下:
有趣的是,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 比較似乎有很大幫助。 要么,要么是一些我不准備研究的時髦界面東西:)
兩個提示:
使用 sharplab.io 查看您降低的代碼,它可能會揭示一些東西( 鏈接)
嘗試通過 do.netBenchmark nuget package 運行這些單獨的測試,它會給你更准確的計時,如果 memory 在一種情況下的使用或分配相當高,那可能就是你的答案。
無論如何,您是在調試模式還是發布模式下運行這些測試? 我只是有一個想法,我最近沒有測試過,但我相信調試器開銷會顯着影響二進制搜索的性能。
給它一個 go,讓我知道
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.