峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums
,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞
。
你必须实现时间复杂度为 O(log n)
__ 的算法来解决此问题。
示例 1:
**输入:** nums = [1,2,3,1]
**输出:** 2
**解释:** 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
**输入:** nums = [1,2,1,3,5,6,4]
**输出:** 1 或 5
**解释:** 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
提示:
1 <= nums.length <= 1000
-231 <= nums[i] <= 231 - 1
- 对于所有有效的
i
都有 nums[i] != nums[i + 1]
方法一:寻找最大值
思路与算法
由于题目保证了 $\textit{nums}[i] \neq \textit{nums}[i+1]$,那么数组 $\textit{nums}$ 中最大值两侧的元素一定严格小于最大值本身。因此,最大值所在的位置就是一个可行的峰值位置。
我们对数组 $\textit{nums}$ 进行一次遍历,找到最大值对应的位置即可。
代码
[sol1-C++]1 2 3 4 5 6
| class Solution { public: int findPeakElement(vector<int>& nums) { return max_element(nums.begin(), nums.end()) - nums.begin(); } };
|
[sol1-Java]1 2 3 4 5 6 7 8 9 10 11
| class Solution { public int findPeakElement(int[] nums) { int idx = 0; for (int i = 1; i < nums.length; ++i) { if (nums[i] > nums[idx]) { idx = i; } } return idx; } }
|
[sol1-C#]1 2 3 4 5 6 7 8 9 10 11
| public class Solution { public int FindPeakElement(int[] nums) { int idx = 0; for (int i = 1; i < nums.Length; ++i) { if (nums[i] > nums[idx]) { idx = i; } } return idx; } }
|
[sol1-Python3]1 2 3 4 5 6 7
| class Solution: def findPeakElement(self, nums: List[int]) -> int: idx = 0 for i in range(1, len(nums)): if nums[i] > nums[idx]: idx = i return idx
|
[sol1-Golang]1 2 3 4 5 6 7 8
| func findPeakElement(nums []int) (idx int) { for i, v := range nums { if v > nums[idx] { idx = i } } return }
|
[sol1-JavaScript]1 2 3 4 5 6 7 8 9
| var findPeakElement = function(nums) { let idx = 0; for (let i = 1; i < nums.length; ++i) { if (nums[i] > nums[idx]) { idx = i; } } return idx; };
|
复杂度分析
方法二:迭代爬坡
思路与算法
俗话说「人往高处走,水往低处流」。如果我们从一个位置开始,不断地向高处走,那么最终一定可以到达一个峰值位置。
因此,我们首先在 $[0, n)$ 的范围内随机一个初始位置 $i$,随后根据 $\textit{nums}[i-1], \textit{nums}[i], \textit{nums}[i+1]$ 三者的关系决定向哪个方向走:
如果 $\textit{nums}[i-1] < \textit{nums}[i] > \textit{nums}[i+1]$,那么位置 $i$ 就是峰值位置,我们可以直接返回 $i$ 作为答案;
如果 $\textit{nums}[i-1] < \textit{nums}[i] < \textit{nums}[i+1]$,那么位置 $i$ 处于上坡,我们需要往右走,即 $i \leftarrow i+1$;
如果 $\textit{nums}[i-1] > \textit{nums}[i] > \textit{nums}[i+1]$,那么位置 $i$ 处于下坡,我们需要往左走,即 $i \leftarrow i-1$;
如果 $\textit{nums}[i-1] > \textit{nums}[i] < \textit{nums}[i+1]$,那么位置 $i$ 位于山谷,两侧都是上坡,我们可以朝任意方向走。
如果我们规定对于最后一种情况往右走,那么当位置 $i$ 不是峰值位置时:
代码
[sol2-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public: int findPeakElement(vector<int>& nums) { int n = nums.size(); int idx = rand() % n;
auto get = [&](int i) -> pair<int, int> { if (i == -1 || i == n) { return {0, 0}; } return {1, nums[i]}; };
while (!(get(idx - 1) < get(idx) && get(idx) > get(idx + 1))) { if (get(idx) < get(idx + 1)) { idx += 1; } else { idx -= 1; } } return idx; } };
|
[sol2-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class Solution { public int findPeakElement(int[] nums) { int n = nums.length; int idx = (int) (Math.random() * n);
while (!(compare(nums, idx - 1, idx) < 0 && compare(nums, idx, idx + 1) > 0)) { if (compare(nums, idx, idx + 1) < 0) { idx += 1; } else { idx -= 1; } } return idx; }
public int[] get(int[] nums, int idx) { if (idx == -1 || idx == nums.length) { return new int[]{0, 0}; } return new int[]{1, nums[idx]}; }
public int compare(int[] nums, int idx1, int idx2) { int[] num1 = get(nums, idx1); int[] num2 = get(nums, idx2); if (num1[0] != num2[0]) { return num1[0] > num2[0] ? 1 : -1; } if (num1[1] == num2[1]) { return 0; } return num1[1] > num2[1] ? 1 : -1; } }
|
[sol2-C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class Solution { public int FindPeakElement(int[] nums) { int n = nums.Length; int idx = new Random().Next(n);
while (!(Compare(nums, idx - 1, idx) < 0 && Compare(nums, idx, idx + 1) > 0)) { if (Compare(nums, idx, idx + 1) < 0) { idx += 1; } else { idx -= 1; } } return idx; }
public int[] Get(int[] nums, int idx) { if (idx == -1 || idx == nums.Length) { return new int[]{0, 0}; } return new int[]{1, nums[idx]}; }
public int Compare(int[] nums, int idx1, int idx2) { int[] num1 = Get(nums, idx1); int[] num2 = Get(nums, idx2); if (num1[0] != num2[0]) { return num1[0] > num2[0] ? 1 : -1; } if (num1[1] == num2[1]) { return 0; } return num1[1] > num2[1] ? 1 : -1; } }
|
[sol2-Python3]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution: def findPeakElement(self, nums: List[int]) -> int: n = len(nums) idx = random.randint(0, n - 1)
def get(i: int) -> int: if i == -1 or i == n: return float('-inf') return nums[i] while not (get(idx - 1) < get(idx) > get(idx + 1)): if get(idx) < get(idx + 1): idx += 1 else: idx -= 1 return idx
|
[sol2-Golang]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func findPeakElement(nums []int) int { n := len(nums) idx := rand.Intn(n)
get := func(i int) int { if i == -1 || i == n { return math.MinInt64 } return nums[i] }
for !(get(idx-1) < get(idx) && get(idx) > get(idx+1)) { if get(idx) < get(idx+1) { idx++ } else { idx-- } }
return idx }
|
[sol2-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| var findPeakElement = function(nums) { const n = nums.length; let idx = parseInt(Math.random() * n);
while (!(compare(nums, idx - 1, idx) < 0 && compare(nums, idx, idx + 1) > 0)) { if (compare(nums, idx, idx + 1) < 0) { idx += 1; } else { idx -= 1; } } return idx; }
const get = (nums, idx) => { if (idx === -1 || idx === nums.length) { return [0, 0]; } return [1, nums[idx]]; }
const compare = (nums, idx1, idx2) => { const num1 = get(nums, idx1); const num2 = get(nums, idx2); if (num1[0] !== num2[0]) { return num1[0] > num2[0] ? 1 : -1; } if (num1[1] === num2[1]) { return 0; } return num1[1] > num2[1] ? 1 : -1; }
|
复杂度分析
方法三:方法二的二分查找优化
思路与算法
我们可以发现,如果 $\textit{nums}[i] < \textit{nums}[i+1]$,并且我们从位置 $i$ 向右走到了位置 $i+1$,那么位置 $i$ 左侧的所有位置是不可能在后续的迭代中走到的。
这是因为我们每次向左或向右移动一个位置,要想「折返」到位置 $i$ 以及其左侧的位置,我们首先需要在位置 $i+1$ 向左走到位置 $i$,但这是不可能的。
并且根据方法二,我们知道位置 $i+1$ 以及其右侧的位置中一定有一个峰值,因此我们可以设计出如下的一个算法:
对于当前可行的下标范围 $[l, r]$,我们随机一个下标 $i$;
如果下标 $i$ 是峰值,我们返回 $i$ 作为答案;
如果 $\textit{nums}[i] < \textit{nums}[i+1]$,那么我们抛弃 $[l, i]$ 的范围,在剩余 $[i+1, r]$ 的范围内继续随机选取下标;
如果 $\textit{nums}[i] > \textit{nums}[i+1]$,那么我们抛弃 $[i, r]$ 的范围,在剩余 $[l, i-1]$ 的范围内继续随机选取下标。
在上述算法中,如果我们固定选取 $i$ 为 $[l, r]$ 的中点,那么每次可行的下标范围会减少一半,成为一个类似二分查找的方法,时间复杂度为 $O(\log n)$。
代码
[sol3-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Solution { public: int findPeakElement(vector<int>& nums) { int n = nums.size();
auto get = [&](int i) -> pair<int, int> { if (i == -1 || i == n) { return {0, 0}; } return {1, nums[i]}; };
int left = 0, right = n - 1, ans = -1; while (left <= right) { int mid = (left + right) / 2; if (get(mid - 1) < get(mid) && get(mid) > get(mid + 1)) { ans = mid; break; } if (get(mid) < get(mid + 1)) { left = mid + 1; } else { right = mid - 1; } } return ans; } };
|
[sol3-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| class Solution { public int findPeakElement(int[] nums) { int n = nums.length; int left = 0, right = n - 1, ans = -1; while (left <= right) { int mid = (left + right) / 2; if (compare(nums, mid - 1, mid) < 0 && compare(nums, mid, mid + 1) > 0) { ans = mid; break; } if (compare(nums, mid, mid + 1) < 0) { left = mid + 1; } else { right = mid - 1; } } return ans; }
public int[] get(int[] nums, int idx) { if (idx == -1 || idx == nums.length) { return new int[]{0, 0}; } return new int[]{1, nums[idx]}; }
public int compare(int[] nums, int idx1, int idx2) { int[] num1 = get(nums, idx1); int[] num2 = get(nums, idx2); if (num1[0] != num2[0]) { return num1[0] > num2[0] ? 1 : -1; } if (num1[1] == num2[1]) { return 0; } return num1[1] > num2[1] ? 1 : -1; } }
|
[sol3-C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class Solution { public int FindPeakElement(int[] nums) { int n = nums.Length; int left = 0, right = n - 1, ans = -1; while (left <= right) { int mid = (left + right) / 2; if (Compare(nums, mid - 1, mid) < 0 && Compare(nums, mid, mid + 1) > 0) { ans = mid; break; } if (Compare(nums, mid, mid + 1) < 0) { left = mid + 1; } else { right = mid - 1; } } return ans; }
public int[] Get(int[] nums, int idx) { if (idx == -1 || idx == nums.Length) { return new int[]{0, 0}; } return new int[]{1, nums[idx]}; }
public int Compare(int[] nums, int idx1, int idx2) { int[] num1 = Get(nums, idx1); int[] num2 = Get(nums, idx2); if (num1[0] != num2[0]) { return num1[0] > num2[0] ? 1 : -1; } if (num1[1] == num2[1]) { return 0; } return num1[1] > num2[1] ? 1 : -1; } }
|
[sol3-Python3]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution: def findPeakElement(self, nums: List[int]) -> int: n = len(nums)
def get(i: int) -> int: if i == -1 or i == n: return float('-inf') return nums[i] left, right, ans = 0, n - 1, -1 while left <= right: mid = (left + right) // 2 if get(mid - 1) < get(mid) > get(mid + 1): ans = mid break if get(mid) < get(mid + 1): left = mid + 1 else: right = mid - 1 return ans
|
[sol3-Golang]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func findPeakElement(nums []int) int { n := len(nums)
get := func(i int) int { if i == -1 || i == n { return math.MinInt64 } return nums[i] }
left, right := 0, n-1 for { mid := (left + right) / 2 if get(mid-1) < get(mid) && get(mid) > get(mid+1) { return mid } if get(mid) < get(mid+1) { left = mid + 1 } else { right = mid - 1 } } }
|
[sol3-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| var findPeakElement = function(nums) { const n = nums.length; let left = 0, right = n - 1, ans = -1; while (left <= right) { const mid = Math.floor((left + right) / 2); if (compare(nums, mid - 1, mid) < 0 && compare(nums, mid, mid + 1) > 0) { ans = mid; break; } if (compare(nums, mid, mid + 1) < 0) { left = mid + 1; } else { right = mid - 1; } } return ans; }
const get = (nums, idx) => { if (idx === -1 || idx === nums.length) { return [0, 0]; } return [1, nums[idx]]; }
const compare = (nums, idx1, idx2) => { const num1 = get(nums, idx1); const num2 = get(nums, idx2); if (num1[0] !== num2[0]) { return num1[0] > num2[0] ? 1 : -1; } if (num1[1] === num2[1]) { return 0; } return num1[1] > num2[1] ? 1 : -1; }
|
复杂度分析