【问题标题】:Frequency of each element of an array considering all contiguos subarrays考虑所有连续子数组的数组每个元素的频率
【发布时间】:2015-11-02 23:42:30
【问题描述】:

考虑一个数组 A = [5,1,7,2,3]

所有连续子数组 = { [5], [1], [7], [2], [3], [5,1], [1,7], [7,2], [2,3 ], [5,1,7], [1,7,2], [7,2,3], [5,1,7,2], [1,7,2,3], [5,1,7 ,2,3] }

将上述集合中的所有数组替换为其中的最大元素:

set 看起来像这样:{ [5], [1], [7], [2], [3], [5], [7], [7], [3], [7], [7], [7], [7], [7], [7] }

频率信息:[5] -> 2, [1] -> 1, [7] -> 9, [2] -> 1, [3] -> 2

我的目标是找到上述频率信息。

我的方法:

首先列出一对 (x,y)。 x 是 A 中的元素,它的索引是 y。

列表:[ (5,1), (1,2), (7,3), (2,4), (3,5) ]

按照第一个元素的降序对列表进行排序。现在,

列表:[ (7,3), (5,1), (3,5), (2,4), (1,2) ]

算法:

def f( array, first_index, last_index):
       ->select an element from LIST starting from left which
         is not marked as visited and (first_index <= element.second <=   
         last_index)
       ->calculate frequency info of element in tuple as  (element.secondvalue-first_index+1) + (element.secondvalue-first_index+1)*(last_index - element.second_value)
       ->Mark above element as visited
       ->Recursively solve f( array, first_index,element.secondvalue-1 ),f( array,element.secondvalue+1,last_index)    

我们可以轻松设置合适的基本情况。

时间复杂度:O(n*n)

我已经尝试了很多上述算法,但无法提高时间复杂度。我该怎么做?任何提示,方法将不胜感激。

【问题讨论】:

  • 好像有一个函数可以描述这种关系
  • 相关:this question。我在那里提供的答案将解决您在O(n) 中的问题,而不是O(n log n) 作为此问题的其他答案。如果您愿意,我可以在此处以新答案的形式重新发布您问题的相关部分。
  • 真的是 O(n) !当然我想知道。请张贴它..

标签: arrays algorithm sub-array


【解决方案1】:

根据 Paul 的回答,我使用分段树的简化版本实现了 O(n log n) 版本。您会注意到这个答案与 Paul 的答案相同,但使用了 leftctsmallerrightctsmaller 的优化版本。

实际上,它的作用是获取一个数组,比如说:

A = [5,1,7,2,3,7,3,1]

并构造一个数组支持的树,如下所示:

在树中,第一个数字是值,第二个是它出现在数组中的索引。每个节点是其两个子节点中的最大值。这棵树由一个数组(很像堆树)支持,其中索引i 的子节点位于索引i*2+1i*2+2 中。

然后,对于每个元素,找到最近的更大元素(之前和之后)变得容易。

例如,要找到离左边最近的较大元素,我们在树中向上搜索值较大且索引小于参数的第一个父元素。答案必须是这个父节点的子节点,然后我们在树中向下寻找满足相同条件的最右边的节点。

我已经在 Python 中实现了它(以及天真的版本,以检查答案),它似乎运行良好。

import sys, random
from collections import defaultdict
from math import log, ceil

def make_tree(A):
    n = 2**(int(ceil(log(len(A), 2))))
    T = [(None, None)]*(2*n-1)

    for i, x in enumerate(A):
        T[n-1+i] = (x, i)

    for i in reversed(xrange(n-1)):
        T[i] = max(T[i*2+1], T[i*2+2])

    return T

def print_tree(T):
    print 'digraph {'
    for i, x in enumerate(T):
        print '    ' + str(i) + '[label="' + str(x) + '"]'
        if i*2+2 < len(T):
            print '    ' + str(i)+ '->'+ str(i*2+1)
            print '    ' + str(i)+ '->'+ str(i*2+2)

    print '}'

def find_generic(T, i, fallback, check, first, second):
    j = len(T)/2+i
    original = T[j]
    j = (j-1)/2

    #go up in the tree searching for a value that satisfies check
    while j > 0 and not check(T[second(j)], original):
        j = (j-1)/2

    #go down in the tree searching for the left/rightmost node that satisfies check
    while j*2+1<len(T):
        if check(T[first(j)], original):
            j = first(j)
        elif check(T[second(j)], original):
            j = second(j)
        else:
            return fallback

    return j-len(T)/2


def find_left(T, i, fallback):
    return find_generic(T, i, fallback, 
        lambda a, b: a[0]>b[0] and a[1]<b[1],  #value greater, index before
        lambda j: j*2+2,                       #rightmost first
        lambda j: j*2+1                        #leftmost second
    ) 


def find_right(T, i, fallback):
    return find_generic(T, i, fallback,
        lambda a, b: a[0]>=b[0] and a[1]>b[1], #value greater or equal, index after
        lambda j: j*2+1,                       #leftmost first
        lambda j: j*2+2                        #rightmost second
    )       

