704 二分查找
文章讲解:programmercarl.com/0704.%E4%BA…
视频讲解:www.bilibili.com/video/BV1fA…
思路
二分查找,即对于一个有序数组,每次进行区间减半查找,从而减少遍历查找的次数,提高查找效率。难点在于查找区间的确定,通常有两种思路,闭区间和半开区间。(要点思路写在注释里了)
- 左闭右闭(闭区间)
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length - 1;//最右要取
while(left <= right)//右边取到,所有left = right有意义
{
int mid = left + (right - left) /2;
if(target > nums[mid])// 取右边[mid + 1,right]
{
left = mid + 1;
}
else if (target < nums[mid]) // 取左边[left, mid - 1]
{
right = mid - 1;
}
else
{
return mid;
}
}
return -1;
}
}
- 左闭右开(半开区间)
class Solution {
public int search(int[] nums, int target) {
int left = 0, right = nums.length;//[left, right)最右不取
while(left < right) //右边取不到,left=right没有实际意义
{
int mid = left + (right - left) / 2;
if(target > nums[mid])//取右边[mid + 1 , right)
{
left = mid + 1;
}
else if (target < nums[mid]) // 取左边[left , mid),闭区间对比,这里实际上等于取的[left, mid - 1]
{
right = mid;
}
else
{
return mid;
}
}
return -1;
}
}
-
时间复杂度:O(log n):
- 每次执行二分查找时,都会将查找区间缩小为原来的一半。因此,在最坏的情况下,需要进行 log₂n 次操作来达到区间的长度为 1。
-
空间复杂度:O(1):
- 存储空间用于存储三个索引:左边界、右边界和中点,无论输入数组的大小如何,这三个变量的占用空间都保持不变,因此空间复杂度为 O(1)。
补充Leetcode 35和Leetcode 34
//题目要求复杂度O(log n)的算法本质上就是二分查找,不存在时,返回left==right的位置(对于半开区间返回left)
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right)
{
int mid = left + (right - left)/2;
if(target < nums[mid]) right = mid - 1;
else if (target > nums[mid]) left = mid + 1;
else return mid;
}
return left;
}
}
这道题稍微难一点,查找两个元素,可以看成使用两次二分查找。第一次找第一个等于target的数,第二次找最后一个等于target的数。关键点在于,二分法找到nums[mid]=target后,还需要继续查找。如果是找第一个数,那继续往前找,找最后一个则继续往后找。由于使用两次二分法,所以可以建立一个binarySearch方法,只需要在nums[mid]=target处判断是找的哪一个数,再进行对应区间划分即可。
class Solution {
public int[] searchRange(int[] nums, int target) {
int first = binarySearch(nums, target, true);
int last = binarySearch(nums, target, false);
return new int[]{first,last};
}
public int binarySearch(int[] nums, int target, boolean isFirst)
{
int left = 0, right = nums.length - 1;
int ans = -1;
while(left <= right)
{
int mid = left + (right - left)/2;
if(nums[mid] == target){
ans = mid;
/*关键段*/
if(isFirst) right = mid - 1; //第一个
else left = mid + 1; // 最后一个
/******/
}
else if(nums[mid] < target){
left = mid + 1;
}
else{
right = mid - 1;
}
}
return ans;
}
}
27.移除元素
文章讲解:programmercarl.com/0027.%E7%A7…
视频讲解:www.bilibili.com/video/BV12A…
思路
这是一道经典双指针题目,双指针精髓在于通过一次循环遍历从而达到两层遍历的操作结果。在本题中定义如下两个指针:
fast:用于遍历整个数组。
slow:用于放置不等于val的元素
可以进一步理解为,fast指针遍历过的数组空间已经没用了,这部分空间可以用来存储新的数组元素,而slow是这个新空间的末尾索引,我们通过slow指针覆盖原数组,最后得到结果新数组。
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];
}
}
return slow;
}
}
-
时间复杂度:O(n)
fast指针需要遍历整个数组一次
-
空间复杂度:O(1)
- 算法除了输入的数组外,只使用了常数个额外的变量(
slow,fast,len)。
- 算法除了输入的数组外,只使用了常数个额外的变量(
自我补充:ACM模式输入
今天的例题的核心算法参数列表都是一个数组和一个int值,所以在这里给自己补充一个ACM模式的输入练习。
不知道什么是ACM输入模式的朋友可以看代码随想录的文章:什么是ACM模式?核心代码模式?
二分查找为例
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入数组(由逗号分隔,例如:1,3,5,7,9):");
String input = scanner.nextLine();
String[] numsStr = input.split(",");
int[] nums = new int[numsStr.length];
for (int i = 0; i < nums.length; i++) {
nums[i] = Integer.parseInt(numsStr[i].trim());
}
System.out.println("输入目标查找值:");
int target = scanner.nextInt();
scanner.close();
//二分查找为例
Solution solution = new Solution();
int result = solution.search(nums, target);
if (result != -1) {
System.out.println("目标值索引为:" + result);
} else {
System.out.println("目标值未找到");
}
}
}