1.二分法

0、絮絮叨叨

守护全网讲的最好的二分法博主
注:本文用JavaScript写的~

1、伪代码

//二分查找伪代码模版
let left = 0, right = length - 1;while (left <= right) {②
  mid = (left + right) / 2;if (array[mid] === target) return result;
  else if (array[mid] < target) left = mid + 1;else if (array[mid] > target) right = mid - 1;
}
return -1;

2、容易出错的地方

1.right = length  还是 length - 1?
答:取哪个对应的是相应的搜索区间
比如取right = length - 1;对应区间[0, length - 1],后边left = mid + 1; 
比如取right = length;对应区间[0, length)。本质是一样的,后边left = mid;

2.whild(left <= right)<=< 有什么区别?
答1:因为初始化 right 的赋值是 nums.length - 1,即最后一个元素的索引,而不是nums.length,所以可以等于right。
这二者可能出现在不同功能的二分查找中,区别是:前者 (<=)相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。
答2:还有一种时候就是 left = right 的时候有意义,比如153<<=  写出来的结果是不一样的

3.mid = (left + right) / 2  这么写为啥不好?
答:用位运算最好:mid = left + ((right - left) >> 1)  速度快,记得加括号,是弱运算

4.为啥 left = mid + 1?1:因为我每次搜的都是闭区间(right = length - 1),所以每次循环到left或者right的时候,说明nums[mid] !== target,mid对应的数都不对,都不对了我还取left或者right=mid干啥,所以就取 left = mid + 1, 或者 right = mid - 1;
答2:left等于mid或者是mid+1,就看nums[mid] 的取值有没有意义,能不能是最终答案,要能是的话就left = mid

5.最后返回的是right还是left?
答1:看题,最后的求的结果一定在答案附近,看看是取左边还是右边,比如35题就取左边(较大的一个),69就取右边(较小的一个)
答2:题做多了,返回的是啥还真不一定,看题目要求的是啥。


6,在做了一些题之后的感受11)有时候会把target变成动态的 比如:153
2)有时候会把比较的双方都变成动态的,但是if判断条件里总有nums[mid] 比如:153
3)有时候会是二维矩阵,你要拆开来比较  比如:1351
4)有的时候二分法不是那么明显,需要一系列数学公式的引导 比如:441
5)有时候所有的部分都是变量,很绕 :153 ,33
6)有时候比较的区间需要先判断比如,153,33


7.感受2
想要用二分法做题,最重要的就是知道  f(mid)   和  target  之间的关系,这两个都有可能都是动态的,而且还得找一下才有。

3、三种类型的模板*(后边做多了,发现都是类型一的变形)*

类型1:正常查找,找到了就返回,没找到就返回-1

function binary_search(nums, target) {
    let left = 0, right = nums.length - 1;
    while(left <= right) {
        let mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else if(nums[mid] == target) {
            return mid; //直接返回
        }
    }
    return -1; // 直接返回
}

类型2:left = *** eg:找数组[1,2,2,2,3]最右边的2的位置序号

function right_bound(nums, target) {
  let left = 0, right = nums.length - 1;
  while (left <= right) {
    let mid = left + (right - left) / 2;
     if (nums[mid] == target) {
      left = mid + 1; // 这里改成收缩左侧边界即可
    } else if (nums[mid] < target) {
      left = mid + 1;
    } else if (nums[mid] > target) {
      right = mid - 1;
    }
  }
  // 这里改为检查 right 越界的情况,见下图
  if (right < 0 || nums[right] != target) return -1;
  return right;
}

类型3:right = *** eg:找数组[1,2,2,2,3]最左边的2的位置序号

function left_bound(nums, target) {
  let left = 0, right = nums.length - 1;
  while (left <= right) {
    let mid = left + (right - left) / 2;
     if (nums[mid] == target) {
      right = mid - 1; // 这里改成收缩右侧边界即可
    } else if (nums[mid] < target) {
      left = mid + 1;
    } else if (nums[mid] > target) {
      right = mid - 1;
    }
  }
  // 最后要检查 left 越界的情况,越界情况可以看上边链接详情
  if (left >= nums.length || nums[left] != target) return -1;
  return left;
}

