在算法面试中,经常会遇到 二分查找(Binary Search)相关的变形题。而今天要讨论的 Bitonic Array Search,正是其中一个 巧妙运用二分查找的经典问题。
这篇文章将带你深入理解 Bitonic 数组的结构,并探讨如何 优化搜索过程,让二分查找真正发挥 O(logN) 的高效特性。
📌 题目背景:什么是 Bitonic 数组?
Bitonic 数组是一种特殊的数组,它先递增后递减,并且在某个位置达到峰值。例如:
ini
array = [1, 3, 8, 12, 4, 2]
在该数组中:
- 左侧
[1, 3, 8, 12]
递增 - 右侧
[12, 4, 2]
递减 - 峰值 在索引
3
处,即12
📝 目标:在这个数组中 高效查找 目标值 target
的索引,如果不存在返回 -1
。
💡 解题思路
🔹 Step 1: 先找峰值
由于 Bitonic 数组有 单调性变化(先增后减),我们可以用 二分查找 找到峰值索引 peakIndex
:
- 若
array[mid] < array[mid+1]
,说明峰值在 右侧,调整left = mid + 1
- 否则,峰值在 左侧或 mid 位置,调整
right = mid
🔹 Step 2: 在两侧进行二分查找
-
先查左侧递增区间:
- 如果
target
在[0, peakIndex]
这个递增区间,使用普通 二分查找。
- 如果
-
再查右侧递减区间:
- 如果
target
在[peakIndex + 1, N-1]
这个递减区间,使用 逆序二分查找(因为该区间是递减的)。
- 如果
这样,我们仅需 O(logN) + O(logN) = O(logN) 的时间复杂度完成搜索。
🔎 代码实现
ini
public class Solution {
public int search(int[] array, int target) {
if (array == null || array.length == 0) return -1;
// 1️⃣ 找到峰值索引
int peakIndex = findPeak(array);
// 2️⃣ 在左侧递增部分查找
int leftResult = binarySearch(array, target, 0, peakIndex, true);
if (leftResult != -1) return leftResult;
// 3️⃣ 在右侧递减部分查找
return binarySearch(array, target, peakIndex + 1, array.length - 1, false);
}
/** 找到峰值索引 */
private int findPeak(int[] array) {
int left = 0, right = array.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (array[mid] < array[mid + 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return left; // 返回峰值索引
}
/** 在指定区间执行二分查找 */
private int binarySearch(int[] array, int target, int left, int right, boolean ascending) {
while (left <= right) {
int mid = left + (right - left) / 2;
if (array[mid] == target) return mid;
if (ascending) {
if (array[mid] < target) left = mid + 1;
else right = mid - 1;
} else {
if (array[mid] < target) right = mid - 1;
else left = mid + 1;
}
}
return -1;
}
}
🚀卡点 & 解决方案
在刷这道题的过程中,可能会遇到一些理解障碍,这里针对几个常见问题做拆解分析:
❌ 卡点 1: 为什么要找峰值?
误区:能不能直接进行二分查找?
✅ 解法:
- Bitonic 数组的单调性变化导致无法直接用标准二分查找。
- 先找峰值,再在两侧二分查找,保证
O(logN)
复杂度。
❌ 卡点 2: 峰值查找的逻辑不清楚
误区:left = mid + 1
vs. right = mid
不知道如何选择?
✅ 解法:
- 如果
array[mid] < array[mid+1]
,说明峰值在 右侧,所以left = mid + 1
- 否则,峰值在 左侧 或
mid
本身,调整right = mid
- 直到
left == right
,即找到峰值索引。
❌ 卡点 3: 二分查找的方向如何调整?
误区:在递增和递减部分都用同一个 binarySearch
逻辑?
✅ 解法:
- 递增部分:标准二分查找(左小右大)。
- 递减部分:反向二分查找(左大右小)。
🎯 Follow-up
💡 变形题
-
如果数组中有重复元素怎么办?
- 可能会导致多个局部峰值,需要额外处理。
-
如果要求返回插入位置,而不是索引?
- 需要修改二分查找逻辑,返回
left
指针的位置。
- 需要修改二分查找逻辑,返回
-
如果数组可能没有 Bitonic 结构,如何处理?
- 需要先验证是否符合
Bitonic
条件。
- 需要先验证是否符合
-
如果数组是 Bitonic Linked List,而不是数组呢?
- 不能直接使用索引,需要通过 快慢指针 或 三段二分查找。
📝 高级优化
-
能用一次二分查找完成搜索吗?
- 直接在二分查找过程中动态判断方向,而不需要先找峰值。
-
这个解法能否在
O(1)
额外空间下完成?- 可以,只需在原数组上操作,无需额外存储。
🔚 总结
关键点 | 思路 |
---|---|
Bitonic 结构 | 先递增后递减,峰值最大 |
二分查找峰值 | O(logN) 时间找到 peakIndex |
两次二分查找 | 先查左侧(递增),再查右侧(递减) |
变形题思考 | 重复元素、多峰值、插入位置、链表等 |
✅ 这道题考察的是:
- 二分查找的变形与灵活运用
- 如何利用 Bitonic 结构优化搜索
- 如何处理面试中的 Follow-up 问题