【问题标题】:Trouble understanding slightly modified binary search?难以理解稍微修改的二分搜索?
【发布时间】:2020-08-12 16:38:49
【问题描述】:

大家好,我正在尝试解决 leetcode 上的一个名为“搜索插入位置”的问题。继承人的问题: https://leetcode.com/problems/search-insert-position/

问题陈述: 给定一个排序数组和一个目标值,如果找到目标,则返回索引。如果不是,则返回按顺序插入的索引。

您可以假设数组中没有重复项。

这是一个简单的二进制搜索,我唯一不明白的是我必须在最后返回下限才能得到正确的答案。我不明白为什么。如果有人能解释一下,我将不胜感激。

class Solution {
    public int searchInsert(int[] nums, int target) {
        int start=0;
        int last= nums.length-1;
        
        while(start<=last){
            int middle=(start+last)/2;
            
            if(target==nums[middle]){
                return middle;
            }
             else if(target>nums[middle]){
            start=middle+1;
             }
            else
                last=middle-1;
            
        }
        return start; // this is the part i don't understand. why do i have to return start? 
    }
}

【问题讨论】:

  • 你需要返回应该插入新元素的位置(如果没有找到),start 变量包含这个
  • 因为start 是什么正在更新?就我个人而言,我可能会称之为不同的东西,但它是每次迭代的“开始”位置。

标签: java data-structures


【解决方案1】:

要理解这一点,您必须注意练习的第二个要求:返回应该插入元素的位置。 假设您要在下表中插入数字 150。

╔═════╦═════╦═════╦═════╗
║ 100 ║ 200 ║ 300 ║ 400 ║
╠═════╬═════╬═════╬═════╣
║ 0   ║ 1   ║ 2   ║ 3   ║
╚═════╩═════╩═════╩═════╝

这样做的方法是创建一个更大的数组,复制所有出现在 150 之前的元素到它们所在的位置,然后添加数字 150,然后复制所有出现的数字 150 之后,指数比原来高一个。

╔═════╦═════╦═════╦═════╦═════╗
║     ║     ║     ║     ║     ║
╠═════╬═════╬═════╬═════╬═════╣
║ 0   ║ 1   ║ 2   ║ 3   ║ 4   ║
╚═════╩═════╩═════╩═════╩═════╝

╔═════╦═════╦═════╦═════╦═════╗
║ 100 ║     ║     ║     ║     ║
╠═════╬═════╬═════╬═════╬═════╣
║ 0   ║ 1   ║ 2   ║ 3   ║ 4   ║
╚═════╩═════╩═════╩═════╩═════╝

╔═════╦═════╦═════╦═════╦═════╗
║ 100 ║ 150 ║     ║     ║     ║
╠═════╬═════╬═════╬═════╬═════╣
║ 0   ║ 1   ║ 2   ║ 3   ║ 4   ║
╚═════╩═════╩═════╩═════╩═════╝

╔═════╦══════╦═════╦═════╦═════╗
║ 100 ║  150 ║ 200 ║ 300 ║ 400 ║
╠═════╬══════╬═════╬═════╬═════╣
║ 0   ║ 1    ║ 2   ║ 3   ║ 4   ║
╚═════╩══════╩═════╩═════╩═════╝

现在您知道这是如何工作的,您知道插入所需的索引是第一个大于您的target 的数字之一。如果所有数字都大于您的target,这意味着 0(并且所有现有数字将移动到位置 1 到 N),如果所有数字都小于您的 target,这将是数字 N -现有数组的长度(它不是现有数组中的合法索引,但正如我所解释的,如果你真的想插入数字,你必须创建一个新数组。但这不是本练习的一部分)。

现在,为什么start 是正确的索引?

当您要查找的元素不在数组中时,middle 元素永远不会匹配。因此,您不断将startlast 彼此靠近。在最后一次迭代中,最终它们指向同一个元素。在这种情况下,start == middle == last

现在,它们都指向的元素要么大于target,要么小于target

小于target

else if(target>nums[middle]){
    start=middle+1;
}

在此语句之后,lastmiddle 仍然指向nums[middle] 数字,该数字小于 target。但是start 将指向它之后的一个位置。 nums[middle] 后面的数字是第一个大于 target 的数字。如果你不明白为什么,想想我们是如何从上一次迭代中得到这种情况的。索引last 总是指向一个大于target 的数字,直到它移动一个位置“太多”,这就是我们在这里看到的。

大于target

else
    last=middle-1;

在这种情况下,我们刚刚将last 移动到低于startmiddle 的位置——我们知道它小于*target. So... the current position is *greater*, the position where last point is *less,然后是当前位置(即startmiddle 仍然指向)是第一个大于 target 的数字。

在这两种情况下,start 将指向正确的位置 - 第一个大于 target 的元素的位置。

让我们在示例数组中看到它。当我们尝试插入 150 时,会发生什么?

  1. start 是 0 (100),last 是 3 (400)。 middle,通过整数除法,是 (0+3)/2,即 1 (200)。 200 > 150,所以我们到达else,并将last 设置为middle - 1,它是0 (100)。
  2. start is still 0 (100), but lastis now also 0 (100). They are equal, andmiddleis now also 0 (100). 100 &lt; 150, so we get to theelse if, and start` 现在设置为 1 (200)。

所以一旦start 移动到一个大于target 的数字,我们就停止了,实际上,插入点应该是1!

让我们对 350 做同样的事情

  1. start 是 0 (100),last 是 3 (400)。 middle,通过整数除法,是 (0+3)/2,即 1 (200)。 200 else if 和 start 现在是 middle +1,所以 2 (300)。
  2. start 是 2 (300),last 是 3 (400)。 middle(2+3)/2,即 2 (300)。 300 else if 和 start 现在是 middle + 1,所以 3 (400)。
  3. start 是 3 (400),last 是 3 (400),中间是一样的。 400 > 350,所以我们到达elselast 将移动到 2 (300)。

现在start 大于last,我们再次看到start 实际上是第一个大于350 的元素。实际上,350 的正确插入点应该是3。

【讨论】:

    【解决方案2】:

    根据数组的大小或其分区(偶数或奇数),或我们设计二分搜索算法的方式,有时在低和高之间会有一个索引差异(基于停止标准,可能是lo &lt; hilo &lt;= hi 例如)。返回哪个并不重要,我们只需要针对这种差异进行调整即可。

    在这个问题中lo 会返回预期的结果:

    public final class Solution {
        public static final int searchInsert(int[] nums, int target) {
            int lo = 0;
            int hi = nums.length - 1;
    
            while (lo <= hi) {
                int mid = lo + (hi - lo) / 2;
    
                if (nums[mid] == target) {
                    return mid;
                }
    
                else if (nums[mid] > target) {
                    hi = mid - 1;
                }
    
                else {
                    lo = mid + 1;
                }
            }
    
            return lo;
        }
    }
    

    【讨论】:

      【解决方案3】:

      在找到中点之前,我们需要初始化起点。每次检查是否找到目标元素,如果没有找到目标元素,则更新起点。

      【讨论】:

        猜你喜欢
        • 2017-06-11
        • 1970-01-01
        • 2013-06-25
        • 1970-01-01
        • 2015-04-12
        • 1970-01-01
        • 2018-09-22
        • 1970-01-01
        • 2011-04-30
        相关资源
        最近更新 更多