7.2 KiB
Executable File
解题思路:
如下图所示,寻找旋转数组的最小元素即为寻找 右排序数组 的首个元素 stock[x] ,称 x 为 旋转点 。
下图中的
numbers对应本题的stock。
排序数组的查找问题首先考虑使用 二分法 解决,其可将 遍历法 的 线性级别 时间复杂度降低至 对数级别 。
算法流程:
- 初始化: 声明
i,j双指针分别指向stock数组左右两端; - 循环二分: 设
m = (i + j) / 2为每次二分的中点( "/" 代表向下取整除法,因此恒有i \leq m < j),可分为以下三种情况:- 当
stock[m] > stock[j]时:m一定在 左排序数组 中,即旋转点x一定在[m + 1, j]闭区间内,因此执行 $i = m + 1$; - 当
stock[m] < stock[j]时:m一定在 右排序数组 中,即旋转点x一定在[i, m]闭区间内,因此执行 $j = m$; - 当
stock[m] = stock[j]时: 无法判断m在哪个排序数组中,即无法判断旋转点x在[i, m]还是[m + 1, j]区间中。解决方案: 执行j = j - 1缩小判断范围,分析见下文。
- 当
- 返回值: 当
i = j时跳出二分循环,并返回 旋转点的值stock[i]即可。
正确性证明:
当 stock[m] = stock[j] 时,无法判定 m 在左(右)排序数组,自然也无法通过二分法安全地缩小区间,因为其会导致旋转点 x 不在区间 [i, j] 内。举例如下:
设以下两个旋转点值为
0的示例数组,则当i = 0,j = 4时m = 2,两示例结果不同。 示例一[1, 0, 1, 1, 1]:旋转点x = 1,因此m = 2在 右排序数组 中。 示例二[1, 1, 1, 0, 1]:旋转点x = 3,因此m = 2在 左排序数组 中。
而证明 j = j - 1 正确(缩小区间安全性),需分为两种情况:
-
当
x < j时: 易得执行j = j - 1后,旋转点x仍在区间[i, j]内。 -
当
x = j时: 执行j = j - 1后越过(丢失)了旋转点x,但最终返回的元素值stock[i]仍等于旋转点值stock[x]。- 由于
x = j,因此stock[x] = stock[j] = stock[m] \leq number[i]; - 又由于
i \leq m <j恒成立,因此有m < x,即此时m一定在左排序数组中,因此stock[m] \geq stock[i];
- 由于
综合 1. , 2. ,可推出 stock[i] = stock[m] ,且区间 [i, m] 内所有元素值相等,即有:
stock[i] = stock[i+1] = \cdots = stock[m] = stock[x]
此时,执行 j = j - 1 后虽然丢失了旋转点 x ,但之后区间 [i, j] 只包含左排序数组,二分下去返回的一定是本轮的 stock[i] ,而其与 stock[x] 相等。
综上所述,此方法可以保证返回值
stock[i]等于旋转点值stock[x],但在少数特例下i \ne x;而本题目只要求返回 “旋转点的值” ,因此本方法正确。
补充思考: 为什么本题二分法不用 stock[m] 和 stock[i] 作比较?
二分目的是判断 m 在哪个排序数组中,从而缩小区间。而在 $stock[m] > stock[i]$情况下,无法判断 m 在哪个排序数组中。本质上是由于 j 初始值肯定在右排序数组中;i 初始值无法确定在哪个排序数组中。举例如下:
对于以下两示例,当
i = 0, j = 4, m = 2时,有stock[m] > stock[i],而结果不同。[1, 2, 3, 4 ,5]旋转点x = 0:m在右排序数组(此示例只有右排序数组);[3, 4, 5, 1 ,2]旋转点x = 3:m在左排序数组。
复杂度分析:
- 时间复杂度
O(\log N): 在特例情况下(例如 $[1, 1, 1, 1]$),会退化到 $O(N)$。 - 空间复杂度
O(1):i,j,m变量使用常数大小的额外空间。
代码:
class Solution:
def stockManagement(self, stock: List[int]) -> int:
i, j = 0, len(stock) - 1
while i < j:
m = (i + j) // 2
if stock[m] > stock[j]: i = m + 1
elif stock[m] < stock[j]: j = m
else: j -= 1
return stock[i]
class Solution {
public int stockManagement(int[] stock) {
int i = 0, j = stock.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (stock[m] > stock[j]) i = m + 1;
else if (stock[m] < stock[j]) j = m;
else j--;
}
return stock[i];
}
}
class Solution {
public:
int stockManagement(vector<int>& stock) {
int i = 0, j = stock.size() - 1;
while (i < j) {
int m = (i + j) / 2;
if (stock[m] > stock[j]) i = m + 1;
else if (stock[m] < stock[j]) j = m;
else j--;
}
return stock[i];
}
};
实际上,当出现 stock[m] = stock[j] 时,一定有区间 [i, m] 内所有元素相等 或 区间 [m, j] 内所有元素相等(或两者皆满足)。对于寻找此类数组的最小值问题,可直接放弃二分查找,而使用线性查找替代。
class Solution:
def stockManagement(self, stock: List[int]) -> int:
i, j = 0, len(stock) - 1
while i < j:
m = (i + j) // 2
if stock[m] > stock[j]: i = m + 1
elif stock[m] < stock[j]: j = m
else: return min(stock[i:j])
return stock[i]
class Solution {
public int stockManagement(int[] stock) {
int i = 0, j = stock.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (stock[m] > stock[j]) i = m + 1;
else if (stock[m] < stock[j]) j = m;
else {
int x = i;
for(int k = i + 1; k < j; k++) {
if(stock[k] < stock[x]) x = k;
}
return stock[x];
}
}
return stock[i];
}
}
class Solution {
public:
int stockManagement(vector<int>& stock) {
int i = 0, j = stock.size() - 1;
while (i < j) {
int m = (i + j) / 2;
if (stock[m] > stock[j]) i = m + 1;
else if (stock[m] < stock[j]) j = m;
else {
int x = i;
for(int k = i + 1; k < j; k++) {
if(stock[k] < stock[x]) x = k;
}
return stock[x];
}
}
return stock[i];
}
};









