高效查找 Bitonic 数组中的目标值:二分查找的应用

在算法面试中,经常会遇到 二分查找(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: 在两侧进行二分查找

  1. 先查左侧递增区间:

    • 如果 target[0, peakIndex] 这个递增区间,使用普通 二分查找。
  2. 再查右侧递减区间:

    • 如果 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

💡 变形题

  1. 如果数组中有重复元素怎么办?

    • 可能会导致多个局部峰值,需要额外处理。
  2. 如果要求返回插入位置,而不是索引?

    • 需要修改二分查找逻辑,返回 left 指针的位置。
  3. 如果数组可能没有 Bitonic 结构,如何处理?

    • 需要先验证是否符合 Bitonic 条件。
  4. 如果数组是 Bitonic Linked List,而不是数组呢?

    • 不能直接使用索引,需要通过 快慢指针三段二分查找

📝 高级优化

  • 能用一次二分查找完成搜索吗?

    • 直接在二分查找过程中动态判断方向,而不需要先找峰值。
  • 这个解法能否在 O(1) 额外空间下完成?

    • 可以,只需在原数组上操作,无需额外存储。

🔚 总结

关键点 思路
Bitonic 结构 先递增后递减,峰值最大
二分查找峰值 O(logN) 时间找到 peakIndex
两次二分查找 先查左侧(递增),再查右侧(递减)
变形题思考 重复元素、多峰值、插入位置、链表等

✅ 这道题考察的是:

  • 二分查找的变形与灵活运用
  • 如何利用 Bitonic 结构优化搜索
  • 如何处理面试中的 Follow-up 问题
相关推荐
董董灿是个攻城狮4 小时前
5分钟搞懂什么是窗口注意力?
算法
Dann Hiroaki4 小时前
笔记分享: 哈尔滨工业大学CS31002编译原理——02. 语法分析
笔记·算法
qqxhb6 小时前
零基础数据结构与算法——第四章:基础算法-排序(上)
java·数据结构·算法·冒泡·插入·选择
FirstFrost --sy7 小时前
数据结构之二叉树
c语言·数据结构·c++·算法·链表·深度优先·广度优先
森焱森7 小时前
垂起固定翼无人机介绍
c语言·单片机·算法·架构·无人机
搂鱼1145148 小时前
(倍增)洛谷 P1613 跑路/P4155 国旗计划
算法
Yingye Zhu(HPXXZYY)8 小时前
Codeforces 2021 C Those Who Are With Us
数据结构·c++·算法
无聊的小坏坏9 小时前
三种方法详解最长回文子串问题
c++·算法·回文串
长路 ㅤ   9 小时前
Java后端技术博客汇总文档
分布式·算法·技术分享·编程学习·java后端
秋说9 小时前
【PTA数据结构 | C语言版】两枚硬币
c语言·数据结构·算法