4、例题

类型1:大多数都是这个最基础类型的变形

1)704. 二分查找:最正常的
// 递归
var search = function (nums, target) {
    return _search(nums, target, 0, nums.length - 1)
};
var _search = function (nums, target, left, right) {
    if (left > right) return -1;
    let mid = Math.floor((left + right) / 2);
    if (nums[mid] === target) {
        return mid
    }
    else if (nums[mid] < target) {
        return _search(nums, target, mid + 1, right)
    } else {
        return _search(nums, target, left, right - 1)
    }
}
//非递归,后来发现这才是常用的方法
var search = function (nums, target) {
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        let mid = Math.floor((left + right) / 2);
        if (nums[mid] === target) {
            return mid
        } else if (nums[mid] < target) {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return -1
}
2)374. 猜数字大小:也挺正常的
var guessNumber = function (n) {
    let left = 1, right = n;//left 是从 1 开始的
    while (left <= right) {
        let mid = left + ((right - left) >> 1);
        // guess 是个函数 所以输入的是参数,用小括号
        if (guess(mid) == 0) {
            return mid
        } else if (guess(mid) == 1) {
            left = mid + 1;
        } else if (guess(mid) == -1) {
            right = mid - 1;
        }
        // return -1   //return 放在这里不对
    }
    return -1
};
3)852:山脉数组:mid和mid+1、mid-1之间比较
var peakIndexInMountainArray = function(arr) {
    let left = 0, right = arr.length - 1;
    while(left <= right){
        let mid = left + ((right - left) >> 1);
        if( arr[mid - 1] < arr[mid] && arr[mid] > arr[mid + 1]){
            // 上边这块不能连着写 会错  arr[mid + 1]  < arr[mid] < arr[mid + 1]
            return mid
        }else if(arr[mid] < arr[mid + 1]){
            left = mid + 1
        }else if(arr[mid] > arr[mid + 1]){
            right = mid -1
        }
    }
    return -1  
    // 这个return 写不写都行 没啥用 因为就给的条件而言一定会返回
};
4)35. 搜索插入位置:正常的思考,在多一点思考
var searchInsert = function(nums, target) {
    let left = 0, right = nums.length - 1;
    while(left <= right){
        let mid = left + ((right - left) >> 1);
        if(nums[mid] == target){
            return mid //这个也是类型1的变形
        }else if(nums[mid] < target){
            left = mid + 1;
        }else if(nums[mid] > target){
            right = mid - 1;
        }
    }
    return left //代码中,0代表位置1,所以返回大的那个
};
5)1385. 两个数组间的距离值:题目描述很傻屌。涉及到二维了已经
//距离值的意思是说在arr1中符合特定条件的元素数量,什么特定条件呢?
//这个特定条件就是 arr1中的这个元素和arr2中任何一个元素相减再求绝对值后都是大于d的.
var findTheDistanceValue = function (arr1, arr2, d) {
    // 用二分法先排序
    arr1.sort((a, b) => a - b);
    arr2.sort((a, b) => a - b);

    let ans = 0;
    // 对于每个arr1,arr2的所有值都拿来对比
    for (let i = 0; i < arr1.length; i++) {
        let left = 0, right = arr2.length - 1;
        while (left <= right) {
            // 这里其实取巧了,arr2 数量大的时候,有一些值是被忽略的,不过不影响最终结果
            // 因为只要有一个错误,就break 了
            let mid = left + ((right - left) >> 1);
            if (arr2[mid] <= arr1[i] + d && arr2[mid] >= arr1[i] - d) {
                break // 进入到这个圈内就是失败的意思,break了之后,下边的ans++不会触发
            } else if (arr2[mid] + d < arr1[i]) {
                left = mid + 1; // 因为已经排序好了,第二个数组加上间距还够不着第一个数组,那就让第二个数组往右移
            } else if (arr2[mid] - d > arr1[i]) {
                right = mid - 1;
            }
        }
        // while能结束 说明是正确的了  计数器 加1
        if (left > right) {
            ans++
        }
    }
    return ans
};
6)367. 有效的完全平方数:就跟做数学题一样,看完答案大明白
var isPerfectSquare = function(num) {
    let left = 0, right = num ;
    while(left <= right){
        let mid = left + ((right - left) >> 1);
        if(mid * mid == num){
            return true
        }else if(mid *mid  < num ){
            left = mid + 1;
        }else if (mid * mid  > num ){
            right = mid - 1;
        }
    }
    return false
};
7)69. x 的平方根
var mySqrt = function(x) {
    let left = 0, right = x;
    while(left <= right){
        let mid = left + ((right - left) >> 1)
        if(mid*mid === x){ //  mid === x/mid :这么写  x是0时不行
            return mid
        }else if(mid < x/mid){
            left = mid + 1;
        }else {
            right = mid -1;
        }
    }
    return right
};
  • 问:为什么return right
  • 答:跳出来的时候一定是在平方根附近的,最后判断一下如果平方大于x的话就返回它前面的一个值
