最大子阵列的长度使得第一元素大于最后一个元素

时间:2017-02-19 11:13:12

标签: algorithm data-structures

我得到了一个阵列。我需要找到第一个元素大于最后一个元素的最大子数组的长度。 例如,5 4 3 2 1.最大子阵列的长度是5,因为第一元素5大于最后一个元素1。 另一个例子,5 10 4 7 9 8.长度再次为5,数组从10开始,直到最后一个元素。 我知道天真的方法,即O(n²),但需要更好的方法。

5 个答案:

答案 0 :(得分:4)

这是线性算法 O(n)

算法描述

首先收集以下为真的所有指数 i

任何j&gt; i: i &lt;一个<子>Ĵ

所以它们有点极小,因为它们右边没有较小或相等的值。

保留对其中最左边的引用,调用该索引 j 。该变量将作为范围的结束指数(包括)。

现在从左侧开始索引 i ,并且只要 i &gt; a j ,转到下一个“最小值”(所以 j 增加),此条件保持不变( i &gt; a Ĵ)。对于最后的 j ,这可能是一个解决方案:验证这是否是目前为止发现的最长范围。

每当 j&lt;我,也接下一个“mimimum”

每当没有更多的最小值时,停止算法。

视觉表现

这个想法在这里用数组值的图形表示来表示,其中红色标记是最小值,绿色标记是解决方案子数组可以开始的可能候选者:

enter image description here

代码

以下是JavaScript中该算法的实现:您可以输入数组值,并在键入时显示最大的子数组:

function longestSubArray(a) {
    // Preprocessing: collect all i, for which holds: 
    //     if j > i, then a[i] < a[j]
    var minima = [-1] // Add a special value first (see later)
    var last = Infinity;
    for (var i = a.length - 1; i >= 0; i--) {
        if (a[i] < last) {
            last = a[i];
            minima.push(i);
            // Optimisation: It is of no use to find more minima if 
            // this value is less than the first value in the aay
            if (last < a[0]) break; 
        }
    }
    // Get first value from minima. This will be the rightmost
    // value that is less than a[0], or if such does not exist,
    // the minimum value in the aay.
    var j = minima.pop();
    var maxSize = 1;
    var maxStart = 0;
    // Look for ranges that start at i:
    for (i = 0; i < a.length; i++) {
        // Check if range (i, j) fulfills the requirement
        while (j !== -1 && (a[i] > a[j] || j <= i) ) {
            // Check if range (i, j) is the largest so far
            if (j - i + 1 > maxSize) {
                maxSize = j - i + 1;
                maxStart = i;
            }
            // Take an end index that is more to the right,
            // but which will represent a higher value also:
            j = minima.pop(); // could be -1: which means "all done"
        }
        if (j == -1) break;
    }
    if (maxSize == 1) return []; // no solution
    return a.slice(maxStart, maxStart+maxSize);
}

// I/0 handling -- not relevant to the algorithm:
var input = document.querySelector('input');
var output = document.querySelector('span');

input.oninput = function () {
    // Translate text input to aay of integers
    var a = input.value.match(/-?\d+/g).map(Number);
    // Apply function
    var sub = longestSubArray(a);
    // Output result
    output.textContent = sub.join(' ');
}
Enter array: <input size="60"><br>
Longest sub-array: <span></span>

请参阅here了解Python中的相同功能。

线性时间复杂度的证明

该算法具有线性时间复杂度:

  • 收集最小值的循环最多迭代 n
  • i 上的循环最多迭代 n
  • 内部循环将在中迭代最多 n 次,即使它可以每次 i 迭代多次,数组<每次迭代都会缩短em> minima 。

所以: O(n)

答案 1 :(得分:3)

您可以尝试应用O(n):

的卡特彼勒方法

这个想法是使用两个索引,一个用于头部,另一个用于尾部以及最大长度变量。然后,只要条件有效,就会尝试前进:从数组的头部到结尾的子序列中的最小值小于尾部的值。

如果你不能抬头,因为条件不满足,那么你就推进了尾巴。因此,与毛虫的相似之处在于它移动时抬起头部然后是尾部。

该最小值可以在阵列的前一次迭代中预先计算。

这是python中可能的实现:

