leetcode练习之数组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)
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目解读

旋转后的排序数组其实是以旋转点为划分,前后两部分都为升序数组,但是我们并不知道旋转点。所以是否需要找到准确的旋转点,我们需要进行考量。

此外,本题对于时间复杂度要求为O(log n) 级别,所以很快我们就能想到用二分法进行搜索。只是二分法需要在有序的数组中操作,但是我们这里旋转后的排序数组是分段有序。

答题思路

分步将旋转数组恢复,然后二分法

首先这种分步思维有利于我们把问题拆解。有些像是想成为救火员的故事中的数学家的思维。尽管令人啼笑皆非,但是有规律可循,如果遇到火,就灭火;如果没有火,那就先点火,再灭火。

其实这里有个小提示,二分法需要在有序数组中使用,且每次划分是以一半进行。那么我们可以一半一半的判断是否有序。这也就引导我们到了一个更完备的算法。如下所示。

直接二分法
  1. 如果mid的值大于等于start的值,那么从start到mid这部分为有序数组
  2. 如果target介于start和mid的值之间,则二分法在前半部分查找
  3. 反之,如果mid的值小于start的值,那么从mid到end这部分为有序数组
  4. 如果target介于mid和end的值之间,则二分法在后半部分查找

PHP版本

执行用时:8 ms
内存消耗:15.2 MB

class Solution {

    /**
     * @param Integer[] $nums
     * @param Integer $target
     * @return Integer
     */
    function search($nums, $target) {
        if (count($nums)==0) {
            return -1;
        }
        $start = 0;
        $end = count($nums)-1;
        while ($start<=$end) {
            $mid = $start + floor(($end-$start)/2);
            if ($target == $nums[$mid]) {
                return $mid;
            }
            //前半部分有序
            if ($nums[$start]<=$nums[$mid]) {
                if ($nums[$start]<=$target&&$target<$nums[$mid]) {
                    $end = $mid-1;
                } else {
                    $start = $mid+1;
                }
            } else {//后半部分有序
                if ($nums[$mid]<$target&&$target<=$nums[$end]) {
                    $start = $mid+1;
                } else {
                    $end = $mid-1;
                }
            }
        }
        return -1;
    }
}

Go版本

执行用时 :0 ms
内存消耗 :2.6 MB

func search(nums []int, target int) int {
    if (len(nums)==0) {
        return -1
    }
    start,end := 0,len(nums)-1
    for start<=end {
        mid := start+(end-start)/2
        if (target == nums[mid]) {
            return mid
        }
        //前半部分有序
        if (nums[start]<=nums[mid]) {
            if (target>=nums[start]&&target<nums[mid]) {
                end = mid-1
            } else {
                start = mid+1
            }
        } else {
            if (target>nums[mid]&&target<=nums[end]) {
                start = mid+1
            } else {
                end = mid-1
            }
        }
    }
    return -1
}

发现耗时和内存占用此消彼长,由于这一版的耗时是0ms,所以我不禁想减少内存消耗。发现可以在while循环外先声明mid,在里面给mid再修改赋值。这样内存占用就打败100%的go提交记录了。当然耗时增加了。

 start,mid,end := 0,0,len(nums)-1

总结

  1. 直接二分法的最好时间复杂度O(1),最坏时间复杂度O(log n),平均时间复杂度O(log n)。
  2. 直接二分法的最好空间复杂度O(1),最坏空间复杂度O(1),平均空间复杂度O(1)。
  3. don’t you quit.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述:给定一个非负整数数组nums和一个整数m,你需要将这个数组分成m个非空的连续子数组。设计一个算法使得这m个子数组的最大和最小。 解题思路: 这是一个典型的二分搜索题目,可以使用二分查找来解决。 1. 首确定二分的左右边界。左边界为数组最大的值,右边界为数组所有元素之和。 2. 在二分搜索的过程,计算出分割数组的组数count,需要使用当前的间值来进行判断。若当前的间值不够分割成m个子数组,则说明mid值偏小,将左边界更新为mid+1;否则,说明mid值偏大,将右边界更新为mid。 3. 当左边界小于等于右边界时,循环终止,此时的左边界即为所求的结果。 具体步骤: 1. 遍历数组,找到数组的最大值,并计算数组的总和。 2. 利用二分查找搜索左右边界,从左边界到右边界间的值为mid。 3. 判断当前的mid值是否满足题目要求,若满足则更新右边界为mid-1; 4. 否则,更新左边界为mid+1。 5. 当左边界大于右边界时,循环终止,返回左边界即为所求的结果。 代码实现: ```python class Solution: def splitArray(self, nums: List[int], m: int) -> int: left = max(nums) right = sum(nums) while left <= right: mid = (left + right) // 2 count = 1 total = 0 for num in nums: total += num if total > mid: total = num count += 1 if count > m: left = mid + 1 else: right = mid - 1 return left ``` 时间复杂度分析:二分搜索的时间复杂度为O(logN),其N为数组的总和,而遍历数组的时间复杂度为O(N),因此总的时间复杂度为O(NlogN)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值