【问题标题】:Find the smallest window of input array that contains all the elements of query array找到包含查询数组所有元素的输入数组的最小窗口
【发布时间】:2011-04-14 06:17:53
【问题描述】:

问题:给定一个大小为 n 的整数的输入数组和一个大小为 k 的整数的查询数组,找到包含查询数组的所有元素且顺序相同的输入数组的最小窗口。

我尝试过以下方法。

        int[] inputArray = new int[] { 2, 5, 2, 8, 0, 1, 4, 7 };
        int[] queryArray = new int[] { 2, 1, 7 };

将查找所有查询数组元素在 inputArray 中的位置。

public static void SmallestWindow(int[] inputArray, int[] queryArray)
    {
        Dictionary<int, HashSet<int>> dict = new Dictionary<int, HashSet<int>>();

        int index = 0;
        foreach (int i in queryArray)
        {
            HashSet<int> hash = new HashSet<int>();
            foreach (int j in inputArray)
            {
                index++;
                if (i == j)
                    hash.Add(index); 
            }
            dict.Add(i, hash);
            index = 0;
        }
      // Need to perform action in above dictionary.??
    }

我有以下字典

  1. int 2--> 位置 {1, 3}
  2. int 1 --> 位置 {6}
  3. int 7 --> 位置 {8}

现在我想执行以下步骤来找出最小窗口

  1. 比较 int 2 位置和 int 1 位置。 As (6-3)

  2. 会像上面一样比较int 1和int 7的位置。

我无法理解如何比较字典的两个连续值。请帮忙。

【问题讨论】:

  • 如果queryArray{ 2, 8, 0 } 预期输出是什么?指数[0-4] 或指数[2-4]?
  • @Ani - 我认为应该是[2-4],这是最短的。
  • 是的,应该是 [2-4] 因为这是最小的窗口
  • queryArray 可以多次包含相同的值吗?

标签: c# algorithm data-structures collections


【解决方案1】:

算法:
对于查询数组中的每个元素,存储在映射 M (V → (I,P)) 中,V 是元素,I 是输入数组的索引,P 是查询数组中的位置。 (对于某个 P​​,输入数组的索引是最大的,这样 query[0..P] 是 input[I..curr] 的子序列)

遍历数组。
如果该值是查询数组中的第一项:将当前索引存储为 I。
Else:将前一个元素的索引值存储在查询数组中,例如M[currVal].I = M[query[M[currVal].P-1]].I.
如果值是最后一项:检查 [I..curr] 是否是新的最佳值。

复杂性
其复杂度为 O(N),其中 N 是输入数组的大小。

注意
此代码要求查询数组中没有重复的元素。为了解决这个问题,我们可以使用映射 M (V → listOf((I,P)))。这是 O(NhC(Q)),其中 hC(Q) 是查询数组的模式计数。
更好的是使用 M (V → listOf((linkedList(I), P)))。在查询数组中连续出现重复元素的地方,我们使用链表。然后更新这些值变成 O(1)。复杂度为 O(N
hC(D(Q))),其中 D(Q) 是 Q 与连续项合并。

实施
示例 java 实现可用 here。这对查询数组中的重复元素不起作用,错误检查等也不起作用。

【讨论】:

  • 不错的解决方案。我很难理解你在这里做什么:currPair.I = M.get(query[currPair.P - 1]).I 和这个 - if (currPair.P == query.length - 1 &amp;&amp; currPair.I != -1 &amp;&amp; i - currPair.I &lt; best[2]) -- 你能用简单的英语解释一下吗? :)
  • 此外,您的解决方案对于此输入失败:input: 2 3 4 5 2 query: 2 4 5 - 答案应该是 2..4,但你的解决方案给了0..3
【解决方案2】:

我不知道使用 HashSetDictionary 将如何帮助您。如果我遇到这个问题,我会采取完全不同的方式。

一种方法(不是最有效的方法)如下所示。此代码假设queryArray 至少包含两项。

int FindInArray(int[] a, int start, int value)
{
    for (int i = start; i < a.Length; ++i)
    {
        if (a[i] == value)
            return i;
    }
    return -1;
}

struct Pair
{
    int first;
    int last;
}

List<Pair> foundPairs = new List<Pair>();