def subSeqLen(arr):                                                                                                                                  
    head = 0                                                                                                                                         
    tail = 0                                                                                                                                         
    maxLength = 0                                                                                                                                    
    minFromLast = [arr[-1]]*len(arr)                                                                                                                 
    for i in xrange(len(arr)-2, -1, -1):                                                                                                             
        minFromLast[i] = min(minFromLast[i+1], arr[i])                                                                                               
    while head >= tail and head < len(arr):                                                                                                          
        if arr[tail]>arr[head] and head-tail+1 > maxLength:                                                                                          
            maxLength = head-tail+1                                                                                                                  
        if head < len(arr)-1 and minFromLast[head+1]<=arr[tail]:                                                                                     
            head += 1                                                                                                                                
        else:                                                                                                                                        
            tail += 1 
            head = max(head, tail)                                                                                                                               
    return maxLength 

答案 2 :(得分:2)

创建另一个对数组,使第一个元素与数组相同,第二个元素是元素的索引,因此对于数组:

  

5 4 3 2 1

新数组将是这样的

5,1 4,2 3,3 2,4 1,5

现在对对数组进行排序,所以它看起来像这样:

1,5 2,4 3,3 4,2 5,1

现在使用已排序数组的索引构建Segment tree

现在遍历数组并且对于每个元素在排序数组中找到它是等价的(这可以在O(1)中完成),假设它的索引是j,知道在段[1,i]中执行范围查询(因为段[1,i]中的所有元素都低于当前元素,因为对数组被排序)以找到log(n)中的最大索引值,最后答案是所有段长度中的最大值

复杂性是O(nlog(n))。

答案 3 :(得分:1)

编辑:看起来像一个非常简单的O(n)解决方案。

您可以使用两种算法来解决问题:

二进制搜索,堆栈,O(n * log(n))

让我们看一些 j 并考虑所有有效的子数组,以 j -th位置的元素结尾。答案将是 i a [i]&gt; a [j] i&lt; Ĵ即可。迭代所有这样的 i 会给我们 O(n 2 复杂性。

为了提高复杂性,请记住一个简单的想法。如果我们 i 1 a [i 1 ] 那么我们永远不会考虑另一个 i <当 i 1 &lt; i 1 时,sub> 2 作为有效子阵列的起点i 2 a [i 1 ]&gt; a [i 2 ] 因为 i 1 会产生更好的结果。让我们在数据结构 d 中存储(a [i],i)这样的对。

在任何时间点 d 看起来像(a [i 0 ],i 0 ),(a [i 1 ],i 1 )...(a [i n ],i n )< / strong>即可。 i p a [i p ] 从左向右增加。

当我们将下一个 j 视为有效子阵列的结尾时,我们可以在 d 中查找当前最佳答案。 d 中的所有 i -s都低于当前的 j 。我们必须寻找最左边的一对(a [i],i),其中 a [i]&gt; a [j] j - i + 1 作为当前答案。由于对是排序的,我们可以使用二进制搜索。

如果 a [j] 大于 d 中的所有 a [i] ,那么我们会有一对新( a [j],j)将附加到 d

ans <- 0
d <- [] // contains (val, idx) structures
for j = 0..n-1
    l <- 0, r <- len(d) - 1
    while l <= r
        mid = (l + r) / 2 // integer division
        if d[mid].val > a[j]
            r <- mid - 1
        else
            l <- mid + 1
    if l = len(d)
        append (a[j], j) to d
    else
        curAns <- j - d[l].idx + 1
        ans <- max(ans, curAns)

总体复杂性为 O(n * log(n))

段树,O(n * log(n))

查看Abdenaceur Lichiheb's answer

a 值的限制没有限制,因为它们可以很容易地排序,然后映射到排序数组中的索引。

答案 4 :(得分:0)

如果最大的意思是说长度,那么确实存在简单的解决方案。

输入-

第一行。

第二行n个数字。

代码-

int main(){
    fast;
    int l=0;
    int n;
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    int m=-IINF;
    int l1=0;
    for(int i=0;i<n;i++){
        if(a[i]>m){
            m=a[i];
            maximize(l,l1);
            l1=1;
        }
        else{
            l1+=1;
            //cout<<l1<<" ";
        }
    }
    if(l1>l){
        cout<<l1<<endl;
        return 0;
    }
    cout<<l<<endl;
    return 0;
}