LeetCode - 旋转数组类总结(二分法)

搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [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

分析: 二分搜索,之前二分搜索只是用到了有序数组中,在有旋转的数组中直接应用并不适用。 旋转数组存在两个有序的序列,我们只需判断每次要查找的范围在哪个有序序列中查找,就可以应用二分查找。如果中间的数大于等于最左边的数,则右半段是有序的,若中间数小于等于最左边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了。

class Solution {
public:
	int search(vector<int>& nums, int target) {
		if (nums.size()== 0) {
			return -1;
		}
		int start = 0;
		int end = nums.size() - 1;
		int mid;
		while (start <= end) {
			mid = start + (end - start) / 2;
			if (nums[mid] == target) {
				return mid;
			}
            //防止出现重复数据
			if (nums[start] == nums[mid]) {
				start++;
				continue;
			}
			//前半部分有序start~mid有序,后续序列无序
			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 -1;
	}
};

搜索旋转排序数组 II

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。这是 搜索旋转排序数组 的延伸题目,本题中的 nums 可能包含重复元素
示例 1: 输入: nums = [2,5,6,0,0,1,2], target = 0。 输出: true
示例 2: 输入: nums = [2,5,6,0,0,1,2], target = 3 。 输出: false

分析: 本题是需要使用二分查找,怎么分是关键,举个例子:

第一类: 10111 和 11101 这种。此种情况下 nums[start] ==nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。

第二类: 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]。则在后半部分找,否则去前半部分找。

class Solution {
public:
	bool search(vector<int>& nums, int target) {
		if (nums.size()== 0) {
			return false;
		}
		int start = 0;
		int end = nums.size() - 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;
	}
};

寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。请找出其中最小的元素。你可以假设数组中不存在重复元素。(考虑重复)
示例 1: 输入: [3,4,5,1,2] 输出: 1
示例 2: 输入: [4,5,6,7,0,1,2] 输出: 0

分析: left一直指向左边有序序列的下标,right一直指向右边有序序列的下标。如果mid下标的值落在左边序列,则left=mid, 如果mid下标的值落在右边序列,则right=mid。当right-left==1为真时,最小值的下标为right。

class Solution {
public:
    int findMin(vector<int>& nums) {
        if (nums.size() == 0)
			return -1;
        if(nums.size()==1)
            return nums[0];
        //未旋转
        if(nums[0]<=nums[nums.size()-1])
           return nums[0];
		//先找到旋转数组的最小数字的下标
		int left = 0;
		int right = nums.size() - 1;
		int mid = 0;
		while (nums[left] >=nums[right]) {
			if (right - left == 1)
				break;
			mid = (left + right) / 2;
			if(nums[mid] == nums[left]&&nums[mid] == nums[right]) //10111 11101这种情况 其实是顺序查找最小值
				return findInOrder(left,right); //o(n)
			if (nums[mid] >= nums[left])
				left = mid;
			if (nums[mid] <= nums[right])
				right = mid;
		}
		return nums[right];
        
    }
};

在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。如果数组中不存在目标值,返回 [-1, -1]。

示例 1: 输入: nums = [5,7,7,8,8,10], target = 8 ,输出: [3,4]
示例 2: 输入: nums = [5,7,7,8,8,10], target = 6, 输出: [-1,-1]

分析: 先找到其中一个下标值为target的下标。再分别用二分查找找左下标和右下标。

class Solution {
public:
	vector<int> searchRange(vector<int>& nums, int target) {
		int left = 0;
		int right = nums.size() - 1;
		int mid = 0;
		int find = -1;
		vector<int> res;
		//先找到其中一个下标值为target的下标。
		while (left <= right) {
			mid = (left + right) / 2;
			if (target ==nums[mid])
			{
				find = mid;
				break;
			}
			else if (target > nums[mid])
				left = mid + 1;
			else
				right = mid - 1;
		}
		if (find == -1)
		{
			res = vector<int>{ -1,-1 };
			return res;
		}
		//找左边
		left = 0;
		right = find;
		while(left<=right){
			mid = (left + right) / 2;
			if (nums[mid] == target)
			{
				if (mid - 1 < 0 || nums[mid - 1] != target)
				{
					res.push_back(mid);
					break;
				}
				else
					right = mid - 1;
			}
			else
				left = mid + 1;
		}
		//找右边
		left = find;
		right = nums.size()-1;
		while (left <= right) {
			mid = (left + right) / 2;
			if (nums[mid] == target)
			{
				if (mid + 1 == nums.size() || nums[mid + 1] != target)
				{
					res.push_back(mid);
					break;
				}
				else
					left = mid + 1;
			}
			else 
				right = mid - 1;

		}
		return res;

	}
};
发布了76 篇原创文章 · 获赞 6 · 访问量 2771

猜你喜欢

转载自blog.csdn.net/u014618114/article/details/104240672