题目描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个存在重复元素的升序数组的一个旋转,输出旋转数组的最小元素。
分析:旋转数组由前后两段非递减元素组成,比如 [3, 4, 5, 1, 2] 是 [1, 2, 3, 4, 5] 的前两个数旋转到数组末尾得到的旋转数组。
首先,前一段的元素一定大于等于后一段的元素,最小数字一定是第一段非递减元素末尾的下一个,遍历即可。
而且使用二分法比较中间元素与右侧元素的大小关系,由于 left < right 时只能是mid <= right,说明最小在left。所以可以分成以下情况:
- left < right , 最小在 left;
- left >=right:
- mid > right, 最小在 [mid + 1, right];
- mid < right, 最小在 [left, mid];
- mid = right,最小在[left+1, right -1]。
求解:
// 暴力解法
function minArray1(numbers: number[]): number {
const len = numbers?.length;
if (!len) return NaN;
for (let i = 1; i < len; i++) {
const cur = numbers[i];
const pre = numbers[i - 1];
// 上一个元素比当前元素大;
if (pre > cur) {
return cur;
}
}
return numbers[0];
}
// 优化解法(二分查找)
function minArray2(numbers: number[]): number {
const len = numbers?.length;
if (!len) return NaN;
let left = 0;
let right = len - 1;
// 旋转数组的前一部分均大于等于后一部分
while (left < right) {
const leftNum = numbers[left];
const rightNum = numbers[right];
// 子数组有序,最小元素在 left
if (leftNum < rightNum) {
return leftNum;
}
// 中间元素的下标;
const mid = Math.floor((left + right) / 2);
const midNum = numbers[mid];
if (midNum > rightNum) {
// 中间数大于右边数, 最小元素在[mid + 1, right]
left = mid + 1;
} else if (midNum < rightNum) {
// 中间数小于右边数, 最小元素在[left, mid]
right = mid;
} else {
// 中间的数等于右边,而且左边大于等于右边 left >= mid = right, 最小元素在[left + 1, right - 1]
left = left + 1;
right = right - 1;
}
}
return numbers[left];
}