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

对有序数组进行查找使用二分查找法 | 二分查找的关键:循环不变量

704. 二分查找

链接

题目链接

https://leetcode.cn/problems/binary-search/

文章讲解链接

https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html

视频讲解链接

https://www.bilibili.com/video/BV1fA4y1o715

做题思路

第一想法

由于本题已说明数组为升序且数组中无重复值,因此二分查找是时间复杂度最低的解决方式,二分查找难点主要在确认边界上,主要有两种:左闭右闭和左闭右开

解法

  • 左闭右闭
    个人倾向于这种解法,更符合本人的思维逻辑,循环逻辑:left <= right 当val < nums[mid]时,right = mid - 1;当val > nums[mid]时left = mid + 1;
  • 左闭右开
    循环逻辑:left < right; 当val < nums[mid]时 right = mid(因为时左闭右开,如果是mid-1会导致一些值取不到)

细节

为防止计算溢出,采用mid = left + (right - left) / 2;

代码

解法一 左闭右闭

class Solution {
    public int search(int[] nums, int target) {
        if(nums[0] > target || nums[nums.length - 1] < target){
            return -1;
        }
        int left = 0;
        int right = nums.length - 1;
        int mid;
        while(left <= right){
            mid = left + (right - left) / 2;
            if(nums[mid] < target){
                left = mid + 1;
            }else if(nums[mid] > target){
                right = mid - 1;
            }else{
                return mid;
            }
        }
        return -1;
    }
}

解法二 左闭右开

class Solution {
    public int search(int[] nums, int target) {
        if(nums[0] > target || nums[nums.length - 1] < target) return -1;
        int left = 0;
        int right = nums.length;
        int mid;
        while(left < right){
            mid = (left + right) / 2;
            if(nums[mid] < target){
                left = mid + 1;
            }else if(nums[mid] > target){
                right = mid;
            }else{
                return mid;
            }
        }
        return -1;
    }
}

35.搜索插入位置

链接

题目链接

https://leetcode.cn/problems/search-insert-position/

文章讲解链接

https://programmercarl.com/0035.%E6%90%9C%E7%B4%A2%E6%8F%92%E5%85%A5%E4%BD%8D%E7%BD%AE.html#%E6%80%9D%E8%B7%AF

思路

解法一:brute force

这道题暴力循环效率也很高,因为数组是已排序的,只要找到第一个大于target的数,那就说明这就是要插入target的位置
时间复杂度:O(n)
空间复杂度:O(1)

class Solution{
    public int searchInsert(int[] nums, int target){
        for(int i = 0; i < nums.length; i++){
            if(nums[i] >= target){
                return i;
            }
        }
        return nums.length;
    }
}

解法二:二分查找

已排序就可用二分查找
时间复杂度:O(logn)
空间复杂度:O(1)
与找数字一样,返回值需考虑多种情况

  • target比数组中所有数字都小
  • target比数组中所有数字都大
  • 数组中存在target
  • 在数组中间插入
// 左闭右闭
class Solution {
    public int searchInsert(int[] nums, int target) {
        int right = nums.length - 1;
        int left = 0;
        int mid = 0;
        while(left <= right){
            mid = left + (right - left) / 2;
            if(nums[mid] > target){
                right = mid - 1;
            }else if (nums[mid] < target){
                left = mid + 1;
            }else{
                return mid;
            }
        }
        // return nums[mid] > target? mid:mid+1;
        return right + 1; 
        // 1. 如果最后查找的数字大于target,那么right = mid - 1,而插入位置是mid,所以返回right + 1;
        // 2. 如果最后查找的数字小于target,那么right = mid, 而插入位置是mid + 1, 所以返回right + 1;
    }
}
// 左闭右开
class Solution {
    public int searchInsert(int[] nums, int target) {
        int right = nums.length;
        int left = 0;
        int mid = 0;
        while(left < right){
            mid = left + (right - left) / 2;
            if(nums[mid] > target){
                right = mid;
            }else if (nums[mid] < target){
                left = mid + 1;
            }else{
                return mid;
            }
        }
        return right;
    }
}

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

题目链接

https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/

文章讲解链接

https://programmercarl.com/0034.%E5%9C%A8%E6%8E%92%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9F%A5%E6%89%BE%E5%85%83%E7%B4%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E4%B8%AA%E4%BD%8D%E7%BD%AE.html#%E6%80%9D%E8%B7%AF

该题是在已排序数组中找元素位置(元素有重复值)

  • 解法一: 先用二分查找改元素在不在这个数组,如果不在数组就返回{-1, -1};如果数组中存在这个数字,那就在找到这个数字的位置向左向右遍历,找左右位置
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] res = {-1, -1};
        int left = 0;
        int right = nums.length;
        int mid;
        while (left < right) {
            mid = left + (right - left) / 2;
            if (nums[mid] > target) {
                right = mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else {
                res[0] = mid;
                res[1] = mid;
                int leftA = mid - 1;
                int rightA = mid + 1;
                while (leftA >= left && nums[leftA] == target) {
                    res[0] = leftA--;
                }
                while (rightA < right && nums[rightA] == target) {
                    res[1] = rightA++;
                }
                return res;
            }
        }
        return res;
    }
}
  • 解法二:用二分查找找左右位置
    先找左右边界,eg找右边界,如果找到nums[mid] == target, 那就判断一下是否已是最后一位或者右边的是否等于target,如果等于,那就接着二分找,如果不等于就返回mid,找完数组都没有target返回负一
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int left = searchLeft(nums, target);
        int right = searchRight(nums, target);
        return new int[]{left, right};
    }

    // 找目标值右边的那位数
    public int searchRight(int[] nums, int target){
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = left + (right - left) / 2;
            if(nums[mid] > target){
                right = mid - 1;
            }else if(nums[mid] < target){
                left = mid + 1;
            }else{
                if(mid == nums.length - 1 || nums[mid + 1] != target){
                    return mid;
                }else{
                    left = mid + 1;
                }

            }
        }
        return -1;
    }

     // 找目标值左边的那位数
    public int searchLeft(int[] nums, int target){
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = left + (right - left) / 2;
            if(nums[mid] > target){
                right = mid - 1;
            }else if (nums[mid] < target){
                left = mid + 1;
            }else{
                if(mid == 0 || nums[mid - 1] != target){
                    return mid;
                }else{
                    right = right - 1;
                }
            }
        }
        return -1;
    }
}

27. 移除元素

链接

题目链接

https://leetcode.cn/problems/remove-element/

文章讲解链接

https://programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html

视频讲解链接

https://www.bilibili.com/video/BV12A4y1Z7LP

解题思路

第一想法

双指针的妙用,其实慢指针相当于一个mark位

代码块

自己写的

class Solution {
    public int removeElement(int[] nums, int val) {
        int mark = nums.length - 1;
        for(int i = 0; i <= mark; i++){
            if(nums[i] == val){
                nums[i] = nums[mark];
                mark--;
                i--;
            }
        }
        return mark + 1;
    }
}

看了卡尔写的

class Solution {
    public int removeElement(int[] nums, int val) {
        int right = nums.length;
        int slow = 0;
        for(int fast = 0; fast < right; fast++){
            if(val != nums[fast]){
                nums[slow++] = nums[fast];
            }
        }
        return slow;
    }
}

今日困难

一年没刷题了,手有点生疏,代码能力也稍有弱化,继续加油,60天后蜕变。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值