如何以递增的顺序找到3个数字并在线性时间内增加数组中的索引

时间:2012-04-04 09:09:00

标签: arrays algorithm

我在网站上遇到过这个问题。正如那里提到的那样,在亚马逊的采访中被问到了。在给定的约束条件下,我无法找到合适的解决方案。

给定n个整数数组,找到 3个元素,以便 O(n)时间内a[i] < a[j] < a[k]i < j < k

14 个答案:

答案 0 :(得分:30)

所以这就是你如何解决问题的方法。您需要迭代数组三次。在第一次迭代时,标记右侧和第二次迭代中具有大于它们的元素的所有值,标记左侧小于它们的所有元素。现在你的答案将是一个同时具有以下内容的元素:

int greater_on_right[SIZE];
int smaller_on_left[SIZE];
memset(greater_on_rigth, -1, sizeof(greater_on_right));
memset(smaller_on_left, -1, sizeof(greater_on_right));

int n; // number of elements;
int a[n]; // actual elements;
int greatest_value_so_far = a[n- 1];
int greatest_index = n- 1;
for (int i = n -2; i >= 0; --i) {
   if (greatest_value_so_far > a[i]) {
     greater_on_right[i] = greatest_index;
   } else {
     greatest_value_so_far = a[i];
     greatest_index = i;
   }
}

// Do the same on the left with smaller values


for (int i =0;i<n;++i) {
  if (greater_on_right[i] != -1 && smaller_on_left[i] != -1) {
    cout << "Indices:" << smaller_on_left[i] << ", " << i << ", " << greater_on_right[i] << endl;
  }
}

此解决方案在整个阵列上迭代3次,因此是线性的。我没有提供整个解决方案,所以你可以在左边训练自己,看看你是否明白我的想法。我很抱歉不提供一些提示但我无法弄清楚如何在没有显示实际解决方案的情况下给出提示。

希望这能解决你的问题。

答案 1 :(得分:3)

一次通过线性时间,带有O(1)额外空间(4个变量)。效率非常高(每次迭代只需要几次比较/分支,而且没有太多的数据改组)。

这是 NOT 我最初的想法或算法,我只是整理并评论the code in an ideone fork。您可以在那里的代码中添加新的测试用例并在线运行。 originalKenneth, posted in comments on a thread on www.geeksforgeeks.org。很棒的算法,但最初的实现在实际循环之外有一些非常愚蠢的代码。 (例如,代替局部变量,让我们在一个类中使用两个成员变量,并将该函数实现为class Solution的成员函数...并且变量名被吸引。我去了相当冗长的。 )

Kenneth,如果您想将代码作为答案发布,请继续。我不是想为这个算法窃取信誉。 (我确实做了一些工作来编写这个解释,然后通过为什么来思考它。)

讨论主题上方的主要文章与Ivaylo Strandjev的答案具有相同的解决方案。 (主要文章的代码是Pramod在Ivalyo回答几个月之后发布的对这个问题的回答。这就是我在那里的评论中找到有趣答案的方法。)

由于您只需要找到一个解决方案,而不是所有解决方案,因此没有像您期望的那样多的极端情况。事实证明,如果你选择合适的东西保持状态,你不需要跟踪你所见过的每一个可能的起点和中间值,甚至可以回溯。

主要技巧是:

  • 单调递减值序列中的最后一个值是您需要考虑的唯一值。这适用于第一(低)和第二(中)候选元素。

  • 每当你看到中间元素的较小候选者时,你可以从那里开始新鲜,只需寻找最终元素或更好的中间候选人。

    如果你没有在一个小于当前中间候选人的元素之前找到3个增加元素的序列,那么min-so-far和新的较小的中间候选者就像那样好(宽容,灵活)你可以用已经检查过的数字来做。 (请参阅代码中的注释,以便更好地说明这一点。)

    其他几个答案在每次看到新的最小或最大元素而不是中间元素时都会犯错误。你跟踪你所看到的当前最小值,但是在你看到一个新的中间点之前你不会做出反应或使用它。

要查找新的候选中间元素,请检查它们是否小于当前中间候选项,以及到目前为止看到的!= min元素。

