二分搜索相关及循环有序序列相关

前言:

        今天做题的时候看到一道题:33. Search in Rotated Sorted Array。由于LeetCode题目不给范围和时间。然我们尽量想复杂度小的方法。所以首先便把顺序便历排除。可是接下来就陷入了僵局。因为一个原本升序的数列进行旋转之后的数列不熟悉。然后果断搜索了一波发现是一个循环有序序列;

循环有序序列:

        该序列是从一个原本有序的数列经过了一定的旋转操作得到的序列。它的性质是将数列从中间分为两个部分,分别记为arr1、arr2。而首尾元素记为arr[low]、arr[hig]。中间记为arr[mid](如果数列元素个数为偶数时不管将mid定为中间元素的哪一个结果都一样);此时循环有序序列的性质可以描述为:

        1. 当arr[mid] > arr[low]:说明前半部分arr1有序,而后半部分arr2则为一个相对小一点的循环有序序列。

        2. 当arr[mid] < arr[hig]:说明后半部分arr2有序,而前半部分arr2则为一个相对小一点的循环有序序列。

在循环有序序列中高效率查找目标元素:

        给定一个循环有序序列和目标元素。在序列中查找目标元素,如果找到返回索引,未找到返回-1。        

        知道了上诉的性质,我们可以利用二分的思想来搜索指定元素。二分思想是建立在有序的基础上的一种思想。当看到该题时也想到过二分。可因为该序列被旋转为无序的状态,无法运用这种思想。不过既然知道了它的性质——局部有序。这时我们可以使用局部二分。先看代码:

// 改进后的二分搜索
int Search(int A[],int n, int num)
{
	int left = 0 ;
	int right = n - 1 ;
	int mid = 0 ;
	int pos = -1 ;    //默认返回-1,表示查找失败

	while(left <= right)
	{
		mid = (left + right)/2 ;        //将mid定为后半部分

		if (A[mid] == num)        //查找成功
		{   
			pos = mid ; 
			break;     
		}
		if (A[left] <= A[mid])    //前半部分是严格递增的,后半部分是一个更小的循环递增数组
		{
			if(A[left] <= num && num  < A[mid] )
			{
				right = mid - 1 ;
			}
			else
			{
				left = mid + 1 ;
			} 
		}
		else    //后半部分是严格递增的,前半部分是一个更小的循环递增数组
		{
			if ( A[mid] < num && num <= A[right])
			{
				left = mid + 1 ;
			}
			else
			{
				right = mid - 1 ;
			}
		}
	}
	return pos;
}

        emmmmm 代码很清晰,当前半部分是有序的就利用left和mid来确定num是否在前半部分,如果在则将right指向mid-1。如果不在则将left指向mid+1;后半部分也是利用了有序来判断num是否在后半部分。

二分查找有重复元素的循环有序序列:Search in Rotated Sorted Array II

        当循环有序序列是从拥有重复元素的有序序列得到的话。上面的方法因为mid、right、left可能三者或两者相等,这时候代码中判断哪一半有序的逻辑便出现问题。

        开始的想法:通过移动mid来避免right、left和mid有相等的情况。(PS:这个相等的情况并不是说三者必须相等,而是代码中用来比较的两者如果相等就丧失了判断部分有序的逻辑。当然可以通过判断mid和另一边,可是也有三者相等的情况。所以我们采取避免代码中判断的两者相等的情况而不是通过更多的判断来确定哪部分有序)

        后来一直在debug,就漏洞百出。便觉得自己的方法是错误的,通过讨论区发现可以通过修改right或者left来达到避免相等的目的。先上代码:

class Solution {
public:
    bool search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int pos = -1;
        int mid = 0;
        mid = (left + right) / 2;
        while(left <= right){
            mid = (left + right)/2;
            if(nums[mid] == target){
                pos = mid;
                break;
            }else if(nums[mid] < nums[right]){    // right is ordered
                if(nums[mid] < target && nums[right] >= target){
                    left = mid+1;
                }else{
                    right = mid - 1;
                }
            }else if(nums[mid] > nums[right]){    // left is ordered
                if(nums[mid] > target && nums[left] <= target){
                    right = mid - 1;
                }else{
                    left = mid + 1;
                }
            }else{
                right --;    //移动right,可以同时修改mid
            }
        }
        return pos != -1;
    }
};

        清晰思路:通过修改边界确实是一个非常清晰的方法,上面代码通过mid与right比较,所以下面的讨论也是围绕这个讨论,不过与left比较也是同样的道理。

        首先mid与right不等的情况可以沿用没有重复元素的情况。如果相等(mid==right)。这时候我们通过移动right,即right--。这时候再进入循环。可以达到避免相等的目的。当然这个方法首先看到的时候我有有个疑惑,就是当right--之后是否right正好是rarget呢?肯定不是。因为right和mid相等,代码只有确定了mid与target不想等才进入下面的逻辑。

        复杂度:复杂度因为有重复元素,所以最好的情况(没有重复元素)时复杂度为O(logn)。最坏情况(全部相等的情况)下,right要移动到left。这时复杂度为O(n);


求循环有序序列的最小元素:

        给定一个循环有序序列及其长度,让你求这个序列的最小元素和它的索引。

        没有重复元素:
class Solution {
public:
    int findMin(vector<int>& nums) {
        int right = nums.size()-1;
        int left = 0;
        while(left < right){
            int mid = (left + right)/2;
            if(nums[mid] > nums[right]){   //[left, mid] sorted, (mid, right] contain minNUm
                left =mid + 1;
            }else{                          //[mid, right] sorted, [left, mid] contain minNume
                right = mid;
            }
        }
        return min(nums[left], nums[right]);
    }
};
        有重复元素:
class Solution {
public:
    int findMin(vector<int>& nums) {
        int right = nums.size()-1;
        int left = 0;
        while(left < right){
            int mid = (left + right)/2;
            if(nums[mid] > nums[right]){        //[left, mid] is sorted, (mid, right] contain minNum
                left =mid + 1;
            }else if(nums[mid] < nums[right]){  //[mid,right] is sorted, [left, mid] contain minNum
                right = mid;
            }else{                              //nums[mid] == nums[right], right--. if nums[right] is minNum. 
                                                //mid can get it
                right --;
            }
        }
        return min(nums[left], nums[right]);
    }
};
//todo  lower bound and upper bound  





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值