int startPos = 0;
bool found = true;
while (found)
{
    found = false;
    // find next occurrence of queryArray[0] in inputArray
    startPos = FindInArray(inputArray, startPos, queryArray[0]);
    if (startPos == -1)
    {
        // no more occurrences of the first item
        break;
    }
    Pair p = new Pair();
    p.first = startPos;
    ++startPos;
    int nextPos = startPos;
    // now find occurrences of remaining items
    for (int i = 1; i < queryArray.Length; ++i)
    {
        nextPos = FindInArray(inputArray, nextPos, queryArray[i]);
        if (nextPos == -1)
        {
            break;  // didn't find it
        }
        else
        {
            p.last = nextPos++;
            found = (i == queryArray.Length-1);
        }
    }
    if (found)
    {
        foundPairs.Add(p);
    }
}

// At this point, the foundPairs list contains the (start, end) of all
// sublists that contain the items in order.
// You can then iterate through that list, subtract (last-first), and take
// the item that has the smallest value.  That will be the shortest sublist
// that matches the criteria.

通过一些工作,这可以提高效率。例如,如果 'queryArray' 包含 [1, 2, 3]inputArray 包含 [1, 7, 4, 9, 1, 3, 6, 4, 1, 8, 2, 3],则上面的代码将找到三个匹配项(从位置 0、4 和 8 开始)。稍微聪明一点的代码可以确定,当找到位置 4 的 1 时,因为在它之前没有找到 2,所以从第一个位置开始的任何序列都将比从位置 4 开始的序列长,因此短- 循环第一个序列并从新位置重新开始。不过,这会使代码有点复杂。

