首页 简历|笔试面试

81.搜索旋转排序数组II

  • 25年9月4日 发布
  • 61.07KB 共6页
81.搜索旋转排序数组II81.搜索旋转排序数组II81.搜索旋转排序数组II81.搜索旋转排序数组II81.搜索旋转排序数组II

81. 搜索旋转排序数组 II

题意

已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了

旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ...,

nums[k-1]](下标 从 0 开始 计数)。

例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是

否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false

你必须尽可能减少整个操作步骤。

难度

中等

示例

示例 1:

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

输出:true

示例 2:

输入:nums = [2,5,6,0,0,1,2], target = 3

输出:false

分析

本题相较于 033.搜索旋转排序数列 来说,最大的不同在于 033.搜索旋转排序数列 是明确

的升序数列,而本题是未降序排列的数列,我们来咬文嚼字分析一下之间的差异。

升序和非降序是两个经常在算法和数据结构中使用的术语,它们的定义如下:

1. 升序 (Ascending Order):

– 当一个序列的任何两个相邻的元素都满足:a[i] < a[i+1] 时,该序列被称为

升序。

– 换句话说,每一个元素都比它前面的元素大。

– 例如:[1, 2, 3, 4, 5] 是升序。

2. 非降序 (Non-descending Order):

– 当一个序列的任何两个相邻的元素都满足:a[i] <= a[i+1] 时,该序列被称为

非降序。

– 换句话说,每一个元素都大于或等于它前面的元素。

– 非降序包括了升序和部分元素相等的情况。

– 例如:[1, 2, 2, 3, 4, 5] 是非降序,但不是纯升序。

这下就明白了吧,就是所有升序的序列都是非降序的,但并非所有非降序的序列都是升序

的,里面存在一些元素相等的情况。

也就是说 033.搜索旋转排序数列 不存在重复的元素,而本题可能存在。

回想一下我们在处理 033.搜索旋转排序数列 时是怎么做的。

我们用了 二分查找。

二分查找的基本原理是每次比较数组的中间值和目标值。如果它们相等,我们找到了目

标;否则,我们将查找范围减半。

在一个标准的升序数组中,二分查找很简单。但这里的数组是一个非降序数组的旋转,所

以可能有两个非降序的子序列。

