22

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

给定一个n整数数组,在 O(n) 时间内找到3个这样的元素a[i] < a[j] < a[k]i < j < k

4

14 回答 14

33

因此,这是解决问题的方法。您需要对数组进行三次迭代。在第一次迭代中,将所有大于它们的元素标记在右侧,在第二次迭代中,将所有小于它们的元素标记在左侧。现在你的答案将是一个同时具有这两者的元素:

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 次,因此是线性的。我没有提供完整的解决方案,所以你可以在左边训练自己,看看你是否明白我的想法。很抱歉没有给出一些提示,但我不知道如何在没有显示实际解决方案的情况下给出提示。

希望这能解决您的问题。

于 2012-04-04T09:40:00.350 回答
3

一次性线性时间,具有 O(1) 额外空间(4 个变量)。非常有效(每次迭代只有几个比较/分支,并且没有太多的数据混洗)。

不是我最初的想法或算法,我只是整理并在 ideone fork 中注释了代码。您可以在那里向代码添加新的测试用例并在线运行。原件Kenneth 撰写,发表在 www.geeksforgeeks.org 上一个线程的评论中。很棒的算法,但最初的实现在实际循环之外有一些非常愚蠢的代码。(例如,代替局部变量,让我们在一个类中使用两个成员变量,并将函数实现为class Solution......的成员函数并且变量名很糟糕。我选择了非常冗长的。)

Kenneth,如果您想发布您的代码作为答案,请继续。我不是想窃取算法的功劳。(不过,我确实做了一些工作来编写这个解释,并思考它为什么有效。)

讨论线程上方的主要文章与 Ivaylo Strandjev 的答案具有相同的解决方案。(主要文章的代码是 Pramod 在 Ivalyo 回答数月后发布的作为对这个问题的回答的内容。这就是我在评论中找到有趣答案的方式。)


由于您只需要找到解决方案,而不是所有解决方案,因此没有您期望的那么多极端案例。事实证明,如果您选择正确的东西作为状态,您不需要跟踪您看到的每个可能的起始值和中间值,甚至根本不需要回溯。


主要技巧是:

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

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

    如果您在小于当前中间候选人的元素之前还没有找到 3 个递增元素的序列,那么 min-so-far 和新的较小中间候选人将尽可能好(宽容、灵活)在您已经检查过的数字中。(请参阅代码中的注释以了解可能更好的表述方式。)

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

要找到新的候选中间元素,您检查它们是否小于当前中间候选,并且 != 到目前为止看到的最小元素。

我不确定这个想法是否可以依次扩展到 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 宏是丑陋的:
是否可以将大括号括起来的初始值设定项作为宏参数传递?

能够轻松地添加新的测试用例,而无需在 4 个地方进行编辑arr4,这是非常值得的。arr5

于 2015-11-29T11:43:34.757 回答
1

我在这里发布了另一种解决方法。

#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;
}
于 2012-06-08T20:35:02.883 回答
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;
}
于 2015-06-02T21:12:00.927 回答
1

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

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

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

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

大小 2 列表 => [a,b]

大小 1 列表 => [c]

初始列表可以很容易地编写(参考下面的代码)。假设下一个要输入的数字是d。那么情况(执行中的级联)如下:

案例一:d > b. 在这种情况下,我们有我们的答案,如a < b < d

案例2 b > d > a:。在这种情况下,大小为 2 的列表可以通过 endd代替来最佳表示,因为在大于b之后出现的每个元素也将大于。所以我们将 b 替换为 d。dbd

案例3 d < c:。由于案例 1 和 2 失败,它自动暗示d < a. 在这种情况下,它可能会启动一个大小为 1 的新列表。比较大小为 1 的列表以获得最有效的活动列表。如果这种情况属实,我们将替换cd

案例4 Otherwise:。这种情况意味着d < bc < d。在这种情况下,大小为 2 的列表效率低下。所以我们替换[a, b][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;
}
于 2017-05-26T12:49:26.197 回答
0

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

对于我们访问的数组的每个元素,我们保持其左侧的最小值以检查该元素是否可能是中间元素,并记录其左侧的最小中间元素以检查该元素是否可能是候选的第三个元素或者它可能会形成一个中间元素,其值低于目前找到的值。将 min so far 和 middle so far 初始化为 INT_MAX,

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

如果一个特定的数组元素大于到目前为止的中间元素的最小值,那么这个数组元素就是答案,其中 thi 作为第三个元素,最小的中间元素作为中间元素(我们必须在后面搜索第三个元素经过)

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

ELSE 如果特定数组元素小于迄今为止的最小值,则使用 arr[i] 更新迄今为止的最小值。

因此,对于我们访问的数组的每个元素,我们保持其左侧的最小值以检查该元素是否可能是中间元素,并记录其左侧的最小中间元素以检查该元素是否可能是候选第三个元素,或者它可能形成一个比目前找到的值更低的中间元素。
#include 使用命名空间标准;

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;
}
于 2017-01-07T09:23:19.300 回答
0

这是我的具有 O(1) 空间复杂度的 O(n) 解决方案:- 只是一个返回由三个值组成的向量的函数(如果 exixts)

`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;
}`
于 2018-07-09T15:43:35.743 回答
0

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

bool increasingTriplet(vector<int>& a) {

    int i,n=a.size(),first=INT_MAX,second=INT_MAX;
    if(n<3)
        return false;

    for(i=0;i<n;i++)
    {
        if(a[i]<=first)
            first = a[i];
        else if(a[i]<=second)
            second = a[i];
        else
            return true;
    }
    return false;
}

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

于 2019-07-02T23:25:39.600 回答
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;
}
于 2020-03-04T15:01:20.657 回答
-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;
    }    
}
于 2012-04-04T09:16:59.237 回答
-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

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

如果您遍历大于 a[index_sequence_length_2] 的元素,那么您会找到您的序列。

于 2012-04-04T09:18:56.050 回答
-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)));
    }
}
于 2012-04-04T19:16:37.880 回答
-1

如果你构建一个最大堆 O(n) 然后提取最大 O(1) 3 次怎么办?

于 2012-04-10T20:59:46.033 回答
-1

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

我正在使用堆栈为每个索引 k 计算是否存在其他两个索引 i 和 j,使得 a[i] < a[j] < a[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 个索引。

于 2015-11-26T18:55:01.443 回答