852. 山脉数组的峰顶索引

360 阅读1分钟

852. 山脉数组的峰顶索引

这是我参与更文挑战的第 6 天,活动详情查看: 更文挑战

题目

来源:力扣(LeetCode)

链接:leetcode-cn.com/problems/pe…

符合下列属性的数组 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 。

示例 1:

输入:arr = [0,1,0]
输出:1

示例 2:

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

示例 3:

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

示例 4:

输入:arr = [3,4,5,1]
输出:2

示例 5:

输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2

提示:

  • 3 <= arr.length <= 104
  • 0 <= arr[i] <= 106
  • 题目数据保证 arr 是一个山脉数组

**进阶:**很容易想到时间复杂度 O(n) 的解决方案,你可以设计一个 O(log(n)) 的解决方案吗?

问题分析

山脉数组的意思是,一个数组中有一个最大值,下标是i,那么i之前数组是递增的,i之后数组是递减的

进阶要求是设计一个 O(log(n)) 的解决方案,看到 O(log(n)),自然就要想到二分查找

一次遍历

这是很常规的解法,就是遍历一次数组,找到第i个元素,满足arr[i]> arr[i-1] 并且arr[i] < arr[i+1],那么i就是要返回的结果了,代码就不写了

二分查找

计算 mid 时需要防止溢出,一般建议写成: mid = left + (right - left) / 2

我觉得写成mid = (left + right) >>> 1这样更好,无符号右移一位也能防止溢出

public class Solution {
  public int peakIndexInMountainArray1(int[] arr) {
    //从[1,arr.length - 2]中查找
    int left = 1;
    int right = arr.length - 2;
    while (left <= right) {
      int mid = (left + right) >>> 1;
      if (arr[mid] < arr[mid - 1]) {
        right = mid - 1;
      } else if (arr[mid] < arr[mid + 1]) {
        left = mid + 1;
      } else {
        return mid;
      }
    }
    return -1;
  }
}

二分查找的思想是很简单,但是代码写起来还是很容易出错的,确实是思路很简单,细节是魔鬼

  • 首先我定义的left = 1,right = arr.length - 2,这是因为根据题意,数组最少有三个数,而且峰顶不会是第0位或者最后一位。我这样定义也就意味这我要查找的区间是一个左右都闭合的区间[1,arr.length - 2]
  • 因为左右都是闭合的区间,所以while循环中用的是小于等于,而不是小于。比如说数组中只有三个数{1,2,3},此时left=1,right=1,如果只用小于号,这种情况是进不了while循环的
  • 因为左右都是闭合的区间,所以 right = mid - 1, left = mid + 1,而不是 right = mid , left = mid。就是mid是在闭合的区间内的,它已经被计算过一次了,下次就要把它去掉

所以也可以写成这样

class Solution {
  public int peakIndexInMountainArray(int[] arr) {
   int left = 1;
    int right = arr.length - 1;
    while (left < right) {
      int mid = (left + right) >>> 1;
      if (arr[mid] < arr[mid - 1]) {
        right = mid ;
      } else if (arr[mid] < arr[mid + 1]) {
        left = mid + 1;
      } else {
        return mid;
      }
    }
    return -1;
  }
}

关于二分查找的细节可以看详解二分查找算法 www.cnblogs.com/kyoner/p/11…

leetcode也总结了二分查找的三个模板 leetcode-cn.com/leetbook/re… 可以看看