我不确定这个想法是否可以按顺序扩展到4个或更多值。找到新的候选第3个值可能需要跟踪当前候选第二个和第三个之间的分钟,与总分钟分开。这可能会变得棘手,并且需要更多的条件。但如果可以使用恒定大小的状态正确完成并且一次传递没有回溯,那么它仍然是线性时间。

// 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个位置编辑arr4arr5

答案 2 :(得分:1)

我发布了另一种解决方法here

#include<stdio.h>

// A function to fund a sorted subsequence of size 3
void find3Numbers(int arr[], int n)
{
   int max = n-1; //Index of maximum element from right side
   int min = 0; //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];
   smaller[0] = -1;  // first entry will always be -1
   for (i = 1; i < n; i++)
   {
       if (arr[i] < arr[min])
       {
          min = i;
          smaller[i] = -1;
       }
       else
          smaller[i] = min;
   }

   // 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;
   }

   // 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 (smaller[i] != -1 && greater[i] != -1)
       {
          printf("%d %d %d", arr[smaller[i]],
                 arr[i], arr[greater[i]]);
          return;
       }
   }

   // If we reach number, then there are no such 3 numbers
   printf("No such triplet found");
   return;
}

// Driver program to test above function
int main()
{
    int arr[] = {12, 11, 10, 5, 6, 2, 30};
    int n = sizeof(arr)/sizeof(arr[0]);
    find3Numbers(arr, n);
    return 0;
}

答案 3 :(得分:1)

只是为了好玩: 在JAVA:

List<Integer> OrderedNumbers(int[] nums){
    List<Integer> res = new LinkedList<>();
    int n = nums.length;
    //if less then 3 elements, return the empty list
    if(n<3) return res;

    //run 1 forloop to determine local min and local max for each index
    int[] lMin = new int[n], lMax = new int[n];
    lMin[0] = nums[0]; lMax[n-1] = nums[n-1];
    for(int i=1; i<n-1; i++){
        lMin[i] = Math.min(lMin[i-1], nums[i]);
        lMax[n-i-1] = Math.max(lMax[n-i],nums[n-i-1]);
    }

    //if a condition is met where min(which always comes before nums[i] and max) < nums[i] < max, add to result set and return;
    for(int i=1; i<n-1; i++){
        if(lMin[i]<nums[i] && nums[i]<lMax[i]){
            res.add(lMin[i]);
            res.add(nums[i]);
            res.add(lMax[i]);
            return res;
        }
    }
    return res;
}

答案 4 :(得分:1)

这个问题非常类似于计算最长的增加子序列,其约束条件是该子序列的大小必须等于3。对于这个特定问题,可以很容易地修改LIS问题(使用O(nlog(n))解决方案)。此解决方案具有 O(n)单通复杂度, O(1)空间。

此解决方案要求列表中仅出现唯一元素。我们使用online solution。当我们遇到任何新元素时,它有可能扩展当前最优的子序列或开始新的子序列。在这种情况下,由于增加子序列的最大长度为3,当前正在处理的任何新元素都可以扩展大小为2到3和1到2的序列。因此,我们维护包含最佳元素的活动列表。

在这个特殊问题中,我们必须维护的最大活动列表数为2 - 大小为2,大小为1的另一个。只要我们找到大小为3的列表,我们就得到了答案。我们确保每个活动列表以最小数量终止。有关此想法的更详细说明,请参阅this

在在线解决方案的任何时间点,这两个活动列表将存储列表中最有效的值 - 列表的末尾将是可放置在那里的最小元素。假设两个列表是:

尺寸2列表=&gt; [A,B]

尺寸1列表=&gt;并[c]

可以轻松编写初始列表(请参阅下面的代码)。假设要输入的下一个数字是d。然后案例(级联执行)如下:

案例1:d > b。 在这种情况下,我们的答案为a < b < d

案例2:b > d > a。在此,大小为2的列表可以通过将结尾设为d而不是b来最佳地表示,因为d之后出现的大于b的每个元素也将大于dd < c。所以我们用d替换b。

案例3:d < a。当案例1和案例2失败时,它会自动隐含c。在这种情况下,它可以开始一个大小为1的新列表。比较大小为1的列表以获得最有效的活动列表。如果是这种情况,我们会将d替换为Otherwise

