给定一个数组,对于每个元素,找出小于它的元素的总数,它们出现在它的右侧

Posted

技术标签:

【中文标题】给定一个数组,对于每个元素,找出小于它的元素的总数,它们出现在它的右侧【英文标题】:given an array, for each element, find out the total number of elements lesser than it, which appear to the right of it 【发布时间】:2012-03-18 17:53:27 【问题描述】:

我之前发过一个问题,Given an array, find out the next smaller element for each element 现在,我想知道,是否有任何方法可以找出“给定一个数组,对于每个元素,找出小于它的元素总数,它出现在它的右侧” 例如,数组 [4 2 1 5 3] 应该产生 [3 1 0 1 0]??

[编辑] 我已经制定了解决方案,请看一下,如果有任何错误,请告诉我。

1 做一个平衡的BST插入元素从右到左遍历数组

2 BST 的制作方式是,每个元素都拥有以该元素为根的树的大小

3 现在,当您搜索插入任何元素的正确位置时,如果向右移动,请考虑以左兄弟 + 1(对于父节点)为根的子树的总大小 现在,由于在插入元素时计算计数,并且我们从右向左移动,因此我们得到的元素的确切计数小于出现在它之后的给定元素。

【问题讨论】:

我们还可以使用改进的归并排序(分而治之范式)在 O(nlogn) 时间内以最佳方式解决这个问题。例如here 【参考方案1】:

修改合并排序:(已经测试过的代码)

花费O(nlogn) 时间。

public class MergeSort 
    static HashMap<Integer, Integer> valueToLowerCount = new HashMap<Integer, Integer>();

    public static void main(String[] args) 
        int []                 arr = new int[] 50, 33, 37, 26, 58, 36, 59;
        int [] lowerValuesOnRight  = new int[] 4,   1,  2,  0,  1,  0,  0;

        HashMap<Integer, Integer> expectedLowerCounts = new HashMap<Integer, Integer>();
        idx = 0;
        for (int x: arr) 
            expectedLowerCounts.put(x, lowerValuesOnRight[idx++]);
        
        
        for (int x : arr) valueToLowerCount.put(x, 0);
        
        mergeSort(arr, 0, arr.length-1);
        
        //Testing       
        Assert.assertEquals("Count lower values on right side", expectedLowerCounts, valueToLowerCount);
    
    public static void mergeSort(int []arr, int l, int r) 
        if (r <= l) return;
        int mid = (l+r)/2;
        mergeSort(arr, l, mid);
        mergeSort(arr, mid+1, r);
        mergeDecreasingOrder(arr, l, mid, r);
    
    public static void mergeDecreasingOrder(int []arr, int l, int lr, int r) 
        int []leftArr = Arrays.copyOfRange(arr, l, lr+1);
        int []rightArr = Arrays.copyOfRange(arr, lr+1, r+1);
        int indexArr = l;
        int i = 0, j = 0;
        while (i < leftArr.length && j < rightArr.length) 
            if (leftArr[i] > rightArr[j]) 
                valueToLowerCount.put(leftArr[i], valueToLowerCount.get(leftArr[i]) + rightArr.length - j);
                arr[indexArr++] = leftArr[i++];
            else 
                arr[indexArr++] = rightArr[j++];
            
        
        while (i < leftArr.length)   
            arr[indexArr++] = leftArr[i++]; 
        
        while (j < rightArr.length) 
            arr[indexArr++] = rightArr[j++];
        
       

要查找右侧大于数组元素的值的总数,只需更改单行代码:

if (leftArr[i] > rightArr[j])

if (leftArr[i] < rightArr[j])

【讨论】:

【参考方案2】:

除了使用 BST,我们还可以通过对归并排序算法进行一些修改(在 O(n*logn) 时间内)来优化解决这个问题。

如果你仔细观察这个问题,你可以说在问题中我们需要统计每个元素所需的反转次数,以使数组按升序排序,对吧? p>

所以这个问题可以用分而治之的范式来解决。在这里,您需要维护一个辅助数组来存储所需的反转计数(即在其右侧小于它的元素)。

下面是一个python程序:

def mergeList(arr, pos, res, start, mid, end):
    temp = [0]*len(arr)
    for i in range(start, end+1):
        temp[i] = pos[i]

    cur = start
    leftcur = start
    rightcur = mid + 1

    while leftcur <= mid and rightcur <= end:

        if arr[temp[leftcur]] <= arr[temp[rightcur]]:
            pos[cur] = temp[leftcur]
            res[pos[cur]] += rightcur - mid - 1
            leftcur += 1
            cur += 1
        else:
            pos[cur] = temp[rightcur]
            cur += 1
            rightcur += 1

    while leftcur <= mid:
        pos[cur] = temp[leftcur]
        res[pos[cur]] += end - mid
        cur += 1
        leftcur += 1

    while rightcur <= end:
        pos[cur] = temp[rightcur]
        cur += 1
        rightcur += 1


