二分查找
Leetcode题目-Binary Search
特殊条件
- 有序数组
- 元素unique
遇到上述限定条件可以考虑用二分法。
思路关键
二分法中,两个左右指针划定的区间的边界定义与指针的移动规则息息相关。
一般常用的写法思路有两种
- 左闭右闭区间
- 左闭右开区间
根据区间的定义,处理每次循环时边界变更的操作。
关键注意
起始的左右边界是0, nums.length-1?还是0, nums.length-1?,循环的条件是left<right还是left<=right,以及调整边界时左右边界是mid?mid+1?还是mid-1?
另在处理过程中注意防止溢出。
代码实现
1. 左闭右闭区间 [left, right]
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length-1; // 右闭区间,所以右边界为nums.length-1
// 因为left=right时,[left, right]即[left, left]无效空间,所以left可取=right
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
// 因为mid所指值小于target,且区间为左闭右闭,所以新区间不包含mid,新区间左边界调整为mid+1
if (nums[mid] < target) left = mid + 1;
// 因为mid所指值大于target,且区间为左闭右闭,所以新区间不包含mid,新区间右边界调整为mid-1
if (nums[mid] > target) right = mid - 1;
// mid所指值等于target时,已找到,直接返回
if (nums[mid] == target) return mid;
}
// 未找到,返回-1
return -1;
}
}
2. 左闭右开区间 [left, right)
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length; // 右开区间,所以右边界为nums.length
// 因为left=right时,[left, right)即[left, left)无效空间,所以left不取=right
while (left < right) {
int mid = left + (right - left) / 2; // 防止溢出
// 因为mid所指值小于target,且区间为左闭右开,所以新区间不包含mid,新区间左边界调整为mid+1
if (nums[mid] < target) left = mid + 1;
// 因为mid所指值大于target,且区间为左闭右开,所以新区间右边界直接为mid即可
if (nums[mid] > target) right = mid;
// mid所指值等于target时,已找到,直接返回
if (nums[mid] == target) return mid;
}
// 未找到,返回-1
return -1;
}
}
总结
时间复杂度:O(log n)
空间复杂度:O(1)
根据二分法的使用条件,看到有序,元素唯一,可以考虑二分法查找。
二分法的关键是理清所操作的边界的定义,并根据边界的定义决定区间移动时的操作。
代码随想录文章讲解:传送门
代码随想录视频讲解:传送门
双指针
Leetcode题目-Remove Element
特殊条件
不使用额外的数组空间,在原数组上操作。
思路关键
数组中的元素时不能删除只能覆盖的,所谓的“删除”即用后面的元素覆盖要删除的元素,所以执行List.remove等库函数时,其实际复杂度为O(n)。
由于java的int数组类型长度固定,所以实际返回的长度后面会有多余元素。不过此题讲了,只检验前面的元素。
两个实现思路
- 暴力解法
- 双指针(快慢指针)
代码实现
1. 暴力解法
class Solution {
public int removeElement(int[] nums, int val) {
// 暴力解法
int i = 0, count = 0;
while (i < nums.length) {
if (nums[i] == val) {
count++;
int j = i;
while (j < nums.length - 1) {
nums[j] = nums[j + 1];
j++;
}
nums[j] = 0;
} else {
i++;
}
}
return count;
}
}
haha此方法没过,超时了
2. 双指针(快慢指针)
class Solution {
public int removeElement(int[] nums, int val) {
int fast = 0, slow = 0;
for (; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
return slow;
}
}
总结
暴力解法的时间复杂度:O(n^2)
暴力解法的空间复杂度:O(1)
双指针法的时间复杂度:O(n)
双指针法的空间复杂度:O(1)
显然双指针法优,且双指针法的思路有很多可扩展移植之处,如链表、字符串、数组等。
代码随想录文章讲解:传送门
代码随想录视频讲解:传送门