是否有一种算法可以在


21

我想证明或反对一种算法的存在,该算法在给定整数数组下找到三个索引和,使得且(或发现没有这样的三元组)线性时间。Ai,jki<j<kA[i]<A[j]<A[k]

这不是一个作业问题。我在一个编程论坛上看到它是“尝试实现这种算法”。我怀疑经过各种实验后这是不可能的。我的直觉告诉我,但这并不算什么。

我想正式证明这一点。你怎么做呢?理想情况下,我希望逐步看到一个证明,然后,如果您愿意,可以对如何证明/证明这样的简单问题进行一些解释。如果有帮助,请举一些例子:

[1,5,2,0,3] → (1,2,3)
[5,6,1,2,3] → (1,2,3)
[1,5,2,3] → (1,2,3)
[5,6,1,2,7] → (1,2,7)
[5,6,1,2,7,8] → (1,2,7)
[1,2,999,3] → (1,2,999)
[999,1,2,3] → (1,2,3)
[11,12,8,9,5,6,3,4,1,2,3] → (1,2,3)
[1,5,2,0,-5,-2,-1] → (-5,-2,-1)

我以为有人可以遍历,并且每次有一个(即我们当前的)时,我们都会创建一个新的三元组并将其推入数组。我们继续步进并比较每个三元组,直到完成其中一个三元组。所以,这就像,!但是我认为这比仅仅更复杂,因为在最坏的情况下,我们的三元组数组中三元组的数量将对应于输入列表的大小。Ai<jj[1,5,2,0,-5,-2,-1] → 1..2.. -5.. -2.. -1[1,5,2,0,-5,-2,3,-1] → 1..2.. -5.. -2.. 3O(n)



请注意,在最坏的情况下(排序数组),您甚至会有许多合适的三元组。请考虑将您建议的算法提供为伪代码;我认为您的解释不完整。Θ(n3)
拉斐尔

Answers:


14

这是最长增长子序列问题的变体。这是维基百科上使用两个辅助数组P给出的解决方案:MP

  • -存储位置 ķ最小值的[ ķ ]使得存在长度的递增子 Ĵ在结束[ ķ ]上的范围 ķ (注意我们有 Ĵ ķ 在这里,因为 j代表增加的子序列的长度, k代表其终止的位置,显然,我们永远不可能有长度 13的增加的子序列终止于位置 11M[j]kA[k]jA[k]kijkijk1311根据定义)。ki
  • 存储的前身的位置-[ ķ ]在结束的最长递增子[ ķ ]P[k]A[k]A[k]

    另外,该算法还存储了一个变量该变量L表示到目前为止找到的最长的递增子序列的长度。L

该算法在最坏情况下的时间。您的问题是一种特殊情况,它允许您在L = 3时返回,从而将运行时间推至O n ),因为二进制搜索仅在长度最多为2的数组上运行,因此时间为O 1 ,而不是一般情况下为Θ log n Θ(nlogn)L=3O(n)O(1)Θ(logn)

考虑修改后的伪代码:

 L = 0
 for i = 1, 2, ... n:
    binary search for the largest positive j ≤ L
      such that X[M[j]] < X[i] (or set j = 0 if no such value exists)
    P[i] = M[j]
    if j == L or X[i] < X[M[j+1]]:
       M[j+1] = i
       L = max(L, j+1)
   if L==3 : return true; // you can break here, and return true.
return false; // because L is smaller than 3.

@SaeedAmiri我看到了评论,但我还没来得及评论(我睡前张贴了问题)。我从您的链接中怀疑,我们的特殊情况L = 3可以以某种方式提供帮助,但没有机会了解细节。我目前在工作和时间有限。请放心,我感谢您的回答。如果您没有完全理解其中的每一行,那么对您来说,这是肤浅的事情。
Christopher Done 2012年

@SaeedAmiri:我同意您在这里期望更多的“填补空白”,但是您仍然必须至少给出一个证明的基本论点(尽管如此)。关于OP,他似乎是意大利人,所以您的评论和回答之间可能已经熟睡了(很可能他现在正忙于东部)。
拉斐尔

@ChristopherDone,我不想让您伤心,对不起,这是我的错误,您绝对正确。

+1:这很好地概括了一下,只进行了一次通过,为空间。O(1)
Aryabhata 2012年

好,看起来不错。我花了一些时间来理解一般最长的递增序列算法的行为。之后,最大长度== 3的变化就可以了。谢谢!
Christopher Done

11

方法论说明

我对此问题进行了思考,并找到了解决方案。当我阅读Saeed Amiri的答案时,我意识到我想到的是针对长度为3的序列的标准最长子序列发现算法的专用版本。我发布了提出该解决方案的方式,因为我认为是解决问题的有趣例子。

两元素版本

让我们从小处着手:与其寻找元素按顺序排列的三个索引,不如寻找两个索引:使得A [ i ] < A [ j ]i<jA[i]<A[j]

如果正在减小(即< Ĵ [ ] [ Ĵ ],或等价[ ] [ + 1 ]),则没有这样的索引。否则,存在一个索引i使得A [ i ] < A [ i + 1 ]Ai<j,A[i]A[j]i,A[i]A[i+1]iA[i]<A[i+1]

这种情况很简单。我们将尝试对其进行概括。它表明上述问题无法解决:所请求的索引并不总是存在。因此,我们宁愿要求该算法返回有效索引(如果存在),或者正确地声明不存在此类索引。

提出算法

我将使用术语亚序列是指从阵列的提取物组成指标的可能不连续的([ 1 ] ... [ 中号 ] 1 < < 中号),以及运行表示AA [ i ] A [ i + 1 ] A [A(A[i1],,A[im])i1<<imA)。(A[i],A[i+1],,A[i+m1])

我们只是看到请求的索引并不总是存在。我们的策略是研究不存在指标的情况。我们通过假设我们正在尝试找到索引并查看我们的搜索可能会出错的方式来完成此操作。然后,搜索不会出错的情况将提供一种找到索引的算法。

4,3,2,1,0

使用两个索引,我们可以找到连续的索引。有了三个索引,我们可能无法得出k = j + 1。但是,我们可以考虑解决三个严格增加的元素(A [ i ] < A [ i + 1 ] < A [ i + 2 ])的情况,因为这种情况很容易识别,并查看如何不满足此条件。假设序列没有严格增加的长度为3的游程。j=i+1k=j+1A[i]<A[i+1]<A[i+2]

4,3,2,1,2,3,2,1,0

该序列仅具有长度2 的严格增加的游程(以下简称为有序对),至少由长度2的递减的间隔隔开。为了严格增加游程要成为递增的3元素序列的一部分,必须有一个较早的元素i使得A [ i ] < A [ j ]或一个较后的元素k使得A [ j + 1 ] < A [ kA[j]<A[j+1]iA[i]<A[j]kA[j+1]<A[k]

4,3,2,2.5,1.5,0.5,1,0

k都不存在的情况是每个有序对完全低于下一个对。这还不是全部:当双绞线交错时,我们需要对其进行更精细的比较。ik

3,2,1,3.5,2.5,1.5,0.5,-0.5,1.25,-0.25 3,2,1,2.5,1.5,0.5,2,1,0

子序列不断增加的最左边的元素需要提早出现并且变小。下一个元素j需要更大,但必须尽可能小才能找到第三个更大的元素k。第一个元素i并不总是序列中最小的元素,也不一定总是第一个元素具有后续的更大元素-有时还会有一个较低的2个元素子序列,有时会有一个更好的元素适合已经找到的最小值。ijki

2.1,3,2,1,2.5,1.5,0.5,2,1,0 1,2,0,2.5,1.5,0.5

从左到右,我们尝试将最小的元素选为。如果在更右边找到一个较大的元素,则选择该对作为暂定i j 。如果找到更大的k,我们就赢了。需要注意的关键是,我们对i的选择和i j )的选择是独立更新的:如果我们有一个候选项i j 并且我们发现i > j使得A [ i ] < A [i(i,j)ki(i,j)(i,j)i>j i '成为下一个候选 i,i j 保留。只有找到 j '使得 A [ j ' ] < A [ j ]才会i 'j '成为新的候选对。A[i]<A[i]ii(i,j)jA[j]<A[j](i,j)

算法说明

以Python语法给出,但请注意,我尚未对其进行测试。

def subsequence3(A):
    """Return the indices of a subsequence of length 3, or None if there is none."""
    index1 = None; value1 = None
    index2 = None; value2 = None
    for i in range(0,len(A)):
        if index1 == None or A[i] < value1:
            index1 = i; value1 = A[i]
        else if A[i] == value1: pass
        else if index2 == None:
            index2 = (index1, i); value2 = (value1, A[i])
        else if A[i] < value2[1]:
            index2[1] = i; value2[1] = A[i]
        else if A[i] > value2[1]:
            return (index2[0], index2[1], i)
    return None

证明草图

index1是已经遍历的数组部分的最小值的索引(如果多次出现,则保留第一次出现),或者None在处理第一个元素之前。index2将长度为2的递增子序列的索引存储在数组中已遍历的,具有最小最大元素的部分中,或者None如果不存在这样的序列,则将其存储。

return (index2[0], index2[1], i)运行时,我们有value2[0] < value[1](这是一个不变量value2)和value[1] < A[i](从上下文明显)。如果循环结束而没有调用提早返回,则不是value1 == None,在这种情况下,长度2的递增子序列就不会增加,更不用说3了,或者value1包含长度2的递增子序列却具有最小的最大元素。在后一种情况下,我们还具有不变性,即长度3的递增子序列没有比早结束的value1;因此,添加到的任何此类子序列的最后一个元素value2将形成长度3的递增子序列,因为我们还有不变量,value2它不属于数组已遍历部分中长度3的递增子序列的一部分,因此在整个数组中没有这样的子序列。

证明上述不变式是留给读者的练习。

复杂

O(1)O(1)O(n)

正式证明

留给读者练习。


8

ØñØñ

首先,从左到右遍历该数组,并保持一个堆栈和一个辅助数组,该数组告诉您每个元素的位置,该元素的索引大于该元素并位于该元素的右侧。

-1个

每当您考虑数组中的新元素时,如果该元素大于堆栈的顶部元素,则将其从堆栈中弹出,并设置与顶部相对应的aux数组元素,以使新元素的索引位于考虑。

当当前元素更大时,继续将元素弹出堆栈并设置相应的索引。一旦顶部具有不小于(或变空)的元素,则将当前元素推入堆栈,然后继续执行数组的下一个元素,重复上述步骤。

再次通过(和另一个辅助阵列),但是从右到左。

-1个

Øñ

ķ-一世

第一遍的伪代码可能如下所示:

Stack <Pair<Elem, Index>> greats;
Elem auxArr[inputArr.Length];

for (Index i = 0; i < inputArr.Length; i++) {

    while (!greats.IsEmpty() && inputArr[i] > greats.PeekTop().Elem) {
        Pair top = greats.Pop();
        auxArr[top.Index] = i;
    }

    Pair p;
    p.Elem = inputArr[i];
    p.Index = i;

    greats.Push(p);
}

“由于只考虑数组的每个元素恒定的次数,所以这是O(n)次。”噢,糟透了。不知何故,我排除了多次常量通过,而不将其丢弃为O(n)。很蠢 感谢您的解释,我将再次尝试解决它。
Christopher Done 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.