观察题目中给出的例子:[0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为

[4,5,6,6,7,0,1,2,4,4]。

如果我们在下标 5 的地方切开,也就是说,我们将数组分成了 [4,5,6,6,7] 和 [0,1,2,4,4]

两个子序列,其中前一个子序列是非降序的,后一个子序列也是非降序的。并且前一个子

序列的任意元素都大于或者等于后一个子序列的任意元素。

这启示我们可以继续使用二分查找:如果 target 位于子序列,我们可以对这个子序列继

续二分查找,否则我们对另一个子序列继续二分查找。

现在唯一的麻烦是本题存在重复的元素,会出现 nums[lef] == nums[mid] ==

nums[rig]的情况。

考虑数组 [2, 2, 2, 2, 4, 2, 2],这是一个由 [2, 2, 2, 2, 2, 2, 4] 旋转得到的数组。假设目

标值 target = 4。

首先,定义 lef = 0 和 rig = 6。

第一步:

• lef = 0, rig = 6, mid = (0 + 6) / 2 = 3,所以 nums[mid] = 2。

• nums[lef] = 2,nums[rig] = 2

此时,我们注意到 nums[lef] == nums[mid] == nums[rig]。

但如果 nums[lef] == nums[mid] == nums[rig],我们将面临一个问题,即解可能在

mid 的左边或右边,我们不能确定。

既然不能确定,索性我们将 lef 向右移动一位,将 rig 向左移动一位,来跳过重复的元

素,这样我们就可以继续在缩小的范围内查找目标。

解决了这个麻烦,接下来我们需要确定中间值是在左段还是右段。

• 如果 nums[lef] <= nums[mid],那么中间值在左段(升序的)。然后我们检查目标

值是在左段还是右段,并相应地调整 lef 或 rig。

• 如果中间值在右段,处理方式与上述类似。

为什么这样就可以确定中间值在左段还是右段呢?

因为旋转排序数组存在这样一个特点:左段的所有值都大于等于首值,且都大于右段的任

意值;右段的所有值都小于等于尾值,且都小于左段的任意值。比如说:

[4,5,6,6,7,0,1,2,4,4],左段为[4,5,6,6,7],右段为[0,1,2,4,4]。

所以,对于数组的中间值:

1. 如果 **nums[mid] >= nums[lef]**,则 mid 位于左段。

– 如果数组没有旋转(例如 [1,2,3,4,5]),中间值当然是大于等于首值的。

– 如果数组旋转了(例如 [4,5,6,1,2,3]),mid 为 6 在左段,它仍然是大于等于

首值的。

2. 如果 **nums[mid] <= nums[rig]**,则 mid 位于右段。同样的逻辑,考虑两种情

况:

– 如果数组没有旋转,中间值当然是小于等于尾值的。

– 如果数组旋转了,并且 mid 在右段,它肯定是小于等于尾值的,例如

[2,5,6,0,0,1,2]。

通过上述方法,我们可以很容易地判断中间值是在左段还是右段。

好了,确定好左段还是右段,就是在子序列中进行二分查找了:

• 如果[lef,mid - 1]是非降序的,而且 nums[lef] <= target && target <

nums[mid],那么答案肯定存在于[lef, mid - 1]中,调整上界 rig 到 mid - 1。

• 如果[mid,rig]是有序的,而且 nums[mid] < target && target <= nums[rig],那

么答案肯定存在于[mid,rig]中,调整下界 lef 到 mid 即可。

代码方面也与 033.搜索旋转排序数列 大差不差,只是增加了对于 nums[lef] ==

nums[mid] == nums[rig]情况的处理。

class Solution {

public boolean search(int[] nums, int target) {

int n = nums.length; // 获取数组长度

// 若数组只有一个元素,直接对比目标值

if(n == 1) {

return nums[0] == target;

}

// 若数组为空,直接返回 false

if(n == 0)

return false;

// 定义左右指针,用于二分查找

int lef = 0, rig = n - 1;

// 进行二分查找

while(lef <= rig) {

int mid = (lef + rig) >> 1; // 计算中间位置

if(nums[mid] == target) // 目标找到,返回 true

return true;

// 当左、中、右指针的值都相等时,无法确定该向哪边收缩,所以两边都收缩

if(nums[lef] == nums[mid] && nums[mid] == nums[rig]) {

++lef;

--rig;

} else {

// 中间值在左段(左段是升序的)

if (nums[lef] <= nums[mid]) {

if (nums[lef] <= target && target < nums[mid]) { // target 在左段中

rig = mid - 1;

} else { // target 在右段中

lef = mid + 1;

}

} else { // 中间值在右段(右段是升序的)

if (nums[mid] < target && target <= nums[n - 1]) { // target 在右段中

lef = mid + 1;

} else { // target 在左段中

rig = mid - 1;

}

}

}

}

return false; // 未找到目标值

}

}

我们来演变一下。

假设我们要查找 target = 6 是否在数组 [4,5,6,6,7,0,1,2,4,4] 中。

首先,初始化下标:

lef = 0

rig = 9

数组长度 n = 10

在循环开始时,我们计算中间位置的索引:

mid = (lef + rig) / 2 = (0 + 9) / 2 = 4

nums[mid] = 7

我们来检查三个关键位置的值:

nums[lef] = 4

nums[mid] = 7

nums[rig] = 4

在第一次循环时,nums[lef]=nums[rig] 但不等于 nums[mid]。

我们知道 mid 点在左半段(升序段)因为 nums[lef] <= nums[mid]。

接下来我们检查 target 是否在这个左段中。因为 nums[lef] <= target 且 target <

nums[mid] 成立,所以我们知道目标在左半部分。

因此,我们更新:

rig = mid - 1 = 3

缩小范围后,进入第二次循环,计算新的中间位置:

mid = (0 + 3) / 2 = 1

nums[mid] = 5

此时:

• nums[lef] = 4

• nums[mid] = 5

• nums[rig] = 6

• target = 6

• let = 0

• rig = 3

• mid = 1

nums[lef] 小于 nums[mid],所以我们知道中间值 mid 在子序列的左半段([4,5])。

nums[lef] 小于 target 但 target 不小于 nums[mid],所以 target 在右段([6,6])。

因此,我们更新:

lef = mid + 1 = 2

进一步缩小范围,此时 rig = 3,继续第三次循环,计算新的中间位置:

mid = (2 + 3) / 2 = 2

好,找到 nums[mid] = 6 = target 了,返回 true。

来看一下运行效率:

081.%E6%90%9C%E7%B4%A2%E6%97%8B%E8%BD%AC%E6%8E%92%E5%BA%8F%E

6%95%B0%E7%BB%84II-20230927083106.png

总结

本题仍然是对过去知识的简单变形(二分查找法),麻烦点在于可能会出现 lef,mid,rig

三者相等的情况,这时候该如何跳出这个困境?答案是去掉重复元素就行了。

力扣链接:https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/

更新: 2023-09-27 02:44:45

原文: https://www.yuque.com/itwanger/czfoq9/gmmv4r

开通会员 本次下载免费

所有资料全部免费下载! 推荐用户付费下载获取返佣积分! 积分可以兑换商品!
一键复制 下载文档 联系客服