给定一个数组A,其中包含1,2,...,n的排列。子块A [i..j]
如果所有数字出现在A [i..j]中,则数组A被称为有效块
是连续的数字(可能不是有序的)。
给定数组A = [7 3 4 1 2 6 5 8],有效块为[3 4],[1,2],[6,5],
[3 4 1 2],[3 4 1 2 6 5],[7 3 4 1 2 6 5],[7 3 4 1 2 6 5 8]
因此上述排列的计数为7。
给出一个O(n log n)算法来计算有效块的数量。
答案 0 :(得分:1)
好的,由于我在一个相关的问题上投了200个赏金,我只有1个代表:Finding sorted sub-sequences in a permutation 所以我暂时不能留下评论。
我有个主意:
1)找到所有排列组。它们是:(78),(34),(12),(65)。与群论不同,他们的秩序和地位,以及他们是否相邻。因此,组(78)可以表示为结构(7, 8, false)
,而(34)可以表示为(3,4,true)
。我正在使用Python的元组符号,但实际上使用整个类可能更好。这里的真或假意味着连续与否。如果(max(gp1) == min(gp2) + 1 or max(gp2) == min(gp1) + 1) and contigous(gp1) and contiguos(gp2)
,则两组“相邻”。这不是union(gp1, gp2)
连续的唯一条件,因为(14)
和(23)
很好地合并到(14)
。这对于算法课堂作业来说是一个很好的问题,但对于采访来说却是一个糟糕的问题。我怀疑这是作业。
答案 1 :(得分:1)
只是一些想法:
乍一看,这听起来不可能:完全排序的数组会有 O(n 2 )有效的子块。
因此,您需要一次计算多个有效的子块。检查子块的有效性是 O(n)。检查子块是否完全排序也是 O(n)。完全排序的子块包含 n·(n - 1)/ 2 个有效子块,您可以在不进一步破坏此子块的情况下对其进行计数。
现在,整个数组显然总是有效的。对于分而治之的方法,你需要解决这个问题。有两个可以想到的断点:最高元素的位置和最低元素的位置。如果在这些点之一处将数组分成两个,包括包含第二个到极值元素的部分中的极值,则不能有超过此断点的有效子块。
通过始终选择产生更均匀分割的极值,这对于“随机”数组应该能够很好地工作(平均 O(n log n))。但是,当您的输入类似于(1 5 2 6 3 7 4 8)
时,我会发现问题,这似乎会产生 O(n 2 )行为。 (1 4 7 2 5 8 3 6 9)
会类似(我希望你看到这种模式)。我目前看不到捕捉这种更糟糕情况的技巧,但似乎它需要其他分裂技术。
答案 2 :(得分:1)
这个问题确实涉及一些“数学技巧”,但一旦你得到它,它就相当直接。但是,我的其余解决方案将不符合O(n log n)标准。
数学部分:
对于任何两个连续数字,它们的总和为2k+1
,其中k
是最小元素。对于三个,3k+3
,4:4k+6
和N
这样的数字为Nk + sum(1,N-1)
。因此,您需要两个可以同时完成的步骤:
动态编程部分
使用前一行条目的结果构建两个表,以构建每个连续行的条目。不幸的是,我完全错了,因为这仍然需要n ^ 2个子阵列检查。啊!
答案 3 :(得分:0)
我的主张
STEP = 2 //探测数量
B [0,0,0,0,0,0,0,0]
B [1,1,0,0,0,0,0,0]
有效(A,B) - 如果无效则移动一个
B [0,1,1,0,0,0,0,0]
VALID(A,B) - 如果有效则移动一步并执行
B [0,0,0,1,1,0,0,0]
有效(A,B)
B [0,0,0,0,0,1,1,0]
STEP = 3
B [1,1,1,0,0,0,0,0]不行
B [0,1,1,1,0,0,0,0]确定
B [0,0,0,0,1,1,1,0]不行
STEP = 4
B [1,1,1,1,0,0,0,0]不行
B [0,1,1,1,1,0,0,0]确定
.....
CON <- 0
STEP <- 2
i <- 0
j <- 0
WHILE(STEP <= LEN(A)) DO
j <- STEP
WHILE(STEP <= LEN(A) - j) DO
IF(VALID(A,i,j)) DO
CON <- CON + 1
i <- j + 1
j <- j + STEP
ELSE
i <- i + 1
j <- j + 1
END
END
STEP <- STEP + 1
END
有效方法检查所有元素是否连续
从未测试过,但可能没问题
答案 4 :(得分:0)
(这是尝试做这个N.log(N)最坏的情况。不幸的是它错了 - 它有时会低估。它错误地假设你可以通过只查看相邻的较小的有效块对来找到所有块。事实上,你必须看三胞胎,四重等,以获得所有更大的块。)
使用表示子块的结构和子块的队列来执行此操作。
struct
c_subblock
{
int index ; /* index into original array, head of subblock */
int width ; /* width of subblock > 0 */
int lo_value;
c_subblock * p_above ; /* null or subblock above with same index */
};
分配与原始数组大小相同的子块数组,并初始化每个子块以使其中只有一个项目。随时将它们添加到队列中。如果你从数组[7 3 4 1 2 6 5 8]开始,你将得到一个像这样的队列:
队列:([7,7] [3,3] [4,4] [1,1] [2,2] [6,6] [5,5] [8,8])subbblock [7,7]的{index,width,lo_value,p_above}值将为{0,1,7,null}。
现在很容易。原谅c-ish伪代码。
loop {
c_subblock * const p_left = Pop subblock from queue.
int const right_index = p_left.index + p_left.width;
if ( right_index < length original array ) {
// Find adjacent subblock on the right.
// To do this you'll need the original array of length-1 subblocks.
c_subblock const * p_right = array_basic_subblocks[ right_index ];
do {
Check the left/right subblocks to see if the two merged are also a subblock.
If they are add a new merged subblock to the end of the queue.
p_right = p_right.p_above;
}
while ( p_right );
}
}
我会想到这一切。它通常是O(N log(N)),但对于完全排序或反排序的列表,它将是O(N ^ 2)。我认为这有一个答案 - 当您构建原始子块数组时,您会查找已排序和反排序的序列,并将它们添加为基本级子块。如果你保持一个计数增量(宽度*(宽度+ 1))/ 2为基本级别。这将给你包括所有1长子块的计数。
之后只需使用上面的循环,弹出并推送队列。如果你在计算,你必须在左右子块上都有一个乘数,然后将它们相乘以计算增量。乘数是最左边(对于p_left)或最右边(对于p_right)基本级子块的宽度。
希望这是明确的,而不是太多的错误。我只是敲打它,所以它甚至可能是错的。 [后来注意。毕竟这不起作用。见下面的注释。]
答案 5 :(得分:0)
和其他人一样,我只是把它扔出去......它适用于下面的单个例子,但是YMMV!
想法是计算非法子块的数量,并从总可能的数量中减去它。我们通过依次检查每个数组元素并排除包含该元素但不包含其前任或后继元素的子块来计算非法数据。
Foreach i in [1,N],计算B [A [i]] = i。
设Count =长度> 1的子块总数,即N-choose-2(每个可能的起始和结束索引组合一个)。
Foreach i,考虑A [i]。忽略边缘情况,设x = A [i] -1,并且让y = A [i] +1。 A [i]不能参与任何不包含x或y的子块。设iX = B [x],iY = B [y]。这里有几个案例需要独立处理。一般情况是iX<i<iY<i
。在这种情况下,我们可以消除子块A [iX + 1 ... iY-1]和包含i的所有插入块。有(i - iX + 1)*(iY - i + 1)这样的子块,所以称这个号码为消除。 (其他案例作为读者留下的练习,以及那些边缘案例。)Set Count = Count - Eliminated。
返回计数。
总成本似乎是N *(步骤2的成本)= O(N)。
WRINKLE:在第2步中,我们必须注意不要多次消除每个子间隔。我们可以通过仅消除完全或部分位于位置i右侧的子间隔来实现这一点。
例:
A = [1,3,2,4]
B = [1,3,2,4]
初始计数=(4 * 3)/ 2 = 6
i = 1:A [i] = 1,因此需要其中包含2的子块。我们可以从考虑中消除[1,3]。消除= 1,计数 - &gt; 5。
i = 2:A [i] = 3,因此需要其中包含2或4的子块。这排除了[1,3]但我们已经从i = 1看起来已经考虑到了它。消除= 0。
i = 3:A [i] = 2,因此需要包含[1]或[3]的子块。我们可以从考虑中消除[2,4]。消除= 1,计数 - &gt; 4。
i = 4:A [i] = 4,所以我们需要带有[3]的子块。这排除了[2,4],但是当我从i = 3看时,我们已经解释了它。消除= 0。
最终计数= 4,对应子块[1,3,2,4],[1,3,2],[3,2,4]和[3,2]。
答案 6 :(得分:0)
原始数组不包含重复项,因此必须是连续的块。让我们调用这个块(1~n)。我们可以通过检查第一个元素是1还是n(即O(1))来测试块(2~n)是否连续。同样,我们可以通过检查最后一个元素是1还是n来测试块(1~n-1)。
我无法将其塑造成一个有效的解决方案,但也许它会帮助一些人......