一次性线性时间,具有 O(1) 额外空间(4 个变量)。非常高效(每次迭代只有几个比较/分支,并且没有太多的数据混洗)。
这不是我最初的想法或算法,我只是整理了一下并评论了the code in an ideone fork。您可以在那里向代码添加新的测试用例并在线运行。 original 是 Kenneth, posted in comments on a thread on www.geeksforgeeks.org。很棒的算法,但最初的实现在实际循环之外有一些非常愚蠢的代码。 (例如,代替局部变量,让我们在一个类中使用两个成员变量,并将该函数实现为class Solution 的成员函数......变量名很烂。我选择了非常冗长的。)
Kenneth,如果您想发布您的代码作为答案,请继续。我不是想窃取算法的功劳。 (不过,我确实在编写这个解释方面做了一些工作,并思考了为什么它有效。)
讨论线程上方的主要文章与 Ivaylo Strandjev 的答案具有相同的解决方案。 (主要文章的代码是 Pramod 作为对这个问题的答案发布的,在 Ivalyo 的回答几个月后。这就是我在 cmets 中找到有趣答案的方式。)
由于您只需要找到一个 解决方案,而不是全部解决方案,因此没有您期望的那么多极端案例。事实证明,如果你选择了正确的东西作为状态,你不需要跟踪你看到的每一个可能的开始和中间值,甚至根本不需要回溯。
主要技巧有:
单调递减值序列中的最后一个值是您唯一需要考虑的值。这适用于第一(低)和第二(中)候选元素。
-
任何时候你看到一个较小的候选中间元素,你可以从那里重新开始,只是寻找最终元素或更好的中间候选。
如果您在小于当前中间候选人的元素之前还没有找到由 3 个递增元素组成的序列,那么 min-so-far 和新的较小中间候选人同样好(宽容、灵活)你可以用你已经检查过的数字来做。 (请参阅代码中的 cmets 以获得更好的表述方式。)
其他几个答案犯了一个错误,即每次看到新的最小或最大元素而不是中间元素时都重新开始。您跟踪您看到的当前最小值,但在看到新中间之前您不会做出反应或使用它。
要找到新的候选中间元素,请检查它们是否小于当前的中间候选元素,以及到目前为止看到的 != min 元素。
我不确定这个想法是否可以依次扩展到 4 个或更多值。寻找新的候选 3rd 值可能需要将当前候选第二和第三之间的最小值与总最小值分开跟踪。这可能会变得棘手,并且需要更多的条件。但是如果它可以在恒定大小的状态下正确完成并且一次通过而不回溯,它仍然是线性时间。
// Original had this great algorithm, but a clumsy and weird implementation (esp. the code outside the loop itself)
#include <iostream>
#include <vector>
using namespace std;
//Find a sorted subsequence of size 3 in one pass, linear time
//returns an empty list on not-found
vector<int> find3IncreasingNumbers(int * arr, int n)
{
int min_so_far = arr[0];
int c_low, c_mid; // candidates
bool have_candidates = false;
for(int i = 1; i < n; ++i) {
if(arr[i] <= min_so_far) // less-or-equal prevents values == min from ending up as mid candidates, without a separate else if()continue;
min_so_far = arr[i];
else if(!have_candidates || arr[i] <= c_mid) {
// If any sequence exists with a middle-numbers we've already seen (and that we haven't already finished)
// then one exists involving these candidates
c_low = min_so_far;
c_mid = arr[i];
have_candidates = true;
} else {
// have candidates and arr[i] > c_mid
return vector<int> ( { c_low, c_mid, arr[i] } );
}
}
return vector<int>(); // not-found
}
int main()
{
int array_num = 1;
// The code in this macro was in the original I forked. I just put it in a macro. Starting from scratch, I might make it a function.
#define TRYFIND(...) do { \
int arr[] = __VA_ARGS__ ; \
vector<int> resultTriple = find3IncreasingNumbers(arr, sizeof(arr)/sizeof(arr[0])); \
if(resultTriple.size()) \
cout<<"Result of arr" << array_num << ": " <<resultTriple[0]<<" "<<resultTriple[1]<<" "<<resultTriple[2]<<endl; \
else \
cout << "Did not find increasing triple in arr" << array_num << "." <<endl; \
array_num++; \
}while(0)
TRYFIND( {12, 11, 10, 5, 6, 2, 30} );
TRYFIND( {1, 2, 3, 4} );
TRYFIND( {4, 3, 1, 2} );
TRYFIND( {12, 1, 11, 10, 5, 4, 3} );
TRYFIND( {12, 1, 11, 10, 5, 4, 7} );
TRYFIND( {12, 11, 10, 5, 2, 4, 1, 3} );
TRYFIND( {12, 11, 10, 5, 2, 4, 1, 6} );
TRYFIND( {5,13,6,10,3,7,2} );
TRYFIND( {1, 5, 1, 5, 2, 2, 5} );
TRYFIND( {1, 5, 1, 5, 2, 1, 5} );
TRYFIND( {2, 3, 1, 4} );
TRYFIND( {3, 1, 2, 4} );
TRYFIND( {2, 4} );
return 0;
}
制作一个可以将初始化列表作为参数的 CPP 宏很难看:
Is it possible to pass a brace-enclosed initializer as a macro parameter?
尽管如此,无需在 4 个地方编辑 arr4 到 arr5 即可轻松添加新的测试用例,这是非常值得的。