Algorithm 具有给定范围内元素的最长长度子数组

Algorithm 具有给定范围内元素的最长长度子数组,algorithm,Algorithm,如果我有一个整数列表,在一个数组中,我如何找到最长子数组的长度,使得该数组的最小和最大元素之间的差值小于给定的整数,比如M 如果我们有一个包含3个元素的数组 [1,2,4] 如果M等于2 那么最长的子数组是[1,2] 因为如果我们包括4,并且我们从一开始就开始,差值将是3,大于M(=2),如果我们从2开始,最大(4)和最小元素(2)之间的差值将是2,并且不小于2(M) 我能想到的最好办法是从左边开始,然后尽可能向右走,而不要让子数组的范围太大。当然,到目前为止,在每一步我们都必须跟踪最小和最大元

如果我有一个整数列表,在一个数组中,我如何找到最长子数组的长度,使得该数组的最小和最大元素之间的差值小于给定的整数,比如M

如果我们有一个包含3个元素的数组

[1,2,4]

如果M等于2

那么最长的子数组是[1,2]

因为如果我们包括4,并且我们从一开始就开始,差值将是3,大于M(=2),如果我们从2开始,最大(4)和最小元素(2)之间的差值将是2,并且不小于2(M)


我能想到的最好办法是从左边开始,然后尽可能向右走,而不要让子数组的范围太大。当然,到目前为止,在每一步我们都必须跟踪最小和最大元素。这有n平方的时间复杂度,我们不能更快吗?

考虑以下想法:

让我们创建MaxLen数组(大小为n),它定义为:MaxLen[i]=直到第i位的max子数组的长度

在填充此数组之后,将很容易(O(n))找到最大子数组

如何填充MaxLen阵列?假设你知道MaxLen[i],那么MaxLen[i+1]中会有什么

我们有2个选项-如果originalArr[i+1]中的数字没有打破在索引i处结束的最长子数组中超过m的diff的限制,那么
MaxLen[i+1]=MaxLen[i]+1
(因为我们只能够使以前的子数组稍微长一点。另一方面,如果originalArr[i+1]使用diff m和最后一个子数组中的一个子数组,我们需要找到diff为m的元素(称其索引为k)并插入到
MaxLen[i+1]=i-k+1
,因为我们新的max子数组必须排除originalArr[k]元素

我们如何找到这个“坏”元素?我们将使用Heap。在传递每个元素之后,我们将它的值和索引插入到最小和最大Heap(在日志(n)中完成)。当您有第i个元素,并且您想检查上一个数组中是否有人破坏了您的序列时,您可以从堆中开始提取元素,直到没有任何元素大于或小于originalArr[i]>取提取元素的最大索引,并取k-破坏您的序列的元素的索引

我将尝试使用伪代码进行简化(我仅针对最小堆进行演示,但它与最大堆相同)

数组是大小为n的输入数组
最小堆=新堆()
maxLen=数组(n)//大小为n
maxLen[0]=1;//大小为1的原始数组的最大子数组
最小heap.push(数组[0],0)
对于(i in(1,n)){
如果(Array[i]-min-heap.topm)
maxIndex=max(maxIndex,min heap.pop.index)
if(空(最小堆))
maxIndex=i//所有元素都是“坏的”,因此需要启动新的子数组
打破
//最大索引是我们的k->
maxLen[i]=i-k+1
} 
最小堆推送(数组[i],i)
完成后,在最大长度数组上运行并选择最大值(从他的索引中可以提取原始数组的开始和结束索引)

因此,我们在数组(n)上循环,并在每个插入中循环到2个堆(logn)。 您可能会说:嗨!但是您也不知道堆提取的次数,这会强制heapify(logn)!但是请注意,这个堆可以有max of n元素,元素可以提取两次,所以计算accumolate Completicity,您将看到它仍然是o(1)。 所以底线是:O(n*logn)

编辑:

这个解决方案可以通过使用AVL树而不是2堆来简化-在AVL树中查找最小值和最大值都是O(logn)-插入、查找和删除也是如此-所以只需使用具有值元素的树,并且原始数组中有索引

编辑2:


@Fei Xiang甚至提出了使用deques更好的O(n)解。

我对David Winder的算法进行了改进。其思想是,我们可以使用我称之为deque DP优化技巧的方法,而不是使用两个堆来寻找最小和最大元素

要理解这一点,我们可以看一个更简单的问题:在数组中某个大小
k
的所有子数组中查找最小元素。其思想是我们保持一个包含最小元素潜在候选项的双端队列。当我们遇到新元素时,我们会弹出队列后端的所有元素超过或在将当前元素推到后面之前等于当前元素

我们可以这样做,因为我们知道将来遇到的任何子阵列,其中包括我们弹出的元素,也将包括当前元素,并且由于当前元素小于弹出的元素,这些元素将永远不会是最小值

推送当前元素后,如果队列中的前元素超过
k
个元素,我们将其弹出。当前子数组中的最小元素只是队列中的第一个元素,因为我们从队列后面弹出元素的方式使其不断增加

要在您的问题中使用此算法,我们将有两个deque来存储最小和最大元素。当我们遇到一个比最小元素大得多的新元素时,我们从deque的前面弹出,直到元素不再太大。在该位置结束的最长数组的开头就是我们弹出的最后一个元素加1

这使得解决方案
O(n)

C++实现:

int best = -1234567890, beg = 0;
//best = length of the longest subarray that meets the requirements so far
//beg = the beginning of the longest subarray ending at the current index
std::deque<int> least, greatest;
//these two deques store the indices of the elements which could cause trouble
for (int i = 0; i < n; i++)
{
    while (!least.empty() && a[least.back()] >= a[i])
    {
        least.pop_back();
        //we can pop this off since any we encounter subarray which includes this
        //in the future will also include the current element
    }
    least.push_back(i);
    while (!greatest.empty() && a[greatest.back()] <= a[i])
    {
        greatest.pop_back();
        //we can pop this off since any we encounter subarray which includes this
        //in the future will also include the current element
    }
    greatest.push_back(i);
    while (a[least.front()] < a[i] - m)
    {
        beg = least.front() + 1;
        least.pop_front();
        //remove elements from the beginning if they are too small
    }
    while (a[greatest.front()] > a[i] + m)
    {
        beg = greatest.front() + 1;
        greatest.pop_front();
        //remove elements from the beginning if they are too large
    }
    best = std::max(best, i - beg + 1);
}
int-best=-123456
int best = -1234567890, beg = 0;
//best = length of the longest subarray that meets the requirements so far
//beg = the beginning of the longest subarray ending at the current index
std::deque<int> least, greatest;
//these two deques store the indices of the elements which could cause trouble
for (int i = 0; i < n; i++)
{
    while (!least.empty() && a[least.back()] >= a[i])
    {
        least.pop_back();
        //we can pop this off since any we encounter subarray which includes this
        //in the future will also include the current element
    }
    least.push_back(i);
    while (!greatest.empty() && a[greatest.back()] <= a[i])
    {
        greatest.pop_back();
        //we can pop this off since any we encounter subarray which includes this
        //in the future will also include the current element
    }
    greatest.push_back(i);
    while (a[least.front()] < a[i] - m)
    {
        beg = least.front() + 1;
        least.pop_front();
        //remove elements from the beginning if they are too small
    }
    while (a[greatest.front()] > a[i] + m)
    {
        beg = greatest.front() + 1;
        greatest.pop_front();
        //remove elements from the beginning if they are too large
    }
    best = std::max(best, i - beg + 1);
}