【问题标题】:Combining different number ranges in O(n)在 O(n) 中组合不同的数字范围
【发布时间】:2014-03-05 16:45:13
【问题描述】:

我目前正在跟踪视频的用户播放时间,并试图确定用户观看视频的百分比。我已经将问题概括为给定一系列可能重叠的数字范围,如何将它们组合成一系列不重叠的数字范围(即转换“0-10、5-15、30-45、20-25”变成“0-15, 20-25, 30-45”。

我有一个相对冗长的解决方案,其前提是如果对数字范围进行了排序,那么将两个相邻的数字范围组合起来是相对简单的(如果它们重叠或者它们保持分离,则将它们组合起来)。因此,我们首先对数字范围进行排序,然后遍历范围并将它们组合起来。

由于排序是最坏情况 O(nlgn),这意味着我的解决方案应该是 O(nlgn),我想知道是否有人知道该问题的 O(n) 解决方案?

http://jsfiddle.net/457PH/2

var testcase = [
        [0, 30], [40, 50], [5, 15], [70, 95], [45, 75], [0, 10],
        [110, 115], [115, 120], [140, 175], [125, 160]
    ];

//sorts the array in ascending order (based on first element)
//if the first elements are the same, order based on second element (prioritising elements that are bigger)
testcase.sort(function(a, b) {
    if (a[0] !== b[0]) return a[0] - b[0];

    return b[1] - a[1]
})


function evaluate(a, b) {
    var result = [];
    //tests that the array is sorted properly
    if ((a[0] > b[0]) || ((a[0] === b[0] ) && (a[1] < b[1]))) throw new Error('Array not sorted properly');

    //if a and b do not overlap, then push both in the result
    if(b[0] > a[1]) {
        result.push(a, b);
    }
    //if a and b overlap
    else {
        var newElement = [a[0], Math.max(a[1], b[1])];
        result.push(newElement);
    }
    return result;
}

console.log(testcase)
var combinedArr = [testcase[0]];
for (var i = 1; i < testcase.length; i++) {
    var popped = combinedArr.pop();
    combinedArr = combinedArr.concat(evaluate(popped, testcase[i]));
}
console.log(combinedArr);

【问题讨论】:

  • 问题的规模有多大?似乎每个列表中的元素不会超过十几个(根据问题的描述,如果我错了,请纠正我)。对于这么小的尺寸,渐近复杂度几乎没有什么意义,而且插入排序,也就是纸上的 O(n^2),不太可能比其他排序技术获得更好的性能。
  • 另外sorting is worst case O(nlgn) - 这取决于实现的排序算法。例如,快速排序有 O(n^2) 最坏情况,而其他(例如合并排序)有 O(nlogn)。
  • 不,那是不可能的。如果您假设您的输入未排序,并且希望您的输出升序,那么您将无法绕过排序。
  • @Bergi:如果输出的顺序无关紧要怎么办?
  • @amit 问题的规模可能不大,并且同意性能提升不大。话虽如此,我想借此机会提高我对算法的了解,因此想看看SO专家是否有任何提示。

标签: javascript algorithm time-complexity


【解决方案1】:

另一种解决方案是O(W+n*|S|),其中|S| 是每个区间的平均大小,W 是列表中的最大值将使用位集,并迭代每个元素并设置所有相关位。
在另一次迭代中 - 打印位集中的所有间隔(已排序)。

所以,这种方法的算法基本上是:

  1. 创建一个大小为 W 的位集,其中只有在某个时间间隔内才设置位。
  2. 迭代 bitset 并打印间隔 - 现在这很容易。

虽然如果 W|S| 很大,则在渐近复杂度方面可能会更糟 - 请注意,这里的常量相当小,因为位运算很容易实现。

应该使用经验基准并实现statistical significance来选择实际上更好的。

伪代码:

//create the bitset:
b <- new bitset
for each interval [x1,x2]:
  for each element i from x1 to x2:
     b[i] = 1

//print intervals:
first <- -1
for each element i from 0 to W+1: //regard b[W] as 0
  if b[i] == 0 and first != -1:
     print (first,i-1)
     first = -1
  else if b[i] == 1 and first == -1:
     first = i

【讨论】:

  • (+1) 我的想法类似……在某种程度上,可以使用数字作为位集,第一部分可能类似于:bitset | (Math.pow(2,top - bottom) - 1) &lt;&lt; (bottom - 1))。在 JavaScript 中,需要自定义一个大于 32 位的数字;和更大的力量,想想吧。
  • @גלעדברקן:你也可以((1 &lt;&lt; (top - bottom + 1)) - 1) &lt;&lt; bottom
【解决方案2】:

如果您只限于每个区间的前半部分与区间后半部分的不同成员重叠的情况,则区间重叠组合的可能性至少为 Omega((n/ 2)!)(即 n/2 阶乘)。因此,在任何基于比较的算法中,您至少需要 log((n/2)!) = Omega(n log n) 比较来区分所有这些情况。因此,在任何基于比较的算法中,在最坏的情况下都需要 Omega(n log n) 时间。

【讨论】:

    【解决方案3】:

    这是在 JavaScript 中实现 bitset 的尝试:

    function percentWatched(ranges,totalMinutes){
        var numPartitions = Math.ceil(totalMinutes / 31),
            bitset = new Array(numPartitions)
    
        for (var i in ranges){
            var top = ranges[i][1]
              , bottom = ranges[i][0]
              , m, shift, k
    
              while (bottom < top){
                  m = Math.floor(bottom / 31)
                  shift = bottom % 31
                  k = shift + top - bottom <= 31 ? top - bottom : 31 - shift
                  bitset[m] |= (1 << k) - 1 << shift
                  bottom += k
              }
        }
    
        var minutesWatched = 0
        for (var i in bitset)
            minutesWatched += numSetBits(bitset[i])
    
        return {percent: 100 * minutesWatched / totalMinutes
               , ranges: bitset}
    }
    
    function numSetBits(i) //copied from http://stackoverflow.com/questions/109023
    {
        i = i - ((i >> 1) & 0x55555555);
        i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
        return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
    }
    

    控制台输出:

    > var a = percentWatched([[0,10], [5,15], [30,45], [20,25]],100)
    
    > for (var i in a.ranges) console.log(a.ranges[i].toString(2))
    "1000001111100000111111111111111"
    "11111111111111"
    
    > a.percent
    35
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-09-17
      • 2012-03-31
      • 2014-11-05
      • 2013-01-24
      • 2017-06-24
      • 1970-01-01
      • 2015-05-04
      相关资源
      最近更新 更多