4

我最近偶然发现了一个有趣的问题,我想知道我的解决方案是否是最优的。

你会得到一个由 0 和 1 组成的数组。目标是在最昂贵的子数组中返回零的数量和一的数量。

数组的成本是 1 的数量除以 0 的数量。如果子数组中没有零,则成本为零。

  1. 起初我尝试了暴力破解,但对于一个包含 10,000 个元素的数组来说,它太慢了,而且我的内存不足。

  2. 我的第二个想法不是创建那些子数组,而是记住子数组的开始和结束。这样我节省了很多内存,但复杂度仍然是 O(n 2 )。

  3. 我想出的最终解决方案是 O(n)。它是这样的:

    从数组的开头开始,对于每个元素,计算从 1 开始到当前索引结束的子数组的成本。所以我们将从一个由第一个元素组成的子数组开始,然后是第一个和第二个等。由于我们唯一需要计算成本的是子数组中 1 和 0 的数量,我可以找到子阵列的最佳末端。

    第二步是从第一步开始的子数组的末尾开始,并重复相同的操作以找到最佳开始。这样,我确信整个阵列中没有更好的组合。

    这个解决方案正确吗?如果不是,是否有反例表明该解决方案不正确?

编辑

为了清楚起见:假设我们的输入数组是 0101。有 10 个子数组:0,1,0,1,01,10,01,010,101 和 0101。

最昂贵子阵列的成本将是 2,因为 101 是最昂贵的子阵列。所以算法应该返回 1,2

编辑 2

还有一件事我忘记了,如果 2 个子阵列的成本相同,则越长的子阵列“越贵”。

4

4 回答 4

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*根据我上一条评论(或)中的假设,这是我在 Java 中的 O(n) impl 1*001*

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);
        }
    }
}
于 2013-10-25T21:27:21.543 回答
1

如果我们可以显示最大数组始终为 1+0+1+、1+0 或 01+(正则表达式符号,那么我们可以计算运行次数

所以对于数组(010011),我们有(总是从 1 开始)

0,1,1,2,2

所以比率是 (0, 1, 0.3, 1.5, 1),这导致数组 10011 作为最终结果,忽略一次运行

左边缘的成本为 0 右边缘的成本为 2

所以在这种情况下,右边是正确的答案——011

我还没有想出一个反例,但证据也不明显。希望我们可以众包一个:)

退化的情况更简单所有 1 和 0 都是显而易见的,因为它们都具有相同的成本。一个只有 1+,0+ 的字符串,反之亦然,都是 1 和一个 0。

于 2013-10-25T18:27:15.673 回答
0

这个怎么样?作为 C# 程序员,我认为我们可以使用类似 Dictionary of <int,int,int>. The first int 将用作键,第二个用作子数组编号,第三个用作子数组的元素。

对于您的示例键|子数组编号|元素

   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 大小的数组内存?

于 2013-10-25T18:27:37.953 回答
0

我认为我们可以修改最大子数组问题以适应这个问题。这是我的尝试:

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.

于 2013-10-25T19:19:02.207 回答