最开始想到的还是暴力求解(o(╥﹏╥)o),参考了一些比较好的解答,做个记录。如有侵权,请联系博主删除。
目录
- 153——题目描述
- 方法一、二分查找法
- Python解法
- Java解法
- 复杂度分析
- 154——题目描述
- 方法一、二分查找
- Java解法
- 复杂度分析
- 方法二、二分查找——从右边元素比较
- Python解法
- Java解法
- 复杂度分析
- 总结
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。
请找出其中最小的元素。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
示例 3:
输入:nums = [1]
输出:1
提示:
1 <= nums.length <= 5000
-5000 <= nums[i] <= 5000
nums 中的所有整数都是 唯一 的
nums 原来是一个升序排序的数组,但在预先未知的某个点上进行了旋转
参考LeetCode官方解法
——当数组长度为1时,直接输出即可。
——当数组未发生旋转时,也直接输出第一个值即可。
——解题的关键要素。我们要找的点,称之为"变化点"。如4,5,6,7,0,1,2,显然变化点0左侧的元素都大于第一个元素,而变化点右侧的元素都小于第一个元素。
因为数组数有序的,我们可以采取二分查找的方法来划分区域查找“变化点”。
也就是
——如果中间元素 > 数组第一个元素,我们需要在 mid右边搜索变化点。
——如果中间元素 < 数组第一个元素,我们需要在mid左边搜索变化点。
——当满足nums[mid]
注意更新mid值,left值和right值。
class Solution(object):
def findMin(self, nums):
if len(nums) == 1:
return nums[0]
left = 0
right = len(nums) - 1
if nums[right] > nums[0]:
return nums[0]
while right >= left:
mid = left + (right - left) / 2
if nums[mid] > nums[mid + 1]:
return nums[mid + 1]
if nums[mid - 1] > nums[mid]:
return nums[mid]
if nums[mid] > nums[0]:
left = mid + 1
else:
right = mid - 1
耗时24ms
class Solution {
public int findMin(int[] nums) {
if (nums.length == 1) {
return nums[0];
}
int left = 0, right = nums.length - 1;
if (nums[right] > nums[0]) {
return nums[0];
}
while (right >= left) {
int mid = left + (right - left) / 2;
if (nums[mid] > nums[mid + 1]) {
return nums[mid + 1];
}
if (nums[mid - 1] > nums[mid]) {
return nums[mid];
}
if (nums[mid] > nums[0]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
}
耗时0ms
时间复杂度:和二分搜索一样 O(log N)
空间复杂度:O(1)
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
注意数组中可能存在重复的元素。这是与153不同的关键要求。
示例 1:
输入: [1,3,5]
输出: 1
示例 2:
示例2:
输入: [2,2,2,0,1]
输出: 0
说明:
这道题是 寻找旋转排序数组中的最小值 的延伸题目。
允许重复会影响算法的时间复杂度吗?会如何影响,为什么?
参考官方用右指针我偏要用左指针
首先来看与153题解类似的思路,用第一个数与mid位置的值作比较。当相等时,既然mid值已经包含了该值,我们就舍弃第一个元素。所以会有下面的算法。
class Solution {
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
if (nums[left] < nums[right]) {
return nums[left];
}
while (left < right) {
int mid = (left + right) / 2;
if (nums[left]<nums[mid]) {
left = mid + 1;
} else if (nums[left]>nums[mid]) {
right = mid;
} else {
left++;
}
}
return nums[left];
}
}
但是会报错10,1,10,10,10输出10的情况,所以需要加一个条件 if (left > 0 && nums[left] < nums[left - 1]) { return nums[left];}
class Solution {
public int findMin(int[] nums) {
int left = 0;
int right = nums.length - 1;
if (nums[left] < nums[right]) {
return nums[left];
}
while (left < right) {
int mid = (left + right) / 2;
if (left > 0 && nums[left] < nums[left - 1]) {
return nums[left];
}
if (nums[left]<nums[mid]) {
left = mid + 1;
} else if (nums[left]>nums[mid]) {
right = mid;
} else {
left++;
}
}
return nums[left];
}
}
耗时0ms
时间复杂度:二分查找平均找一半,复杂度为O(log(n)),但是最坏情况是全部相等,所以复杂度为O(n)
空间复杂度:O(1)
然后再来看看LeetCode官方解答
官方的思路的落点是最后一个元素大于等于“变化点”右侧的元素,而小于等于“变化点”左侧的元素。然后中间元素的值与最后一个元素的值作比较会有三种情况。
class Solution(object):
def findMin(self, nums):
low, high = 0, len(nums) - 1
while low < high:
pivot = low + (high - low) // 2
if nums[pivot] < nums[high]:
high = pivot
elif nums[pivot] > nums[high]:
low = pivot + 1
else:
high -= 1
return nums[low]
耗时28ms
class Solution {
public int findMin(int[] nums) {
int low = 0;
int high = nums.length - 1;
while (low < high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
} else if (nums[pivot] > nums[high]) {
low = pivot + 1;
} else {
high -= 1;
}
}
return nums[low];
}
}
耗时0ms
时间复杂度:平均时间复杂度为 O(log n),其中 n是数组nums的长度。如果数组是随机生成的,那么数组中包含相同元素的概率很低,在二分查找的过程中,大部分情况都会忽略一半的区间。而在最坏情况下,如果数组中的元素完全相同,那么 while 循环就需要执行 n 次,每次忽略区间的右端点,时间复杂度为 O(n)。时间复杂度考虑最坏的情况,所以为O(n)。
空间复杂度:O(1)
这两道题主要是锻炼使用有序情况数组的二分查找方法及部分有序的数组的二分查找变体。
博主比较小白,但是热爱分享。一直感觉自己写代码的能力,算法能力都不太行,所以最近开始刷LeetCode,一方面记录方便自己学习,另一方面给需要的同伴参考。虽然失败并不可怕,但是也希望同伴们少踩一些坑。分析算法挺费劲的,留个赞或评论支持一下博主吧!同时我也非常希望写出更通俗易懂的文章,知识尚浅,如有遗漏或错误,欢迎指正~