找到包含查询数组的所有元素的输入数组的最小窗口

时间:2010-09-19 05:16:53

标签: c# algorithm data-structures collections

问题:给定一个大小为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 - &gt;职位{1,3}
  2. int 1 - &gt;职位{6}
  3. int 7 - &gt;位置{8}
  4. 现在我想执行以下步骤来找到最小窗口

    1. 将int 2位置与int 1位置进行比较。如(6-3)&lt; (6-1)..所以我将在hashmap中存储3,6。

    2. 比较上面的int 1和int 7的位置。

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

8 个答案:

答案 0 :(得分:4)

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

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

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

<强> N.B。
此代码期望查询数组中不重复任何元素。为了满足这个要求,我们可以使用地图M(V→listOf((I,P)))。这是O(N hC(Q)),其中hC(Q)是查询数组的模式计数。

更好的是使用M(V→listOf((linkedList(I),P)))。如果重复元素在查询数组中连续出现,我们使用链表。然后更新这些值将变为O(1)。然后复杂度为O(N
hC(D(Q))),其中D(Q)为Q,连续项合并。

<强>实施
示例Java实现可用 here 。这不适用于查询数组中的重复元素,也不适用于错误检查等。

答案 1 :(得分:0)

我不知道如何使用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开始的序列长。 ,因此将第一个序列短路并在新位置重新开始。不过,这会使代码变得复杂。

答案 2 :(得分:0)

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

然后执行以下操作

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

(请注意,“最大条目大”是一个由排序树提供的操作,或者可以通过二元搜索在排序数组中找到。)

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

答案 3 :(得分:0)

获得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)结果(最短窗口)。

答案 4 :(得分:0)

算法:

  1. 将所有索引放入inputArray 所有queryArray值
  2. 按索引递增顺序
  3. 使用每个索引(x)作为起始 点找到第一个更高的指数 (y)使该段 inputArray [x-y]包含所有 queryArray values
  4. 仅保留那些按顺序包含queryArray项目的细分
  5. 按长度排序细分, 升
  6. 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),使得segment 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
    

答案 5 :(得分:0)

感谢大家的投入。我已经改变了我的代码并发现它正常工作。虽然它可能不是很有效但我很高兴用我的脑袋来解决:)。请提供反馈

这是我的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 + " ");
         }
        }

答案 6 :(得分:0)

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

答案 7 :(得分:0)

以防有人对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);
    }