【剑指offer】——剑指 Offer 11. 旋转数组的最小数字

【剑指offer】——剑指 Offer 11. 旋转数组的最小数字

👀先看这里👈
😀作者:江不平
📖博客:江不平的博客
📕学如逆水行舟,不进则退
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍

🔍题目详情

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 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. 两个为一组,进行比较,若保持非递减的顺序,则继续遍历,下标+1,找新的一组,循环往复,直到出现递减
  2. 若到最后,都没出现递减,则最小值为刚开始的数组元素。

时间复杂度:O(N)
空间复杂度:O(1)

方法二:
数组旋转的次数不管深浅,都会将数组分成两个部分,并且这两个部分都是非递减的,并且前半部分整体要大于后半部分。我们要考虑的就是在部分的左半部分还是右半部分,这里我们就很容易想到可以利用二分查找来解决这个问题。
逻辑&步骤
3. 找中间下标,分三种情况:是mid,在mid左侧,在mid右侧,我们要做的就是不断缩小这个范围。

  • 当a[mid]>a[left],就说明mid位置在原始数组的前半部分,那么最小值就在mid位置的右侧,而且是后半部分的开头,让left=mid
  • a[mid]<a[left],就说明mid位置在原始数组的后半部分,那么最小值就在mid位置的左侧,让right=mid,就这样不断缩小范围
  1. 当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;

在这里插入图片描述

觉得还不错的铁汁点赞关注一下吧😀
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江不平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值