题目链接:
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
};