Given a non-empty array of non-negative integers nums, the degree of this array is defined as the maximum frequency of any one of its elements.
Your task is to find the smallest possible length of a (contiguous) subarray of nums, that has the same degree as nums.
Example 1:
Input: [1, 2, 2, 3, 1]
Output: 2
Explanation:
The input array has a degree of 2 because both elements 1 and 2 appear twice.
Of the subarrays that have the same degree:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
The shortest length is 2. So return 2.
Example 2:
Input: [1,2,2,3,1,4,2]
Output: 6
Note:
- nums.length will be between 1 and 50,000.
- nums[i] will be an integer between 0 and 49,999.
题中给定一个数组,其中元素出现最多的次数成为该数组的度数(degree ),然后对数组进行划分而且必须连续,要求划分的子数组的度和原数组的度一致,然后求出子数组最短的长度。
刚开始的思维就是,首先遍历一遍原数组找到数组的度数,并且找到生成该度数对应的数字,就如题中的例子: [1, 2, 2, 3, 1],对应的度数就是2,能够生成该度数对应的数字为:1,2。
然后再次遍历数组,找到度数对应数字出现时候的开始位置和结束位置,然后求出距离。就如题中的1,2,其中1对应的开始位置为:0,结束位置为:4;其中的2开始位置为:1,结束为止为2。所以最端的距离就是2-1+1。
class Solution {
public:
int findShortestSubArray(vector<int>& nums) {
int maxCount = 0;//数字出现的最多次数,也就是度数
unordered_map<int,int> numCount;//用来存放每个数字出现的次数
unordered_set<int> maxNums;//存放最大次数的数字
for(int i=0;i<nums.size();i++) {
numCount[nums[i]]++;//对应的次数加一
if(numCount[nums[i]] > maxCount) {//交换位置,并清空maxNums数组,存放新的最大值
maxCount = numCount[nums[i]];
maxNums.clear();
maxNums.insert(nums[i]);
}
if(numCount[nums[i]] == maxCount) {//如果相等的话,把新的数字添加进去
maxNums.insert(nums[i]);
}
}
int startIndex = -1;
int endIndex = -1;
int minLen = -1;
for(int num : maxNums) {//遍历所有度数最大的数字,找到距离最短的
startIndex = -1;
endIndex = -1;
for(int i=0; i<nums.size(); i++) {
if(nums[i] == num) {
if(-1 == startIndex) {
startIndex = i;
}
if(i >= endIndex) {
endIndex = i;
}
}
}
minLen = (minLen < 0) ? (endIndex-startIndex+1) : min(endIndex-startIndex+1,minLen);
//cout << startIndex << "," << endIndex << endl;
//cout << (endIndex-startIndex+1) << endl;
}
return minLen;
}
};
上面的解题方法明显时间复杂度很高,而且从题中能够提取出关键的东西:数组的度数、每个元素的开始位置和结束位置。如果能够在时间复杂度为O(n)的情况下计算出关键的内容,就可以大大提高算法的性能。
仔细想一下,每个元素的开始位置是固定不变的。结束的位置是会发生变换的,但是在循环的过程中如果使用for(int i=0;i < n; i ++)这种形式的话,那么i就可以作为结束的位置的。同样的数组的度数在循环的过程中也是可以变换的,度数的更新是跟每个元素的出现次数有关的。所以我们只需要记录下元素的开始位置和出现的次数,然后利用循环时的i和开始位置就能够算出距离,再利用元素出现的次数实时更新度数。最后就能够在时间复杂度为O(n)的情况下算出结果。
class Solution {
public:
int findShortestSubArray(vector<int>& nums) {
if(nums.size() <= 1) {
return nums.size();
}
int minLen = 0;//最小的为1
int degree = 0;//最小的度为1
unordered_map<int,pair<int,int>> ans;//用来存储元素和该元素开始下标和出现的次数
for(int i=0; i<nums.size(); i++) {
ans[nums[i]].second++;//出现的次数加一
if(ans[nums[i]].second == 1) {//代表该元素首次出现
//cout << "数字:" << nums[i] << ",开始的坐标" << i << endl;
ans[nums[i]].first = i;//记录开始的坐标
}
if(ans[nums[i]].second > degree) {//如果此时出现的次数大于数组中的最高度数
degree = ans[nums[i]].second;//重置最高度数
minLen = i - ans[nums[i]].first + 1;//当前位置减去开始位置得到中间长度
//cout << "当前坐标" << i << ",开始坐标:" << ans[nums[i]].first << endl;
} else if(ans[nums[i]].second == degree) {//如果度数相等则需要比较哪一个比较短
minLen = min(minLen,i - ans[nums[i]].first + 1);
}
}
return minLen;
}
};