《程序员面试金典(第6版)》面试题 10.11. 峰与谷(双指针,贪心思想)

题目描述

在一个整数数组中,“峰”是大于或等于相邻整数的元素,相应地,“谷”是小于或等于相邻整数的元素。例如,在数组{5, 8, 4, 2, 3, 4, 6}中,{8, 6}是峰, {5, 2}是谷。现在给定一个整数数组,将该数组按峰与谷的交替顺序排序。

示例:

  • 输入: [5, 3, 1, 2, 3]
  • 输出: [5, 1, 3, 2, 3]

提示:

  • nums.length <= 10000

解题思路与代码

这道题我对他的理解就是我要把它给我的数组里的元素排列成一个大一个小的模样。当然这道题的答案是不唯一的。你想怎么排就怎么排。第一个元素是波峰还是波谷都无所谓。

那我们就来看看我们可以写出多少种做法。

方法1,sort排序后 + 分割数组 + 用了额外的vector

  • 我这方法一写出来,就感觉有一点土味,不过无所谓,提交成功就行。

  • 首先把这个数组用sort函数从新排列一下,这个时候它就变成了一个有序数组,然后从数组中间将数组一分为二,分别存入两个新的数组中。聪明的大家肯定能知道我这一步是为啥,更聪明的同学可能知道了一种更好的方法。

  • 此时,原数组前半部分元素的数组记为n1,另一部分记为n2

  • 用一个while循环,每循环一次就重新替换原数组中的两个元素直到替换完毕

具体实现请看代码:

class Solution {
public:
    void wiggleSort(vector<int>& nums) {
        if(nums.empty()) return ;
        sort(nums.begin(),nums.end());
        int mid = (nums.size()) / 2;
        vector<int> n1 (nums.begin(),nums.begin() + mid);
        vector<int> n2 (nums.begin() + mid, nums.end());
        int p1 = 0;
        int p2 = 0;
        int i = 0;

        while(p1 < n1.size() && p2 < n2.size() ){
            nums[i] = n2[p2];
            ++i;
            ++p2;
            nums[i] = n1[p1];
            ++i;
            ++p1;
        }
        if(p2 < n2.size()){
            nums[i] = n2[p2];
        }

        return ;
    }
};

《程序员面试金典(第6版)》面试题 10.11. 峰与谷(双指针,贪心思想)_第1张图片

复杂度分析

时间复杂度:

  • 排序:sort 函数的时间复杂度为 O(n*logn),其中 n 是数组 nums 的长度。
    创建两个子数组:这部分的时间复杂度为 O(n),因为需要遍历整个数组。
  • 合并子数组:这部分的时间复杂度也为 O(n),因为需要遍历两个子数组的长度之和。
  • 综上,整个算法的时间复杂度为 O(n*logn)。

空间复杂度:

  • 创建两个子数组 n1 和 n2:这部分的空间复杂度为 O(n),因为需要存储原数组的所有元素。
  • 其他变量的空间消耗可以忽略不计。
  • 综上,整个算法的空间复杂度为 O(n)。

方法2,对方法1的优化,使用双指针替代额外vector

首先,当数组元素的数量等于0,1,2时,原地返回

之后对数组的元素进行排序,排序后开始while循环

循环中用双指针分别指向数组的第2个和第3个元素,交换它们。

双指针分别加2,直到循环结束,返回

具体代码如下:

class Solution {
public:
    void wiggleSort(vector<int>& nums) {
        int size = nums.size();
        if(size == 0 || size == 1 || size == 2) return;
        sort(nums.begin(),nums.end());
        int left = 1;
        int right = 2;
        while(left < size  && right < size){
            swap(nums[left],nums[right]);
            left += 2;
            right += 2;
        }
        return ;
    }
};

《程序员面试金典(第6版)》面试题 10.11. 峰与谷(双指针,贪心思想)_第2张图片

复杂度分析

这个改进后的代码确实更简洁且高效。现在来分析一下新版本的时间复杂度和空间复杂度:

时间复杂度:

  • 排序:sort 函数的时间复杂度仍为 O(n*logn),其中 n 是数组 nums 的长度。
  • 交换元素:这部分的时间复杂度为 O(n),因为最多需要遍历整个数组。
  • 综上,整个算法的时间复杂度仍为 O(n*logn)。

空间复杂度:

  • 原地交换元素:这个改进后的实现不再需要额外的数组空间,所以空间复杂度降低。
    其他变量的空间消耗可以忽略不计。
  • 综上,整个算法的空间复杂度为 O(1)。

方法3,继续优化,不使用排序,直接在原数组上操作(用到了贪心的思想)

  • 对于这道题来说,时间复杂度最低的算法,也莫过于O(n)的算法了,也就是遍历一次原数组,就得出答案。

  • 说实话这道题平平无奇,我做完了才后知后觉我这道题其实采用了贪心的做法,因为贪心算法的思想经常与直观的解题思路相符。贪心算法的核心是在每一步都做出当前看起来最优的选择,而这种方法很容易与我们在解决问题时的自然直觉相吻合。

那我们来讲讲这道题我是如何做的吧:

  • 这个方法的思路是从数组的第二个元素开始,按照奇数索引位置为峰、偶数索引位置为谷的规律,依次遍历数组。
  • 在遍历过程中,如果当前元素与要求的峰谷规律不符,就与前一个元素交换位置。这样一来,整个数组就会按照峰谷交替的顺序排列。

具体的代码如下

class Solution {
public:
    void wiggleSort(vector<int>& nums) {
        for(int i = 1; i < nums.size(); ++i){
            if(i % 2 == 0){
                if(nums[i] > nums[i-1]) swap(nums[i],nums[i-1]);
            }else{
                if(nums[i] < nums[i-1]) swap(nums[i],nums[i-1]);
            }
        }
    }
};

《程序员面试金典(第6版)》面试题 10.11. 峰与谷(双指针,贪心思想)_第3张图片

复杂度分析

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

总结

这道题,我认为,总体上不难。考察了排序,双指针,与贪心思想。其实掌握其中任何一种知识点。对于解决这道题都不是太难的事情。

这道题贪心的解法,你可能要多想想才能想出来。不过也很好理解啦。

最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货文章

你可能感兴趣的:(算法题解析与个人做题技巧总结,#,面试,算法,c++,贪心算法)