8)35. 搜索插入位置
var searchInsert = function (nums, target) {
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        let mid = left + ((right - left) >> 1);
        if (nums[mid] === target) {
            return mid
        } else if (nums[mid] < target) {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return left
};
  • 问:为什么要return left
  • 答:返回的时候一定是在结果附近的,返回的时候 左比右多一,又数组下标的特性 0 表示位置1,所以返回的是left

9)278:第一个错误的版本

var solution = function(isBadVersion) {
    /**
     * @param {integer} n Total versions
     * @return {integer} The first bad version
     */
    return function(n) {
        // 左等于0或者1都没有问题,但是这里为了有实际意义,我让他等于1
        let left = 1, right = n; 
        while(left <= right){
            let mid = left + ((right - left) >> 1);
            if(isBadVersion(mid) == false) {
                left = mid + 1;// false 是版本低了 双否表肯定
            }else if(isBadVersion(mid) == true){
                right = mid - 1;
            }
        }// 可以找个栗子试一下 为什么返回left
        return left
    };
};

10)441. 排列硬币:有的时候可能需要用含有mid的式子和target比较

var arrangeCoins = function(n) {
    let left = 0, right = n;
    while(left <= right){
        let mid = left + ((right - left) >> 1);
        // 这块是我看答案看的 真没想到,但是我发现了,要用二分法需要有两个值
        // 进行比较  这两个值可能有一静一动,也有可能两个都是动态的
        let now_num = ((mid + 1)*mid)/2;
        if(now_num == n){
            return mid
        }else if(now_num < n){
            left = mid + 1;
        }else if(now_num > n){
            right = mid - 1;
        }
    }
    return right // 这块我是举一个具体的例子搞的
};

11)167. 两数之和 II - 输入有序数组:这题就是需要自己弄一个含有mid的表达式和target比较。

//题目的意思就是从一个数组中找个两个数,使之和等于target
var twoSum = function (numbers, target) {
    // 固定一个,找另一个   n^2
    for (let i = 0; i < numbers.length - 1; i++) {
        let left = i + 1, right = numbers.length - 1;
        while (left <= right) {
            let mid = left + ((right - left) >> 1);
            if (numbers[i] + numbers[mid] == target) {
                return [i+ 1, mid + 1]
            } else if (numbers[i] + numbers[mid] < target) {
                left = mid + 1;
            } else if (numbers[i] + numbers[mid] > target) {
                right = mid - 1;
            }
        }
    }
};

12.1608:特殊数组的特征值:挺抽象的一道题