def mergeSort(arr, pos, res, start, end):
    if start < end:
        mid = (start + end)/2
        mergeSort(arr, pos, res, start, mid)
        mergeSort(arr, pos, res, mid+1, end)
        mergeList(arr, pos, res, start, mid, end)


def printResult(arr, res):
    print
    for i in range(0, len(arr)):
        print arr[i], '->', res[i]


if __name__ == '__main__':
    inp = input('enter elements separated by ,\n')
    inp = list(inp)
    res = [0]*len(inp)
    pos = [ind for ind, v in enumerate(inp)]
    mergeSort(inp, pos, res, 0, len(inp)-1)
    printResult(inp, res)

时间:O(n*logn)

空间:O(n)

【讨论】:

【参考方案3】:

另一种不使用树的方法。

    构造另一个排序数组。例如对于输入数组 12, 1, 2, 3, 0, 11, 4,它将是 0, 1, 2, 3, 4, 11, 12 现在将输入数组中每个元素的位置与排序数组进行比较。例如,第一个数组中的 12 的索引为 0,而排序数组的索引为 6 比较完成后,从两个数组中删除元素

【讨论】:

但是从数组中删除需要 O(n)。不会吧??那么总体复杂度将是 O(n^2)。如果我错了,请纠正我.. 二进制堆可能有助于解决删除问题而无需整个数组移动【参考方案4】:

您可以使用 stl map 代替 BST。

从右侧开始插入。 插入一个元素后,找到它的迭代器:

auto i = m.find(element);

然后从 m.end() 中减去它。这为您提供了地图中大于当前元素的元素数量。

map<int, bool> m;
for (int i = array.size() - 1; i >= 0; --i) 
  m[array[i]] = true;
  auto iter = m.find(array[i])
  greaterThan[i] = m.end() - iter;

希望对您有所帮助。

【讨论】:

你会在这一行得到一个编译数组 "greaterThan[i] = m.end() - iter;"你不能减去map迭代器。 @mb1994 你知道 STL 映射在内部使用自平衡 BST(红黑树),所以基本上如果你没有构建自己的 BST,你仍然在内部使用 BST,算法复杂性仍然 O( logn) 假设使用自平衡 BST,否则如果 BST 偏斜,则 O(n)。【参考方案5】:

假设数组是6,-1,5,10,12,4,1,3,7,50

步骤

1.我们从数组的右端开始构建 BST。因为我们关心的是任何元素的所有元素。

2.假设我们已经形成了直到10个的部分解树。

3.现在当插入 5 时,我们进行树遍历并插入到 4 的右侧。 请注意,每次我们遍历任何节点的右侧时,我们都会增加 1 并添加编号。该节点的左子树中的元素。 例如: 50 是 0 7 是 0 对于 12,它是 1 个右遍历 + 左子树大小为 7 = 1+3 =4 10 同上。 对于 4 是 1+1 =2

在构建 bst 时,我们可以轻松地维护每个节点的左子树大小,只需维护一个与其对应的变量,并在每次节点向左遍历时将其递增 1。 因此解决方案平均情况为 O(nlogn)。

我们可以使用其他优化,例如预先确定数组是否按降序排序 按降序查找元素组,将它们视为单个元素。

【讨论】:

请注意,虽然使用 BST 会起作用,但最坏情况的复杂度将是 O(n^2) 输入数组已经排序。 (因为 BST 会完全偏斜)【参考方案6】:

你也可以使用二叉索引树

int tree[1000005];
void update(int idx,int val)

   while(idx<=1000000)
   
       tree[idx]+=val;
       idx+=(idx & -idx);
   


int sum(int idx)

    int sm=0;
    while(idx>0)
    
       sm+=tree[idx];
       idx-=(idx & -idx);
    
    return sm;


int main()

    int a[]=4,2,1,5,3;
    int s=0,sz=6;
    int b[10];
    b[sz-1]=0;
    for(int i=sz-2;i>=0;i--)
    
        if(a[i]!=0)
        
            update(a[i],1);
            b[i]=sum(a[i]-1)+s;
        
        else s++;
    
    for(int i=0;i<sz-1;i++)
    
       cout<<b[i]<<" ";
    
   return 0;

【讨论】:

如果 a[i] 【参考方案7】:

可以在 O(n log n) 内解决。

如果在 BST 中,当您搜索节点(从根到达该节点)时存储以该节点为根的子树的元素数,您可以计算大于/小于路径中的元素数:

int count_larger(node *T, int key, int current_larger)
    if (*T == nil)
        return -1;
    if (T->key == key)
        return current_larger + (T->right_child->size);
    if (T->key > key)
        return count_larger(T->left_child, key, current_larger + (T->right_child->size) + 1);
    return count_larger(T->right_child, key, current_larger)

** 例如,如果 this 是我们的树并且我们正在搜索键 3,则将调用 count_larger:

->(节点 2、3、0) --> (节点 4, 3, 0) --->(节点 3、3、2)

最终答案将是预期的 2。

【讨论】:

不,这行不通你是第一次构造树,现在假设控制转到 if (T->key > key) return count_larger(T->left_child, key, current_larger + ( T->right_child->size) + 1);在搜索任何元素时..问题是 (T->right_child->size) + 1);将包括在被搜索元素之前已插入的元素.. @RamanBhatia 它会工作的。对于从右侧开始的每个元素,(1)增加该元素的计数并更新树,以及(2)查找累积计数。当您进行查找时,树只包含当前元素右侧的项目和元素本身。 是的..这就是我发布的内容(已经编辑了问题,并在那里发布了我的解决方案)并且我误认为您的“当您搜索节点时(从根目录到达)”作为搜索在构建整个树之后,对于每个元素..我的坏.. @RamanBhatia:+1 提问。 “(T->right_child->size)”中的大小是什么意思是节点中的特殊字段还是其他什么......当az说“你存储以该节点为根的子树的元素数时他是什么意思当您搜索节点时(从根到达该节点)“。请用少量输入数据进行解释。提前致谢 @Imposter: "T->right_child" 是指向树中节点 *T 的右子节点的指针。我们将根节点(例如 *T)的子树的大小存储在名为“size”的变量中;所以“T->right_child->size”表示以 *T 的右孩子为根的子树的大小。该算法只是在 BST 中搜索一个键,我们只计算大于我们的键并且在我们接下来要走的子树(左或右)之外的元素的数量。【参考方案8】:

您也可以使用数组代替二叉搜索树。

def count_next_smaller_elements(xs):
    # prepare list "ys" containing item's numeric order
    ys = sorted((x,i) for i,x in enumerate(xs))
    zs = [0] * len(ys)

    for i in range(1, len(ys)):
        zs[ys[i][1]] = zs[ys[i-1][1]]
        if ys[i][0] != ys[i-1][0]: zs[ys[i][1]] += 1

    # use list "ts" as binary search tree, every element keeps count of
    # number of children with value less than the current element's value
    ts = [0] * (zs[ys[-1][1]]+1)
    us = [0] * len(xs)

    for i in range(len(xs)-1,-1,-1):
        x = zs[i]+1
        while True:
            us[i] += ts[x-1]
            x -= (x & (-x))
            if x <= 0: break

        x = zs[i]+1
        while True:
            x += (x & (-x))
            if x > len(ts): break
            ts[x-1] += 1

    return us

print count_next_smaller_elements([40, 20, 10, 50, 20, 40, 30])
# outputs: [4, 1, 0, 2, 0, 1, 0]

【讨论】:

【参考方案9】:

我认为可以在O(nlog(n)) 中使用修改后的快速排序版本来实现。基本上每次将元素添加到 less 时,都会检查该元素在原始数组中的排名是否优于当前枢轴的排名。它可能看起来像

oldrank -> original positions 
count -> what you want
function quicksort('array')
  if length('array') ≤ 1
      return 'array'  // an array of zero or one elements is already sorted
  select and remove a pivot value 'pivot' from 'array'
  create empty lists 'less' and 'greater'
  for each 'x' in 'array'
      if 'x' ≤ 'pivot' 
         append 'x' to 'less'
         if oldrank(x) > = oldrank(pivot)  increment count(pivot)
      else 
         append 'x' to 'greater'
         if oldrank(x) <  oldrank(pivot)  increment count(x) //This was missing
  return concatenate(quicksort('less'), 'pivot', quicksort('greater')) // two recursive calls

编辑:

实际上它可以使用任何基于比较的排序算法来完成。每次比较两个元素以使两者之间的相对顺序发生变化时,都会增加较大元素的计数器。

***中的原始伪代码。

【讨论】:

不,这行不通。第二个递归调用中的枢轴需要知道“另一半”,但事实并非如此。好主意。 恐怕还是不行。 greater 中的元素需要了解less 中的所有元素,而不仅仅是枢轴。【参考方案10】:
//some array called newarray
for(int x=0; x <=array.length;x++)

for(int y=x;y<array.length;y++)

if(array[y] < array[x])

newarray[x] = newarray[x]+1;



类似这样,其中 array 是您的输入数组, newarray 是您的输出数组 确保正确初始化所有内容(新数组值为 0)

【讨论】:

这是天真的 O(n^2) 方法。我想知道是否有相同的 O(nlogn) 方法 不是 O(n^2) 而是 O(n*(n-1)),不是吗? 常量值不会改变渐近增长率! 如果内部循环从 0 变为 arraylength 则为 n^2,但它不是,所以它更少....

以上是关于给定一个数组,对于每个元素,找出小于它的元素的总数,它们出现在它的右侧的主要内容,如果未能解决你的问题,请参考以下文章

在数组中的每个项目之前有多少个连续元素较小

Codeforces Round #450 (Div. 2) C. Remove Extra One 题解

找出数组中每个数右边第一个比它大的元素

找出数组中每个数右边第一个比它大的元素

对于给定的n个元素的数组a[1..n] 要求从中找出第k小的元素,输出这个元素 pascal

子串判断