【讨论】:

    【解决方案3】:

    您想要的不是 HashSet,而是(排序的)树或数组作为字典中的值;该字典包含从您在输入数组中找到的值到该值出现的(排序的)索引列表的映射。

    然后你执行以下操作

    • 查找查询中的第一个条目。选择它出现的最低索引。
    • 查找第二个条目;选择大于第一个索引的最低条目。
    • 查找第三个;选择大于第二个的最小的。 (等等)
    • 当您到达查询中的最后一个条目时,(1 + 最后一个索引 - 第一个索引)是最小匹配的大小。
    • 现在选择第一个查询的第二个索引,重复等。
    • 从任何起始索引中选择找到的最小匹配项。

    (请注意,“最低条目更大”是排序树提供的操作,或者可以通过对排序数组的二进制搜索找到。)

    其复杂度约为O(M*n*log n),其中M 是查询的长度,n 是输入数组中出现给定值的平均索引数。您可以通过选择最不常出现的查询数组值作为起点并从那里上下移动来修改策略;如果这些条目中有k (k n),那么复杂度是O(M*k*log n)

    【讨论】:

      【解决方案4】:

      在你得到 inputArray 中的所有位置(索引)之后:

      2 --> position {0,2}   // note: I change them to 0-based array
      1 --> position {5,6}  // I suppose it's {5,6} to make it more complex, in your code it's only {5}
      7 --> position {7}
      

      我使用递归来获取所有可能的路径。 [0->5->7] [0->6->7] [2->5->7] [2->6->7]。总共有 2*2*1=4 条可能的路径。显然,拥有Min(Last-First) 的是最短路径(最小窗口),路径中间的那些数字无关紧要。代码来了。

       struct Pair
       {
           public int Number;  // the number in queryArray
           public int[] Indexes;  // the positions of the number
       }
       static List<int[]> results = new List<int[]>(); //store all possible paths
       static Stack<int> currResult = new Stack<int>(); // the container of current path
       static int[] inputArray, queryArray; 
       static Pair[] pairs;
      

      数据结构之后,这里是Main

      inputArray = new int[] { 2, 7, 1, 5, 2, 8, 0, 1, 4, 7 }; //my test case
      queryArray = new int[] { 2, 1, 7 };
      pairs = (from n in queryArray
            select new Pair { Number = n, Indexes = inputArray.FindAllIndexes(i => i == n) }).ToArray();
      Go(0);
      

      FindAllIndexes 是帮助查找所有索引的扩展方法。

      public static int[] FindAllIndexes<T>(this IEnumerable<T> source, Func<T,bool> predicate)
      {
           //do necessary check here, then
           Queue<int> indexes = new Queue<int>();
           for (int i = 0;i<source.Count();i++)
                 if (predicate(source.ElementAt(i))) indexes.Enqueue(i);
           return indexes.ToArray();
      }
      

      递归方法:

      static void Go(int depth)
      {
          if (depth == pairs.Length)
          {
              results.Add(currResult.Reverse().ToArray());
          }
          else
          {
              var indexes = pairs[depth].Indexes;
              for (int i = 0; i < indexes.Length; i++)
              {
                  if (depth == 0 || indexes[i] > currResult.Last())
                  {
                      currResult.Push(indexes[i]);
                      Go(depth + 1);
                      currResult.Pop();
                  }
              }
          }
      }
      

      最后,results 的循环可以找到Min(Last-First) 结果(最短窗口)。

      【讨论】:

        【解决方案5】:

        算法:

        1. 获取输入数组的所有索引 所有 queryArray 值
        2. 按索引升序排列
        3. 使用每个索引 (x) 作为开始 点找到第一个更高的索引 (y) 使得该段 inputArray[x-y] 包含所有 查询数组值
        4. 仅按顺序保留具有 queryArray 项的那些段
        5. 按段的长度排序, 升序

        c#实现:

        首先获取所有queryArray值的inputArray中的所有索引,并按索引升序排列。

        public static int[] SmallestWindow(int[] inputArray, int[] queryArray)
        {
            var indexed = queryArray
                .SelectMany(x => inputArray
                                     .Select((y, i) => new
                                         {
                                             Value = y,
                                             Index = i
                                         })
                                     .Where(y => y.Value == x))
                .OrderBy(x => x.Index)
                .ToList();
        

        接下来,使用每个索引 (x) 作为起点,找到第一个更高的索引 (y),使得段 inputArray[x-y] 包含所有 queryArray 值。

            var segments = indexed
                .Select(x =>
                    {
                        var unique = new HashSet<int>();
                        return new
                            {
                                Item = x,
                                Followers = indexed
                                    .Where(y => y.Index >= x.Index)
                                    .TakeWhile(y => unique.Count != queryArray.Length)
                                    .Select(y =>
                                        {
                                            unique.Add(y.Value);
                                            return y;
                                        })
                                    .ToList(),
                                IsComplete = unique.Count == queryArray.Length
                            };
                    })
                .Where(x => x.IsComplete);
        

        现在只保留那些有 queryArray 项的段。

            var queryIndexed = segments
                .Select(x => x.Followers.Select(y => new
                    {
                        QIndex = Array.IndexOf(queryArray, y.Value),
                        y.Index,
                        y.Value
                    }).ToArray());
        
            var queryOrdered = queryIndexed
                .Where(item =>
                    {
                        var qindex = item.Select(x => x.QIndex).ToList();
                        bool changed;
                        do
                        {
                            changed = false;
                            for (int i = 1; i < qindex.Count; i++)
                            {
                                if (qindex[i] <= qindex[i - 1])
                                {
                                    qindex.RemoveAt(i);
                                    changed = true;
                                }
                            }
                        } while (changed);
                        return qindex.Count == queryArray.Length;
                    });
        

        最后,按段的长度升序排列。结果中的第一段是 inputArray 的最小窗口,其中包含按 queryArray 顺序排列的所有 queryArray 值。

            var result = queryOrdered
                .Select(x => new[]
                    {
                        x.First().Index,
                        x.Last().Index
                    })
                .OrderBy(x => x[1] - x[0]);
        
            var best = result.FirstOrDefault();
            return best;
        }
        

        测试一下

        public void Test()
        {
            var inputArray = new[] { 2, 1, 5, 6, 8, 1, 8, 6, 2, 9, 2, 9, 1, 2 };
            var queryArray = new[] { 6, 1, 2 };
        
            var result = SmallestWindow(inputArray, queryArray);
        
            if (result == null)
            {
                Console.WriteLine("no matching window");
            }
            else
            {
                Console.WriteLine("Smallest window is indexes " + result[0] + " to " + result[1]);
            }
        }
        

        输出:

        Smallest window is indexes 3 to 8
        

        【讨论】:

          【解决方案6】:

          感谢大家的投入。我已经稍微更改了我的代码并发现它可以正常工作。虽然它可能不是很有效,但我很乐意用我的头脑来解决:)。请提供您的反馈

          这是我的 Pair 类,具有数字和位置作为变量

              public class Pair
              {
              public int Number;
              public List<int> Position;
              }
          

          这是一个返回所有 Pairs 列表的方法。

               public static Pair[]  GetIndex(int[] inputArray, int[] query)
                {
                  Pair[] pairList = new Pair[query.Length]; 
                  int pairIndex = 0;
                  foreach (int i in query)
                  {
                      Pair pair = new Pair();
                      int index = 0;
                      pair.Position = new List<int>();
                      foreach (int j in inputArray)
                      {                    
                          if (i == j)
                          {
                              pair.Position.Add(index);
                          }
                          index++;
                      }
                      pair.Number = i;
                      pairList[pairIndex] = pair;
                      pairIndex++;
                  }
                  return pairList;
              }
          

          这是Main方法中的代码行

                  Pair[] pairs = NewCollection.GetIndex(array, intQuery);
          
                  List<int> minWindow = new List<int>();
                  for (int i = 0; i <pairs.Length - 1; i++)
                  {
                      List<int> first = pairs[i].Position;
                      List<int> second = pairs[i + 1].Position;
                      int? temp = null;
                      int? temp1 = null;
                      foreach(int m in first)
                      {
                          foreach (int n in second)
                          {
                              if (n > m)
                              {
                                  temp = m;
                                  temp1 = n;
                              }                        
                          }                    
                      }
                      if (temp.HasValue && temp1.HasValue)
                      {
                          if (!minWindow.Contains((int)temp))
                              minWindow.Add((int)temp);
                          if (!minWindow.Contains((int)temp1))
                              minWindow.Add((int)temp1);
                      }
                      else
                      {
                          Console.WriteLine(" Bad Query array");
                          minWindow.Clear();
                          break;                    
                      }
                  }
          
                  if(minWindow.Count > 0)
                  {
                   Console.WriteLine("Minimum Window is :");
                   foreach(int i in minWindow)
                   {
                       Console.WriteLine(i + " ");
                   }
                  }
          

          【讨论】:

            【解决方案7】:

            值得注意的是,这个问题与最长公共子序列问题有关,因此在出现重复的一般情况下,提出运行时间优于 O(n^2) 的算法将具有挑战性。

            【讨论】:

              【解决方案8】:

              以防万一有人对使用 O(nlog(k)) 实现 C++ 感兴趣

                  void findMinWindow(const vector<int>& input, const vector<int>& query) {
                       map<int, int> qtree;
                       for(vector<int>::const_iterator itr=query.begin(); itr!=query.end(); itr++) {
                          qtree[*itr] = 0;
                       }
              
                       int first_ptr=0;
                       int begin_ptr=0;
              
                       int index1 = 0;
                       int queptr = 0;
              
                       int flip = 0;
              
                       while(true) {
                           //check if value is in query
                           if(qtree.find(input[index1]) != qtree.end()) {
                              int x = qtree[input[index1]];
                              if(0 == x) {
                                flip++;
                              }
                              qtree[input[index1]] = ++x;
                            }
              
                            //remove all nodes that are not required and
                            //yet satisfy the all query condition.
                            while(query.size() == flip) {
                              //done nothing more
                              if(queptr == input.size()) {
                                break;
                              }
              
                              //check if queptr is pointing to node in the query
                              if(qtree.find(input[queptr]) != qtree.end()) {
                                int y = qtree[input[queptr]];
                                //more nodes and the queue is pointing to deleteable node
                                //condense the nodes
                                if(y > 1) {
                                  qtree[input[queptr]] = --y;
                                  queptr++;
                                } else {
                                  //cant condense more just keep that memory
                                  if((!first_ptr && !begin_ptr) ||
                                      ((first_ptr-begin_ptr)>(index1-queptr))) {
                                    first_ptr=index1;
                                    begin_ptr=queptr;
                                  }
                                  break;
                                }
                              } else {
                                queptr++;
                              }
                            }
              
                           index1++;
              
                           if(index1==input.size()) {
                              break;
                           }
                       }
                       cout<<"["<<begin_ptr<<" - "<<first_ptr<<"]"<<endl;
                  }
              

              这里是调用它的主要部分。

                  #include <iostream>
                  #include <vector>
                  #include <map>
              
                  using namespace std;
              
                  int main() {
                      vector<int> input;
                      input.push_back(2);
                      input.push_back(5);
                      input.push_back(2);
                      input.push_back(8);
                      input.push_back(0);
                      input.push_back(1);
                      input.push_back(4);
                      input.push_back(7);
              
                      vector<int> query1;
                      query1.push_back(2);
                      query1.push_back(8);
                      query1.push_back(0);
              
                      vector<int> query2;
                      query2.push_back(2);
                      query2.push_back(1);
                      query2.push_back(7);
              
                      vector<int> query3;
                      query3.push_back(1);
                      query3.push_back(4);
              
                      findMinWindow(input, query1);
                      findMinWindow(input, query2);
                      findMinWindow(input, query3);
                  }
              

              【讨论】:

                猜你喜欢
                • 2019-01-22
                • 1970-01-01
                • 2019-11-17
                • 1970-01-01
                • 2016-09-08
                • 1970-01-01
                • 2017-01-17
                • 1970-01-01
                • 2015-04-18
                相关资源
                最近更新 更多