我们给出了一个数字数组,我们希望找到一个按升序排序的大小为4的子序列。
for eg ARRAY : -4 2 8 3 1 5
sorted subsequence of size 4 : -4 2 3 5
PS:有一种方法可以找到大小为3的排序子序列(see this)。我试图按照相同的思路思考,但似乎无法找到4个整数的解决方案。
答案 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:创建辅助阵列smaller
。 p2[0..n-1]
应存储小于p2[i]
的数字的索引j
,位于arr[i]
的左侧,并且arr[i]
(在其他位置)单词:p1[j] != -1
,j<i
和arr[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] != -1
,j<i
和arr[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;
}
上述方法在设置当前最小值时会记住所有最小值层。通过删除代码的3
和main_main_small
部分,相同的代码也可用于数组中middle_2
大小的已排序子序列。
如果相同的代码要扩展到k
大小,那么最小i
,我们必须记住i
之前的所有最小值,即min_1
, min_2
,......直到min_i
。只有在最后一个最小值,即我们子序列中的最大值,我们才会中断,并且不需要记住先前或当前的最小值。
如果发现任何错误,请告知!
答案 2 :(得分:1)
你可以找到增长最长的子序列,看看它的大小是否大于等于4(如果你需要找到一个更一般的问题,甚至是k)。如果最长增加子序列的长度小于4(或k),则可以报告不存在此类子序列。 LIS可以在O(nlog(n))
答案 3 :(得分:0)
创建一个smaller
和greater
数组,类似于对大小为3的子序列所做的操作。除此之外,还有betweenSmallerAndCurrent
数组,用于存储a的索引最小值和当前元素之间的值 - 包括值和索引。更明确地说:
betweenSmallerAndCurrent[i] = -1 or
input[smaller[i]] < input[betweenSmallerAndCurrent[i]] < input[value[i]] and
smaller[i] < betweenSmallerAndCurrent[i] < value[i]
构建这个应该很容易。
您只需返回i
,betweenSmallerAndCurrent[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
的循环。代码应该很容易理解 - 如果遇到问题,请尝试将其翻译成单词。
答案 4 :(得分:0)
对于每个元素,找到下一个更大的元素索引,否则为-1 现在将其视为图形并找到长度为k的路径(如果存在) 这可以使用散列表和memoization在线性时间内轻松完成。