文章讲解:代码随想录 (programmercarl.com)
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
状态:
Leetcode704二分查找 题目链接704. 二分查找 - 力扣(LeetCode)
二分查找的条件:数组有序且没有重复元素
时间复杂度O(logn)
空间复杂度O(1)
两种情况:
1.左闭右闭[left, right]:
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right){
int middle = (left + right) / 2;
if(nums[middle] > target)right = middle - 1;
else if(nums[middle] < target)left = middle + 1;
else return middle;
}
return -1;
}
}
2.左闭右开[left, right):
class Solution {
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length;
while(left < right){
int middle = (left + right) / 2;
if(nums[middle] > target)right = middle;
else if(nums[middle] < target)left = middle + 1;
else return middle;
}
return -1;
}
}
Leetcode35. 搜索插入位置 - 力扣(LeetCode)
在二分查找法结束后,left = right + 1,nums[right] < target,nums[left] > target,因此left所在的位置一定是元素要插入的位置。
明白这点,题目就能解了
时间复杂度O(logn)
空间复杂度O(1)
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left <= right){
int middle = (left + right) / 2;
if(nums[middle] > target)right = middle - 1;
else if(nums[middle] < target)left = middle + 1;
else return middle;
}
return left;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
在这道题目中有三种情况需要考虑
之后要明白二分查找法如何确定左边界和右边界:当确认左边界时,我们应当采用以下代码
public int getLeftBorder(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
int leftRecord = -2;
while(left <= right){
int mid = (left + right) / 2;
if(nums[mid] < target)left = mid + 1;
else{
right = mid - 1;
leftRecord = right;
}
}
return leftRecord;
}
在上述代码中,可以看到如果nums >= target的话right = mid - 1,之后我们对leftReocrd的值进行分情况讨论。
如果是第一种情况:target要在nums数组的右边界之外,则leftRecord仍等于-2;如果在左边界之外,则leftRecord等于-1
如果是第二种情况:则leftReocrd最终的值为target对应的左边界-1
如果是第三种情况:则最终得到的左边界和右边界之间的差值一定 <= 1
右边界同理,下附题目的完整源码
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] res = new int[2];
int rightBorder = getRightBorder(nums, target);
int leftBorder = getLeftBorder(nums, target);
if(rightBorder == -2 || leftBorder == -2){ // 情况1
res[0] = -1;
res[1] = -1;
return res;
}
if(rightBorder - leftBorder > 1){ // 情况2
res[0] = leftBorder + 1;
res[1] = rightBorder - 1;
return res;
}
res[0] = -1; // 情况3
res[1] = -1;
return res;
}
public int getRightBorder(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
int rightRecord = -2;
while(left <= right){
int mid = (left + right) / 2;
if(nums[mid] > target)right = mid - 1;
else{
left = mid + 1;
rightRecord = left;
}
}
return rightRecord;
}
public int getLeftBorder(int[] nums, int target){
int left = 0;
int right = nums.length - 1;
int leftRecord = -2;
while(left <= right){
int mid = (left + right) / 2;
if(nums[mid] < target)left = mid + 1;
else{
right = mid - 1;
leftRecord = right;
}
}
return leftRecord;
}
}
27. 移除元素 - 力扣(LeetCode)
暴力解法
时间复杂度O(n^2)
空间复杂度O(1)
class Solution {
public int removeElement(int[] nums, int val) {
int length = nums.length;
for(int i = 0; i < nums.length; i++){
if(i == length)break;
if(nums[i] == val){
for(int j = i; j < length - 1; j++){
nums[j] = nums[j + 1];
}
nums[length - 1] = 0;
length--;
i--;
}
}
return length;
}
}
双指针解法
时间复杂度O(n)
空间复杂度O(1)
快指针找到要加进新数组的元素,慢指针找到要加元素的位置。
class Solution {
public int removeElement(int[] nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.length; fast++){
if(nums[fast] != val){
nums[slow] = nums[fast];
slow += 1;
}
}
return slow;
}
}
相向双指针解法
时间复杂度O(n)
空间复杂度O(1)
left指针从左边寻找与target相等的值的地址,右指针从右边找与target不相等的值的地址。
代码如下
class Solution {
public int removeElement(int[] nums, int val) {
int left = 0;
int right = nums.length - 1;
while(left <= right){
while(left <= right && nums[left] != val)left++;
while(left <= right && nums[right] == val)right--;
if(left <= right)nums[left++] = nums[right--];
}
return left;
}
}
为什么left <= right?
如果left < right,则当left = right时,若nums[left] != target,则数组遗漏了一个值
心得:704、35、移除元素暴力解法都可以自己做出来,其他的需要讲解下思路
二分查找本身有过一定基础,但是左闭右闭和左闭右开还是第一次接触,更能理解这个范围的概念了。
34、35更像是二分查找的变种,从而理解二分查找结束后left、right的含义。
现在还是不太熟练,自己想出的代码需要一些数据来测试完善。