二分查找法(C语言版本)
二分查找法
力扣题目:704.二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
1 | 输入: nums = [-1,0,3,5,9,12], target = 9 |
示例 2:
1 | 输入: nums = [-1,0,3,5,9,12], target = 2 |
思路
这道题目的前提是==数组为有序数组==,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。
大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。
写法(左闭右闭写法):
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。
区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
代码如下:(详细注释)
1 | int search(int* nums, int numsSize, int target){ |
相关题目推荐
力扣题目:35.搜索插入位置
题目描述:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
1 | 输入: nums = [1,3,5,6], target = 5 |
示例 2:
1 | 输入: nums = [1,3,5,6], target = 2 |
示例 3:
1 | 输入: nums = [1,3,5,6], target = 7 |
题解:
1 | int searchInsert(int* nums, int numsSize, int target){ |
力扣题目:34.在排序数组中查找元素的第一个和最后一个位置
题目描述:
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
示例 1:
1 | 输入:nums = [5,7,7,8,8,10], target = 8 |
示例 2:
1 | 输入:nums = [5,7,7,8,8,10], target = 6 |
示例 3:
1 | 输入:nums = [], target = 0 |
题解:
解法一(自己写的,虽然与二分查找法没什么关系,但感觉好想一些,但这个方法的时间复杂度为 O(n),没有利用到数组升序排列的条件.)
1 | int* searchRange(int* nums, int numsSize, int target, int* returnSize) { |
解法二(二分查找法)
思路:
直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 target\textit{target}target 的下标,但这个方法的时间复杂度为 O(n)O(n)O(n),没有利用到数组升序排列的条件。
由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。
考虑 target\textit{target}target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target\textit{target}target 的位置」(记为 leftIdx\textit{leftIdx}leftIdx)和「第一个大于 target\textit{target}target 的位置减一」(记为 rightIdx\textit{rightIdx}rightIdx)。
二分查找中,寻找 leftIdx\textit{leftIdx}leftIdx 即为在数组中寻找第一个大于等于 target\textit{target}target 的下标,寻找 rightIdx\textit{rightIdx}rightIdx 即为在数组中寻找第一个大于 target\textit{target}target 的下标,然后将下标减一。两者的判断条件不同,为了代码的复用,我们定义 binarySearch(nums, target, lower) 表示在 nums\textit{nums}nums 数组中二分查找 target\textit{target}target 的位置,如果 lower\textit{lower}lower 为 true\rm truetrue,则查找第一个大于等于 target\textit{target}target 的下标,否则查找第一个大于 target\textit{target}target 的下标。
最后,因为 target\textit{target}target 可能不存在数组中,因此我们需要重新校验我们得到的两个下标 leftIdx\textit{leftIdx}leftIdx 和 rightIdx\textit{rightIdx}rightIdx,看是否符合条件,如果符合条件就返回 [leftIdx,rightIdx][\textit{leftIdx},\textit{rightIdx}][leftIdx,rightIdx],不符合就返回 [−1,−1][-1,-1][−1,−1]。
1 | int binarySearch(int* nums, int numsSize, int target, bool lower) { |
力扣题目:69.X的平方根
题目描述:
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
示例 1:
1 | 输入:x = 4 |
示例 2:
1 | 输入:x = 8 |
题解:
方法一(暴力求解):
1 | int mySqrt(int x){ |
方法二(二分查找):
1 | int mySqrt(int x){ |
力扣题目:367.有效的完全平方数
题目描述:
给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
不能使用任何内置的库函数,如 sqrt 。
示例 1:
1 | 输入:num = 16 |
示例 2:
1 | 输入:num = 14 |
题解:
1 | bool isPerfectSquare(int num){ |
