代码随想录打卡day1- 704 二分查找,27 移除元素

862 阅读2分钟

题目链接:

704.二分查找

27.移除元素

704. 二分查找

题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

解题思路: 数组有序(升序排列)且无重复元素,适合使用二分法求解。

遇到的难点:判断循环条件,选择区间边界

二分法的原理是清晰简单的,问题在于如何设定循环条件和适当的循环边界。

  • 解法1 左闭右闭区间内,寻找target [left,right]
var search = function(nums, target) {
    let left = 0
    let right = nums.length - 1        // 1
    let mid 
    while(left <= right){              //  2
        mid = left + Math.floor((right-left)/2)   // *
        if(target > nums[mid]){
            left = mid + 1
        }else if (target < nums[mid]){
            right = mid - 1          //3
        }else{
            return mid
        }
    }
    return -1 
};
  • 解法2 左闭右开区间内,寻找target [left,right)
var search = function(nums, target) {
    let left = 0
    let right = nums.length      //  1
    let mid 
    while(left < right){          //2
        mid = left + Math.floor((right-left)/2)
        if(target > nums[mid]){
            left = mid + 1
        }else if (target < nums[mid]){
            right = mid          // 3
        }else{
            return mid
        }
    }
    return -1 
};

解法1 与 解法2 的区别在于,对于不同的判断区间,其循环的条件不同(对应代码注释2),二分法左右两边的初值也不同(注释1),确定新边界的方法不同(注释3)。

当 target 在 闭区间 [left,right]中时,left 有机会等于right, 循环条件left <= right, right的初始值为数组的最后一个元素的索引;且如果target 不在闭区间时,其也必定不在区间边界上面,所以新的 right = mid -1 ,left = mid + 1

同理可得到target 在左闭右开区间时,left 永不等于right, 循环条件left < right, right的初始值为数组长度;且如果target 不在区间时,可能会在右边界right开始的左闭右开区间中,所以新的 right = mid, left = mid + 1

在代码注释 * 的一行中,是在运算中的一个防止溢出操作。

可能会有这种状况,left 和 right 在进行相加之后,得到的结果可能会溢出;但是如果相减,就减小了溢出的可能

由求left right平均值 -> left index + 中间段距离

     

27. 移除元素

题目描述:给你一个数组 nums 和一个值 val,要求 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。
例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案

初始解题思路

使用两个指针,一快一慢遍历数组,在遇到 val 值在前而其他值在后面时,将二者进行交换,最后得到的结果是 val 值在数组的最后几个位置,其他数字按原有顺序排列在数组前部。

var removeElement = function(nums, val) {
    let left = 0
    let right = 1
    let count = 0
    while(right < nums.length){
        if(nums[left]!= val && nums[right]!= val ){
            left ++ 
            right ++
        }else if(nums[left] == val && nums[right] != val){
            nums[left] = nums[right]
            nums[right] = val
        }else if(nums[left] == val && nums[right] == val){
            right ++
        }else if(nums[left] != val && nums[right] == val){
            left ++
            right ++
        }
    }
    let len = nums.length
    while(nums[nums.length-1] == val){
        count ++
        nums.pop()
    }
    return len - count
};

缺点:操作复杂,完全没必要进行交换保留,直接按序替换即可,所以有双指针思路

双指针思路:

设置快慢指针 slow 和 fast, fast用于遍历数组,同步值不为 val 的元素到慢指针处,待到fast将数组遍历完成,值不为val 的数字被“移动”到了数组前面,最后的slow 返回的就是新数组的长度。

var removeElement = function(nums, val) {
    let slow = 0
    for(let fast=0;fast<nums.length;fast++){
        if(nums[fast] !== val){
            nums[slow] = nums[fast]
            slow ++
        }
    }
    console.log(nums)
    return slow
};