var specialArray = function (nums) {
    // 这个题就只能取right = nums.length 
    let left = 0, right = nums.length;
    while (left <= right) {
        let mid = left + ((right - left) >> 1);
        if (mid == bigger(mid, nums)) {
            return mid
        } else if (mid < bigger(mid, nums)) {
            left = mid + 1;
        } else if (mid > bigger(mid, nums)) {
            right = mid - 1;
        }
    }
    return -1
};

// 这个函数的作用是,看看在nums这个数组里大于n的有几个,返回数字 
var bigger = function (n, num) {
    let result = 0;
    for (let i = 0; i < num.length; i++) {
        if (num[i] >= n) {
            result++
        }
    }
    return result
}

13)1351:统计有序矩阵中的负数

var countNegatives = function (grid) {
    // 第二步:一共m行数组,for循环累加
    let sum = 0, m = grid.length;
    for (let i = 0; i < m; i++) {
        sum = sum + findFirstNegative(grid[i])
    }
    return sum
};

// 第一步:先判断一个数组:查找一个递减数组小于0的个数
var findFirstNegative = function (row) {
    let left = 0, right = row.length - 1, count = 0;
    while (left <= right) {
        let mid = left + ((right - left) >> 1);
        if (row[left] < 0) {
            count++;
            left++;
        } else if (row[mid] <= 0) {
            left ++
        } else if (row[mid] >= 0) {
            left = mid + 1;
        }
    }
    return count
};

14)74:搜索二维矩阵:题目的意思是看看矩阵里有没有target,送分题

var searchMatrix = function (matrix, target) {
    // 第一步,把二维变成一维的arr数组
    let m = matrix.length, n = matrix[0].length;
    let arr = [];
    for (let i = 0; i < m; i++) {
        // concat不会改变原来的数组,所以需要重新赋值
        arr = arr.concat(matrix[i])
    }

    // 第二步,正常的二分法
    let left = 0, right = arr.length - 1;
    while(left <= right){
        let mid = left + ((right - left) >> 1);
        if(arr[mid] == target){
            return true
        }else if(arr[mid] < target){
            left = mid + 1;
        }else if (arr[mid] > target){
            right = mid - 1
        }
    }
    return false
};

15)633:平方数之和

var judgeSquareSum = function (c) {
	// 这也是一到二分法的题,只不过这个数组需要你自己想
    let a = 0, b = Math.floor(Math.sqrt(c));
    while (a <= b) {
        let cur = a * a + b * b;
        if (cur === c) {
            return true
        } else if (cur < c) {
            a++
        } else if (cur > c) {
            b--
        }
    }
    return false
};
// 关于为什么每次左加一以及右减一不会错过正确答案的问题,参考:
// https://leetcode.cn/problems/sum-of-square-numbers/solution/shuang-zhi-zhen-de-ben-zhi-er-wei-ju-zhe-ebn3/

16)153. 寻找旋转排序数组中的最小值

var findMin = function (nums) {
    // 法一:暴力枚举法
    let min = nums[0];
    for(let i = 1; i< nums.length; i++){
        if(nums[i] < min){
            min = nums[i]
        }
    }
    return min

    // 法二:
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        let mid = left + ((right - left) >> 1);
        if (nums[mid] > nums[right]) {
            left = mid + 1   // 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid
        } else if (nums[mid] < nums[right]) {
            right = mid     //  因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处
        } else if (nums[mid] == nums[right] && nums[mid] == nums[left]) {
            return nums[left]     //  等于的时候,其实就找出答案了,此时左和右一样
        }
    }
    // return nums[right] 这个返回也就没必要了


    //法二的变形
    let left = 0, right = nums.length - 1;
    while (left < right) {// 这题如果判断条件加一个=号,当中值和右值相等的时候,就是结果
        let mid = left + ((right - left) >> 1);
        if (nums[mid] > nums[right]) {
            left = mid + 1   // 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid
        } else if (nums[mid] < nums[right]) {
            right = mid     //  因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处
        }
    }
    return nums[right]
};

