我给了一个看起来像这样的字符串:
1011010100
我的任务是找到子串的长度,其中空值的数量始终为< = 1的数量。这应该始终在从右到左和从左到右'扫描'子串时发生。所以在这个例子中,答案是:
10110101 => 8
我知道复杂性应该是O(n)或O(n log n),因为长度最多可以达到10 ^ 6
有什么想法吗?
答案 0 :(得分:1)
这是我的算法:
从右侧开始:
1. if you find 0 increment the value of count
2. if you find 1 decrement the count
将这些值存储在数组中,即v[]
。
e.g。
a[] = {1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1}
v[] = {0, 1, 0,-1, 0, 1, 0, 1, 2, 1, 2, 1, 0, -1}
现在问题从V
i, j
v[i] < v[j]
i<j
和i=0
找到了索引。
<强>证据强>:
如果您在此处看到j=11
,则v[i]=0
是可能的答案,值为v[j]=1
,j
。
这意味着,直到0
我们在字符串中有一个v[i]=0
额外字符,而i to j
表示从0
窗口大小开始,额外的1
会被放置额外{{1}}。因此答案。
希望它有所帮助。如果您有疑问,请告诉我。感谢。
答案 1 :(得分:1)
O(n)
解决方案实际上非常简单,通过构建&#34; height数组&#34;,表示相对于0的数量的1的数量。所以2的高度意味着比0还要多2个1。一旦在某些条件下执行某些最大性检查,我们就会迭代高度数组。
关键观察
请注意,满足条件的子数组必须在开头具有最小高度,在结尾处最大(相对于子数组,而不是整个数组)。
问题中样本的高度数组可以这样绘制,标记为:
v /\/\/\ /\/ \ ^
证明:
假设高度在开始时不是最小值,这意味着子阵列内部有一个高度低于开头的点。此时,0的数量应大于1.矛盾。
假设高度在末尾不是最大值,这意味着子阵列中有一个高度大于结尾的点,比如在索引j
处。然后在索引j
到最后有0到1(因为高度减小),所以当我们&#34;扫描&#34;从右到左的子阵列,我们会在索引j
找到0到1以上的数据。矛盾。
<强>算法强>
现在问题可以解释为找到最长的子阵列,该子阵列以子阵列中的最高高度结束,同时保持最小值不超过开头的高度。这与klrmlr提到的maximum subarray problem非常相似(&#34;数组的连续子序列&#34;更好地说是&#34;子阵列&#34;)。而且这个想法并没有保持O(n)
状态,而是保持到目前为止最大的状态&#34;并且&#34;此时最大值&#34;。
遵循该算法,下面是伪代码,遍历数组一次:
程序Balance_Left_Right
然而,我们很快就会发现这个测试用例的问题(正如Adam Jackson通过个人通信所指出的那样):1100101
,可视化如下:
/\ / \/\/
正确的答案是3(最后的101),但上面的算法将得到2(前11)。这是因为我们的答案显然隐藏在一座高山之后#34; (即答案中的最低点不低于山峰,答案中的最高点不高于山峰。)
所以我们需要确保当我们运行Procedure Balance_Left_Right(上面)时,没有&#34;高山&#34;隐藏答案。因此,解决方案是从右侧遍历数组一次,尝试将数组划分为多个部分,在每个部分中,此属性保持:&#34; 1的数量始终是&gt; =数字从0和#34;以及每个部分遍历的0,它不能再向左延伸。
然后,在每个部分中,当从左侧移动时,将在该部分的末尾具有最大高度,这是最大值。并且可以证明,使用此属性,方法balance_left_right将为此部分找到正确的答案。所以,我们只是在每个部分调用我们的balance_left_right方法,然后在这些方法中获得最大的答案。
现在,您可能会问,为什么在每个部分运行Balance_Left_Right足够了?这是因为答案要求属性从左侧和右侧保持,因此它必须位于其中一个部分内,因为每个部分都满足属性的一半。
算法仍为O(n)
,因为我们只访问每个元素两次,一次是从右边开始,一次是从左边开始。
最后一个测试用例将按如下方式进行分区:
/|\ | / | \|/\/ ** ***
其中只有标有星号(*)的部分。
所以新算法如下:
程序Max_Balance_Left_Right
这是Python中的代码:
def balance_left_right(arr):
lower = 0
upper = -2**32
lower_idx = 0 # Optional
upper_idx = -1 # Optional
result = (0,0,0)
height = 0
length = 0
for idx, num in enumerate(arr):
length += 1
height += 1 if num==1 else -1
if height<lower:
lower = height # Reset the lowest
upper = height # Reset the highest
lower_idx = idx+1 # Optional, record the starting point
length = 0 # Reset the answer
if height>=upper:
upper = height
upper_idx = idx # Optional, record the end point
if length > result[0]: # Take maximum length
result = (length, lower_idx, upper_idx)
return result
def max_balance_left_right(arr):
all_partitions = []
start = 0
end = len(arr)
right_partitions = balance_left(reversed(arr[start:end]))
for right_start, right_end in right_partitions:
all_partitions.append((end-right_end, end-right_start))
result = (0,0,0)
for start, end in all_partitions:
candidate = balance_left_right(arr[start:end])
if result[0] < candidate[0]:
result = (candidate[0], candidate[1]+start, candidate[2]+start)
return result
def balance_left(arr):
lower = 0
start_idx = 0
end_idx = -1
height = 0
result = []
for idx, num in enumerate(arr):
height += 1 if num==1 else -1
if height < lower:
if end_idx != -1:
result.append((start_idx,end_idx))
lower = height
start_idx = idx+1
end_idx = -1
else:
end_idx = idx+1
if end_idx != -1:
result.append((start_idx, end_idx))
return result
test_cases = [
[1,0,1,1,0,1,0,1,0,0],
[0,0,1,0,1,0,0,1,0,1,0,0,1,1,0,1,0,1,0,0,1],
[1,1,1,0,0,0,1,0,0,1,1,0,1,1,0,1,1,0],
[1,1,0,0,1,0,1,0,1,1,0,0,1,0,0],
[1,1,0,0,1,0,1],
[1,1,1,1,1,0,0,0,1,0,1,0,1,1,0,0,1,0,0,1,0,1,1]
]
for test_case in test_cases:
print 'Balance left right:'
print test_case
print balance_left_right(test_case)
print 'Max balance left right:'
print test_case
print max_balance_left_right(test_case)
print
将打印:
Balance left right: [1, 0, 1, 1, 0, 1, 0, 1, 0, 0] (8, 0, 7) Max balance left right: [1, 0, 1, 1, 0, 1, 0, 1, 0, 0] (8, 0, 7) Balance left right: [0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1] (6, 12, 17) Max balance left right: [0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1] (6, 12, 17) Balance left right: [1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0] (8, 9, 16) Max balance left right: [1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0] (8, 9, 16) Balance left right: [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0] (10, 0, 9) Max balance left right: [1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0] (10, 0, 9) Balance left right: [1, 1, 0, 0, 1, 0, 1] (2, 0, 1) Max balance left right: [1, 1, 0, 0, 1, 0, 1] (3, 4, 6) Balance left right: [1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1] (5, 0, 4) Max balance left right: [1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1] (6, 8, 13)
为了您的眼睛享受,测试用例的高度数组:
First: v /\/\/\ /\/ \ ^ Second: \ \/\/\ v \/\/\ /\/\/\ \/ \/ ^ Third: v /\ /\ / \ /\/ / \/\ /\/ \/ ^ Fourth: v /\ /\ / \/\/\/ \/\ ^ \ Fifth: /\ v / \/\/ ^ Sixth: /\ v / \ /\ / \/\/\/ \/\ / / ^ \/\/ /
关于问题的澄清
由于一些读者对OP究竟想要什么感到困惑,尽管问题已经明确说明了,但让我通过一些例子来解释这个问题。
首先,来自问题的任务:
我的任务是找到子串的长度,其中空值的数量始终为&lt; = 1的数量。这应该始终发生在扫描时#39;子串从右到左,从左到右
这指的是&#34; Catalan Number选票问题&#34;或&#34; Available Change Problem&#34;。在Wiki中,您可以检查&#34;单调路径&#34;问题,你可以在哪里映射&#34;向右移动&#34; as&#34; 1&#34;并且&#34;向上移动&#34; as&#34; 0&#34;。
问题是找到原始数组的子数组,这样,当从左到右和从右到左遍历子数组时,此属性成立:
到目前为止看到的0的数量不应超过到目前为止看到的1的数量。
例如,字符串1010
从从左到右保存属性,因为如果我们从左到右扫描数组,则总会有更多1&# 39;而不是0。但是这个属性并不是从右到左,因为从右边遇到的第一个字符是0,所以在开始时我们有更多的0(有一个)而不是1&#39; s(没有)。
例如,由OP给出,我们看到字符串1011010100
的答案是前八个字符,即:10110101
。为什么呢?
好的,所以当我们从左到右遍历子阵列时,我们发现总有比0更多的1。当我们从左到右遍历数组时,让我们检查1和0的数量:
1: num(0) = 0, num(1) = 1 0: num(0) = 1, num(1) = 1 1: num(0) = 1, num(1) = 2 1: num(0) = 1, num(1) = 3 0: num(0) = 2, num(1) = 3 1: num(0) = 2, num(1) = 4 0: num(0) = 3, num(1) = 4 1: num(0) = 3, num(1) = 5
我们可以看到,在任何时间点,0的数量总是小于或等于1的数量。这就是该物业从左到右持有的原因。同样的检查可以从右到左进行。
那么为什么不是1011010100
并回答?
让我们看看当我们从右到左遍历字符串时:
0: num(0) = 1, num(1) = 0 0: num(0) = 2, num(1) = 0 1: num(0) = 2, num(1) = 1 ...
我没有进行完整的遍历,因为自第一步以来该属性已被违反,因为我们有num(0) > num(1)
。这就是字符串1011010100
无法满足问题约束的原因。
你也可以看到我的&#34;身高阵列&#34;实际上是1的数量和0的数量之间的差异,即:num(1) - num(0)
。因此,为了拥有财产,我们必须使[相对]高度为正。这可以通过使高度不小于初始高度来显示。
答案 2 :(得分:0)
有两个问题的回忆(一个稍后删除......)和一个滑动窗口。
您可以压缩输入以产生后续零或1的数量:
+1 -1 +2 -1 +1 -1 +1 -2
这会产生编码A 并需要O(n)时间。
现在,在编码A时,只要遇到两个连续数字,总计达到&gt; 0,你进一步压缩。在编码B 中,括号中的数字表示子字符串的长度:
+2(4)-1 +1 -1 +1 -2 ==&gt; +2(6)-1 +1 -2 ==&gt; +2(8)-2
这也需要O(n)。在这里,我们立即得到解决方案:一个长度为8的字符串,其中两个比0更多1。让我们尝试一个更复杂的实例(在编码A中给出):
+5 -8 +4
这里,对编码B的转换没有帮助:
+5(5)-8 +4(4)
还要考虑以下实例(编码B):
+5(9)-6 +4(4)-6 +5(7)-6 +4(6)-6 +5(9)
此序列将用于演示......
首先,确定从左侧开始的最佳解决方案:
+5 -6 +4 -6 +5&gt; 0 ==&gt; 9 + 6 + 4 + 6 + 7 = 32
现在,扩展此项以找到从第三个位置开始的最佳解决方案(+4(4)):
+ 4-6 +5-6 +4&gt; 0 ==&gt; 4 + 6 + 7 + 6 + 6 = 29
这个解决方案并不比我们发现的第一个好。继续:
+5 -6 +4 -6 +5&gt; 0 ==&gt; 7 + 6 + 6 + 6 + 9 = 34
这是最好的解决方案。该算法可以在O(n)中实现,因为头部和尾部仅向前移动。
上面的简要描述并未涵盖所有细微之处(编码B左侧的负数,头尾相遇,......)。此外,可能不需要重新编码,滑动窗口可以直接在0-1表示上实现。但是,我只有在重新编码后才能完全理解这个问题。
实际上,正如米莉史密斯所指出的那样,“编码B”可能是有损的,这意味着它可能导致某些(尚未确定的)极端情况下的劣质解决方案。但是滑动窗口算法在编码A时也能正常工作,因此甚至可能需要跳过转换为编码B.(太懒了,不能重写算法的解释......)