33. Search in Rotated Sorted Array

leetcode

Posted by chl on May 8, 2019

题目

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Your algorithm’s runtime complexity must be in the order of O(log n).

题意

给一个可能旋转过的排序,找出目标元素的下标,要求时间复杂度为O(logn)

解法

题目不难,一开始能想到的是通过二分的方法找到旋转点,即原排序数组的第一个元素下标,之后按正常的二分查找寻找目标值,只是index要根据原初始值下标进行转换。
然而还有一种更直接的解法,也是二分查找,只是因为数组旋转过,所以需要多加判断,判断mid值左右两边哪边是有序的,在有序的一边判断target是否存在,不存在则在乱序的另外一边,依次折半查找即可。

代码

解法一

func search(nums []int, target int) int {
    Len := len(nums)
    if Len==0{
        return -1
    }
    
    begin := FindBeginIndex(nums,0,Len-1)
    
    start,end := 0,Len-1
    for start<=end{
        mid := (end-start)/2+start
        if nums[NewIndex(mid,Len,begin)]==target{
            return NewIndex(mid,Len,begin)
        }
        
        if nums[NewIndex(mid,Len,begin)]<target{
            start=mid+1
        }else{
            end=mid-1
        }
    }
    return -1
}

func FindBeginIndex(nums []int,start,end int)int{
	if start==end{
		return start
	}

	mid := (end-start)/2+start

    //如果当前元素比后面元素大,则后一个为起始元素
	if nums[mid]>nums[mid+1]{
		return mid+1
	}

    //如果前面有元素,并且前一个元素比当前元素大,则当前元素为起始元素
	if mid-1>=start&&nums[mid]<nums[mid-1]{
		return mid
	}

    //如果mid小于start,则表示起始元素在左边
	if nums[mid]<nums[start]{
		return FindBeginIndex(nums,start,mid-1)
	}

    //如果mid大于start,并且mid大于end,说明在右边
	if nums[mid]>nums[start]&&nums[mid]>nums[end]{
		return FindBeginIndex(nums,mid+1,end)
	}

	return start
}

func NewIndex(index,num,begin int)int{
	return (index+begin)%num
}

解法二

func search(nums []int, target int) int {
    Len := len(nums)
    if Len==0{
        return -1
    }
    start,end := 0,Len-1
    for start<=end{
        mid := (end-start)/2+start
        if nums[mid]==target{
            return mid
        }
        
        if nums[start]<=nums[mid]{ //左边有序
            if target>=nums[start]&&nums[mid]>=target{ //在左边
                end = mid-1
            }else{ //不在左边,那就在右边
                start=mid+1
            }
        }else{ //右边有序
            if target>=nums[mid]&&nums[end]>=target{
                start = mid+1
            }else{
                end = mid-1
            }            
        }
        
    }
    return -1
}

总结

虽然知道这道题是需要二分查找来解决的,只是没能想到可以这么直接。大部分时间花在如何寻找第一个元素的下标,这样就可以根据那个下标来进行转换了,虽然也可以解决问题,但就比直接二分要麻烦(找初始值下标的过程比直接找targer好像还要麻烦一点呵呵。。)
这道题直接二分就是先找出有序的一边,然后判断target是否在那一边,不在那就是在另外一边,然后不断折半进去就好了。。