案例4:d < b。这种情况意味着c < d[a, b]。在这种情况下,大小为2的列表效率低下。因此,我们将[c, d]替换为#include <bits/stdc++.h> using namespace std; class Solution { public: int two_size_first; int two_size_mid; int one_size; int end_index; vector<int> arr; Solution(int size) { end_index = two_size_mid = two_size_first = one_size = -1; int temp; for(int i=0; i<size; i++) { cin >> temp; arr.push_back(temp); } } void solve() { if (arr.size() < 3) return; one_size = two_size_first = arr[0]; two_size_mid = INT_MAX; for(int i=1; i<arr.size(); i++) { if(arr[i] > two_size_mid) { end_index = i; return; } else if (two_size_first < arr[i] && arr[i] < two_size_mid) { two_size_mid = arr[i]; } else if (one_size > arr[i]) { one_size = arr[i]; } else { two_size_first = one_size; two_size_mid = arr[i]; } } } void result() { if (end_index != -1) { cout << two_size_first << " " << two_size_mid << " " << arr[end_index] << endl; } else { cout << "No such sequence found" << endl; } } }; int main(int argc, char const *argv[]) { int size; cout << "Enter size" << endl; cin >> size; cout << "Enter " << size << " array elements" << endl; Solution solution(size); solution.solve(); solution.result(); return 0; }

thread t1(&salinecyObjectDT::colorBasedSegmentation, objDT, image, confParams.getHsvParams(), confParams.getSpectrum(), ref(binaryColorBasedFilter));                   
t1.join();

答案 5 :(得分:0)

我的方法 - O(N)时间两次通过O(1)空间,使用两个变量

对于我们访问的数组的每个元素,我们保持其左边的最小值,以检查该元素是否可能是中间元素,并且还保留其左边最小中间元素的记录,以检查该元素是否可能是候选第三元素或它可能形成一个中间元素,其值低于目前为止所发现的值。到目前为止最初和中间的最小值为INT_MAX,

Fr每个元素因此我们必须检查:

如果一个特定的数组元素大于中间元素的最小值,那么这个数组元素就是thi作为第三个元素而min中间元素作为mid元素的答案(我们将不得不搜索第三个元素)之后一次通过)

否则如果一个特定的数组元素大于最小值,那么这个元素可能是候选中间元素,现在我们必须检查候选中间元素是否小于当前中间元素,如果这样更新当前中间元素

ELSE如果特定的数组元素小于目前为止的最小值,那么使用arr [i]更新目前为止的最小值。

因此,对于我们访问的数组的每个元素,我们保持最小可能向左,以检查此元素是否可能是中间元素,并且还保留其左侧最小中间元素的记录以检查此元素是否可能是候选第三元素,或者它可能形成一个中间元素,其值低于目前为止所发现的值     #包括     使用namespace std;

int main()
{
int i,j,k,n;
cin >> n;
int arr[n];
for(i = 0;i < n;++i)
    cin >> arr[i];
int m = INT_MAX,sm = INT_MAX,smi;// m => minimum so far found to left
for(i = 0;i < n;++i)// sm => smallest middle element found so far to left
{
    if(arr[i]>sm){break;}// This is the answer
    else if(arr[i] < m ){m = arr[i];}
    else if(arr[i] > m){if(arr[i]<sm){sm = arr[i];smi = i;}}
    else {;}
}
if((i < n)&&(arr[i]>sm))
{
    for(j = 0;j < smi;++j){if(arr[j] < sm){cout << arr[j] << " ";break;}}
    cout << sm << " " << arr[i]<< endl;
}
else 
    cout << "Such Pairs Do Not Exist" << endl;
return 0;
}

答案 6 :(得分:0)

这是我的O(n)解决方案,其空间复杂度为O(1): 只是一个返回包含三个值(如果存在)的向量的函数

`vector<int> find3Numbers(vector<int> A, int N)
 {
    int first=INT_MAX,second=INT_MAX,third=INT_MAX,i,temp=-1;
    vector<int> ans;
    for(i=0;i<N;i++)
    {
        if(first!=INT_MAX&&second!=INT_MAX&&third!=INT_MAX)
        {
            ans.push_back(first);
            ans.push_back(second);
            ans.push_back(third);
            return ans;
        }
        if(A[i]<=first)
        {
            if(second!=INT_MAX)
            {
                if(temp==-1)
                {
                    temp=first;
                }
                first=A[i];
            }
            else
            {
                first=A[i];
            }
        }
        else if(A[i]<=second)
        {
            second=A[i];
            temp=-1;
        }
        else
        {
            if(temp!=-1)
            {
                first=temp;
            }
            third=A[i];
        }
    }
    if(first!=INT_MAX&&second!=INT_MAX&&third!=INT_MAX)
    {
        ans.push_back(first);
        ans.push_back(second);
        ans.push_back(third);
        return ans;
    }
    return ans;
}`

