在数组中查找连续范围

Posted

技术标签:

【中文标题】在数组中查找连续范围【英文标题】:Finding contiguous ranges in arrays 【发布时间】:2011-07-21 21:07:43 【问题描述】:

给你一个整数数组。您必须输出最大范围,以便该范围内的所有数字都存在于数组中。这些数字可能以任何顺序出现。例如,假设数组是

2, 10, 3, 12, 5, 4, 11, 8, 7, 6, 15

这里我们找到两个(非平凡的)范围,这些范围中的所有整数都存在于数组中,即 [2,8] 和 [10,12]。其中 [2,8] 是较长的一个。所以我们需要输出它。

当我被问到这个问题时,我被要求在线性时间内完成此任务,并且不使用任何排序。我以为可能有一个基于哈希的解决方案,但我想不出什么。

这是我的解决方案尝试:

void printRange(int arr[])

    int n=sizeof(arr)/sizeof(int);
    int size=2;
    int tempans[2]; 

    int answer[2];// the range is stored in another array
    for(int i =0;i<n;i++)
    
        if(arr[0]<arr[1])
        
             answer[0]=arr[0];
             answer[1]=arr[1];
        
        if(arr[1]<arr[0])
        
            answer[0]=arr[1];
            answer[1]=arr[0];
        

        if(arr[i] < answer[1])
            size += 1;
        else if(arr[i]>answer[1]) 
            initialize tempans to new range;
             size2=2;
        
        else  
            initialize tempans  to new range
        


//I have to check when the count becomes equal to the diff of the range

我被困在这部分...我不知道应该使用多少个 tempanswer[] 数组。

【问题讨论】:

这个问题的措辞有点混乱,虽然我现在明白了。您想在数组中找到最大的一组连续数字。在您的示例中,2, 3, 4, 5, 6, 7, and 8 是数组中的值,但 1 and 9 不是,因此您的候选结果之一是 [2 - 8] 【参考方案1】:

使用 javascript 稀疏数组功能的非常短的解决方案:

O(n) 时间使用 O(n) 额外空间。

var arr = [2, 10, 3, 12, 5, 4, 11, 8, 7, 6, 15];

var a = [];
var count = 0, max_count = 0;

for (var i=0; i < arr.length; i++) a[arr[i]] = true;
for (i = 0; i < a.length; i++) 
    count = (a[i]) ? count + 1 : 0;
    max_count = Math.max(max_count, count);
    

console.log(max_count); // 7

【讨论】:

【参考方案2】:

我在多个平台上阅读了很多关于这个问题的解决方案,其中一个引起了我的注意,因为它非常优雅地解决了这个问题,而且很容易理解。

这个方法的主干是创建一个需要 O(n) 时间的集合/哈希,并且从那里每次访问该集合/哈希将是 O(1)。由于 O-Notation 省略了常数项,该算法总体上仍然可以描述为O(n)

def longestConsecutive(self, nums):
    nums = set(nums)                    # Create Hash O(1)   
    best = 0
    for x in nums:                   
        if x - 1 not in nums:           # Optimization
            y = x + 1                   # Get possible next number
            while y in nums:            # If the next number is in set/hash
                y += 1                  # keep counting
            best = max(best, y - x)     # counting done, update best
    return best

如果你用简单的数字来处理它,那就很简单了。 Optimization 步骤只是确保您开始计数的短路,当该特定数字是序列的 beginning 时。

感谢 Stefan Pochmann。

【讨论】:

【参考方案3】:

一种快速的方法(php):

$tab = array(14,12,1,5,7,3,4,10,11,8);
asort($tab);
$tab = array_values($tab);
$tab_contiguous = array();
$i=0;
foreach ($tab as $key => $val) 
    $tab_contiguous[$i][] = $tab[$key];
    if (isset($tab[$key+1])) 
        if($tab[$key] + 1 != $tab[$key+1])
            $i++;
    

echo(json_encode($tab_contiguous));

