1. 旋转数组中找target的index
由于题目说数字了无重复,举个例子:
1 2 3 4 5 6 7 可以大致分为两类,
第一类 2 3 4 5 6 7 1 这种,也就是 nums[start] <= nums[mid]。此例子中就是 2 <= 5。
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第二类 6 7 1 2 3 4 5 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2。
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end],则在后半部分找,否则去前半部分找。
leetcode 33:搜索旋转排序数组中的某个值target(不含重复)
比较nums[mid]和nums[right]的大小,即判断后半部分是否有序。
public int search(int[] nums, int target) {
if(nums == null || nums.length == 0){
return -1;
}
int start = 0;
int end = nums.length - 1;
while (start <= end){
int mid = start + (end -start)/2;
if (nums[mid] == target){
return mid;
}
//后半部分有序
if(nums[mid] < nums[end]){
if(nums[mid] < target && target <= nums[end]){
start = mid + 1;
} else {
end = mid - 1;
}
} else {
if(nums[mid] > target && target >= nums[start]){
end = mid - 1;
} else {
start = mid + 1;
}
}
}
return -1;
}
也可以写成比较nums[start]和nums[mid]的大小,判断前半部分是否有序。
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return -1;
}
int start = 0;
int end = nums.length - 1;
int mid;
while (start <= end) {
mid = start + (end - start) / 2;
if (nums[mid] == target) {
return mid;
}
//前半部分有序,注意此处用小于等于
if (nums[start] <= nums[mid]) {
//target在前半部分
if (target >= nums[start] && target < nums[mid]) {
end = mid - 1;
} else {
start = mid + 1;
}
} else {
if (target <= nums[end] && target > nums[mid]) {
start = mid + 1;
} else {
end = mid - 1;
}
}
}
return -1;
}
为什么是小于等于?其实就是为了最后只剩两个数的时候,即left=mid的时候(因为mid向靠近left的方向取整)。
leetcode 81:搜索旋转排序数组中的某个值target(含重复)
第一类
1011110111 和 1110111101 这种。此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。
第二类
22 33 44 55 66 77 11 这种,也就是 nums[start] < nums[mid]。此例子中就是 2 < 5;
这种情况下,前半部分有序。因此如果 nums[start] <=target<nums[mid],则在前半部分找,否则去后半部分找。
第三类
66 77 11 22 33 44 55 这种,也就是 nums[start] > nums[mid]。此例子中就是 6 > 2;
这种情况下,后半部分有序。因此如果 nums[mid] <target<=nums[end]。则在后半部分找,否则去前半部分找。
public boolean search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return false;
}
int start = 0;
int end = nums.length - 1;
int mid;
while (start <= end) {
mid = start + (end - start) / 2;
if (nums[mid] == target) {
return true;
}
if (nums[start] == nums[mid]) {
start++;
continue;
}
//前半部分有序
if (nums[start] < nums[mid]) {
//target在前半部分
if (nums[mid] > target && nums[start] <= target) {
end = mid - 1;
} else { //否则,去后半部分找
start = mid + 1;
}
} else {
//后半部分有序
//target在后半部分
if (nums[mid] < target && nums[end] >= target) {
start = mid + 1;
} else { //否则,去后半部分找
end = mid - 1;
}
}
}
//一直没找到,返回false
return false;
}
2. 旋转数组中找最小值
找最小值,将nums[mid]和nums[right]比较,判断后半部分是否有序,最小值一定在无序的一边:
中值 < 右值,后半部分有序,所以最小值在左半边,收缩右边界。(h = mid:中值 < 右值,中值也可能是最小值,右边界只能取到mid处)
中值 > 右值,后半部分无序,最小值在右半边,收缩左边界;(l = mid + 1:因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid)
leetcode 153:Find Minimum in Rotated Sorted Array (不含重复)
public int findMin(int[] nums) {
int l = 0, h = nums.length - 1;
while (l < h) {
int mid = l + (h - l) / 2;
//判断后半部分是否有序
if (nums[mid] <= nums[h]) {//后半部分有序,不包含最小值,因为mid比h小,所以不排除mid为最小值
h = mid;
} else if (nums[mid] > nums[h]){//后半部分无序,包含最小值,所以更新l到mid+1,因为mid比h大,所以排除mid
l = mid + 1;
}
}
return nums[l];
}
为什么mid == h合并到mid<h了呢?
和谁合并都无所谓,因为nums[mid]不会等于nums[right]。
如果输入数组多于一个数,循环到最后,会只剩两个数,nums[left] == nums[mid],以及nums[right],这里的位置left == mid == right - 1。
如果nums[left] == nums[mid] > nums[right],则左边大、右边小,
需要执行left = mid + 1,使得left == right,左右边界位置重合,循环结束,nums[left]与nums[right]都保存了最小值。
如果nums[left] == nums[mid] < nums[right],则左边小、右边大,
会执行right = mid,使得left == right,左右边界位置重合,循环结束,nums[left]、nums[mid]、nums[right]都保存了最小值。
leetcode 154:Find Minimum in Rotated Sorted Array (含重复)
使用left < right作while循环条件可以很方便推广到数组中有重复元素的情况,只需要在nums[mid] == nums[right]时挪动右边界(相当于去掉一个重复的干扰项):
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[right]) {
right = mid;
} else if (nums[mid] > nums[right]) {
left = mid + 1;
} else if (nums[mid] == nums[right]) {
right--;
}
}
return nums[left];
}
能否将while循环的条件也选为左闭右闭区间left <= right?
可以,只要将nums[mid] == nums[right] 归到 nums[mid] > nums[right],最后返回nums[right]:
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left <= right) { // 循环的条件选为左闭右闭区间left <= right
int mid = left + (right - left) / 2;
if (nums[mid] >= nums[right]) { // 注意是当中值大于等于右值时,
left = mid + 1; // 将左边界移动到中值的右边
} else { // 当中值小于右值时
right = mid; // 将右边界移动到中值处
}
}
return nums[right]; // 最小值返回nums[right]
}
reference:
- https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/solution/er-fen-cha-zhao-wei-shi-yao-zuo-you-bu-dui-cheng-z/
- https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.md#5-%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%B0%8F%E6%95%B0%E5%AD%97
- https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/ji-bai-liao-9983de-javayong-hu-by-reedfan/