按照算法和数据结构进行分类,一起来刷题,用于自己在面试前查漏补缺。我的意向岗位是前端,选择用javascript来刷题,优点是动态语言,语法简单,缺点是遇见复杂数据结构会出现较难的写法,如堆、并查集,每题对应leetcode的题号。本篇是二分查找
专题部分 二分查找 整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
示例 1:
1 2 输入:nums = [4,5,6,7,0,1,2], target = 0 输出:4
示例 2:
1 2 输入:nums = [4,5,6,7,0,1,2], target = 3 输出:-1
示例 3:
1 2 输入:nums = [1], target = 0 输出:-1
提示:
1 <= nums.length <= 5000
-10^4 <= nums[i] <= 10^4
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-10^4 <= target <= 10^4
进阶: 你可以设计一个时间复杂度为 O(log n) 的解决方案吗?
二分查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var search = function (nums, target ) { let left = 0 , right = nums.length - 1 ; while (left <= right){ let mid = (left + right + 1 ) >> 1 ; if (nums[mid] == target) return mid; if (nums[left] < nums[mid]){ if (nums[left] <= target && target < nums[mid]){ right = mid -1 ; } else { left = mid + 1 ; } } else { if (nums[mid] < target && target <= nums[right]){ left = mid + 1 ; } else { right = mid -1 ; } } } return -1 ; };
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 1:
1 2 输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]
示例 2:
1 2 输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1]
示例 3:
1 2 输入:nums = [], target = 0 输出:[-1,-1]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
分别找下边界和上边界,注意两种的判断条件是不同的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 var searchRange = function (nums, target ) { let left1 = 0 , right1 = nums.length - 1 ; while (left1 <= right1) { let mid = left1 + ((right1 - left1) >> 1 ); if (nums[mid] === target) { right1 = mid - 1 ; } else if (nums[mid] > target) { right1 = mid - 1 ; } else if (nums[mid] < target) { left1 = mid + 1 ; } } if (left1 >= nums.length || nums[left1] !== target) { return [-1 , -1 ]; } let left2 = 0 , right2 = nums.length - 1 ; while (left2 <= right2) { let mid = left2 + ((right2 - left2) >> 1 ); if (nums[mid] === target) { left2 = mid + 1 ; } else if (nums[mid] > target) { right2 = mid - 1 ; } else if (nums[mid] < target) { left2 = mid + 1 ; } } return [left1, right2]; };
将两个判断条件写成函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 var searchRange = function (nums, target ) { let left1 = 0 , right1 = nums.length - 1 ; while (left1 <= right1) { let mid = left1 + ((right1 - left1) >> 1 ); if (nums[mid] === target) { right1 = mid - 1 ; } else if (nums[mid] > target) { right1 = mid - 1 ; } else if (nums[mid] < target) { left1 = mid + 1 ; } } if (left1 >= nums.length || nums[left1] !== target) { return [-1 , -1 ]; } let left2 = 0 , right2 = nums.length - 1 ; while (left2 <= right2) { let mid = left2 + ((right2 - left2) >> 1 ); if (nums[mid] === target) { left2 = mid + 1 ; } else if (nums[mid] > target) { right2 = mid - 1 ; } else if (nums[mid] < target) { left2 = mid + 1 ; } } return [left1, right2]; };
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
1 2 3 4 5 6 7 给定 n = 5,并且 version = 4 是第一个错误的版本。 调用 isBadVersion(3) -> false 调用 isBadVersion(5) -> true 调用 isBadVersion(4) -> true 所以,4 是第一个错误的版本。
其实例子已经给了,关键是返回一个函数,该函数接收一个参数n,再进行二分查找判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 var solution = function (isBadVersion ) { return function (n ) { let left = 1 , right = n; while (left < right) { const mid = left + ((right - left) >> 1 ); if (isBadVersion(mid)) { right = mid; } else { left = mid + 1 ; } } return left; }; };
猜数字游戏的规则如下:
每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。
你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):
-1:我选出的数字比你猜的数字小 pick < num
1:我选出的数字比你猜的数字大 pick > num
0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num
返回我选出的数字。
示例 1:
1 2 输入:n = 10, pick = 6 输出:6
示例 2:
示例 3:
示例 4:
提示:
1 <= n <= 231 - 1
1 <= pick <= n
凡是这种不考虑leftbound和rightbound的都还算简单的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var guessNumber = function (n ) { let left = 1 , right = n; while (left < right) { const mid = left + ((right - left) >> 1 ); if (guess(mid) === 0 ) { return mid; } else if (guess(mid) < 0 ) { right = mid - 1 ; } else { left = mid + 1 ; } } return left; };
符合下列属性的数组 arr 称为 山脉数组 :
给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i 。
示例 1:
示例 2:
示例 3:
1 2 输入:arr = [0,10,5,2] 输出:1
示例 4:
示例 5:
1 2 输入:arr = [24,69,100,99,79,78,67,36,26,19] 输出:2
提示:
3 <= arr.length <= 104
0 <= arr[i] <= 106
题目数据保证 arr 是一个山脉数组
进阶: 很容易想到时间复杂度 O(n) 的解决方案,你可以设计一个 O(log(n)) 的解决方案吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var peakIndexInMountainArray = function (arr ) { let left = 0 , right = arr.length - 1 ; while (left < right) { const mid = left + ((right - left) >> 1 ); if (arr[mid - 1 ] > arr[mid]) { right = mid - 1 ; } else if (arr[mid + 1 ] > arr[mid]) { left = mid + 1 ; } else { return mid; } } return left; };