文章目录
旋转数组:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转
0. 二分法
0.1 用二分的两个前提
-
【顺序存储】比如说数据存放在数组中,可以用索引 O(1) 时间内查找到数据;
-
【有序】数组中的元素是有序的。(包括部分有序)
0.2 步骤
- 先初始化左右两个指针;
while
循环体,其中判断条件一般为left < right
或者left <= right
;- 循环体内,先判断数组长度是否为1、要找的数不在范围内的边界条件;
- 计算中间的指针
int mid = (l + r) / 2
; - 分别讨论左右两边的情况,缩短两个指针的间距,直到退出循环体。
0.3 注意事项
- 在循环体内,缩小边界的时候,为什么有时候是
left = mid + 1
、right = mid - 1
,有时候却知只是left = mid
、right = mid
呢?
>>> 若当前这个数也算在范围内,则不能 ±1,例如 LeetCode 154。
154. 寻找旋转排序数组中的最小值 II
1. 解题思路
本题可以用二分 + 暴力迭代(处理重复问题)。
二分的两个前提:
- 【顺序存储】√
- 【有序】虽说本题只是局部有序,但也足够了,因为我们可以知道 target 是否在有序的那部分里面。√
具体地,
根据数组的首尾元素 left
和 right
得出中间点 mid
。
- 如果
mid
比right
大:说明 分界点在右边,例如[3,4,5,1,2]
; - 如果
mid
比right
小:说明 分界点在左边,例如[5,1,2,3,4]
; - 如果
mid == right
:有重复的数,右指针左移,例如[4,1,4,4,4]
、[4,4,4,1,4]
。
2. 代码(Java实现)
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = (left + right) / 2;
if (nums[mid] > nums[right]) {
left = mid + 1;
} else if (nums[mid] < nums[right]) {
right = mid;
} else { // nums[mid] == nums[right] 的情况
right--;
}
}
return nums[left];
}
153. 寻找旋转排序数组中的最小值
本题是 154 的简化版本,数组里面的元素不会重复。代码实现与上面一样。(去掉最后一个 else
也能通过)
35. 搜索插入位置
这题不是旋转数组,但也用的二分,感觉解法有些类似
-
如果要找的
target
不在数组上下界范围内:return 0
或者nums.length
; -
如果要找的
target
数组中没有,则把left
定在即将要插入的位置上(该位置上的数是比target
大的); -
如果找到了,就直接返回
mid
。
注意:
left = mid + 1
是因为此时的mid
小于target
,所以真正要返回的索引肯定在下一个之后。- 但是
right = mid
是因为,此时的mid
已经大于target
了,很有可能就是我们要找的位置,不能+ 1
。
代码
class Solution {
public int searchInsert(int[] nums, int target) {
if (target <= nums[0]) return 0;
if (target > nums[nums.length - 1]) return nums.length;
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = (left + right) / 2;
if (target > nums[mid]) {
left = mid + 1;
} else if (target < nums[mid]) {
right = mid;
} else {
return mid;
}
}
return left;
}
}
33. 搜索旋转排序数组
找出 target
在旋转数组中的索引是多少,如果没有这个数,则返回 -1 。
注意的点:
- 始终让
mid
去判断是否有target
这个数,两部的指针只用作缩小范围。 while (left <= right)
这里的等于,是为了循环体中的return mid
能够执行。- 由于这里面
mid
如果是我们需要的就在前面return
出去了,所以后面的判断中,mid
不可能是我们要找的数,left
、right
可以放心地 ±1 。
代码
public int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) { // 等于是为了return mid
int mid = (left + right) / 2;
if (nums[mid] == target) return mid; // 先判断 mid 是否为 target
// 以下的判断仅用作缩小范围,但不在里面 return
if (nums[mid] > nums[right]) { // 分界线在右边 ——> 【左边是完整的】
if (nums[left] <= target && target < nums[mid]) { // target 在mid左边
right = mid - 1;
} else { // target 在mid右边
left = mid + 1;
}
} else { // 分界线在左边 ——> 右边是完整的
if (nums[mid] < target && target <= nums[right]) { // target 在mid右边
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
81. 搜索旋转排序数组 II
与前面的题LC 33类似,要找出 target
在旋转数组中的索引是多少,如果没有这个数,则返回 -1 。不同的地方在于,数组中的数可以重复。
解题思路
与153到154类似,这题可以在上面的33题基础上,增加一个 right--
的暴力步骤。
代码
class Solution {
public boolean search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] == target) return true;
if (nums[mid] > nums[right]) { // 分隔在右边 ——> 左半边是顺序的
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else if (nums[mid] < nums[right]){
// nums[mid] <= nums[right] 分隔就是mid或者还在更左边的位置 ——> 右半边是顺序的
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
} else {
right--;
}
}
return false;
}
}
注意:
- 如果
while
循环条件不用=
,nums=[1]
这种情况就过不了了,因为left
和right
一开始就相等了。