在线性时间内在数组中查找大小为4的已排序子序列

时间:2013-07-15 12:49:30

标签: arrays algorithm language-agnostic

我们给出了一个数字数组,我们希望找到一个按升序排序的大小为4的子序列。

for eg ARRAY                :  -4 2 8 3 1 5
sorted subsequence of size 4 : -4 2 3 5

PS:有一种方法可以找到大小为3的排序子序列(see this)。我试图按照相同的思路思考,但似乎无法找到4个整数的解决方案。

5 个答案:

答案 0 :(得分:19)

这是一个解决方案,通过对输入进行k+1次传递,可以找到固定大小k的已排序子序列。每个传球都是从左到右完成的。

传递1:创建辅助阵列p1[0..n-1]p1[i]应存储小于j且位于arr[i]左侧的数字的索引arr[i](换句话说:j<i和{ {1}})。如果没有这样的元素,arr[j]<arr[i]应该包含-1。 (p1[i]与大小为3的解决方案中的p1数组相同。)

传递2:创建辅助阵列smallerp2[0..n-1]应存储小于p2[i]的数字的索引j,位于arr[i]的左侧,并且arr[i](在其他位置)单词:p1[j] != -1j<iarr[j]<arr[i])。如果没有这样的元素,p1[j]!=-1应该包含-1。

...

传递k:创建一个辅助数组p2[i]pk[0..n-1]应存储小于pk[i]的数字的索引j,位于arr[i]的左侧,并且arr[i](在其他位置)单词:p(k-1)[j] != -1j<iarr[j]<arr[i])。如果没有这样的元素,p(k-1)[j]!=-1应该包含-1。

pk[i]次传递后,k对应于大小为pk[i] != -1的已排序子序列中最大元素的每个元素。

k+1次传递的伪代码(k> 1):

k

示例:

function do_kth_pass(pk[], p_k_minus_1[])
    min = -1
    for i in 0..n-1:
        if min != -1 and arr[i] > arr[min]:
            pk[i] = min
        else
            pk[i] = -1
        if p_k_minus_1[i] != -1 and (min == -1 or arr[i] < arr[min]):
            min = i

经过3次传递后,你有p3 [5]!= -1,所以存在大小为4的已排序子序列。其元素的索引是:Index: 0 1 2 3 4 5 Array: -4 2 8 3 1 5 p1: -1 0 0 0 0 0 p2: -1 -1 1 1 -1 4 p3: -1 -1 -1 -1 -1 3 ,即0,1,3,5

答案 1 :(得分:4)

拥有更大和更小的阵列是一个不错的选择,但它会增加空间复杂性。下面是一个解决方案,在线性子序列中找到四个数字,没有额外的数组空间,而是使用常量空间,并且只有一个通过数组。

#include <iostream>

void sortedSubseqFour(int a[], int n)
{
    int small = INT_MAX;
    int middle_1 = INT_MAX;
    int middle_2 = INT_MAX; 
    int greater = 0;

    int main_small = 0;
    int main_middle_1 = 0;

    int main_main_small = 0;

    for(int i = 0; i<n; i++)
    {
        if (a[i] <= small)
            small = a[i];
        else if (a[i] <= middle_1)
        {
            middle_1 = a[i];
            main_small = small;
        }
        else if (a[i] <= middle_2)
        {
            middle_2 = a[i];
            main_middle_1 = middle_1;
            main_main_small = main_small;
        }
        else
        {
            greater = a[i];
            break;
        }
    }
    //end of loop

    if (greater != 0)
        std::cout << main_main_small << '\t' << main_middle_1 << '\t'
                  << middle_2 << '\t' << greater;
    else
        std::cout << "No such Quadruple";
}

int main()
{
    int arr[10] = {6, 7, 5, 1, 4, 3, 0, 7, 2, 11};
    int n = 10; 

    sortedSubseqFour(arr, n);
    return 0;
}

上述方法在设置当前最小值时会记住所有最小值层。通过删除代码的3main_main_small部分,相同的代码也可用于数组中middle_2大小的已排序子序列。

如果相同的代码要扩展到k大小,那么最小i,我们必须记住i之前的所有最小值,即min_1min_2,......直到min_i。只有在最后一个最小值,即我们子序列中的最大值,我们才会中断,并且不需要记住先前或当前的最小值。