17)33. 搜索旋转排序数组,这个是153的升级版

var search = function (nums, target) {
    let left = 0, right = nums.length - 1;
    while (left <= right) {
        const mid = Math.floor(left + (right - left) / 2)
        if (nums[mid] === target) {
            return mid

        } else if (nums[left] <= nums[mid]) {// 如果nums[left] <= nums[mid],说明是左半段是有序的
            if (nums[left] <= target && target < nums[mid]) { // nums[mid]的等于号可以省略,因为等于的时候就退出了
                right = mid - 1// 此时有两种情况,若target在左递增区间,则排除mid右边的所有值
            } else if (target < nums[left] || target > nums[mid]) {
                left = mid + 1// 若不在则排除mid左边的所有值
            }
            
        } else if (nums[mid] <= nums[right]) {// 如果nums[mid]<=nums[right],说明是右半段是有序的
            if (nums[mid] < target && target <= nums[right]) {
                left = mid + 1// 此时有两种情况,若target在右递增区间,则排除mid左边的所有值
            } else if (target < nums[mid] || target > nums[right]) {
                right = mid - 1// 若不在则排除mid右边的所有值。
            }
        }
    }
    return -1
};

类型2:相等的时候,left = ***

1)744. 寻找比目标字母大的最小字母

var nextGreatestLetter = function (letters, target) {
    let left = 0, right = letters.length - 1;
    while (left <= right) {
        let mid = left + ((right - left) >> 1);
        if (letters[mid] == target) {
            left = mid + 1 // 相等的时候,说明差不多就到中间了
        } else if (letters[mid] < target) {
            left = mid + 1
        } else if (letters[mid] > target) {
            right = mid - 1
        }
    }
    // return letters[left % letters.length] // 另一种思路
     return left == letters.length ? letters[0] : letters[left];
};

类型3:相等的时候,right = ***

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

