找到包含所有元素的最短子数组

时间:2011-03-27 06:08:58

标签: arrays algorithm

假设您有一组数字和另一组数字。您必须找到包含所有数字且最小复杂度的最短子数组。

数组可以有重复项,让我们假设数字集不重复。它没有被排序 - 子数组可以包含任何顺序的数字集。

例如:

Array: 1 2 5 8 7 6 2 6 5 3 8 5
Numbers: 5 7

然后最短的子阵列显然是Array[2:5](python表示法)。

另外,如果你想避免出于某种原因对数组进行排序(在线算法),你会怎么做?

6 个答案:

答案 0 :(得分:14)

线性时间解的证明

我将写右扩展表示将范围的右端点增加1,左收缩表示将范围的左端点增加1这个答案是Aasmund Eldhuset's answer的略微变化。这里的区别在于,一旦我们找到最小的j使得[0,j]包含所有有趣的数字,我们此后仅考虑包含所有有趣数字的范围。 (这有可能以这种方式解释Aasmund的答案,但也有可能将其解释为允许由于左收缩而丢失一个有趣的数字 - 一种尚未建立正确性的算法。)

基本思想是,对于每个位置j,我们将找到在位置j结束的最短满足范围,假设我们知道在位置j-1处结束的最短满足范围。

编辑:修复了基本案例中的故障。

基本情况:找到最小的j',使[0,j']包含所有有趣的数字。通过构造,可以没有范围[0,k <1。 j']包含所有有趣的数字,所以我们不需要进一步担心它们。现在找到最小最大i,使得[i,j']包含所有有趣的数字(即保持j'固定)。这是在j'位置结束的最小满意范围。

为了找到在任意位置j结束的最小满足范围,我们可以将在位置j-1结束的最小满足范围向右扩展1个位置。此范围必然也包含所有有趣的数字,但它可能不是最小长度。 事实上我们已经知道这是一个令人满意的范围意味着我们不必担心向左“向后”扩展范围,因为这只能在其最小长度上增加范围(即制定解决方案)更糟糕的。我们需要考虑的唯一操作是左收缩,它们保留了包含所有有趣数字的属性。因此,当此属性成立时,范围的左端点应尽可能地前进。当不能再进行左侧收缩时,我们将最小长度满足范围结束于j(因为进一步的左侧收缩显然不能使范围再次满足)并且我们已经完成了。

由于我们对每个最右边的位置j执行此操作,我们可以在最右边的位置采用最小长度范围来找到总体最小值。这可以使用嵌套循环来完成,其中j在每个外循环周期中前进。显然j提前1 n次。因为在任何时间点我们只需要j的前一个值的最佳范围的最左边位置,我们可以将其存储在i中,并在我们去时更新它。我从0开始,始终&lt; = j&lt; = n,并且只向上前进1,这意味着它最多可以前进n次。 i和j都最多前进n次,这意味着算法是线性时间。

在下面的伪代码中,我将两个阶段合并为一个循环。如果我们已达到拥有所有有趣数字的阶段,我们只会尝试收缩左侧:

# x[0..m-1] is the array of interesting numbers.
# Load them into a hash/dictionary:
For i from 0 to m-1:
    isInteresting[x[i]] = 1

i = 0
nDistinctInteresting = 0
minRange = infinity
For j from 0 to n-1:
    If count[a[j]] == 0 and isInteresting[a[j]]:
        nDistinctInteresting++
    count[a[j]]++

    If nDistinctInteresting == m:
        # We are in phase 2: contract the left side as far as possible
        While count[a[i]] > 1 or not isInteresting[a[i]]:
            count[a[i]]--
            i++

        If j - i < minRange:
            (minI, minJ) = (i, j)

count[]isInteresting[]是哈希/词典(如果涉及的数字很小,则为普通数组)。

答案 1 :(得分:5)

这听起来像是一个非常适合滑动窗口方法的问题:维护一个逐渐扩展和收缩的窗口(一个子阵列),并使用一个hashmap,用于跟踪窗口中每个“有趣”数字出现的次数。例如。从一个空窗口开始,然后将其展开为仅包含元素0,然后是元素0-1,然后是0-2,0-3,依此类推,添加后续元素(并使用hashmap跟踪存在哪些数字)在窗口中)。当hashmap告诉您窗口中存在所有有趣的数字时,您可以开始收缩它:例如0-5,1-5,2-5等,直到你发现窗口不再包含所有有趣的数字。然后,您可以再次开始在右侧扩展它,依此类推。我完全(但不完全)确定这对您的问题有效,并且可以实现以线性时间运行。

