本文主要是帮助大家熟练掌握利用数组进行有关判断的题目,看完本文后在之后的刷题中都可以利用这种思想,当然举例中的题目利用该种方法可能不是最优解,但绝对是你看到题目不用思考太多就可以做出来的方法,当然我也会介绍所列题目的其他方法以供大家思考,话不多说,进入正题。
基数排序
思想:已经给定一个数组,数组中就是我们需要排序的数列,再开辟一个数组,该数组的个数n为要排序的数中最大的数+1且全部初始化为0,根据需要排序数组中的数,找到新开辟的数组中该数的下标的位置,将该位置的数++。
例如
逻辑很简单,直接谈如何优化,一眼看出,某种情况下这种思路排序开的空间太大了,就比如要排序的数全部在1000~2000之间的数,我们从0开始开空间,那么前1000的空间就是白开了,造成很大的空间浪费。
我们可以先进行遍历,找到要排序数组中最大值和最小值,两者作差再加1,就是我们需要开的空间。
代码如下
//基数排序
void CountSort(int* a, int n)
{int min = a[0], max = a[0];for (int i = 0; i < n; i++){if (a[i] < min){min = a[i];}if (a[i] > max){max = a[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail");return;}memset(count, 0, sizeof(int) * range);//统计数据出现的次数for (int i = 0; i < n; i++){count[a[i] - min]++;//开的空间是减去min的,所以排序数组中的数在存储时也要减去min。}int j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j++] = i + min;//该位置的数出现几次,就统计出几次}}
}
基数排序总结
- 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。如果是小数呢?你就无法用下标进行存储了。
- 时间复杂度:O(N(范围))
- 空间复杂度:O(N)
OK,介绍5个用到该思想的题目,帮大家掌握住这种做题方法
1,错误的集合
这道题的解法很多
思路一:最容易想到的就是先排序,然后就可以很轻易的找到消失的数字,而且我们已经知道了包含1到n,可以直接求和减去出现差错的数组中的所有元素,例如将5复制成了7,减完之后结果为-2,那么重复的数就是5加上2为7。
思路二:直接利用基数排序的思想。重新开一个数组(初始化全部为0),不用排序,直接用所给集合里面的所有数对应的下标位置加加,当然,如果没出现某个数,那么新开数组这个位置的数就是0,重复的就是1。
代码如下
int* findErrorNums(int* nums, int numsSize, int* returnSize)
{int *array1,*array2,i;array1=(int*)calloc(numsSize,sizeof(int));array2=(int*)malloc(sizeof(int)*2);for(i=0;i<numsSize;i++){array1[nums[i]-1]+=1;}for(i=0;i<numsSize;i++){if(array1[i]==2)array2[0]=i+1;else if(array1[i]==0)array2[1]=i+1;}*returnSize=2;return array2;
}
逻辑清晰,时间复杂度为O(N),空间复杂度为O(N)。当然也可以加一个判断,当已经找到两个数字后直接跳出循环,减少循环次数。
2,字符个数统计
同样还是找一段区间中是否存在某个数,或者某个数出现的次数,字符也是整形,可以利用字符表示的整形还是同样依照上边的那种思路进行解题只需要强制转化,其他思路全部相同。
代码如下
#include <stdio.h>
int main() {int i = 0;int count = 0;char arr1[500];int arr[127] = { 0 };scanf("%s", arr1);while (arr1[i] != '\0'){arr[(int)arr1[i]] = 1;i++;}for (int j = 0; j < 127; j++){if (arr[j] == 1){count++;}}printf("%d", count);return 0;
}
同样,只要使用这种思路时间复杂度一般都是O(N),由于题目都说了,范围在0~127之间,用这种思路多爽啊。
3,多数元素
给定数组,找到数组中出现次数大于numsSize/2的数,我们还可以使用这种方法,但是这道题使用这种方法跑不过,虽然已经知道了我们要开多少的空间(时间复杂度为O(1)),但是数组中数的最大值和最小值相差2*10^9,这个数字太大了,意味着我们要开的空间也是非常大的,这道题要想用时间复杂度为O(N),用这种方法是跑不过的。但是可以骗骗分,正如上边所说,介绍的是一种思路而已,当然还会给出另外的解法。
利用数组下标做法代码如下啊
int majorityElement(int* nums, int numsSize) {int min = nums[0], max = nums[0];for (int i = 0; i < numsSize; i++){if (nums[i] < min){min = nums[i];}if (nums[i] > max){max = nums[i];}}int range=max-min+1;int*arr=(int*)malloc(sizeof(int)*(range));for (int k = 0; k < range; k++){arr[k] = 0;}for (int i = 0; i < numsSize; i++){arr[nums[i] - min]++;}for(int j=0;j<range;j++){if(arr[j]>numsSize/2){return j+min;}}return -1;
}
仔细观察可以发现和前边基数排序的方法手段如此相像,只不过在存储完成后要判断或得到的东西不同而已。
用这个代码跑的话,是通过不了的,因为某些用例专门针对。
可以发现,过了大部分用例
剩下的估计都含有这两个数字,不知道优化能不能成功,也不想费那么多事,如果用例中数组内的数字差别没有这么大,那么这种解法的时间复杂度为O(N),空间复杂度为O(1)。
OK,不能就这么摆着一个不能通过的方法在这里
谈一谈投票法(很巧妙)
利用该数组中多数元素的次数绝对大于numsSIze/2这一特性,所以用不同的元素消去相同的对象,那么身下到最后的绝对是相同元素,即我们要找的多数元素。
代码如下
int majorityElement(int*nums,int numsSize)
{int candidate=nums[0];int flag=1;for(int i=1;i<numsSize;i++){if(nums[i]==candidate){flag++;}else{flag--;if(flag<0){candidate=nums[i];flag=1;}}}return candidate;
}
4,找到所有数组中消失的数据
同样,这道题也是与数组中某个数字是否存在或者某个数字出现了几次的问题,利用同样的思路,不需要排序即可找出,且时间复杂度为O(N),但是我们开了额外的空间,至于进阶的写法,先放一放。
代码如下
/*** Note: The returned array must be malloced, assume caller calls free().*/
int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize){int k=0;int*arr=(int*)malloc(sizeof(int)*(numsSize));int*ret=(int*)malloc(sizeof(int)*(numsSize));//memset(arr,0,(sizeof(int)*(numsSize+1)));for(int l=0;l<numsSize;l++){arr[l]=0;}for(int i=0;i<numsSize;i++){arr[nums[i]-1]++;}for(int j=0;j<numsSize;j++){if(arr[j]==0){ret[k++]=j+1;//*returnSize++;}}*returnSize=k;return ret;
}
总结:判断数组中某数(尤其是无序)出现的个数(判断有没有出现就是出现个数是不是0嘛),就可以使用这种方法,和基数排序原理一样,空间换时间,开空间时一定要的。
结束啦结束啦,希望你有所收获,这就是我创作的最大动力。