def optimized_version(A):
    T = make_tree(A)

    answer = defaultdict(lambda: 0)
    for i, x in enumerate(A):
        left = find_left(T, i, -1)
        right = find_right(T, i, len(A))
        answer[x] += (i-left) * (right-i)

    return dict(answer)

def naive_version(A):
    answer = defaultdict(lambda: 0)
    for i, x in enumerate(A):
        left = next((j for j in range(i-1, -1, -1) if A[j]>A[i]), -1)
        right = next((j for j in range(i+1, len(A)) if A[j]>=A[i]), len(A))
        answer[x] += (i-left) * (right-i)
    return dict(answer)


A = [random.choice(xrange(32)) for i in xrange(8)]    
MA1 = naive_version(A)
MA2 = optimized_version(A)

sys.stderr.write('Array:     ' + str(A) + '\n')
sys.stderr.write('Naive:     ' + str(MA1) + '\n')
sys.stderr.write('Optimized: ' + str(MA2)  + '\n')
sys.stderr.write('OK:        ' + str(MA1==MA2)  + '\n')
#print_tree(make_tree(A))

【讨论】:

    【解决方案2】:

    我怀疑您的代码在 O(n^2) 中运行。无论如何,以更有效的方式解决此问题的一种方法是将每个数字映射到左侧/右侧小于给定项目的项目数。例如:

    input = [2 , 3 , 1 , 5 , 4 , 8 , 0]
    for number n = 5
    leftctsmaller(n) = 3
    rightctsmaller(n) = 1
    

    此地图需要O(n^2) 才能生成。其余的很简单。给定左边和右边的空间,我们可以很容易地确定只包含比n 更小的数字的子数组的数量,除了n 本身。

    【讨论】:

    • 我们能否进一步提高它的时间复杂度
    • @DeepakYadav 应该是可能的。虽然我必须考虑一种优化搜索的方法。我明天更新
    • 我很确定您可以使用分段树来做到这一点。
    • @JuanLopes 谢谢。这是一个非常简单的解决方案,但我没有想到。
    • @Paul 实现了您使用分段树优化操作的想法
    【解决方案3】:

    从下到上遍历您的值到索引图 - 维护一个增强的区间树。每次添加索引时,调整适当的区间,从相关段计算总和:

    A = [5,1,7,2,3] => {1:1, 2:3, 3:4, 5:0, 7:2}
    
    indexes     interval     total sub-arrays with maximum exactly
    1           (1,1)        1 =>           1
    1,3         (3,3)        2 =>           1 
    1,3,4       (3,4)        3 =>           2
    1,3,4,0     (0,1)        5 =>           2
    1,3,4,0,2   (0,4)        7 => 3 + 2*3 = 9
    

    augmented trees 中的插入和删除具有O(log n) 的时间复杂度。最坏情况下的总时间复杂度为O(n log n)

    【讨论】:

      【解决方案4】:

      我很难用语言解释我的解决方案。我只会添加代码。它会自己解释:

      #include <iostream>
      #include <fstream>
      using namespace std;
      
      #define max 10000
      
      int main(int argc, const char * argv[]) {
      
          ifstream input("/Users/appleuser/Documents/Developer/xcode projects/SubArrayCount/SubArrayCount/input.in");
      
          int n, arr[max], before[max]={0}, after[max]={0}, result[max];
          input >> n;
          for (int i=0; i<n; i++)
              input >> arr[i];
      
          for (int i=0;i<n;i++)
              for (int j=i-1;j>=0&&arr[j]<arr[i];j-=before[j]+1)
                  before[i]+=before[j]+1;
      
          for (int i=n-1;i>=0;i--)
              for (int j=i+1;j<n&&arr[j]<arr[i];j+=after[j]+1)
                  after[i]+=after[j]+1;
      
          for (int i=0;i<n;i++)
              result[i]= (before[i]+1)*(after[i]+1);
      
          for (int i=0; i<n; i++)
              cout << result [i] << " ";
          cout << endl;
      
          return 0;
      }
      

      (before[i]+1)*(after[i]+1)的解释:

      对于每个值,我们需要数字位于该值之前且小于该值,数字位于该值之后且小于该值。

        | 0  1  2  3  4  5 .... count of numbers less than the value and appears before.
      ---------------------
      0 | 1  2  3  4  5  6
      1 | 2  4  6  8  10 12
      2 | 3  6  9  12 15 18
      3 | 4  8  12 16 20 24
      4 | 5  10 15 20 25 30
      5 | 6  12 18 24 30 36
      . | 
      . |
      . |
      count of numbers less than the value and appears after.
      

      示例:对于一个比它少 3 个值并出现在之前的数字,并且比它少 4 个值并出现在之后的数字。答案是 V(3,4) = 20 = (3+1) * (4+1)

      请告诉我结果。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-11-09
        • 2019-04-08
        • 2015-11-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-11
        • 1970-01-01
        相关资源
        最近更新 更多