算法二分法 ③ ( 山脉数组的峰顶索引 | 枚举法 | 二分法 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法二分法 ③ ( 山脉数组的峰顶索引 | 枚举法 | 二分法 )相关的知识,希望对你有一定的参考价值。

文章目录





一、山脉数组的峰顶索引



https://leetcode.cn/problems/peak-index-in-a-mountain-array/description/

符合下列属性的数组 arr 称为 山脉数组 :

  • arr.length >= 3
  • 存在 i(0 < i < arr.length - 1)使得:
    • arr[0] < arr[1] < … arr[i-1] < arr[i]
    • arr[i] > arr[i+1] > … > arr[arr.length - 1]

给你由整数组成的山脉数组 arr ,返回任何满足 arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1] 的下标 i 。


输入:arr = [0,10,5,2]
输出:1


山脉数组 就是 先增后减 的序列 , 山顶 就是最大值 , 本题目求的是 最大值的索引 ;


上一篇博客 【算法】二分法 ① ( 二分法基本原理简介 | 二分法与哈希表对比 | 常见算法对应的时间复杂度 ) 中提到了常见的算法的时间复杂度如下 , 时间复杂度从小到大进行排序为 :

  • O ( 1 ) O(1) O(1) : 位运算 , 哈希表查询
  • O ( log ⁡ n ) O(\\log n) O(logn) : 二分法 , 快速幂算法 , 辗转相除法 , 倍增法 ;
  • O ( n ) O(n) O(n) : 枚举法 , 单调栈算法 , 双指针算法 ;
  • O ( n log ⁡ n ) O(n \\log n) O(nlogn) : 快速排序 , 归并排序 , 堆排序 ;
  • O ( n 2 ) O(n^2) O(n2) : 枚举法 , 动态规划 ;
  • O ( n 3 ) O(n^3) O(n3) : 枚举法 , 动态规划 ;
  • O ( 2 n ) O(2^n) O(2n) : 组合相关的搜索问题 ;
  • O ( n ! ) O(n!) O(n!) : 排列相关的搜索问题 ;

解决该算法问题有两种方案 :

  • 枚举法 : 从头到尾进行遍历一遍 , 时间复杂度 O ( n ) O(n) O(n) ;
  • 二分法 : 使用二分法遍历数组 , 时间复杂度 O ( log ⁡ n ) O(\\log n) O(logn) ;




二、枚举法



代码示例 :

  • 验证参数 : 任何函数都必须先 验证参数合法性 ;
  • 枚举遍历 : 从头到尾进行遍历一遍 , 时间复杂度 O ( n ) O(n) O(n) ;
  • 算法逻辑 : 数组前半部分是递增的, array[i] < array[i + 1] , 如果遇到 array[i] > array[i + 1] 则说明 从 i 开始递减 , i 就是封顶索引 ;
class Solution 
    public int peakIndexInMountainArray(int[] array) 
        // 1. 验证参数合法性
        if (array == null || array.length == 0) 
            return -1;
        

        // 2. 使用枚举法遍历数组元素
        int index = -1;
        for (int i = 1; i < array.length - 1; ++i) 
            // 前半部分是递增的, array[i] < array[i + 1]
            // 如果遇到 array[i] > array[i + 1] 从 i 开始递减
            // i 就是封顶索引
            if (array[i] > array[i + 1]) 
                index = i;
                break;
            
        
        return index;
    





三、二分法



参考上一篇博客的 二分法模板 : 注意以下二分法的要点 ;

  • ★ 要点一 : 循环控制变量 , 尽量不要使用 start <= end 或 start < end 作为循环判定条件 , 在某些情况下会执行失败 , 为了让程序有更多的适应性 , 这里使用 start + 1 < end 作为循环判定条件 , 可以有效避免死循环 ;
  • ★ 要点二 : 取中间值的时候 , 尽量不要使用 (start + end) / 2 , 如果 两个数值都接近 Int.MAX_VALUE 则会溢出 ;
  • ★ 要点三 : 缩小区间范围时 , 可以不需要 加减 1 ;
    • 范围向左缩小 : 由于循环判定条件是 start + 1 < end , 范围缩小到中心点左侧时 , end 赋值可以不使用 mid - 1 , 直接使用 mid ;
    • 范围向右缩小 : 由于循环判定条件是 start + 1 < end , 范围缩小到中心点左侧时 , start 赋值可以不使用 mid + 1 , 直接使用 mid ;
  • ★ 要点四 : 循环完毕 , 判定 start 和 end 是不是要找的值 , 如果数组只有两个数的情况下 , while(start + 1 < end) 循环控制条件中的 start + 1 < end 直接为 false , 循环直接退出 , 此处判定一下 start 和 end 是不是要找的值 ;

参考的二分法模板 :

