我最近在编程竞赛中未能解决以下问题 -
给定包含 n 值的数组ar[]
,您需要找到包含大于或等于 k 反转的子数组的数量。
数组中的反转次数定义为索引(i,j)的对数,以便
1 <= i < j <= n and ar[i] > ar[j].
预计O(nlogn)
最严重的案件复杂性。
答案 0 :(得分:2)
您可以在O(N log N)时间内执行此操作:
如果ar [i] ... ar [j]中至少存在K个反转,则将(i,j)定义为 minimal 子数组,并且在任何较短的子数组中至少有K个反转从i开始,即,如果你移除一个元素作为最小子数组的末尾,那么它将具有少于K的反转。
如果(i,j)是最小子阵列,则确切地存在N + 1-j个子阵列,其至少K个倒置从位置i开始,因为向末尾添加元素从不减少反转次数。此外,如果有任何从i开始的&gt; = K反转的子阵列,则确实存在从开始的一个最小子阵列。
所以我们所要做的就是在每个位置找到最小子阵列(如果存在),并将相应的子阵列总数加起来,如下所示(伪代码):
let total=0;
let i,j=1,1;
while(i<=N)
{
while (j<i || inversions in ar[i]...ar[j] < K)
{
++j;
if (j>N)
{
return total; //out of subarrays
}
}
// (i,j) is now a minimal subarray
total+=N+1-j
++i; //next start position
//now, IF (i,j) has enough inversions, then it is STILL
//minimal, because otherwise the previous subarray would not have
//been minimal either
}
为了满足我们的总复杂度目标,我们需要inversions in ar[i]...ar[j]
检查每次迭代最多花费O(log N)。
我们可以通过将子阵列元素保存在平衡搜索树中来实现这一点,每个节点中的计数会记住该节点的子树的总大小。当J从1增加到N时,我们将向该树添加最多N个元素,总成本为O(N log N)。当我从1增加到N时,我们将从这棵树中删除最多N个元素,总成本为O(N log N)。
此外,当我们向树中添加元素时,子数组中的反转次数会增加大于我们添加的元素的数量,因为新元素位于子数组的末尾。当我们从子阵列的开头移除一个元素时,反转的数量会减少少于我们添加的元素的数量。这两个数字都需要O(log N)来确定,因此我们可以在O(N log N)时间内跟踪子阵列中的反转总量。