代码随想录算法训练营第一天 | 704. 二分查找,27. 移除元素

98 阅读4分钟

704 二分查找

题目链接:leetcode.cn/problems/bi…

文章讲解:programmercarl.com/0704.%E4%BA…

视频讲解:www.bilibili.com/video/BV1fA…

思路

二分查找,即对于一个有序数组,每次进行区间减半查找,从而减少遍历查找的次数,提高查找效率。难点在于查找区间的确定,通常有两种思路,闭区间和半开区间。(要点思路写在注释里了)

  1. 左闭右闭(闭区间)
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;
    }
}
  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;
    }
}
  1. 时间复杂度:O(log n):

    • 每次执行二分查找时,都会将查找区间缩小为原来的一半。因此,在最坏的情况下,需要进行 log₂n 次操作来达到区间的长度为 1。
  2. 空间复杂度:O(1):

    • 存储空间用于存储三个索引:左边界、右边界和中点,无论输入数组的大小如何,这三个变量的占用空间都保持不变,因此空间复杂度为 O(1)。

补充Leetcode 35和Leetcode 34

35. 搜索插入位置

//题目要求复杂度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;
    }
}

34. 在排序数组中查找元素的第一个和最后一个位置

这道题稍微难一点,查找两个元素,可以看成使用两次二分查找。第一次找第一个等于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.移除元素

题目链接:leetcode.cn/problems/re…

文章讲解: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;
    }
}
  1. 时间复杂度:O(n)

    • fast 指针需要遍历整个数组一次
  2. 空间复杂度: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("目标值未找到");
        }
    }
}