【问题标题】:Using Binary Search with sorted Array with duplicates [duplicate]使用带有重复的排序数组的二分搜索[重复]
【发布时间】:2012-10-23 05:58:43
【问题描述】:

我的任务是创建一个方法,该方法将打印在排序数组中找到值 x 的所有索引。

我知道,如果我们只是从 0 到 N(数组长度)扫描数组,最坏的情况下运行时间为 O(n)。由于将传递给该方法的数组将被排序,我假设我可以利用使用二进制搜索,因为这将是 O(log n)。但是,这仅适用于数组具有唯一值的情况。因为二进制搜索将在第一次“找到”特定值之后完成。我正在考虑进行二进制搜索以在已排序的数组中找到 x,然后检查该索引之前和之后的所有值,但是如果数组包含所有 x 值,它似乎不会好得多。

我想我要问的是,有没有比 O(n) 更好的方法来找到排序数组中特定值的所有索引?

public void PrintIndicesForValue42(int[] sortedArrayOfInts)
{
    // search through the sortedArrayOfInts

    // print all indices where we find the number 42. 
}

例如:sortedArray = { 1, 13, 42, 42, 42, 77, 78 } 将打印:“42 was found at Indices: 2, 3, 4”

【问题讨论】:

  • 您的解决方案听起来不错,如果数组包含所有 x 值,无论如何您都必须查看所有这些值
  • @JonSkeet - 对不起那个错字。我已将数组更新为已排序的数组。

标签: java duplicates binary-search


【解决方案1】:

好吧,如果您确实有一个排序数组,您可以进行二进制搜索,直到找到您要查找的索引之一,然后从那里,其余的应该很容易找到,因为它们都在下一个彼此。

一旦你找到了你的第一个实例,你就会去寻找它之前的所有实例,然后是它之后的所有实例。

使用该方法,您应该大致得到 O(lg(n)+k),其中 k 是您要搜索的值的出现次数。

编辑:

而且,不,您将永远无法在不到 O(k) 的时间内访问所有 k 值。


第二次编辑:让我觉得我实际上在贡献一些有用的东西:

除了搜索 X 的第一次和最后一次出现之外,您还可以对第一次出现进行二进制搜索,对最后一次出现进行二进制搜索。这将导致 O(lg(n)) 总数。一旦你这样做了,你就会知道所有的索引之间也包含 X(假设它是排序的)

您可以通过搜索检查值是否等于 xAND 检查值是否在左侧(或右侧,具体取决于您是否正在查看)对于第一次出现或最后一次出现)等于 x

【讨论】:

  • 问题中已经说明了该解决方案,对吧?
  • @akaIDIOT 不,他在问题中提出的解决方案是从索引 0 开始的线性扫描,直到他找到他正在寻找的值之后的最后一个索引,即 O(n) 并且是线性的搜索,而不是二分搜索。这个答案中的解决方案是二分查找,然后是线性扫描,但线性扫描只发生在数组的一个子部分上。
  • @Brian 来自问题:“我正在考虑进行二进制搜索以在排序数组中查找 x,然后检查该索引之前和之后的所有值,但是那么如果数组包含所有 x 值,它似乎不会好得多。” -- 听起来和你发布的完全一样。
  • @akaIDIOT 实际上在二读之后是的,他确实有这个问题。我现在将进行编辑以反映这一点。
  • +1 因为能够在 O(lg(n)) 中做到这一点。我应该多考虑一下。
【解决方案2】:

如果您不需要使用二分搜索,Hashmap 可能会起作用。

创建一个 HashMap,其中 Key 是值本身,然后 value 是一个索引数组,其中该值在数组中。循环遍历您的数组,为每个值更新 HashMap 中的每个数组。

每个值的索引查找时间约为 O(1),创建地图本身约为 O(n)。

【讨论】:

  • 会起作用,但如果您已经有一个排序数组,这听起来有点矫枉过正。
  • 是的。 SamIam 的解决方案还是比较好,只要排序好了。
  • @jlordo 如何对数组进行排序以帮助查找重复项?
  • @Shark 对数组进行排序后,所有重复项都彼此相邻;)因此,如果您找到一个,您只需左右查看即可找到所有其他的。
  • @jlordo 但你怎么知道要寻找哪个值?如果您最初知道重复值,是的,您会找到一个并左右查看,直到找到另一个值。
【解决方案3】:
public void printCopies(int[] array)
{
    HashMap<Integer, Integer> memberMap = new HashMap<Integer, Integer>();
    for(int i = 0; i < array.size; i++)
       if(!memberMap.contains(array[i]))
           memberMap.put(array[i], 1);
       else
       {
           int temp = memberMap.get(array[i]); //get the number of occurances
           memberMap.put(array[i], ++temp); //increment his occurance
       }

    //check keys which occured more than once
    //dump them in a ArrayList
    //return this ArrayList
 }

或者,您可以将它们的索引放入数组列表中,然后将其放入映射而不是计数中,而不是计算出现次数。

   HashMap<Integer, ArrayList<Integer>> 
   //the integer is the value, the arraylist a list of their indices

public void printCopies(int[] array)
{
    HashMap<Integer, ArrayList<Integer>> memberMap = new HashMap<Integer, ArrayList<Integer>>();
    for(int i = 0; i < array.size; i++)
       if(!memberMap.contains(array[i]))
       {
           ArrayList temp = new ArrayList();
           temp.add(i);
           memberMap.put(array[i], temp);
       }
       else
       {
           ArrayList temp = memberMap.get(array[i]); //get the lsit of indices
           temp.add(i);
           memberMap.put(array[i], temp); //update the index list
       }

    //check keys which return lists with length > 1
    //handle the result any way you want
 }

嘿,我想这必须发布。

 int predefinedDuplicate = //value here;
 int index = Arrays.binarySearch(array, predefinedDuplicate);
 int leftIndex, rightIndex;
 //search left
 for(leftIndex = index; array[leftIndex] == array[index]; leftIndex--); //let it run thru it
 //leftIndex is now the first different element to the left of this duplicate number string
 for(rightIndex = index; array[rightIndex] == array[index]; rightIndex++); //let it run thru it

 //right index contains the first different element to the right of the string
 //you can arraycopy this [leftIndex+1, rightIndex-1] string or just print it
 for(int i = leftIndex+1; i<rightIndex; i++)
 System.out.println(array[i] + "\t");

【讨论】:

  • 这会给你数字的出现次数,而不是数字的位置,这是他想要的。
  • @Michael 查看编辑。随时删除反对票;)
  • 您的代码仍然存在大量问题。你的基本想法是对的,但我已经在我的回答中提出了这一点。
  • @Michael 一个“TON”?请指出一些,我在这里没有发现任何问题......:/它将每个值映射到它的索引列表。查找 'x' 的出现只是 return memberMap.get(x); 并打印它。
  • 这是一种使用局部变量的 void 方法。对它的任何调用都将无效...
【解决方案4】:
public void PrintIndicesForValue42(int[] sortedArrayOfInts) {
    int index_occurrence_of_42 = left = right = binarySearch(sortedArrayOfInts, 42);
    while (left - 1 >= 0) {
        if (sortedArrayOfInts[left-1] == 42)
            left--;
    }
    while (right + 1 < sortedArrayOfInts.length) {
        if (sortedArrayOfInts[right+1] == 42)
            right++;
    }
    System.out.println("Indices are from: " + left + " to " + right);
}

这将在 O(log(n) + #occurrences) 中运行 阅读并理解代码。很简单。

【讨论】:

  • 我假设如果数组中的每个元素都是 42,这将是 O(log n + n) = O(n)。但是,这将是一个非常有限的最坏情况。话虽如此,可以安全地假设在更“平均”的情况下它将是 O(log n + k),其中 k 是某个恒定的出现次数,即 O(log n)?只是想知道,因为这是我最初计划的,但由于它基于可变数量的可能重复项,我很好奇是否有一种算法可以保证比 O(n) 更好的东西。但是看答案,好像不是这样的。
  • 你没有跳出那些 while 循环,所以如果你找到第一个非 42 数字,循环不会停止。
【解决方案5】:

你会得到 O(lg n) 的结果

public static void PrintIndicesForValue(int[] numbers, int target) {
    if (numbers == null)
        return;

    int low = 0, high = numbers.length - 1;
    // get the start index of target number
    int startIndex = -1;
    while (low <= high) {
        int mid = (high - low) / 2 + low;
        if (numbers[mid] > target) {
            high = mid - 1;
        } else if (numbers[mid] == target) {
            startIndex = mid;
            high = mid - 1;
        } else
            low = mid + 1;
    }

    // get the end index of target number
    int endIndex = -1;
    low = 0;
    high = numbers.length - 1;
    while (low <= high) {
        int mid = (high - low) / 2 + low;
        if (numbers[mid] > target) {
            high = mid - 1;
        } else if (numbers[mid] == target) {
            endIndex = mid;
            low = mid + 1;
        } else
            low = mid + 1;
    }

    if (startIndex != -1 && endIndex != -1){
        for(int i=0; i+startIndex<=endIndex;i++){
            if(i>0)
                System.out.print(',');
            System.out.print(i+startIndex);
        }
    }
}

【讨论】:

    【解决方案6】:
    Find_Key(int arr[], int size, int key){
    int begin = 0;
    int end = size - 1;
    int mid = end / 2;
    int res = INT_MIN;
    
    while (begin != mid)
    {
        if (arr[mid] < key)
            begin = mid;
        else
        {
            end = mid;
            if(arr[mid] == key)
                res = mid;
        }
        mid = (end + begin )/2;
    }
    return res;
    }
    

    假设整数数组是升序排列的;返回键出现的第一个索引或 INT_MIN。运行时间为 O(lg n)。

    【讨论】:

    • 这只有在begin-1(不是0)和endsize(不是size - 1)开始时才有效。此外,您还必须处理空数组。
    【解决方案7】:

    下面是返回搜索键在给定排序数组中展开的范围的 java 代码:

    public static int doBinarySearchRec(int[] array, int start, int end, int n) {
        if (start > end) {
            return -1;
        }
        int mid = start + (end - start) / 2;
    
        if (n == array[mid]) {
            return mid;
        } else if (n < array[mid]) {
            return doBinarySearchRec(array, start, mid - 1, n);
        } else {
            return doBinarySearchRec(array, mid + 1, end, n);
        }
    }
    
    /**
     * Given a sorted array with duplicates and a number, find the range in the
     * form of (startIndex, endIndex) of that number. For example,
     * 
     * find_range({0 2 3 3 3 10 10}, 3) should return (2,4). find_range({0 2 3 3
     * 3 10 10}, 6) should return (-1,-1). The array and the number of
     * duplicates can be large.
     * 
     */
    public static int[] binarySearchArrayWithDup(int[] array, int n) {
    
        if (null == array) {
            return null;
        }
        int firstMatch = doBinarySearchRec(array, 0, array.length - 1, n);
        int[] resultArray = { -1, -1 };
        if (firstMatch == -1) {
            return resultArray;
        }
        int leftMost = firstMatch;
        int rightMost = firstMatch;
    
        for (int result = doBinarySearchRec(array, 0, leftMost - 1, n); result != -1;) {
            leftMost = result;
            result = doBinarySearchRec(array, 0, leftMost - 1, n);
        }
    
        for (int result = doBinarySearchRec(array, rightMost + 1, array.length - 1, n); result != -1;) {
            rightMost = result;
            result = doBinarySearchRec(array, rightMost + 1, array.length - 1, n);
        }
    
        resultArray[0] = leftMost;
        resultArray[1] = rightMost;
    
        return resultArray;
    }
    

    【讨论】:

      【解决方案8】:

      它正在使用修改后的二进制搜索。它将是 O(LogN)。空间复杂度为 O(1)。 我们调用了 BinarySearchModified 两次。一个用于查找元素的开始索引,另一个用于查找元素的结束索引。

      private static int BinarySearchModified(int[] input, double toSearch)
          {
              int start = 0;
              int end = input.Length - 1;
      
              while (start <= end)
              {
                  int mid = start + (end - start)/2;
                  if (toSearch < input[mid]) end = mid - 1;
                  else start = mid + 1;
              }
      
              return start;
          }
      
      
          public static Result GetRange(int[] input, int toSearch)
          {
              if (input == null) return new Result(-1, -1);
      
              int low = BinarySearchModified(input, toSearch - 0.5);
      
              if ((low >= input.Length) || (input[low] != toSearch)) return new Result(-1, -1);
      
              int high = BinarySearchModified(input, toSearch + 0.5);
      
              return new Result(low, high - 1);
          } 
      
       public struct Result
          {
              public int LowIndex;
              public int HighIndex;
      
              public Result(int low, int high)
              {
                  LowIndex = low;
                  HighIndex = high;
              }
          }
      

      【讨论】:

        【解决方案9】:

        log(n) 对最左边目标和最右边目标的二进制搜索的另一个结果。这是用 C++ 编写的,但我认为它非常易读。

        我们的想法是我们总是以left = right + 1 结束。因此,要找到最左边的目标,如果我们可以将right 移动到小于目标的最右边的数字,那么左边将是最左边的目标。

        对于最左边的目标:

        int binary_search(vector<int>& nums, int target){
            int n = nums.size();
            int left = 0, right = n - 1;
        
            // carry right to the greatest number which is less than target.
            while(left <= right){
                int mid = (left + right) / 2;
                if(nums[mid] < target)
                    left = mid + 1;
                else
                    right = mid - 1;
            }
            // when we are here, right is at the index of greatest number
            // which is less than target and since left is at the next, 
            // it is at the first target's index
            return left;
        }
        

        对于最右边的目标,思路很相似:

        int binary_search(vector<int>& nums, int target){
            while(left <= right){
                int mid = (left + right) / 2;
                // carry left to the smallest number which is greater than target.
                if(nums[mid] <= target)
                    left = mid + 1;
                else
                    right = mid - 1;
            }
            // when we are here, left is at the index of smallest number
            // which is greater than target and since right is at the next, 
            // it is at the first target's index
            return right;
        }
        

        【讨论】:

          【解决方案10】:

          我想出了使用二进制搜索的解决方案,唯一的事情是如果找到匹配项,则在两侧进行二进制搜索。

          public static void main(String[] args) {
              int a[] ={1,2,2,5,5,6,8,9,10};
              System.out.println(2+" IS AVAILABLE  AT = "+findDuplicateOfN(a, 0, a.length-1, 2));
              System.out.println(5+" IS AVAILABLE  AT = "+findDuplicateOfN(a, 0, a.length-1, 5));
              int a1[] ={2,2,2,2,2,2,2,2,2};
              System.out.println(2+" IS AVAILABLE  AT = "+findDuplicateOfN(a1, 0, a1.length-1, 2));
          
              int a2[] ={1,2,3,4,5,6,7,8,9};
              System.out.println(10+" IS AVAILABLE  AT = "+findDuplicateOfN(a2, 0, a2.length-1, 10));
          }
          
          public static String findDuplicateOfN(int[] a, int l, int h, int x){
              if(l>h){
                  return "";
              }
              int m = (h-l)/2+l;
              if(a[m] == x){
                  String matchedIndexs = ""+m;
                  matchedIndexs = matchedIndexs+findDuplicateOfN(a, l, m-1, x);
                  matchedIndexs = matchedIndexs+findDuplicateOfN(a, m+1, h, x);
                  return matchedIndexs;
              }else if(a[m]>x){
                  return findDuplicateOfN(a, l, m-1, x);
              }else{
                  return findDuplicateOfN(a, m+1, h, x);
              }
          }
          
          
          2 IS AVAILABLE  AT = 12 
          5 IS AVAILABLE  AT = 43 
          2 IS AVAILABLE  AT = 410236578 
          10 IS AVAILABLE  AT =
          

          我认为这仍然提供 O(logn) 复杂度的结果。

          【讨论】:

          • 在所有重复的最坏情况下,例如[5, 5, 5, 5, 5, 5, 5, 5],这个是不是访问了数组的每一个成员,都变成了O(n)?
          猜你喜欢
          • 2015-10-09
          • 2021-01-20
          • 2011-03-19
          • 2015-09-28
          • 2016-03-24
          • 1970-01-01
          • 1970-01-01
          • 2017-09-24
          相关资源
          最近更新 更多