答案 7 :(得分:0)

这是O(n)时间和O(1)空间复杂度解决方案

{{1}}

如果存在一对3个元素,它们按数组中的升序排序,则此函数返回true。 您还可以修改此功能以打印所有3个元素或其索引。只需更新其索引以及变量first和second。

答案 8 :(得分:0)

我在下面的解决方法。

public boolean increasingTriplet(int[] nums) {

    int min1 = Integer.MAX_VALUE;
    int min2 = Integer.MAX_VALUE;

    for (int i =0; i<nums.length; i++) {
        if (nums[i]<min1) {
            min1 = nums[i];
        } else if (nums[i]<min2 && nums[i]>min1) {
            min2=nums[i];
        } else if (nums[i]>min2) {
            return true;
        }
    }
    return false;
}

答案 9 :(得分:-1)

抱歉,我无法抗拒,但要解决这个难题...... 这是我的解决方案。

//array indices
int i, j, k = -1;
//values at those indices
int iv, jv, kv = 0;
for(int l=0; l<a.length(); l++){
    //if there is a value greater than the biggest value
    //shift all values from k to i
    if(a[l]>kv || j == -1 || i == -1){
        i = j;
        iv = jv;
        j = k;
        jv = kv
        kv = a[l]
        k = l
    }
    if(iv < jv && jv < kv && i < j && j < k){
        break;
    }    
}

答案 10 :(得分:-1)

尝试创建两个变量:

1. index_sequence_length_1 = index i such
   a[i] is minimal number
2. index_sequence_length_2 = index j such
   There is index i < j such that a[i] < a[j] and a[j] is minimal

迭代整个数组并在每次迭代中更新这些变量。

如果迭代大于[index_sequence_length_2]的元素,则比找到序列。

答案 11 :(得分:-1)

迭代一次并完成:

public static int[] orderedHash(int[] A){
    int low=0, mid=1, high=2;
    for(int i=3; i<A.length; i++){
        if(A[high]>A[mid] && A[mid]>A[low])
            break;

        if(A[low]>A[i])
            low=mid=high=i;
        else if(low == mid && mid == high)
            mid = high = i;
        else if(mid == high){
            if(A[high]<A[i])
                high = i;
            else
                mid = high = i;
        }
        else if(A[mid]<A[i])
            high = i;
        else if( A[high]<A[i]){
            mid = high;
            high =i;
        }
        else
            mid=high=i;
    }
    return new int[]{A[low],A[mid],A[high]};
}//

然后用main测试:

public static void main(String[] args) {
    int[][] D = {{1, 5, 5, 3, 2, 10},
        {1, 5, 5, 6, 2, 10},
        {1, 10, 5, 3, 2, 6, 12},
        {1, 10, 5, 6, 8, 12, 1},
        {1, 10, 5, 12, 1, 2, 3, 40},
        {10, 10, 10, 3, 4, 5, 7, 9}};
    for (int[] E : D) {
        System.out.format("%s GIVES %s%n", Arrays.toString(E), Arrays.toString(orderedHash(E)));
    }
}

答案 12 :(得分:-1)

如果你构建一个最大堆O(n),然后做Extract-Max O(1)3次怎么办?

答案 13 :(得分:-1)

这是一个只有一次迭代的解决方案。

我正在使用堆栈计算每个索引k是否存在另外两个索引i&amp; j使得a [i]&lt; a [j]&lt;一个[K]

bool f(vector<int> a) {

    int n = a.size();
    stack<int> s;
    for (int i = 0; i < n; ++i)
    {
        while(!s.empty() and a[s.top()]>=a[i]){
            s.pop();
        }

        if (s.size()>=2) // s.size()>=k-1
        {
            return 1;
        }
        s.push(i);

    }

    return 0;
}

重要的是我们可以在一般情况下将此问题扩展到M个指数而不是k指数。