给定一个非空且只包含非负数的整数数组 nums,数组的度的定义是指数组里任一元素出现频数的最大值。
你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
题目地址:数组的度
或直转到:https://leetcode-cn.com/problems/degree-of-an-array/
- 我的错误题解
//数组的度,利用对应数组(类哈希表)一次走完
//经过仔细读题之后,发现这道题没有想的那么简单
//求出数组的度只是第一步,在度求出后,要求出与当前数组度相同的最短子数组的度的数量
//很明显,最短的子数组一定是由两个对应频数最高的数字作为边界
//现在的目标很明显了,在直到数组频数的情况下,得到这个边界值。
//得到边界值的方法
//——————————————————————————————————————————————————
//我发现了我的算法缺陷,即将元素大小作为角标并不理智,题目没有标记数组小元素大不可以
//我要进行修改
//我修改进了,增加了移动的类指针指向出现频率最高的数字的两端。
//我发现了我的算法缺陷,因为若出现多个频率一样的数字我无法判断两次进行比较。
int getdistance(int* nums,int numsSize,int maxone) {//得到出现次数最多元素的最小角标和最大角标
int _minipt,_maxpt;//用来记录边界的指针
_minipt=50000;
_maxpt=0;
for (int p = 0; p < numsSize; ++p) {
if(_minipt>p&&nums[p]==maxone){
_minipt=p;
}
if(_maxpt<p&&nums[p]==maxone){
_maxpt=p;
}
}
return (_maxpt-_minipt+1);
}
int findShortestSubArray(int* nums, int numsSize){
int* pt;
int max;//取到最多的出现次数
int jb;//角标
int zuihouxuanze;//最后用来接收最大值的
zuihouxuanze=50000;
jb=0;
int maxsize;//可能会造成内存使用太多,不过一定可行
int* sz;
//
sz=(int*)malloc(sizeof(int)*(numsSize+1));//负责装着频率最高的数字组
for (int l = 0; l < numsSize; ++l) {
sz[l]=-1;
}
max=0;
maxsize=0;
if(numsSize>1){
for (int j = 0; j < numsSize; ++j) {
if (maxsize<nums[j]){
maxsize=nums[j];
}
}//得到元素最大值,这里其实可以继续优化,我不想写了,懒了
//不需要进行模块化
pt=(int*)malloc(sizeof(int)*(maxsize+1));//创建键值空间
for (int i = 0; i < maxsize+1; ++i) {
pt[i]=0;
}//初始化键值数组
//不需要进行模块化
for (int q = 0; q < numsSize; ++q) {
pt[nums[q]]++;
}//记录每个元素出现的次数
//不需要进行模块化
for (int k = 0; k < numsSize; ++k) {
if (max<pt[nums[k]]){
max=pt[nums[k]];
}
}//看看哪个元素出现的最多
//这里是分界线,也是我需要进行二次扫描的
//这里得到了最高频率数字,但相同频率的可能出现多次
if(max==1){
return 1;
}//最大的只有单次频率就别扫描了,浪费资源
for (int k = 0; k < numsSize; ++k) {
if (max==pt[nums[k]]){
sz[jb]=nums[k];
jb++;
}//从这里降低时间复杂度吧。
}//用一个数组装下同为最大频数的数字
jb=0;
while(sz[jb]>=0){
sz[jb]=getdistance(nums,numsSize,sz[jb]);
jb++;
}
for (int i = 0; i < jb; ++i) {
if (zuihouxuanze>sz[i]&&sz[i]>0){
zuihouxuanze=sz[i];
}
}
//new func
return zuihouxuanze;
}
else{
return 1;
}
}
分析我的做题思路:
1、为了得到数组的度,首先要得到出现频率最多的数字。为了得到出现频率最高的数字,我决定使用键值的形式。即创建一个数组,对应角标处存储的数据大小就代表着对应角标数字出现的次数。例:pt[50]=6 就代表数字50出现了6次 。为利用这种形式,就需要调用很多空间,我当时也没想太多,搞就完事了。具体也分这么几步:得到最大数字,创建最大数字+1大小的空间,利用键值对数组进行扫描,然后对键值数组进行扫描,得到一个对应键值数组最大数字的角标,角标即出现频率最高或最高的之一。
2、这里其实可以通过这个最高频率再次对键值数组进行扫描,将所有出现频率均为最高的数字记录,以便后续的操作。之后我排除了一种情况,即所有数字的出现频率都是1,那度肯定就是1了。接下来就确实是对其进行了一次扫描,利用sz装卸所有同为最大频数的数字。
3、既然得到了出现频数均为最高的数字的数组,那直接就看大家能代表的最短子数组为多少。这时候就利用到了"getdistance"函数,计算距离存入数组中,只需要将最短距离取出就可以得到数组的度了。可这期间进行了多次n循环,大大提高了时间复杂度,进而提高了程序运行的时间。一个全是1的例子也让我彻彻底底的傻了。
优化嘛~先不谈,然我们看看大佬的思路
- 大佬的代码
int cnt[50000];
int findShortestSubArray(int* nums, int numsSize){
int i ;
int l = 0;
int r = 0;
int maxCnt = 0;
int res = numsSize;
memset(cnt, 0, sizeof(cnt));
/* 计算数组的度(出现最多元素的次数) */
for (i = 0; i < numsSize; i++) {
maxCnt = fmax(maxCnt, ++cnt[nums[i]]);
}
/* 双指针实现, 当窗口内的度等于maxCnt, 收缩窗口 */
memset(cnt, 0, sizeof(cnt));
for (r = 0; r < numsSize; r++) {
cnt[nums[r]]++;
while (cnt[nums[r]] == maxCnt) {
res = fmin(res, r - l + 1);
cnt[nums[l++]]--;
}
}
return res;
}
整体思路运用了神奇的滑动窗口机制,是个我在《计算机网络》中遇到过的机制,可惜没有扩展,这里我要仔细思考一下。这个编程思路真的值得借鉴。
1、计算数组的度,即得到数组中出现最多的元素次数。这里作者运用了memset进行初始化,利用fmax与灵活的自增来计算数组中出现最多次数的元素。这里注意maxCnt初始值为0 。每次都取到最大值,可以注意到cnt[nums[i]]
就是我说的键值操作。
/最开始是maxCnt是0,在每一次对应键值的自增中,maxCnt总是对应的出现频率最高的数字的出现频率/
2、得到maxCnt后。就可以进行滑动窗口了。然而,拥有最高出现次数的数字并不一定只有一个。我们要想一个办法来对所有出现最高次数的数字来求本数组拥有出现频率最高数字的最短子数组。
3、作者的想法很巧妙,他再次将cnt初始化,原来装的是各个数字出现的次数,不过我们得到了数字出现的最高次数了(即maxCnt)。只需要利用for循环,再求一次每个数字的出现次数,一旦出现次数与maxCnt相等,就求res,而res的变化和 l 变量有关。下面我们要仔细分析cnt[nums[l++]]--
每次运行这段代码都伴随着变量 l 的自增,与对应数字出现次数的自减,这个是上面cnt[nums[r]]++
的一个逆过程,不过方向是正着的。一直到nums[l++]再一次等于当时的nums[r],则触碰到了这个边缘,即最短子数组。
可是这里有一个问题了,若一个数组有多个频率都是一样高的数字该怎么办呢?就比如第一个例子{1,2,2,3,1} 。这里我们可以看出利用这个方法,可以完美的进行求解
可能的穿插情况可以简化为1212、1221、1122,我们发现除了1221另外两种都可直接用这段滑动窗口求解,然而1221中22肯定是短于1221的,因此这段代码可以直接取到最小值。我只能说妙啊,太妙了。这也太能滑了。