package cn.zkhw.schedule.utils;

public class Solution 
    public int search(int[] nums, int target) 
        // 1. 判断参数合法性
        if(nums == null || nums.length == 0) 
            return -1;
        

        // 2. 二分查找的范围
        int start = 0, end = nums.length - 1;

        // 3. 开始循环进行二分查找
        // 此处注意 start 和 end 区间需要能覆盖住所有目标值
        // 该循环条件很重要 , 是通用模板
        // ★ 要点一 : 此处尽量不要使用 start <= end 或 start < end 作为循环判定条件 , 在某些情况下会执行失败
        // 为了让程序有更多的适应性 , 这里使用 start + 1 < end 作为循环判定条件 , 可以有效避免死循环
        while(start + 1 < end) 
            // 3.1 计算中间索引
            // ★ 要点二 : 此处尽量不要使用 (start + end) / 2 , 如果 两个数值都接近 Int.MAX_VALUE 则会溢出
            int mid = start + (end - start) / 2;
            // 3.2 对比中间元素与目标值
            if(nums[mid] == target) 
                // 如果 中心元素 = 目标值 , 找到了目标元素的第一个位置
                end = mid;
             else if(nums[mid] > target) 
                // 如果 中心元素 > 目标值 , 则需要去 该查找区间的 左侧继续查找
                // ★ 要点三 : 由于循环判定条件是 start + 1 < end , 此处 end 赋值可以不使用 mid - 1
                end = mid;
             else 
                // 如果 中心元素 < 目标值 , 则需要去 该查找区间的 右侧继续查找
                // ★ 要点四 : 由于循环判定条件是 start + 1 < end , 此处 start 赋值可以不使用 mid + 1
                start = mid;
            
        

        // 4. ★ 要点五 : 循环完毕 , 判定 start 和 end 是不是要找的值
        // 如果数组只有两个数的情况下 
        // while(start + 1 < end) 循环控制条件中的 start + 1 < end 直接为 false 
        // 循环直接退出 , 此处判定一下 start 和 end 是不是要找的值 
        if(nums[start] == target) 
            return -1;
        
        if(nums[end] == target) 
            return -1;
        

        // 5. 没有找到目标值
        return -1;
    


根据二分法模板写出的 山脉数组的峰顶索引 算法 :

class Solution 
    public int peakIndexInMountainArray(int[] array) 
        // 1. 验证参数合法性
        if (array == null || array.length == 0) 
            return -1;
        
        
        // 2. 二分查找的范围
        int start = 1, end = array.length - 2, index = 0;
        
        // 3. 开始循环进行二分查找
        // 此处注意 start 和 end 区间需要能覆盖住所有目标值
        // 该循环条件很重要 , 是通用模板
        // ★ 要点一 : 此处尽量不要使用 start <= end 或 start < end 作为循环判定条件 , 在某些情况下会执行失败
        // 为了让程序有更多的适应性 , 这里使用 start + 1 < end 作为循环判定条件 , 可以有效避免死循环
        while (start + 1 < end) 
            // 3.1 计算中间索引
            // ★ 要点二 : 此处尽量不要使用 (start + end) / 2 , 如果 两个数值都接近 Int.MAX_VALUE 则会溢出
            int mid = start + (end - start) / 2;
            // 3.2 对比中间元素与目标值
            if (array[mid] > array[mid + 1]) 
                index = mid;
                // ★ 要点三 : 由于循环判定条件是 start + 1 < end , 此处 end 赋值可以不使用 mid - 1
                end = mid;
             else 
                // ★ 要点四 : 由于循环判定条件是 start + 1 < end , 此处 start 赋值可以不使用 mid + 1
                start = mid;
            
        

        // 4. ★ 要点五 : 循环完毕 , 判定 start 和 end 是不是要找的值
        // 如果数组只有两个数的情况下 
        // while(start + 1 < end) 循环控制条件中的 start + 1 < end 直接为 false 
        // 循环直接退出 , 此处判定一下 start 和 end 是不是要找的值 
        if(array[start] > array[start + 1]) 
            return start;
        
        if(array[end]  > array[end + 1]) 
            return end;
        
        return index;
    

以上是关于算法二分法 ③ ( 山脉数组的峰顶索引 | 枚举法 | 二分法 )的主要内容,如果未能解决你的问题,请参考以下文章

[E二分] lc852. 山脉数组的峰顶索引(二分+思维+脑筋急转弯)

LeetCode 852 山脉数组的峰顶索引[二分法] HERODING的LeetCode之路

JS Leetcode 852. 山脉数组的峰顶索引图解分析,高高的山峰一起吹山风吧。

[LeetCode] 852. 山脉数组的峰顶索引

852. 山脉数组的峰顶索引

Python描述 LeetCode 852. 山脉数组的峰顶索引