Find Peak Element

Question

Problem Statement

A peak element is an element that is greater than its neighbors.

Given an input array where num[i] ≠ num[i+1], find a peak element and return
its index.

The array may contain multiple peaks, in that case return the index to any one
of the peaks is fine.

You may imagine that num[-1] = num[n] = -∞.

For example, in array [1, 2, 3, 1], 3 is a peak element and your function
should return the index number 2.

Note:

Your solution should be in logarithmic complexity.

Credits:

Special thanks to @ts for adding
this problem and creating all test cases.

题解1

由时间复杂度的暗示可知应使用二分搜索。首先分析若使用传统的二分搜索,若A[mid] > A[mid - 1] && A[mid] < A[mid + 1],则找到一个peak为A[mid];若A[mid - 1] > A[mid],则A[mid]左侧必定存在一个peak,可用反证法证明:若左侧不存在peak,则A[mid]左侧元素必满足A[0] > A[1] > ... > A[mid -1] > A[mid],与已知A[0] < A[1]矛盾,证毕。同理可得若A[mid + 1] > A[mid],则A[mid]右侧必定存在一个peak。如此迭代即可得解。
由于题中假设端点外侧的值均为负无穷大,即num[-1] < num[0] && num[n-1] > num[n], 那么问题来了,这样一来就不能确定峰值一定存在了,因为给定数组为单调序列的话就咩有峰值了,但是实际情况是——题中有负无穷的假设,也就是说在单调序列的情况下,峰值为数组首部或者尾部元素,谁大就是谁了。

备注:如果本题是找 first/last peak,就不能用二分法了。

Python

  1. class Solution:
  2. #@param A: An integers list.
  3. #@return: return any of peek positions.
  4. def findPeak(self, A):
  5. if not A:
  6. return -1
  7. l, r = 0, len(A) - 1
  8. while l + 1 < r:
  9. mid = l + (r - l) / 2
  10. if A[mid] < A[mid - 1]:
  11. r = mid
  12. elif A[mid] < A[mid + 1]:
  13. l = mid
  14. else:
  15. return mid
  16. mid = l if A[l] > A[r] else r
  17. return mid

C++

  1. class Solution {
  2. public:
  3. /**
  4. * @param A: An integers array.
  5. * @return: return any of peek positions.
  6. */
  7. int findPeak(vector<int> A) {
  8. if (A.size() == 0) return -1;
  9. int l = 0, r = A.size() - 1;
  10. while (l + 1 < r) {
  11. int mid = l + (r - l) / 2;
  12. if (A[mid] < A[mid - 1]) {
  13. r = mid;
  14. } else if (A[mid] < A[mid + 1]) {
  15. l = mid;
  16. } else {
  17. return mid;
  18. }
  19. }
  20. int mid = A[l] > A[r] ? l : r;
  21. return mid;
  22. }
  23. };

Java

  1. class Solution {
  2. /**
  3. * @param A: An integers array.
  4. * @return: return any of peek positions.
  5. */
  6. public int findPeak(int[] A) {
  7. if (A == null || A.length == 0) return -1;
  8. int lb = 0, ub = A.length - 1;
  9. while (lb + 1 < ub) {
  10. int mid = lb + (ub - lb) / 2;
  11. if (A[mid] < A[mid + 1]) {
  12. lb = mid;
  13. } else if (A[mid] < A[mid - 1]){
  14. ub = mid;
  15. } else {
  16. // find a peak
  17. return mid;
  18. }
  19. }
  20. // return a larger number
  21. return A[lb] > A[ub] ? lb : ub;
  22. }
  23. }

源码分析

典型的二分法模板应用,需要注意的是需要考虑单调序列的特殊情况。当然也可使用紧凑一点的实现如改写循环条件为l < r,这样就不用考虑单调序列了,见实现2.

复杂度分析

二分法,时间复杂度 O(\log n).

Java - compact implementation[^leetcode_discussion]

  1. public class Solution {
  2. public int findPeakElement(int[] nums) {
  3. if (nums == null || nums.length == 0) {
  4. return -1;
  5. }
  6. int start = 0, end = nums.length - 1, mid = end / 2;
  7. while (start < end) {
  8. if (nums[mid] < nums[mid + 1]) {
  9. // 1 peak at least in the right side
  10. start = mid + 1;
  11. } else {
  12. // 1 peak at least in the left side
  13. end = mid;
  14. }
  15. mid = start + (end - start) / 2;
  16. }
  17. return start;
  18. }
  19. }

C++ 的代码可参考 Java 或者 @xuewei4d 的实现。

Warning leetcode 和 lintcode 上给的方法名不一样,leetcode 上的为findPeakElement而 lintcode 上为findPeak,弄混的话会编译错误。

Reference