704. 二分查找 - 力扣(LeetCode)
关于二分查找
- 最重要的是要处理好边界问题,每次写完边界可以带入特殊值进行测试
- 确定区间的不变量是什么?比如区间的左闭右闭,和左闭右开,每次二分完的新区间,一定要一直和最开始的区间的定义保持一致,要时刻明确区间的含义
- 注意二分的几种情况(当数组长度为n的情况下)
- 当l = 0, r = n的时候因为r这个值我们在数组中无法取到,while(l < r) 是正确写法
- 当l = 0, r = n - 1的时候因为r这个值我们在数组中可以取到,while(l <= r) 是正确写法 主要看能不能取到这个值
- 注意处理二分mid溢出的问题
- 当l 和 r 较大时,mid = (l + r) / 2 可能会出现大于INT_MAX的情况,会溢出。
- 可以写成 mid = l + (r - l) / 2 或者 mid = l + ((r - l) >>1)的形式
思路:这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
一开始的代码:
class Solution {
public:int search(vector<int>& nums, int target) {int size = nums.size();int l = 0, r = size - 1;while(l < r){int mid = (l + r) >> 1;if(nums[mid] < target)l = mid+1;elser = mid;}if(nums[r] != target)return -1;return r;}
};
这个代码可以解决数组中存在重复元素的情况,但是要注意的点有许多,第一个 l < r 当我写成 l <= r 时会死循环,因为当r = l时,进去循环,会一直r = mid 不能跳出循环,导致死循环还是要注意,这可能是不规范的写法吧。
下面有两个规范的模版(只能用于严格的升序排序,不能出现相同的元素,如果目标元素出现多次就不能正确的返回下标)
左闭右闭
class Solution {
public:int search(vector<int>& nums, int target) {int size = nums.size();int l = 0, r = size - 1;while(l <= r){int mid = l + ((r - l) >> 1);if(nums[mid] < target)//mid 小于就去mid的右面去找,并且mid不为targetl = mid+1;else if(nums[mid] > target)//mid 大于就去mid的左面去找,并且mid不为targetr = mid-1;else//就是 mid = target 的情况,所以直接返回mid就可以了return mid;}//没有在循环中返回说明,没有找到目标,返回-1return -1;}
};
左闭右开
class Solution {
public:int search(vector<int>& nums, int target) {int size = nums.size();int l = 0, r = size;while(l < r) // r不能取到,l = r 没有意义{int mid = l + ((r - l) >> 1);if(nums[mid] < target)//mid 小于就去mid的右面去找,并且mid不为targetl = mid+1;else if(nums[mid] > target)//mid 大于就去mid的左面去找,并且mid不为target,但是区间不包含mid 所以 r = midr = mid;else//就是 mid = target 的情况,所以直接返回mid就可以了return mid;}//没有在循环中返回说明,没有找到目标,返回-1return -1;}
};
其实二分还有很多应用场景,有着许多变体,比如说查找第一个大于target的元素或者第一个满足条件的元素,都是一样的,根据是否满足题目的条件来缩小答案所在的区间,这个就是二分的本质。另外需要注意,二分的使用前提:有序数组
27. 移除元素 - 力扣(LeetCode)
我的思路(对撞指针)
题目要求返回不等于val的个数和nums数组,我们只需要考虑返回数组前面的部分是不等于val的就可以,所以这道题的解法是双指针(对撞的),现从前面开始,如果不等于就走,等于之后 右指针在走,右指针找到不等于val的数字,与左指针指向的数字互换
class Solution {
public:int removeElement(vector<int>& nums, int val) {int l = 0, r = nums.size()-1;//如果为空直接返回0if(l > r) return 0;while(l < r){if(nums[l] != val)l++;else{if(nums[r] != val){int tmp = nums[r];nums[r] = nums[l];nums[l] = tmp;}r--;}}//循环出来一定是 l = r 再判断 l=r时等不等于valif(nums[l] == val) //因为返回的是个数return l ;elsereturn l+1;}
};
暴力解:
一开始要求暴力解第一时间还真没想到,后来看到题解,才想到的暴力解。
数组中元素不能删除只能进行覆盖,所以暴力的解法就是从前往后遍历,找到val然后用后面的元素将val进行覆盖,同时将nums数组的长度减减,最后返回记录好的数组长度就可以了。
class Solution {
public:int removeElement(vector<int>& nums, int val) {int size = nums.size();for(int i = 0; i < size; i++){if(nums[i] == val){//val 后面的元素覆盖val都向前进行覆盖for(int j = i+1; j < size; j++){nums[j-1] = nums[j];}//交换之后,后面数组向前覆盖,不知道后面数字是不是等于val然后要再一次判断是不是等于vali--;//每覆盖一次就要size--size--;}}return size;}
};
双指针(同向指针 \ 快慢指针)
快指针可以理解成在旧数组中找非目标元素,然后赋值给慢指针指向的新数组,虽然都指向一个数组
- 关于二分法和移除元素的共性思考 这两题之间有点类似的,他们都是在不断缩小 left 和 right 之间的距离,每次需要判断的都是 left 和 right 之间的数是否满足特定条件。对于「移除元素」这个写法本质上还可以理解为,我们拿 right 的元素也就是右边的元素,去填补 left 元素也就是左边的元素的坑,坑就是 left 从左到右遍历过程中遇到的需要删除的数,因为题目最后说超过数组长度的右边的数可以不用理,所以其实我们的视角是以 left 为主,这样想可能更直观一点。用填补的思想的话可能会修改元素相对位置,这个也是题目所允许的。
- fast < nums.size() 和 fast <= nums.size()-1 没什么区别,那为什么第二个会在空数组时报数组越界的错误? vector的size()函数返回值是无符号整数,空数组时返回了0,再减个一会溢出;变成一个很大的正数,nums[fast] 会发生 null pointer of type 'int' 异常。
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
定义快慢指针
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
所以思路就是,快指针在前面寻找不是目标元素的元素,慢指针可以理解为用来维护新数组,也就是不含目标元素的数组,如果不等于val 快慢指针都++,等于慢指针用来记录val的位置,快指针向后寻找不等于val的值与慢指针的val进行交换,直到快指针走到最后
class Solution {
public:int removeElement(vector<int>& nums, int val) {int l = 0, r = 0;//r 每一步都要走for(int i = r; i < nums.size(); i++){//可以理解为,i为一个符合条件的数组,不等于val就往里面加if(nums[i] != val)nums[l++] = nums[i];}// l 就为新数组的长度return l;}
};
977. 有序数组的平方 - 力扣(LeetCode)
思路:看到这道题,我首先想到的是,将他们的平方都放入数组中,然后进行排序,这种时间复杂度比较高的暴力解法。后面自己想了一下可不可以使用一个时间复杂度为O(1)的方法解出来呢,一开始想用双指针,每个分别用来记录一个数组,比较然后先比较在插入,发现最后回到了插入排序的思想。
先看看暴力解法
class Solution {
public:vector<int> sortedSquares(vector<int>& nums) {for(int i = 0; i < nums.size(); i++){nums[i] = nums[i]*nums[i];}sort(nums.begin(), nums.end());return nums;}
};
可以发现虽然通过了,但是方法还是不太好。
双指针
我们发现有负数和正数,我们不能判断负数和正数谁的平方谁更大(为什么判断谁更大呢,因为数组是从小到大排的,负数越小平方就越大,看到这,我们可不可以先开一个一摸一样大小的数组,然后用两个指针分别记录数组两端平方较大的值呢,谁更大谁就先从后往前插入)
class Solution {
public:vector<int> sortedSquares(vector<int>& nums) {int size =nums.size();int l = 0, r = nums.size()-1;//开一个新数组vector<int>a(nums.size());//x 用来为数组下标int x = size-1;while(l <= r){//右面大,右面赋值if(nums[l]*nums[l] < nums[r]*nums[r]){a[x--] = nums[r]*nums[r];r--;//指针向左移} else{//左面大,左面赋值a[x--] = nums[l]*nums[l];//左指针右移l++;}}return a;}
};