二分法模板

非递归版本:

 1 public class Solution {
 2     /**
 3      * @param A an integer array sorted in ascending order
 4      * @param target an integer
 5      * @return an integer
 6      */
 7     public int findPosition(int[] nums, int target) {
 8         if (nums == null || nums.length == 0) {
 9             return -1;
10         }
11         
12         int start = 0, end = nums.length - 1;
13         // 要点1: start + 1 < end
14         while (start + 1 < end) {
15         // 要点2:start + (end - start) / 2
16             int mid = start + (end - start) / 2;
17             // 要点3:=, <, > 分开讨论,mid 不+1也不-1
18             if (nums[mid] == target) {
19                 return mid;
20             } else if (nums[mid] < target) {
21                 start = mid;
22             } else {
23                 end = mid;
24             }
25         }
26         
27         // 要点4: 循环结束后,单独处理start和end
28         if (nums[start] == target) {
29             return start;
30         }
31         if (nums[end] == target) {
32             return end;
33         }
34         return -1;
35     }
36 }

死循生:when target is the last position of the range
nums = [1,1], target = 1
使用 start < end 如何都会出死循

 

递归版本:

 1 public class Solution{
 2     public int findPosition(int[] nums, int target){
 3         return binarySearch(nums, 0, nums.length-1, target);
 4     }
 5 
 6     public int binarySearch(int[] nums, int start, int end, int target){
 7         if(start>end)
 8             return(-1);
 9 
10         int mid=(start+end)/2;
11         if(nums[mid]==target)
12             return(mid);
13         if(nums[mid]<target)
14             return binarySearch(nums, mid+1, end, target);
15         else
16             return binarySearch(nums, start, mid-1, target);
17     }
18 }

 

 

常见问题

Q: 为什么要用 start + 1 < end?而不是 start < end 或者 start <= end?

A: 为了避免死循环。二分法的模板中,整个程序架构分为两个部分:

  1. 通过 while 循环,将区间范围从 n 缩小到 2 (只有 start 和 end 两个点)。
  2. 在 start 和 end 中判断是否有解。

start < end 或者 start <= end 在寻找目标最后一次出现的位置的时候,出现死循环。

Q: 为什么明明可以 start = mid + 1 偏偏要写成 start = mid?

A: 大部分时候,mid 是可以 +1 和 -1 的。在一些特殊情况下,比如寻找目标的最后一次出现的位置时,当 target 与 nums[mid] 相等的时候,是不能够使用 mid + 1 或者 mid - 1 的。因为会导致漏掉解。那么为了节省脑力,统一写成 start = mid / end = mid 并不会造成任何解的丢失,并且也不会损失效率——log(n) 和 log(n+1) 没有区别。

 


 

许多同学在写二分法的时候,都比较习惯性的写 while (start < end) 这样的循环条件。这样的写法及其容易出现死循环,导致 LintCode 上的测试“超时”(Time Limit Exceeded)。

什么情况下会出现死循环?

在做 last position of target 这种模型下的二分法时,使用 while (start < end) 就容易出现超时。

在线练习:http://www.lintcode.com/problem/last-position-of-target/

我们来看看会超时的代码:
Java版本:

int start = 0, end = nums.length - 1;
while (start < end) {
    int mid = start + (end - start) / 2;
    if (nums[mid] == target) {
        start = mid;
    } else if (nums[mid] < target) {
        start = mid + 1;
    } else {
        end = mid - 1;
    }
}

上面这份代码是大部分同学的实现方式。看上去似乎没有太大问题。我们来注意一下 `nums[mid] == target` 时候的处理。这个时候,因为 mid 这个位置上的数有可能是最后一个出现的target,所以不能写成 start = mid + 1(那样就跳过了正确解)。而如果是这样写的话,下面这组数据将出现超时(TLE):

nums = [1,1], target = 1

 

将数据带入过一下代码:

start = 0, end = 1
while (0 < 1) {
     mid = 0 + (1 - 0) / 2 = 0
     if (nums[0] == 1) {
         start = 0;
     }
     ...
}

 

我们发现,start 将始终是 `0`。

出现这个问题的主要原因是,mid = start + (end - start) / 2 这种写法是偏向start的。也就是说 mid 是中间偏左的位置。这样导致如果 start 和 end 已经是相邻关系,会导致 start 有可能在一轮循环之后保持不变。

或许你会说,那么我改成 mid = (start + end + 1) / 2 是否能解决问题呢?没错,确实可以解决 last position of target 的问题,但是这样之后 first position of target 就超时了。我们比较希望能够有一个理想的模板,无论是 first position of target 还是 last position of target,代码的区别尽可能的小和容易记住。

 


 

 

类型1:裸题

704. Binary Search

 1 class Solution:
 2     def bd(self, nums, target, start, end):
 3         while(start+1<end):
 4             mid=(start+end)//2
 5             if(nums[mid]==target):
 6                 return mid
 7             if(nums[mid]>target):
 8                 end=mid
 9             if(nums[mid]<target):
10                 start=mid
11         if(nums[start]==target):
12             return start
13         if(nums[end]==target):
14             return end
15         return -1
16 
17     def search(self, nums, target):
18         """
19         :type nums: List[int]
20         :type target: int
21         :rtype: int
22         """
23         sl=len(nums)-1
24         if(sl==-1):
25             return -1
26         ans=self.bd(nums, target, 0, sl)
27         return ans
View Code

相关文章:

  • 2021-09-04
  • 2022-12-23
  • 2021-09-20
  • 2022-12-23
  • 2021-06-14
  • 2022-12-23
  • 2022-12-23
  • 2021-06-01
猜你喜欢
  • 2021-06-19
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-11-03
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案