Как найти 3 числа в порядке возрастания и увеличения индексов в массиве в линейном времени
Я сталкивался с этим вопросом на веб-сайте. Как уже упоминалось, об этом спрашивали в интервью Amazon. Я не мог найти правильное решение в данном ограничении.
Учитывая массив из n
целых чисел, найдите 3 элемента, таких что a[i] < a[j] < a[k]
и i < j < k
за O (n) времени.
Ответы
Ответ 1
Итак, вот как вы можете решить проблему. Вам нужно перебирать массив по три раза. На первом итерационном знаке все значения, у которых есть элемент, больший, чем они справа и на второй итерации, маркируют все элементы, меньшие их слева. Теперь ваш ответ будет с элементом, который имеет оба:
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 раза по всему массиву и поэтому линейна. Я не дал полного решения, чтобы вы могли тренироваться слева, чтобы узнать, поняли ли вы мою идею. Я сожалею, что не дал никаких советов, но я не мог понять, как дать отзыв, не показывая фактическое решение.
Надеюсь, что это решает вашу проблему.
Ответ 2
Однопроходное линейное время с O (1) дополнительным пространством (4 переменные). Очень эффективный (только пара сравнений/ветвей на итерацию и не так много перетасовки данных).
Это НЕ моя оригинальная идея или алгоритм, я просто прибрал и прокомментировал код в ideone fork. Вы можете добавить новые тестовые примеры в код и запустить его в Интернете. оригинал - Кеннет, размещенный в комментариях к теме на www.geeksforgeeks.org. Отличный алгоритм, но оригинальная реализация имела некоторый действительно глупый код вне фактического цикла. (например, вместо локальных переменных, позволяет использовать две переменные-члены в классе и реализовать функцию как функцию-член class Solution
... И имена переменных были втянуты. Я пошел на довольно многословные.)
Кеннет, если вы хотите опубликовать свой код в качестве ответа, продолжайте. Я не пытаюсь украсть кредит для алго. (Я написал некоторые работы, чтобы написать это объяснение и подумать, почему это работает.)
Основная статья, приведенная выше темы обсуждения, имеет то же решение, что и Ивайло Странджев. (Код главной статьи - это то, что Pramod опубликовал как ответ на этот вопрос, спустя месяцы после ответа Ивалоо. Вот как я нашел интересные ответы в комментариях там.)
Поскольку вам нужно всего лишь найти решение, не все из них, как и следовало ожидать, не так много угловых случаев. Оказывается, вам не нужно отслеживать все возможные начальные и средние значения, которые вы видели, или даже отступать вообще, если вы выберете правильные вещи для сохранения как состояния.
Основные трюки:
-
Последнее значение в последовательности монотонно уменьшающихся значений является единственным, что вам нужно рассмотреть. Это относится как к первым (низким), так и к второму (среднему) элементам-кандидатам.
-
Каждый раз, когда вы видите меньший кандидат для элемента средний, вы можете начинать с него свежий, просто ищет либо последний элемент, либо даже лучший средний кандидат.
Если вы еще не нашли последовательность из трех увеличивающихся элементов до того, как элемент будет меньше вашего текущего среднего кандидата, 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, который может принимать список инициализаторов в качестве параметра, является уродливым:
Возможно ли передать инициализатор, заключенный в фигурные скобки, в качестве параметра макроса?
Было очень полезно иметь возможность легко добавлять новые тестовые файлы, но без редактирования arr4
до arr5
в 4 местах.
Ответ 3
Я опубликовал другой подход, чтобы разрешить его здесь.
#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;
}
Ответ 4
Просто для удовольствия:
В 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;
}
Ответ 5
Эта проблема очень похожа на вычисление самой длинной растущей подпоследовательности с ограничением, что размер этой подпоследовательности обязательно должен быть равен трем. Задача LIS (с решением O (nlog (n)) может быть легко модифицирована для этой конкретной задачи. Это решение имеет O (n) однопроходную сложность с пространством O (1).
Это решение требует, чтобы в списке присутствовали только уникальные элементы. Мы используем онлайн-решение. Когда мы сталкиваемся с каким-либо новым элементом, у него есть потенциал для расширения существующей самой оптимальной подпоследовательности или начала новой подпоследовательности. В этом случае, поскольку максимальная длина возрастающей подпоследовательности равна трем, любой новый элемент, который в настоящее время обрабатывается, может либо расширить последовательность от 2 до 3, либо от 1 до 2. Таким образом, мы сохраняем активные списки, содержащие наиболее оптимальные элементы.
В этой конкретной задаче максимальное количество активных списков, которые мы должны поддерживать, - это 2 - один размер 2 и другой размером 1. Как только мы попадаем в список с размером 3, у нас есть наш ответ. Мы гарантируем, что каждый активный список заканчивается минимальным числом. Для более подробного объяснения этой идеи обратитесь this.
В любой момент времени в онлайн-решении эти два активных списка будут хранить наиболее эффективные значения списка - конец списка будет наименьшим элементом, который можно разместить там. Предположим, что два списка:
Размер 2 списка = > [a, b]
Размер 1 список = > [c]
Исходный список можно легко записать (см. код ниже). Предположим, что следующее число, которое нужно ввести, d
. Тогда случаи (каскадные в выполнении) следующие:
Случай 1: d > b
.
В этом случае мы имеем наш ответ как a < b < d
.
Случай 2: b > d > a
. В этом список размера 2 может быть оптимально представлен, если конец d
вместо b
, поскольку каждый элемент, следующий за d
больше, чем b
, также будет больше, чем d
. Поэтому мы заменяем b на d.
Случай 3: d < c
. Поскольку случаи 1 и 2 не выполняются, это автоматически означает, что d < a
. В таком случае он может начать новый список с размером один. Список с размером один сравнивается с тем, чтобы получить наиболее эффективный активный список. Если этот случай верен, мы заменим c
на d
.
Случай 4: Otherwise
. Из этого случая следует, что d < b
и c < 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;
}
Ответ 6
Мой подход - O (N) время два проходит O (1) пространство с двумя используемыми переменными
для каждого элемента массива, который мы посещаем, мы сохраняем минимум слева от него, чтобы проверить, может ли этот элемент быть средним элементом, а также вести запись минимального среднего элемента слева от него, чтобы проверить, может ли этот элемент быть или он может образовать средний элемент с более низким значением, чем найденный до сих пор. Показывать мин до сих пор и до середины до INT_MAX,
Fr каждый элемент, таким образом, мы должны проверить:
Если конкретный элемент массива больше, чем минимальный средний элемент до сих пор, чем этот элемент массива является ответом с тем, что третий элемент и средний средний элемент в качестве среднего элемента (нам нужно будет искать третий элемент через один проход)
Else Если конкретный элемент массива больше минимального до тех пор, пока этот элемент не может быть средним элементом-кандидатом, и теперь мы должны проверить, меньше ли средний элемент-кандидат, чем текущий средний элемент, если так обновлять текущий средний элемент
ELSE Если данный элемент массива меньше минимального, то до сих пор обновляем минимум с помощью arr [i].
Таким образом, для каждого элемента массива, который мы посещаем, мы сохраняем минимум слева от него, чтобы проверить, является ли этот элемент средним элементом, а также сохраняет запись минимального среднего элемента слева от него, чтобы проверить, является ли этот элемент может быть третьим кандидатом-кандидатом или может образовать средний элемент с более низким значением, чем найденный до сих пор. #включают используя пространство имен 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;
}
Ответ 7
Вот мое O (n) решение с O (1) пространственной сложностью: - Просто функция, которая возвращает вектор, состоящий из трех значений (если 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;
}'
Ответ 8
Здесь 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;
}
Эта функция возвращает true, если существует пара из 3 элементов, которые расположены в порядке возрастания в массиве. Вы также можете изменить эту функцию, чтобы печатать все 3 элемента или их индексы. Просто обновите их индексы, а также переменную first и second.
Ответ 9
Извините, я не мог удержаться, но решить головоломку...
Вот мое решение.
//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. 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
Повторите операцию:
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
Что делать, если вы создаете max-heap O (n), а затем выполняете Extract-Max O (1) 3 раза?
Ответ 13
Вот решение с одной итерацией.
Я использую стек для вычисления для каждого индекса k, существует ли два других индекса я и j, так что a [i] a [j] [к].
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 индексов.