如果发现任何错误,请告知!

答案 2 :(得分:1)

你可以找到增长最长的子序列,看看它的大小是否大于等于4(如果你需要找到一个更一般的问题,甚至是k)。如果最长增加子序列的长度小于4(或k),则可以报告不存在此类子序列。 LIS可以在O(nlog(n))

中找到

答案 3 :(得分:0)

创建一个smallergreater数组,类似于对大小为3的子序列所做的操作。除此之外,还有betweenSmallerAndCurrent数组,用于存储a的索引最小值和当前元素之间的值 - 包括值和索引。更明确地说:

betweenSmallerAndCurrent[i] = -1 or

input[smaller[i]] < input[betweenSmallerAndCurrent[i]] < input[value[i]] and
smaller[i] < betweenSmallerAndCurrent[i] < value[i]

构建这个应该很容易。

您只需返回ibetweenSmallerAndCurrent[i]smaller[betweenSmallerAndCurrent[i]]全部初始化的索引greater[i]。请注意,我们不能简单地检查smaller[i],因为我们可能会有类似[2,3,1,4,5]的内容,在这种情况下,当我们到达4时,第二个最小值3就在之前当前最小值1

示例:

Indices: 0  1  2  3  4  5  6  7
Input:   12 11 10 5  6  2  9  30

smaller: -1 -1 -1 -1 3  -1 5  5
betweenSmallerAndCurrent: 
         -1 -1 -1 -1 -1 -1 4  4

greater: 7  7  7  7  7  7  7  -1

初始化所有值的唯一索引是6(输入值9)。

Java代码:(未经过广泛测试)

void find4Numbers(int arr[], int n)
{
   int max = n-1; //Index of maximum element from right side
   int min = 0, second = -1; //Index of minimum element from left side
   int i;

   // Create an array that will store index of a smaller
   // element on left side. If there is no smaller element
   // on left side, then smaller[i] will be -1.
   int[] smaller = new int[n];
   int[] betweenSmallerAndCurrent = new int[n];
   smaller[0] = -1;  // first entry will always be -1
   betweenSmallerAndCurrent[0] = -1;
   for (i = 1; i < n; i++)
   {
       if (arr[i] <= arr[min])
       {
          min = i;
          smaller[i] = -1;
          betweenSmallerAndCurrent[i] = -1;
       }
       else
       {
          smaller[i] = min;
          if (second != -1 && arr[second] < arr[i])
             betweenSmallerAndCurrent[i] = second;
          else 
             betweenSmallerAndCurrent[i] = -1;
          if (second == -1 || arr[i] < arr[second])
             second = i;
       }
   }

   // Create another array that will store index of a
   // greater element on right side. If there is no greater
   // element on right side, then greater[i] will be -1.
   int[] greater = new int[n];
   greater[n-1] = -1;  // last entry will always be -1
   for (i = n-2; i >= 0; i--)
   {
       if (arr[i] >= arr[max])
       {
          max = i;
          greater[i] = -1;
       }
       else
          greater[i] = max;
   }

   // Make sure they're right
   System.out.println(Arrays.toString(smaller));
   System.out.println(Arrays.toString(betweenSmallerAndCurrent));
   System.out.println(Arrays.toString(greater));

   // Now find a number which has both a greater number on
   // right side and smaller number on left side
   for (i = 0; i < n; i++)
   {
       if (betweenSmallerAndCurrent[i] != -1 && smaller[betweenSmallerAndCurrent[i]] != -1 && greater[i] != -1)
       {
          System.out.printf("%d %d %d %d\n",
                            arr[smaller[betweenSmallerAndCurrent[i]]],
                            arr[betweenSmallerAndCurrent[i]],
                            arr[i],
                            arr[greater[i]]);
          return;
       }
   }

   // If we reach number, then there are no such 3 numbers
   System.out.println("No such triplet found");
}

您可能会注意到主要代码从this更改,除了C到Java转换和添加的初始化之外,还在于设置smaller的循环。代码应该很容易理解 - 如果遇到问题,请尝试将其翻译成单词。

Test

答案 4 :(得分:0)

对于每个元素,找到下一个更大的元素索引,否则为-1 现在将其视为图形并找到长度为k的路径(如果存在) 这可以使用散列表和memoization在线性时间内轻松完成。