LeetCode刷题之路-697. 数组的度

LeetCode刷题之路-697. 数组的度

题目介绍

给定一个非空且只包含非负数的整数数组 nums ,数组的度的定义是指数组里任一元素出现频数的最大值。

你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1:

输入:[1, 2, 2, 3, 1]
输出:2
解释:
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.

示例 2:

输入:[1,2,2,3,1,4,2]
输出:6

提示:

  • nums.length 在1到 50,000 区间范围内。
  • nums[i] 是一个在 0 到 49,999 范围内的整数。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/degree-of-an-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

  1. 这个题在LeetCode上被标记为简单题,思路确实也很容易想出来。题目的意思是让找出具有最大度的最短连续子数组的长度,最大度意思就是数组中某一元素x出现的最大次数。首先要明确一点就是:具有最大度的最短连续子数组,其两端肯定是x第一次出现的位置和最后一次出现的位置。那么思路很容易,我们首先统计出每个元素的出现频数、第一次出现位置及最后一次出现位置,然后得出频数最大元素的第一次出现位置及最后一次出现位置,将两者相减加1即可得出最短长度。代码如下:

    class Solution {
    public:
        int findShortestSubArray(vector<int>& nums) {
            unordered_map<int, int> mv; // 统计nums数组中各个元素的频数
            unordered_map<int, pair<int, int> > mse; // 统计nums数组中的各个元素的第一次出现位置和最后一次的出现位置
            int maxv = 0; // 记录最大的频数
            for(int i = 0; i < nums.size(); i++){
                mv[nums[i]]++; // nums[i]的频数加1
                if(maxv < mv[nums[i]]){
                    maxv = mv[nums[i]]; // 更新最大频数
                }
                if(mse[nums[i]].first == 0) // 记录nums[i]第一次出现的位置(mp的int型的value一开始初始化为0)
                    mse[nums[i]].first = i + 1; // 规避开value初始化的值0,所以nums[i]的出现位置加1,最小为1
                mse[nums[i]].second = i + 1; // 更新nums[i]最后一次的出现位置
            }
            int ans = nums.size(); // 最短连续子数组的长度最大可能为nums数组的长度
            for(auto it = mv.begin(); it != mv.end(); it ++){
                if(it -> second == maxv){ // 频数等于最大频数
                    if(mse[it->first].second - mse[it->first].first + 1 < ans)
                        ans = mse[it->first].second - mse[it->first].first + 1; // 更新最短连续子数组的长度
                }
            }
            return ans;
        }
    };
    

    执行结果

  2. 看了C++版的官方题解感觉思路差不多,但是官方题解更加简单,将我的两个map综合成一个,代码如下:

    class Solution {
    public:
        int findShortestSubArray(vector<int>& nums) {
            unordered_map<int, vector<int> > mp;
            int n = nums.size();
            for(int i = 0; i < n; i++){
                if(mp.count(nums[i])){ // 检测nums[i]是否第一次出现
                    mp[nums[i]][0]++; // nums[i]的频数加1
                    mp[nums[i]][2] = i; // 更新nums[i]最后一次的出现位置
                }else{
                    mp[nums[i]] = {1, i, i}; // nums[i]第一次出现,对value进行初始化
                }
            }
            int maxNum = 0, minLen = 0;
            for(auto it = mp.begin(); it != mp.end(); it++){
                if(maxNum < it->second[0]){
                    maxNum = it->second[0]; // 更新最大频数
                    minLen = it->second[2] - it->second[1] + 1; // 更新最短长度
                }else if(maxNum == it->second[0]){ // 频数相等
                    if(minLen > it->second[2] - it->second[1] + 1){ // 频数相等但长度更短
                        minLen = it->second[2] - it->second[1] + 1; // 更新最短长度
                    }
                }
            }
            return minLen;
        }
    };
    

    执行结果

  3. 从其他题解中还学到了另一种解答方法,即Map+滑动窗口 的方法,看来以后得考虑用一下滑动窗口了,这种方法确实编码起来比较简单。但这种方法实际上思路与上面两种方法大同小异,只是运用滑动窗口来求出最短子数组的两端的位置,对于效率的提升并不明显,甚至运行时间更长。代码如下:

    class Solution {
    public:
        int findShortestSubArray(vector<int>& nums) {
            unordered_map<int, int> mp; // 记录频数
            int n = nums.size();
            int maxNum = 0; // 记录最大的频数
            for(int i = 0; i < n; i++){
                mp[nums[i]]++; // nums[i]的频数加1
                maxNum = max(maxNum, mp[nums[i]]); // 更新最大频数
            }
            mp.erase(mp.begin(), mp.end()); // 重构map
            int left = 0, right = 0; // 定义窗口
            int ans = nums.size(); // 满足频数的最短长度
            while(right < n){ // 寻找满足频数的元素
                mp[nums[right]]++;
                while(mp[nums[right]] == maxNum){ // 频数达到要求后,移动左边界到元素出现的第一个位置
                    ans = min(ans, right - left + 1); // 更新最短连续子数组的长度
                    mp[nums[left++]]--;
                }
                right++;
            }
            return ans;
        }
    };
    

    执行结果

结语

​ 这道题只要能仔细理解题目的含义及熟练运用map便不难做出,以后要运用一下滑动窗口这种方法来解决问题,编码起来确实简易懂,这次算是简单地尝试了一下。

坚持不懈地努力才能成为大神!

posted @ 2021-02-22 11:57  己平事  阅读(63)  评论(0编辑  收藏  举报