var searchRange = function (nums, target) {

    var right_fun = function (nums, target) {
        let left = 0, right = nums.length - 1;
        let rightmax = -1;// 这块的思路挺好的 我想了一大气
        while (left <= right) {
            let mid = left + ((right - left) >> 1);
            if (nums[mid] == target) {
                rightmax = mid;
                left = mid + 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            }
        }
        return rightmax
    }



    var  left_fun = function (nums, target) {
        let left = 0, right = nums.length - 1;
        let leftmin = -1;
        while (left <= right) {
            let mid = left + ((right - left) >> 1);
            if (nums[mid] == target) {
                leftmin = mid;
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            }
        }
        return leftmin
    }

    return [left_fun(nums, target), right_fun(nums, target)]
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
a. 二分法: 取区间 [0,1],中点为 0.5,f(0.5) = e^0.5 + 0.5 - 2 = -0.010986 因为 f(0) = e^0 - 2 < 0,f(1) = e + 1 - 2 = e - 1 > 0 所以根在区间 [0,0.5] 中,取中点 0.25,f(0.25) = e^0.25 + 0.25 - 2 = 0.133968 所以根在区间 [0.25,0.5] 中,取中点 0.375,f(0.375) = e^0.375 + 0.375 - 2 = 0.054383 所以根在区间 [0.375,0.5] 中,取中点 0.4375,f(0.4375) = e^0.4375 + 0.4375 - 2 = 0.021279 所以根在区间 [0.4375,0.5] 中,取中点 0.46875,f(0.46875) = e^0.46875 + 0.46875 - 2 = 0.005126 所以根在区间 [0.46875,0.5] 中,取中点 0.484375,f(0.484375) = e^0.484375 + 0.484375 - 2 = -0.002939 所以根在区间 [0.46875,0.484375] 中,取中点 0.4765625,f(0.4765625) = e^0.4765625 + 0.4765625 - 2 = 0.001096 所以根在区间 [0.4765625,0.484375] 中,取中点 0.48046875,f(0.48046875) = e^0.48046875 + 0.48046875 - 2 = -0.000923 所以根在区间 [0.4765625,0.48046875] 中,取中点 0.478515625,f(0.478515625) = e^0.478515625 + 0.478515625 - 2 = 0.000086 所以根在区间 [0.478515625,0.48046875] 中,取中点 0.4794921875,f(0.4794921875) = e^0.4794921875 + 0.4794921875 - 2 = -0.000418 所以根在区间 [0.478515625,0.4794921875] 中,取中点 0.47899890625,f(0.47899890625) = e^0.47899890625 + 0.47899890625 - 2 = -0.000166 所以根在区间 [0.478515625,0.47899890625] 中,取中点 0.478757265625,f(0.478757265625) = e^0.478757265625 + 0.478757265625 - 2 = -0.000040 所以根在区间 [0.478515625,0.478757265625] 中,取中点 0.4786364453125,f(0.4786364453125) = e^0.4786364453125 + 0.4786364453125 - 2 = 0.000023 所以根在区间 [0.4786364453125,0.478757265625] 中,取中点 0.47869685546875,f(0.47869685546875) = e^0.47869685546875 + 0.47869685546875 - 2 = -0.000008 所以根在区间 [0.4786364453125,0.47869685546875] 中,取中点 0.478666650390625,f(0.478666650390625) = e^0.478666650390625 + 0.478666650390625 - 2 = 0.000008 所以根在区间 [0.478666650390625,0.47869685546875] 中,取中点 0.4786817529296875,f(0.4786817529296875) = e^0.4786817529296875 + 0.4786817529296875 - 2 = 0.000000 所以根在区间 [0.4786817529296875,0.47869685546875] 中,取中点 0.47868930419921875,f(0.47868930419921875) = e^0.47868930419921875 + 0.47868930419921875 - 2 = -0.000004 所以根在区间 [0.4786817529296875,0.47868930419921875] 中,取中点 0.4786855285644531,f(0.4786855285644531) = e^0.4786855285644531 + 0.4786855285644531 - 2 = -0.000002 因此,根为 0.47868(准确到小数点后5位)。 b. 不动点迭代法: 将 f(x) = 0 改写成 x = g(x) 的形式,即 x = 2 - e^x。 取 x0 = 1,迭代公式为 xn+1 = 2 - e^xn。 迭代计算如下: n=0: x1 = 2 - e^1 = 0.718281828459045 n=1: x2 = 2 - e^0.718281828459045 = 0.537882842739990 n=2: x3 = 2 - e^0.537882842739990 = 0.483336916719046 n=3: x4 = 2 - e^0.483336916719046 = 0.478942062213334 n=4: x5 = 2 - e^0.478942062213334 = 0.478719267536133 n=5: x6 = 2 - e^0.478719267536133 = 0.478690047764798 n=6: x7 = 2 - e^0.478690047764798 = 0.478685669215687 n=7: x8 = 2 - e^0.478685669215687 = 0.478684639576916 因此,根为 0.47868(准确到小数点后5位)。 c. 牛顿法: f(x) 的导数为 f'(x) = e^x + 1。 取 x0 = 1,迭代公式为 xn+1 = xn - f(xn)/f'(xn)。 迭代计算如下: n=0: x1 = 1 - (e^1 + 1)/(e^1 + 1) = 0.632120558828558 n=1: x2 = 0.632120558828558 - (e^0.632120558828558 + 0.632120558828558 - 2)/(e^0.632120558828558 + 1) = 0.489386010416507 n=2: x3 = 0.489386010416507 - (e^0.489386010416507 + 0.489386010416507 - 2)/(e^0.489386010416507 + 1) = 0.478969489214822 n=3: x4 = 0.478969489214822 - (e^0.478969489214822 + 0.478969489214822 - 2)/(e^0.478969489214822 + 1) = 0.478684977394539 因此,根为 0.47868(准确到小数点后5位)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值