【讨论】:

【参考方案4】:

这是Java中的解决方案:

public class Solution   
    public int longestConsecutive(int[] num)   
        int longest = 0;  
        Map<Integer, Boolean> map = new HashMap<Integer, Boolean>();  
        for(int i = 0; i< num.length; i++)  
            map.put(num[i], false);  
          

        int l, k;  
        for(int i = 0;i < num.length;i++)  

            if(map.containsKey(num[i]-1) || map.get(num[i])) continue;  
            map.put(num[i], true);  
            l = 0; k = num[i];  
            while (map.containsKey(k))  
                l++;  
                k++;  
              
            if(longest < l) longest = l;  

          
        return longest;  
      
  

其他方法here。

【讨论】:

我们可以通过这样做来优化这个算法吗:就像我们遍历查找 (map.containsKey(k)) 时,我们还使用另一个循环来减少 k,这样我们就可以找到左侧和右侧连续的 no 和 plus 我们可以将它们设置为 true,这样我们就不必再次遍历。【参考方案5】:

Grigor Gevorgyan 解决方案的 Haskell 实现,来自另一个在 question 被标记为重复之前没有机会发布的人......(只需更新哈希和迄今为止的最长范围,同时遍历列表)

import qualified Data.HashTable.IO as H
import Control.Monad.Random

f list = do 
  h <- H.new :: IO (H.BasicHashTable Int Int)
  g list (0,[]) h where
    g []     best h = return best
    g (x:xs) best h = do 
      m <- H.lookup h x
      case m of
        Just _     -> g xs best h
        otherwise  -> do 
          (xValue,newRange) <- test
          H.insert h x xValue
          g xs (maximum [best,newRange]) h
       where 
         test = do
           m1 <- H.lookup h (x-1)
           m2 <- H.lookup h (x+1)
           case m1 of
             Just x1 -> case m2 of
                          Just x2 -> do H.insert h (x-1) x2
                                        H.insert h (x+1) x1
                                        return (x,(x2 - x1 + 1,[x1,x2]))
                          Nothing -> do H.insert h (x-1) x
                                        return (x1,(x - x1 + 1,[x,x1]))
             Nothing -> case m2 of
                          Just x2 -> do H.insert h (x+1) x
                                        return (x2,(x2 - x + 1,[x,x2]))
                          Nothing -> do return (x,(1,[x]))

rnd :: (RandomGen g) => Rand g Int
rnd = getRandomR (-100,100)

main = do
  values <- evalRandIO (sequence (replicate (1000000) rnd))
  f values >>= print

输出:

*Main> main
(10,[40,49])
(5.30 secs, 1132898932 bytes)

【讨论】:

【参考方案6】:

我认为以下解决方案将在 O(n) 时间内使用 O(n) 空间。

首先将数组中的所有条目放入哈希表中。接下来,创建第二个哈希表来存储我们“访问过”的元素,该哈希表最初是空的。

现在,一次遍历一个元素数组。对于每个元素,检查该元素是否在访问集中。如果是这样,请跳过它。否则,从该元素向上计数。在每一步,检查当前数字是否在主哈希表中。如果是这样,继续前进并将当前值标记为已访问集的一部分。如果没有,请停止。接下来,重复此过程,但向下计数。这告诉我们包含此特定数组值的范围内的连续元素的数量。如果我们跟踪以这种方式找到的最大范围,我们将找到解决问题的方法。

该算法的运行时间复杂度为 O(n)。要看到这一点,请注意我们可以在 O(n) 时间的第一步中构建哈希表。接下来,当我们开始扫描数组以查找最大范围时,扫描的每个范围所花费的时间与该范围的长度成正比。由于范围长度的总和是原始数组中元素的数量,并且由于我们从不扫描同一范围两次(因为我们标记了我们访问的每个数字),所以第二步需要 O(n) 时间好吧,对于 O(n) 的净运行时间。

