簡體   English   中英

如何在線性時間內按升序查找 3 個數字並在數組中增加索引

[英]How to find 3 numbers in increasing order and increasing indices in an array in linear time

我在一個網站上遇到了這個問題。 正如那里提到的,它是在亞馬遜采訪中被問到的。 我無法在給定的約束下找到合適的解決方案。

給定一個包含n整數的數組,在O(n)時間內找到3 個元素,使得a[i] < a[j] < a[k]i < j < k

所以這是解決問題的方法。 您需要遍歷數組三次。 在第一次迭代中,在右側標記所有元素大於它們的值,在第二次迭代中,在左側標記所有小於它們的元素。 現在你的答案將是一個同時具有以下兩個元素的元素:

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 次,因此是線性的。 我沒有提供完整的解決方案,所以你可以在左邊訓練自己,看看你是否明白我的想法。 很抱歉沒有給出一些提示,但我不知道如何在不顯示實際解決方案的情況下給出提示。

希望這能解決您的問題。

一次性線性時間,具有 O(1) 額外空間(4 個變量)。 非常有效(每次迭代只有幾個比較/分支,並且沒有太多的數據改組)。

不是我最初的想法或算法,我只是整理並評論了一個 ideone fork 中的代碼 您可以向那里的代碼添加新的測試用例並在線運行它。 原文Ke.neth 發表在 www.geeksforgeeks.org 上的一個線程的評論中 很棒的算法,但最初的實現在實際循環之外有一些非常愚蠢的代碼。 (例如,讓我們在 class 中使用兩個成員變量,而不是局部變量,並將 function 實現為class Solution的成員函數......而且變量名很爛。我選擇了非常冗長的變量名。)

Ke.neth,如果您想發布您的代碼作為答案,請提前撥打 go。 我不是想竊取算法的功勞。 (不過,我確實做了一些工作來編寫這個解釋,並思考它為什么有效。)

討論線程上方的主要文章與 Ivaylo Strandjev 的回答具有相同的解決方案。 (主要文章的代碼是 Pramod 作為這個問題的答案發布的,在 Ivalyo 的回答幾個月后。這就是我在那里的評論中找到有趣答案的方式。)


由於您只需要找到一個解決方案,而不是所有的解決方案,因此沒有您預期的那么多極端情況。 事實證明,如果您選擇正確的東西保留為 state,則不需要跟蹤您看到的每個可能的起始值和中間值,甚至根本不需要回溯。


主要技巧是:

  • 單調遞減值序列中的最后一個值是您唯一需要考慮的值。 這適用於第一(低)和第二(中)候選元素。

  • 任何時候你看到中間元素的較小候選者,你都可以從那里重新開始,只是尋找最終元素或更好的中間候選者。

    如果您還沒有在小於當前中間候選元素的元素之前找到一個由 3 個遞增元素組成的序列,則 min-so-far 和新的較小的中間候選元素與您所能做的一樣好(寬容,靈活)在你已經檢查過的數字中。 (請參閱代碼中的注釋,了解可能更好的表達方式。)

    其他幾個答案犯了每次看到新的最小或最大元素而不是中間元素時都重新開始的錯誤。 您跟蹤您看到的當前最小值,但在看到新的中間值之前您不會做出反應或使用它。

要找到新的候選中間元素,您可以檢查它們是否小於當前的中間候選元素,並且.= 到目前為止看到的最小元素。

我不確定這個想法是否可以按順序擴展到 4 個或更多值。 尋找新的候選第三值可能需要跟蹤當前候選第二和第三之間的最小值,與總最小值分開。 這可能會變得棘手,並且需要更多的條件。 但是,如果可以使用恆定大小 state 並且一次通過而無需回溯正確完成,它仍然是線性時間。

// 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 個地方編輯arr4arr5

我在這里發布了另一種解決方法。

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

只是為了好玩:在 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;
}

這個問題與計算最長遞增子序列非常相似,但這個子序列的大小必須等於 3。 LIS 問題(具有 O(nlog(n)) 解決方案)可以針對此特定問題輕松修改。 該解決方案具有O(n)單程復雜度和O(1)空間。

此解決方案要求列表中只出現唯一元素。 我們使用在線解決方案 當我們遇到任何新元素時,它都有可能擴展當前的最佳子序列或開始一個新的子序列。 在這種情況下,由於遞增子序列的最大長度為 3,因此當前正在處理的任何新元素都可以將大小為 2 的序列擴展為 3 或將 1 擴展為 2。因此我們維護包含最佳元素的活動列表。

在這個特定問題中,我們必須維護的活動列表的最大數量是 2 - 一個大小為 2,另一個大小為 1。一旦我們找到大小為 3 的列表,我們就會得到答案。 我們確保每個活動列表以最小數量終止。 有關此想法的更詳細說明,請參閱

在在線解決方案中的任何時間點,這兩個活動列表將存儲列表中最有效的值 - 列表的末尾將是可以放置在那里的最小元素。 假設這兩個列表是:

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

尺寸 1 列表 => [c]

初始列表可以很容易地編寫(參考下面的代碼)。 假設下一個要輸入的數字是d 那么案例(級聯執行)如下:

情況 1: d > b 在這種情況下,我們有自己的答案,如a < b < d

情況 2: b > d > a 在此,大小為 2 的列表可以通過將 end 設為d而不是b來最佳表示,因為在d之后出現的每個大於b的元素也將大於d 所以我們用 d 替換 b。

案例 3: d < c 由於案例 1 和案例 2 失敗,它自動暗示d < a 在這種情況下,它可能會開始一個大小為 1 的新列表。 比較大小為 1 的列表以獲得最有效的活動列表。 如果這種情況成立,我們將c替換為d

情況 4: Otherwise 這種情況意味着d < bc < d 在這種情況下,大小為 2 的列表效率低下。 所以我們用[c, d]替換[a, b] ] 。

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

我的方法 - O(N) 時間兩次通過 O(1) 空間,使用兩個變量

對於我們訪問的數組的每個元素,我們維護其左側的最小可能值以檢查該元素是否可能是中間元素,並且還記錄其左側的最小中間元素以檢查該元素是否可能是候選的第三個元素或者它可能形成一個中間元素,其值低於目前找到的值。將 min so far 和 middle so far 初始化為 INT_MAX,

因此我們必須檢查每個元素:

如果一個特定的數組元素大於中間元素的最小值,那么這個數組元素就是以 thi 作為第三個元素和最小中間元素作為中間元素的答案(我們將不得不搜索第三個元素之后的一個經過)

Else 如果特定數組元素大於最小值,則該元素可能是候選中間元素,現在我們必須檢查候選中間元素是否小於當前中間元素,如果是,則更新當前中間元素

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

這是我的 O(n) 解決方案,空間復雜度為 O(1):- 只是一個 function,它返回一個由三個值組成的向量(如果存在)

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

這是此問題的 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 個元素,則此 function 返回 true。 您還可以修改此 function 以打印所有 3 個元素或其索引。 只需更新它們的索引以及變量 first 和 second。

我的解決方案如下。

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

抱歉,我忍不住要解決這個難題......這是我的解決方案。

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

嘗試創建兩個變量:

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] 的元素,那么您會找到您的序列。

迭代一次並完成:

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

如果你構建一個最大堆 O(n) 然后執行 Extract-Max O(1) 3 次怎么辦?

這是一個只有一次迭代的解決方案。

我正在使用堆棧計算每個索引 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 個索引。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM