【问题标题】:O(log n) algorithm to find best insert position in sorted arrayO(log n) 算法在排序数组中找到最佳插入位置
【发布时间】:2014-04-03 03:03:22
【问题描述】:

我正在尝试制定一种算法,以找到将 目标 插入已排序数组的最佳位置。

目标是要么返回项目的位置(如果它存在于列表中),否则返回它将进入的位置以保持列表排序。

假设我有一个清单:

   0   1   2   3   4    5    6
 ---------------------------------
 | 1 | 2 | 4 | 9 | 10 | 39 | 100 |
 ---------------------------------

我的目标项目是14 它应该返回5的索引位置

我目前拥有的伪代码:

array = generateSomeArrayOfOrderedNumbers()

number findBestIndex(target, start, end)
    mid = abs(end - start) / 2

    if (mid < 2) 
        // Not really sure what to put here
        return start + 1 // ??

    if (target < array[mid])
        // The target belongs on the left side of our list //
        return findBestIndex(target, start, mid - 1)
    else
        // The target belongs on the right side of our list //
        return findBestIndex(target, mid + 1, end)

我现在不确定该放什么。我试图对此采取二进制搜索方法,但这是我在 5 次左右重写后能想到的最好的方法。

【问题讨论】:

  • 二分搜索是最好的选择——这就是你所需要的。你不需要使用递归,一个简单的while 循环就可以了。
  • 如果您检查 array[mid] == 目标,您的递归将大大简化,如果是,则通过返回索引终止。你还需要弄清楚你的基本情况。在递归中,基本情况总是最难的部分。
  • @ldog:这实际上使代码复杂化(额外的if)。只需将您的基本情况设置为start &gt; end 并根据比较递归到[start, mid] 或[mid + 1, end]。请记住,我们不想找到target,我们想找到target 的successor。
  • @NiklasB.:我认为如果目标存在,我们想返回它的索引。这就是为什么我建议对此进行特殊检查的原因。否则我们可以按照您的建议找到继任者。见林伟的回答,我认为应该是正确的。
  • 为什么这个问题被标记为linked-list?结构是数组还是链表?它是链表,然后二进制搜索将无济于事,因为您需要随机访问才能从二进制搜索中受益,而链表没有。

标签: arrays algorithm sorting linked-list


【解决方案1】:

在java中稍微修改二分查找的解决方案

int findInsertionIndex(int[] arr, int t) {
    int s = 0, e = arr.length - 1;
  
    if(t < arr[s])return s;
    if(t > arr[e])return e;

      while (s < e){

        int mid = (s + e)/2;

        if(arr[mid] >= t){
            e = mid - 1;
        }

        if(arr[mid] < t){
            s = mid + 1;
        }
      }

    return arr[s] < t? s + 1 : s;
 }

以上代码适用于这些可能的场景:

  • 如果arr[mid] > target -> target index在left half,找到target的第一个最大值的索引并返回。
  • 如果arr[mid] target index在右半边,找到target第一个最小值的index,返回index+1指向point目标/插入索引。
  • if arr[mid] == target -> 找到目标值的第一个出现的索引并返回它。

【讨论】:

    【解决方案2】:

    这是通过使用 python 调整二进制搜索的解决方案。

    def func(x, y):
        start = 0
        end = len(x)
        while start <= end:
            mid = (start + end)//2
            print(start, end, mid)
            if mid + 1 >= len(x):
                return mid + 1
            if x[mid] < y and x[mid + 1] > y:
                return mid + 1
            elif x[mid] > y:
                end = mid - 1
            else:
                start = mid + 1
        return 0
    
    func([1,2,4,5], 3)
    

    【讨论】:

      【解决方案3】:

      以下是用于从排序后的数组(包含重复值)中搜索目标值(数组列表)的代码。

      它返回我们可以插入目标值的位置数组。

      希望这段代码对您有所帮助。

      欢迎提出任何建议。

      static int[] climbingLeaderboard(int[] scores, int[] alice) {
          int[] noDuplicateScores = IntStream.of(scores).distinct().toArray();
          int[] rank = new int[alice.length];
      
          for (int k = 0; k < alice.length; k++) {
              int i=0;
              int j = noDuplicateScores.length-1;
              int pos=0;
              int target = alice[k];
              while(i<=j) {
                  int mid = (j+i)/2;
                  if(target < noDuplicateScores[mid]) {
                      i = mid +1;
                      pos = i;
                  }else if(target > noDuplicateScores[mid]) {
                      j = mid-1;
                      pos = j+1;
                  }else {
                      pos = mid;
                      break;
                  }
              }
              
              rank[k] = pos+1;
          }
      
          return rank;
       }
      

      【讨论】:

        【解决方案4】:

        这是我使用的代码:

        int binarySearch( float arr[] , float x , int low , int high )
        {
            int mid;
            while( low < high ) {
                mid = ( high + low ) / 2;
                if( arr[mid]== x ) {
                    break;
                }
                else if( arr[mid] > x ) {
                    high=mid-1;
                }
                else {
                    low= mid+1;
                }
            }
            mid = ( high + low ) / 2;
            if (x<=arr[mid])
                return mid;
            else 
                return mid+1;
        }
        

        关键是,即使低等于高,你也必须检查。

        例如看这个例子: 0.5->0.75 并且您正在寻找 0.7 或 1 的真实位置。

        在退出 while 循环的两种情况下:low=high=1 但其中一个应该放在位置 1,另一个放在位置 2。

        【讨论】:

          【解决方案5】:

          我通过计算严格小于 (

          int binarySearchCount(int array[], int left, int right, int key) {
              if(left > right) {
                  return -1; // or throw exception
              }
              int mid = -1;   //init with arbitrary value 
          
              while (left <= right) {
                  // Middle element
                  mid = (left + right) / 2;
          
                  // If the search key on the left half
                  if (key < array[mid]) {
                      right = mid - 1;
                  }
                  // If the search key on the right half
                  else if (key > array[mid]) {
                      left = mid + 1;
                  }
                  // We found the key
                  else {
                      // handle duplicates
                      while(mid > 0 && array[mid-1] == array[mid]) {
                          --mid;
                      }
                      break;
                  }
              }
          
              // return the number of elements that are strictly smaller (<) than the key
              return key <= array[mid] ? mid : mid + 1;
          }
          

          【讨论】:

            【解决方案6】:

            你在正确的轨道上。

            首先,mid = abs(end + start) / 2中不需要abs

            这里假设 abs 表示绝对值,因为 end 应该总是不小于 start,除非你的代码有错误。所以这里 abs 从来没有帮助,但可能会隐藏您的问题,使其难以调试。

            你也不需要if (mid &lt; 2) 部分,中间小于两个没什么特别的。

            array = generateSomeArrayOfOrderedNumbers()
            
            int start = 0;
            int end = array.size(); 
            
            int findBestIndex(target, start, end){
            
            if (start == end){   //you already searched entire array, return the position to insert
              if (stat == 0) return 0; // if it's  the beginning of the array just return 0.
              if(array[start] > target) return start -1; //if last searched index is bigger than target return the position before it.
            else return start;
            }
            mid = (end - start) / 2
            
            // find correct position 
            if(target == array[mid]) return mid;
            
            if (target < array[mid])
            {
             // The target belongs on the left side of our list //
            return findBestIndex(target, start, mid - 1)
            }
            else
            {
             // The target belongs on the right side of our list //
             return findBestIndex(target, mid + 1, end)
            }
            }
            

            【讨论】:

              【解决方案7】:

              你的代码有几个问题:

              mid = abs(end - start) / 2
              

              这不是start 和end 之间的中间值,它是它们之间距离的一半(向下舍入为整数)。稍后您使用它,就像它确实是一个有效的索引:

              findBestIndex(target, start, mid - 1)
              

              事实并非如此。您可能打算在这里使用mid = (start + end) // 2 或其他东西。 您还错过了一些索引,因为您跳过了中间:

              return findBestIndex(target, start, mid - 1)
               ...
              return findBestIndex(target, mid + 1, end)
              

              您的基本情况现在也必须有所不同。一个好的候选人是条件

              if start == end
              

              因为现在你肯定知道你已经完成了搜索。请注意,您还应该考虑所有数组元素都小于target的情况,因此您需要在末尾插入。

              我不经常搜索二进制,但如果我这样做,就是这样

              如果您以前从未做过二分搜索,那么您很难做到这一点。如果我进行二分搜索,我通常使用以下模式:

              lo, hi = 0, n // [lo, hi] is the search range, but hi will never be inspected.
              while lo < hi:
                  mid = (lo + hi) // 2
                  if check(mid): hi = mid
                  else:          lo = mid + 1
              

              在check是一个单调二元谓词的条件下(直到某个点它总是false,从那个点开始是true),在这个循环之后,lo == hi将是第一个数字范围 [0..n] 和 check(lo) == true。 check(n) 被隐含地假定为 true(这是这种方法的魔力的一部分)。

              那么什么是单调谓词,对于包括目标位置在内的所有索引以及之后的所有索引都是true,对于之前的所有位置都是false?

              如果我们考虑一下,我们想找到数组中第一个大于目标的数字,所以我们只需将其插入就可以了:

              lo, hi = 0, n
              while lo < hi:
                  mid = (lo + hi) // 2
                  if (a[mid] > target): hi = mid
                  else:                 lo = mid + 1
              return lo;
              

              【讨论】:

              • +1,有些人通常因为使用闭区间而苦苦挣扎,在大多数情况下,半开区间更容易使用,并且会产生更优雅的代码,就像这种情况一样。
              • @pasztorpisti:在这一点上我不同意你的观点,这段代码实际上不适用于半开区间,这就是它优雅的原因。循环不变式是第一个元素在封闭范围内[lo..hi],特殊情况是hi == n。否则循环条件将是while (lo + 1 &lt; hi) 或其他东西。
              • 我没有说你的代码不能在半开区间工作。
              • @pasztorpisti:是的,我只是想让你知道,使用 close 间隔是使该算法优雅的原因。独占右边框有点不优雅。
              • 这可能是一个愚蠢的问题,但假设我有一个包含 7 个整数的列表。它们按以下顺序添加 1, 6, 3, 5, 4, 7, 2。在我的实现中,一切都很顺利,直到我达到 7。将它们全部插入后的顺序是:1,2,3,7, 4、5、6。关于这段代码,我有什么遗漏吗?这是代码:gist.github.com/headdetect/b8a889075198578ccb46
              猜你喜欢
              • 1970-01-01
              • 2012-09-08
              • 1970-01-01
              • 1970-01-01
              • 2016-09-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-06-24
              相关资源
              最近更新 更多