二分查找和移除元素

1,176 阅读1分钟

二分查找

题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果目标值存在,返回下标,否则返回 -1。

代码

1. 左闭右闭

 class Solution {
 public:
     int search(vector<int>& nums, int target) {
         int left = 0, right = nums.size() - 1;
         while (left <= right)
         {
             int mid = left + (right - left) / 2;  // think why?
             if (nums[mid] > target)
                 right = mid - 1;
             else if (nums[mid] < target)
                 left = mid + 1;
             else  // find target
                 return mid; 
         }
         return -1;
     }
 };

采用左闭右闭的方式:搜索区间 [left, right]

对于这种左闭右闭的区间来说,左端点值等于右端点值的情况是可以的,因此while循环判断条件写的是 left <= right,而不是 left < right

而对于下面的左闭右开区间来说,左端点值等于右端点值的情况是不可以的,[num, num)是一个空集,因此while循环判断条件写的是left < right,而不是 left <= right

一般推荐左闭右闭这种写法。

下面给出采用左闭右开方式的代码。

2. 左闭右开

 class Solution {
 public:
     int search(vector<int>& nums, int target) {
         int left = 0, right = nums.size();
         while (left < right)
         {
             int mid = left + (right - left) / 2;  // think why?
             if (nums[mid] > target)
                 right = mid;
             else if (nums[mid] < target)
                 left = mid + 1;
             else  // find target
                 return mid; 
         }
         return -1;
     }
 };

常见问题

1. int mid = left + (right - left) / 2

为什么要这样写?为什么不写成 mid = (left + right) / 2 ?

主要原因是防止 left + right 溢出,即两者之和超出了int的上限,后面再除以2就没意义了。

int的范围是 -2147483648~2147483647。

举个例子,left的值为2147483640,right的值为2147483642,此时 left + right 的值为4294867282,远远超出了int的上限,而采用 left + (right - left) / 2 的方式,首先计算 right - left 得2,接着除以2得1,最终 left + 1 得2147483641。

只要right不超过int上限,那么采用 left + (right - left) / 2 方式就不会出现溢出现象。

2. while循环中是 <= 还是 <

关键在于前面right的定义,取决于 right = n - 1 还是 right = n,其中n为数组长度。

right = n - 1 表示搜索区间从 left 到 right;right = n 表示搜索区间从 left 到 right - 1。

具体分析可参考“代码”。

“35 搜索插入位置”和“704 二分查找”基本相同

移除元素

题目描述:给定一个数组 nums 和一个值 val,需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

要求:空间复杂度:O(1)

本题主要是数组中双指针的使用,更准确说是快慢指针。

具体做法是让快指针fast先走,如果遇到的是不重复的元素,把fast指针指向的元素的值传给慢指针slow指向的元素,然后慢指针slow往前走一步,重复以上操作直至快指针走完整个数组。

无论是不是重复元素,快指针fast都会向前走一步;而只有当不是重复元素时,慢指针slow才会向前走一步。

若刚开始时slow和fast的值均置0,最终slow的值即为数组的新长度。

代码

 class Solution {
 public:
     int removeElement(vector<int>& nums, int val) {
         int slow = 0, fast = 0;
         while (fast < nums.size())
         {
             if (nums[fast] != val)
             {
                 nums[slow++] = nums[fast];
             }
             fast++;
         }
         return slow;
     }
 };

在有关数组的算法问题中,经常会用到双指针。一般有三种情况:从两头到中间、从中间到两头和快慢指针。

解决二分查找也是考双指针,从两头到中间,int left = 0, right = n - 1。