我最近偶然发现了一个有趣的问题,我想知道我的解决方案是否是最佳的。
您将获得一系列零和一组。目标是返回 数量零和最昂贵的子阵列中的数量。
数组的成本是1的数量除以0的数量。在 如果子阵列中没有零,则成本为零。
起初我尝试过暴力破解,但是对于10,000个元素的数组来说,它太慢了,我的内存耗尽了。
我的第二个想法是创建那些子阵列,而不是记住子阵列的开始和结束。这样我节省了大量内存,但复杂性仍然是O(n 2 )。
我提出的最终解决方案是我认为O(n)。它是这样的:
从数组的开头开始,对于每个元素,计算从1开始,以当前索引结束的子数组的开销。所以我们从一个由第一个元素组成的子数组开始,然后是第一个和第二个等等。由于我们需要计算成本的唯一因素是子数组中的1和0的数量,我可以找到子阵列的最佳结束。
第二步是从第一步的子阵列结束开始,并重复相同的步骤以找到最佳的开始。这样我相信整个阵列中没有更好的组合。
此解决方案是否正确?如果没有,是否有反例会显示此解决方案不正确?
为清楚起见: 假设我们的输入数组是0101。 有10个子阵列: 0,1,0,1,01,10,01,010,101和0101。
最昂贵的子阵列的成本将是2,因为101是最昂贵的子阵列。所以算法应该返回1,2
还有一件事我忘记了,如果2个子阵列具有相同的成本,则更长的一个“更贵”。
答案 0 :(得分:2)
让我勾画出一个我的假设的证据:
(a =整个数组,*
=零或更多,+
=一个或多个,{n}
=正好n)
案例a=0*
和a=1+
:c = 0
案例a=01+
和a=1+0
:符合1*0{1,2}1*
,a是最佳
For the normal case, a contains one or more 0s and 1s.
This means there is some optimum sub-array of non-zero cost.
(S) Assume s is an optimum sub-array of a.
It contains one or more zeros. (Otherwise its cost would be zero).
(T) Let t be the longest `1*0{1,2}+1*` sequence within s
(and among the equally long the one with with most 1s).
(Note: There is always one such, e.g. `10` or `01`.)
Let N be the number of 1s in t.
Now, we prove that always t = s.
By showing it is not possible to add adjacent parts of s to t if (S).
(E) Assume t shorter than s.
We cannot add 1s at either side, otherwise not (T).
For each 0 we add from s, we have to add at least N more 1s
later to get at least the same cost as our `1*0+1*`.
This means: We have to add at least one run of N 1s.
If we add some run of N+1, N+2 ... somewhere than not (T).
If we add consecutive zeros, we need to compensate
with longer runs of 1s, thus not (T).
This leaves us with the only option of adding single zeors and runs of N 1s each.
This would give (symmetry) `1{n}*0{1,2}1{m}01{n+m}...`
If m>0 then `1{m}01{n+m}` is longer than `1{n}0{1,2}1{m}`, thus not (T).
If m=0 then we get `1{n}001{n}`, thus not (T).
So assumption (E) must be wrong.
结论:最佳子阵列必须符合1*0{1,2}1*
。
根据我上次评论(1*01*
或1*001*
)中的假设,这是我在Java中的O(n)impl:
public class Q19596345 {
public static void main(String[] args) {
try {
String array = "0101001110111100111111001111110";
System.out.println("array=" + array);
SubArray current = new SubArray();
current.array = array;
SubArray best = (SubArray) current.clone();
for (int i = 0; i < array.length(); i++) {
current.accept(array.charAt(i));
SubArray candidate = (SubArray) current.clone();
candidate.trim();
if (candidate.cost() > best.cost()) {
best = candidate;
System.out.println("better: " + candidate);
}
}
System.out.println("best: " + best);
} catch (Exception ex) { ex.printStackTrace(System.err); }
}
static class SubArray implements Cloneable {
String array;
int start, leftOnes, zeros, rightOnes;
// optimize 1*0*1* by cutting
void trim() {
if (zeros > 1) {
if (leftOnes < rightOnes) {
start += leftOnes + (zeros - 1);
leftOnes = 0;
zeros = 1;
} else if (leftOnes > rightOnes) {
zeros = 1;
rightOnes = 0;
}
}
}
double cost() {
if (zeros == 0) return 0;
else return (leftOnes + rightOnes) / (double) zeros +
(leftOnes + zeros + rightOnes) * 0.00001;
}
void accept(char c) {
if (c == '1') {
if (zeros == 0) leftOnes++;
else rightOnes++;
} else {
if (rightOnes > 0) {
start += leftOnes + zeros;
leftOnes = rightOnes;
zeros = 0;
rightOnes = 0;
}
zeros++;
}
}
public Object clone() throws CloneNotSupportedException { return super.clone(); }
public String toString() { return String.format("%s at %d with cost %.3f with zeros,ones=%d,%d",
array.substring(start, start + leftOnes + zeros + rightOnes), start, cost(), zeros, leftOnes + rightOnes);
}
}
}
答案 1 :(得分:1)
如果我们可以显示最大数组总是1 + 0 + 1 +,1 + 0或01+(正则表达式表示法,那么我们可以计算运行次数
因此,对于数组(010011),我们(始终以1的运行开始)
0,1,1,2,2
因此比率为(0,1,3,0,1.5,1),这导致最终结果为10011的数组,忽略了一次运行
左边缘的成本为0 右边缘的成本是2
所以在这种情况下,右边缘是正确的答案 - 011
我还没有能够提出一个反例,但证据也不明显。希望我们可以挤满源头:)
退化的情况更简单 所有的1和0都很明显,因为它们都有相同的成本。 只有1 +,0 +的字符串或反之亦然1是1和0。
答案 2 :(得分:0)
这个怎么样?作为一名C#程序员,我想我们可以使用像<int,int,int>.
的词典这样的东西。
第一个int将用作键,第二个用作子数组,第三个用于子数组的元素。
为你的例子 key |子数组|元素
1|1|0
2|2|1
3|3|0
4|4|1
5|5|0
6|5|1
7|6|1
8|6|0
9|7|0
10|7|1
11|8|0
12|8|1
13|8|0
14|9|1
15|9|0
16|9|1
17|10|0
18|10|1
19|10|0
20|10|1
然后,您可以浏览字典并将最高值存储在变量中。
var maxcost=0
var arrnumber=1;
var zeros=0;
var ones=0;
var cost=0;
for (var i=1;i++;i<=20+1)
{
if ( dictionary.arraynumber[i]!=dictionary.arraynumber[i-1])
{
zeros=0;
ones=0;
cost=0;
if (cost>maxcost)
{
maxcost=cost;
}
}
else
{
if (dictionary.values[i]==0)
{
zeros++;
}
else
{
ones++;
}
cost=ones/zeros;
}
}
这将是log(n ^ 2),我希望你只需要3n大小的数组内存?
答案 3 :(得分:0)
我认为我们可以修改maximal subarray problem以适应这个问题。这是我的尝试:
void FindMaxRatio(int[] array, out maxNumOnes, out maxNumZeros)
{
maxNumOnes = 0;
maxNumZeros = 0;
int numOnes = 0;
int numZeros = 0;
double maxSoFar = 0;
double maxEndingHere = 0;
for(int i = 0; i < array.Size; i++){
if(array[i] == 0) numZeros++;
if(array[i] == 1) numOnes++;
if(numZeros == 0) maxEndingHere = 0;
else maxEndingHere = numOnes/(double)numZeros;
if(maxEndingHere < 1 && maxEndingHere > 0) {
numZeros = 0;
numOnes = 0;
}
if(maxSoFar < maxEndingHere){
maxSoFar = maxEndingHere;
maxNumOnes = numOnes;
maxNumZeros = numZeros;
}
}
}
我认为关键是如果比率小于1,我们可以忽略那个子序列,因为
总是有一个子序列01
或10
,其比率为1.这似乎适用于010011
。