《LeetCode之每日一题》:151.寻找峰值

Posted 是七喜呀!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《LeetCode之每日一题》:151.寻找峰值相关的知识,希望对你有一定的参考价值。

这里写目录标题

有关题目

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 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]
输出:15 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6
提示:

1 <= nums.length <= 1000
-2^31 <= nums[i] <= 2^31 - 1
对于所有有效的 i 都有 nums[i] != nums[i + 1]

题解

Tips

两个 pair 类型数据可以直接使用 ==,!=<<=>>= 比较大小,
比较规则是先以 first 的大小作为标准,
只有当 first 相等时才去判别 second 的大小。

法一:一次遍历(O(n))

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        int ans = 0;
        if (n == 1) return 0;//[1]
        if (nums[0] > nums[1]) return 0;
        if (nums[n - 2] < nums[n - 1]) return n - 1;
        for (int i = 1; i < n - 1; ++i){
            if (nums[i - 1] < nums[i] && nums[i] > nums[i +  1]){
                ans = i;
                break;
            }
        }
        return ans;
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

法二:寻找最大值

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        return max_element(begin(nums), end(nums)) - begin(nums);
    }
};

时间复杂度:O(n),n为nums长度
空间复杂度:O(1)

法三:迭代爬坡
参考官方题解

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        int idx = rand() % n;
        
		//辅助函数, 输入下标 i,返回pair类型(0 / 1, nums[i])
		//巧妙地处理了i = 0, 与i = n的状态
		//对于下标0, n,限制pair->first为 0,而合法的下标为1,保证满足题干条件nums[-1] = nums[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 - 1) > get(idx)){
                idx -= 1;
            } else if (get(idx) < get(idx + 1)){
                //题干条件严格保证数组中无重复的元素,故可以使用else if
                idx += 1;
            } 
         }
         return idx;
    }
};

时间复杂度:O(n),当数组单调的时候,最坏的情况O(n)仍有可能达到O(n)
空间复杂度:O(1)

法四:法三的二分查找优化

思路:
在法二基础上,我们若找到nums[i - 1] > nums[i],
那[i, n - 1]部分我们就不需要在遍历了,由于num[-1] = -∞,
所以肯定在[-1, i - 1]存在峰值

同理右边也是,故我们可以使用二分查找加快这一过程
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 l = 0, r = n - 1, ans = -1;
        while(l <= r){
            int mid = l + (r - l) / 2;
            if (get(mid - 1) < get(mid) && get(mid) > get(mid + 1)){
                ans = mid;
                break;
            } 
            if (get(mid) < get(mid + 1)){
                l = mid + 1;
            } else if (get(mid - 1) > get(mid)){
                r = mid - 1;
            }
        }
        return ans;
    }
};

时间复杂度:O(log n)
空间复杂度:O(1)

法五:贪心 + 二分查找

思路:
碰见上升则右侧必有峰
贪心:不去寻找可能最近的答案,而是每次尽可能用二分缩小范围

如果存在nums[mid] < nums[mid + 1],由于nums[n] = -∞ 那么一定存在下标(mid, n - 1]范围内,满足条件的峰值

我们使用二分查找加快这一进程:
如果nums[mid] < nums[mid + 1] 我们左边界l = mid + 1;
不然我们就r = mid,(由于在nums[mid] > nums[mid + 1],nums[mid]可能不是最大的,所以我们需要左移右边界,直至找到峰值)

代码一:

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        int l = 0, r = n - 1;
        while(l < r){
            int mid = l + (r - l) / 2;
            if (nums[mid] < nums[mid + 1]){
                l = mid + 1;
            } else {
                r = mid;
            }
            //缩写为
            //nums[mid] < nums[mid + 1] ? l = mid + 1 : r = mid;
        }
        return l;
    }
};

时间复杂度:O(log n)
空间复杂度:O(1)

代码二:
参考官方题解评论区下梦璃夜·天星

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        nums.push_back(INT_MIN);
        int n = nums.size();
        int l = 0, r = n - 2;
        while(l < r){
            int mid = l + (r - l) / 2;

            //等号是由于上面我们尾插了一个可能跟数组元素相同的值INT_MIN
            if (nums[mid] >= nums[mid + 1] && (mid == 0 || nums[mid] >= nums[mid - 1])){
                return mid;
            } else if (nums[mid] < nums[mid + 1]) {//上面有 =,无法取到等于,所以可以加减 一
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return l;
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

以上是关于《LeetCode之每日一题》:151.寻找峰值的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 162. 寻找峰值(二分)/ 212. 单词搜索 II(Trie字典树) / 36. 有效的数独

Java算法 每日一题 编号151:反转字符串中的单词

Java算法 每日一题 编号151:反转字符串中的单词

Java算法 每日一题 编号151:反转字符串中的单词

leetcode之162寻找峰值Golang

《LeetCode之每日一题》:280.矩阵置零