leetcode #33 搜索旋转排序数组 | 刷题之路第一站——数组类相关问题

题号 33

题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

链接:leetcode #33 搜索旋转排序数组

以下四种解题思路中,符合题目要求 O(logn) 时间复杂度的为思路3和4,可忽略1、2。 : >

解题思路1【顺序查找】:

从前往后依次比较数组中各元素值与目标值是否相等:
(1)若有一个元素值与目标值相等,则说明数组中存在这个目标值,返回当前元素值在数组中的位置即可;
(2)若数组中所有元素均与目标值不等,则说明数组中不存在这个目标值,返回-1。
时间复杂度: 最坏情况下,时间复杂度为O(n)

虽然这种思路通过了leetcode的提交,但是显然不满足题目中 O(logn) 的时间复杂度。

代码实现:

class Solution {
public:
	int search(vector<int>& nums, int target) {
		for (int i = 0; i < nums.size(); i++)
		{
			if (nums[i] == target)
			{
				return i;
			}
		}
		return -1;
	}
};

先说一下为什么会想到用二分查找的方法来解决这个问题:题目中要求时间复杂度为 O(logn),而二分查找的时间复杂度也是 O(logn)

解题思路2【二分查找】: (一个奇奇怪怪的思路)

1- 首先找到旋转的位置:从后往前依次遍历各元素,如果该元素大于其后一个元素,则该元素的位置就是旋转的位置;
2- 找到元素之后可分为三种情况进行处理,设立两个指针 min 和 max ,初始情况下 min 指向数组中第一个元素,max 指向数组中最后一个元素:
 (1)如果当前位置的元素等于目标值,则直接返回该元素的位置;
 (2)如果当前位置的元素大于目标值,并且 min 所指元素大于目标值的话,则表明目标值位于当前位置的右侧,令 min 指向当前元素的下一个元素;
 (2)否则,表明目标值位于当前位置的左侧,令 max 指向当前元素的前一个元素。
3- 经过步骤2之后,min和max之间的所有元素都是升序排列的了,使用二分查找法查找目标值即可。
时间复杂度: 虽然步骤3二分查找的时间复杂度为O(logn),但是步骤2的时间复杂度是O(n),因此整体时间复杂度为O(n)
虽然这种思路通过了leetcode的提交,但是仍然然不满足题目中 O(logn) 的时间复杂度。

代码实现:

class Solution {
public:
	int search(vector<int>& nums, int target) {
		int min = 0, max = nums.size() - 1;
		for (int i = max-1; i >= 0; i--)
		{
			if (nums[i] > nums[i + 1])
			{
				if (nums[i] == target)
				{
					return i;
				} else if (nums[i] > target && nums[min] > target) {
					min = i + 1;
				}
				else {
					max = i;
				}
				break;
			}
		}
		while (min <= max)
		{
			int mid = (min + max) / 2;
			if (nums[mid] == target)
				return mid;
			else if (nums[mid] > target) {
				max = mid - 1;
			}
			else {
				min = mid + 1;
			}
		}
		return -1;
	}
};

解题思路3【二分查找】:

整体采用二分查找的框架,重点是如何移动指针。
1- 设立三个指针,初始状态下:
min 指向数组中第一个元素
max 指向数组中最后一个元素
mid 指向min和max的中间元素
经过观察可知,目标值位于 mid 左侧的情况如下所示:
在这里插入图片描述
其他情况下,目标值均位于 mid 右侧。
2- 当 min 小于或者等于 max 时,进行如下的循环:
 (1)mid = (min + max) / 2
 (2)判断mid、min、max所指元素是否与目标相等,若相等则返回相应的位置即可,循环结束;
 (3)如果满足上图列出的四种情况,则表明目标值位于当前位置的左侧,令max = mid-1;
 (4)否则表明目标值位于当前位置的右侧,令min = mid +1。
3- 如果未找到相应的目标值,则返回-1即可。
 
时间复杂度: O(logn)

代码实现:

class Solution {
public:
	int search(vector<int>& nums, int target) {
		int min = 0, max = nums.size()-1, mid = 0;
		while (min <= max)
		{
			mid = (min + max) / 2;
			if (nums[mid] == target)
				return mid;
			if (nums[min] == target)
				return min;
			if (nums[max] == target)
				return max;
			if ((nums[min]<target&&nums[mid]>target&&nums[max] > target) || (nums[min]<target&&nums[mid]>target&&nums[max] < target) || (nums[min] > target&&nums[mid] > target&&nums[max] > target&&nums[min] > nums[mid]) || (nums[min] < target&&nums[mid] < target&&nums[max]<target&&nums[min]>nums[mid]))
			{
				max = mid - 1;
			}
			else 
			{
				min = mid + 1;
			}
		}
		return -1;
	}
};

解题思路4【二分查找】:

思路参考:jimmy00745
首先将给定数组分为 高处 和 低处,高处位于数组的左侧,低处位于数组的右侧,二者内的元素都分别是升序的,并且低处的最大值小于高处的最小值。
例如:
在这里插入图片描述由于给定数组不是单调递增,原二分查找移动 min 和 max 的条件显然是不能满足的,因此我们的重点工作就是找出 在何种情况下需要移动 min,在何种情况下需要移动 max。
在这里插入图片描述
对上图进行总结,可以看出移动min的几种情况:
(1)num[0]<target  nums[mid]>nums[0]  nums[mid]<target
(2)num[0]>target  nums[mid]<nums[0]  nums[mid]<target
(3)num[0]>target  nums[mid]>nums[0]  nums[mid]>target
当然可以将这三个条件写入 if 条件语句中进行判断,更高效的解法是利用异或运算:
观察可知,当仅满足以下三个条件中的一个或者同时满足三个条件时,需要移动min:
(1)num[0]>target  
(2)nums[mid]<nums[0]  
(3)nums[mid]<target
因此在 if 条件语句中对以上三个条件进行异或运算即可。

当不满足上述情况时,则移动max。

时间复杂度: O(logn)

代码实现:

class Solution {
public:
	int search(vector<int>& nums, int target) {
		int min = 0, max = nums.size() - 1, mid = 0;
		while (min < max)
		{
			mid = (min + max ) / 2;
			if ((nums[0] > target) ^ (nums[mid] < nums[0]) ^ (nums[mid] < target))
				min = mid + 1;
			else
				max = mid;
		}
		/*if (min == max && nums[min] == target)
			return min;
		else
			return -1;*/
		return min == max && nums[min] == target ? min : -1;
	}
};

优化代码:
来自:qsctech-sange

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0;
        int r = nums.size() - 1;
        int mid;
        while (l < r){
            mid = l + (r - l) / 2;
            if ((nums[0]> target) ^ (nums[mid] < nums[0]) ^ (nums[mid] < target))
                l = mid + 1;
            else
                r = mid;
        }
        return l == r && nums[l] == target? l:-1;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值