编辑:如果你很好奇,我有一个关于这个算法的 Java implementation,以及关于它为什么有效以及为什么它的更详细的分析正确的运行时间。它还探讨了一些在算法的初始描述中不明显的边缘情况(例如,如何处理整数溢出)。

希望这会有所帮助!

【讨论】:

但在最坏的情况下,即使是“检查元素是否在访问集中”,每个元素都需要 O(n)(如果所有元素都映射到同一个哈希)。此外,给定任何散列函数,在最坏的情况下,此检查永远不会比某些 w(1) (litte omega) 好,因此整体算法似乎不是 O(n)。我错过了什么吗? @dcn- 如果你使用动态完美哈希表或杜鹃哈希表,那么任何哈希查找都是最坏情况 O(1),所以你不必担心查找需要 O (n)。此外,您是正确的,哈希插入可能会退化到比 O(1) 更糟糕的程度,但是对于上述任何一种哈希系统,这种情况发生的概率都是指数级的; IIRC 对于任何常数 k,n 次插入动态完美哈希表的运行时间大于 kn 的概率是 1/2^k,因此这比线性慢得多的可能性非常小。 那么当输入为 0,9000000000000,1000000000000,8000000000000 时呢? @greim- 在这种情况下,算法返回长度为 1 的范围,因为没有两个连续的数字。 美丽的解释。但是这不能通过将两个哈希表合并为一个来完成吗?【参考方案7】:

实际上考虑到我们只是对整数进行排序,因此不需要比较排序,您可以使用 Radix- 或 BucketSort 对数组进行排序,然后遍历它。

简单,当然不是受访者想听到的,但还是正确的;)

【讨论】:

虽然在 O(n) 中不会发生排序 @user1767754 对于固定大小的整数,基数排序非常复杂。如果我们不处理固定大小的整数,据我所知,其他解决方案都不会是 O(N)。【参考方案8】:

解决方案可以使用BitSet:

public static void detect(int []ns) 
    BitSet bs = new BitSet();
    for (int i = 0; i < ns.length; i++) 
        bs.set(ns[i]);
    
    int begin = 0;
    int setpos = -1;
    while((setpos = bs.nextSetBit(begin)) >= 0) 
        begin = bs.nextClearBit(setpos);
        System.out.print("[" + setpos + " , " + (begin - 1) + "]");
    

示例 I/O:

detect(new int[] 2,10, 3, 12, 5,4, 11, 8, 7, 6, 15 );
[2,8] [10,12] [15,15]

【讨论】:

【参考方案9】:

上面的模板答案可以工作,但您不需要哈希表。根据您使用的算法,散列可能需要很长时间。你可以问面试官这个整数是否有最大数量,然后创建一个该大小的数组。称它为exist[],然后扫描arr并标记exist[i] = 1;然后遍历存在 [] 跟踪 4 个变量、当前最大范围的大小、当前最大范围的开始、当前范围的大小和当前范围的开始。当您看到存在[i] = 0 时,比较当前范围值与最大范围值,并根据需要更新最大范围值。

如果没有最大值,那么您可能必须使用散列方法。

【讨论】:

我认为它可以得到的最好结果是 O(maxValue - minValue)。我不明白这怎么可能是 O(n)。 (除非是 O(n),但我一直理解 O(n) 与数组的大小成正比。 如果你使用像动态完美散列或布谷鸟散列这样的散列系统,那么对于 n 次散列插入,运行时间很有可能是 O(n),你可以保证最坏情况 O(1 ) 查找时间。

以上是关于在数组中查找连续范围的主要内容,如果未能解决你的问题,请参考以下文章

从数组中识别和收集连续范围[重复]

如何查找R中的数字是不是连续?

在数组中查找具有最小差异和连续元素的 2 个子集

根据日期范围查找至少 2 个连续项目

查找数组中连续的数

谷歌面试:在给定的整数数组中找到所有连续的子序列,其总和在给定范围内。我们能比 O(n^2) 做得更好吗?