答案 2 :(得分:0)

假设数组有n个元素,并且set有m个元素

Sort the array, noting the reverse index (position in the original array)
// O (n log n) time

for each element in given set
   find it in the array
// O (m log n) time  - log n for binary serch, m times

keep track of the minimum and maximum index for each found element

min - max定义你的范围

总时间复杂度:O((m + n)log n)

答案 3 :(得分:0)

这个解决方案肯定不会像上面的一些伪代码所建议的那样在O(n)时间内运行,但是它是真正的(Python)代码解决了这个问题并且我的估计在O(n ^ 2)中运行:< / p>

def small_sub(A, B):
    len_A = len(A)
    len_B = len(B)

    sub_A = []
    sub_size = -1
    dict_b = {}

    for elem in B:
        if elem in dict_b:
            dict_b[elem] += 1
        else:
            dict_b.update({elem: 1})

    for i in range(0, len_A - len_B + 1):
        if A[i] in dict_b:
            temp_size, temp_sub = find_sub(A[i:], dict_b.copy())

            if (sub_size == -1 or (temp_size != -1 and temp_size < sub_size)):
                sub_A = temp_sub
                sub_size = temp_size

    return sub_size, sub_A

def find_sub(A, dict_b):
    index = 0
    for i in A:
        if len(dict_b) == 0:
            break

        if i in dict_b:
            dict_b[i] -= 1

            if dict_b[i] <= 0:
                del(dict_b[i])

        index += 1

    if len(dict_b) > 0:
        return -1, {}
    else:
        return index, A[0:index]

答案 4 :(得分:0)

这是我使用collections.Counter对象在线性时间内解决此问题的方法

from collections import Counter

def smallest_subsequence(stream, search):
    if not search:
        return []  # the shortest subsequence containing nothing is nothing

    stream_counts = Counter(stream)
    search_counts = Counter(search)

    minimal_subsequence = None

    start = 0
    end = 0
    subsequence_counts = Counter()

    while True:
        # while subsequence_counts doesn't have enough elements to cancel out every
        # element in search_counts, take the next element from search
        while search_counts - subsequence_counts:
            if end == len(stream):  # if we've reached the end of the list, we're done
                return minimal_subsequence
            subsequence_counts[stream[end]] += 1
            end += 1

        # while subsequence_counts has enough elements to cover search_counts, keep
        # removing from the start of the sequence
        while not search_counts - subsequence_counts:
            if minimal_subsequence is None or (end - start) < len(minimal_subsequence):
                minimal_subsequence = stream[start:end]
            subsequence_counts[stream[start]] -= 1
            start += 1

print(smallest_subsequence([1, 2, 5, 8, 7, 6, 2, 6, 5, 3, 8, 5], [5, 7]))
# [5, 8, 7]

答案 5 :(得分:0)

Java解决方案

    List<String> paragraph = Arrays.asList("a", "c", "d", "m", "b", "a");
    Set<String> keywords = Arrays.asList("a","b");

    Subarray result = new Subarray(-1,-1);

    Map<String, Integer> keyWordFreq = new HashMap<>();

    int numKeywords = keywords.size();

    // slide the window to contain the all the keywords**
    // starting with [0,0]
    for (int left = 0, right = 0 ; right < paragraph.size() ; right++){

      // expand right to contain all the keywords
      String currRight = paragraph.get(right);

      if (keywords.contains(currRight)){
        keyWordFreq.put(currRight, keyWordFreq.get(currRight) == null ? 1 : keyWordFreq.get(currRight) + 1);
      }

      // loop enters when all the keywords are present in the current window
      // contract left until the all the keywords are still present
      while (keyWordFreq.size() == numKeywords){
        String currLeft = paragraph.get(left);

        if (keywords.contains(currLeft)){

          // remove from the map if its the last available so that loop exists
          if (keyWordFreq.get(currLeft).equals(1)){

            // now check if current sub array is the smallest
            if((result.start == -1 && result.end == -1) || (right - left) < (result.end - result.start)){
              result = new Subarray(left, right);
            }
            keyWordFreq.remove(currLeft);
          }else {

            // else reduce the frequcency
            keyWordFreq.put(currLeft, keyWordFreq.get(currLeft) - 1);
          }
        }
        left++;
      }

    }

    return result;
  }