👀先看这里👈
😀作者:江不平
📖博客:江不平的博客
📕学如逆水行舟,不进则退
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍
🔍题目详情
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 numbers,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
示例 1:
输入:numbers = [3,4,5,1,2]
输出:1
示例 2:
输入:numbers = [2,2,2,0,1]
输出:0
题目来源:[力扣(LeetCode)]
🤔解题思路
方法一:
常规的遍历查找,但我们挖掘题干可以使用效率稍微高点的遍历方式,根据题目得到信息:有个特征是在遍历的时候,原始数组是非递减的,如果数组旋转(最小值在中间某个地方),就可能会出现递减,引起递减的地方就是最小值。
逻辑&步骤
- 两个为一组,进行比较,若保持非递减的顺序,则继续遍历,下标+1,找新的一组,循环往复,直到出现递减
- 若到最后,都没出现递减,则最小值为刚开始的数组元素。
时间复杂度:O(N)
空间复杂度:O(1)
方法二:
数组旋转的次数不管深浅,都会将数组分成两个部分,并且这两个部分都是非递减的,并且前半部分整体要大于后半部分。我们要考虑的就是在部分的左半部分还是右半部分,这里我们就很容易想到可以利用二分查找来解决这个问题。
逻辑&步骤
3. 找中间下标,分三种情况:是mid,在mid左侧,在mid右侧,我们要做的就是不断缩小这个范围。
- 当a[mid]>a[left],就说明mid位置在原始数组的前半部分,那么最小值就在mid位置的右侧,而且是后半部分的开头,让left=mid
- a[mid]<a[left],就说明mid位置在原始数组的后半部分,那么最小值就在mid位置的左侧,让right=mid,就这样不断缩小范围
- 当left和right相邻时,right指向的位置就是最小元素的位置
注意:题目说的是非递减,说明可能有重复元素的存在,会出现a[left]==a[mid]==a[right]的情况,这样就无法在mid左侧还是右侧。还是需要遍历进行查找。
时间复杂度:平均时间复杂度为 O(logN),最坏为O(N)
空间复杂度:O(1)
💻代码实现
方法一:
c++
class Solution {
public:
int minArray(vector<int>& numbers) {
if(numbers.empty()){
return 0;
}
for(int i = 0; i < numbers.size()-1; i++){
if(numbers[i] > numbers[i+1]){
return numbers[i+1];
}
}
return numbers[0];
}
};
方法二:
c++:
class Solution {
public:
int minArray(vector<int>& numbers) {
if (numbers.empty()) {
return 0;
} int left = 0;
int right = numbers.size() - 1;
int mid = 0;
//要一直满足该条件,以证明旋转特性
while (numbers[left] >= numbers[right]) {
if (right - left == 1) {
//两个下标已经相邻了
mid = right;
break;
} mid = left + ((right - left) >> 1); //注意操作符优先级
if (numbers[mid] == numbers[left] && numbers[left] ==
numbers[right]) {
//无法判定目标数据在mid左侧,还是右侧我们采用线性遍历方式
int result = numbers[left];
for (int i = left + 1; i < right; i++) {
if (result > numbers[i]) {
result = numbers[i];
}
} return result;
} if (numbers[mid] >= numbers[left]) { //试想两者相等, 隐含条件
numbers[left] >= numbers[right];
//说明mid在前半部分
left = mid;
}
else {
//说明mid在后半部分
right = mid;
}
} return numbers[mid];
}
};
💬总结
在代码中mid=left+(right-left)/2,而没有写为mid=(left+right)/2是因为(left+right)/2可能存在溢出的威胁:如果数组很长,left和right可以会非常大,left+right会变得更大,可能超过了int的最大范围。所以改写为mid=left+(right-left)/2;