Leetcode刷题——数组题01
题目来源:Leetcode
题一:消失的数字
题目解读:一个长度为
n
数组中包含0-n
所有整数,唯独缺失了一个,即很容易就知道完整的数组形式
代码(C语言版):
int missingNumber(int* nums, int numsSize){
int res = 0;
for(int i = 0;i<numsSize;i++){
res ^= nums[i];
}
for(int i = 0;i<numsSize+1;i++){
res ^= i;
}
return res;
}
异或运算知识点:a^a=0,0^a=a
代码解读:原数组与有缺失元素的数组逐一进行异或运算,两个数组中相同的异或后就为
0
,最后仅剩一个没有与它相同的数,即数组所缺失的元素。
题二:数组中数字出现的次数
题目解读:一个数组中只有一个两个数出现了一次,其余的数都出现了两次,求出这两个数,同时还有时间和空间复杂度的要求
代码(C语言版):
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* singleNumbers(int* nums, int numsSize, int* returnSize){
int res = 0;
for(int i = 0;i<numsSize;i++){
res ^= nums[i];
}
//res = a ^ b
//a != b,即a ^ b !=0,即res二进制中必有一位为1
//找出最右1是哪一位
int m = 0;
while(m < 32){
if(res&(1<<m)){
break;
}
else{
m++;
}
}
//分离
int x1 = 0, x2 = 0;
for(int i = 0;i<numsSize;i++){
if(nums[i]&(1<<m)){
x1 ^=nums[i];
}
else{
x2 ^=nums[i];
}
}
int* retArr = (int*)malloc(sizeof(int)*2);
retArr[0] = x1;
retArr[1] = x2;
*returnSize = 2;
return retArr;
}
代码解读:这道题相当于上一道题的进阶版,我们同时可以用异或的方法进行思考,出现两次的数异或完都等于0,最终得到的结果为
a^b
(假设那两个数为a和b),下面只要求出a,b其中之一即可。异或运算法则是同为0异为1,因为a不等于b,所有a^b
的结果中必定有一二进制位为1,则a和b的二进制位所在的那一位必然不同,即一个为0,另一个为1,我们可以通过这点不同将数组分为该二进制位为1的为一组,为0的为一组,这样,a和b就会被分进两个不同的组,将这两个组分别单独异或即可分别求出a和b.
难点在于如何获取到那个二进制为1的所在位
方法一
int m = 0;
while(m < 32){
if(res&(1<<m)){
break;
}
else{
m++;
}
}
通过不断地将1左移每次都与异或地结果进行与运算,当结果为1则返回,得出m即左移的位数
方法二(附上完整代码)
int* singleNumbers(int* nums, int numsSize, int* returnSize){
int res = 0;
for(int i = 0;i<numsSize;i++){
res ^= nums[i];
}
//res = a ^ b
//a != b,即a ^ b !=0,即res二进制中必有一位为1
int rightOne = res & (~res +1);//取出最右1
//为1的那一位两个数必定一个为0,一个为1
int onlyOne = 0;
for(int i = 0;i<numsSize;i++){
if((nums[i]&rightOne)==0){//找出所有那一位为1的数
onlyOne ^= nums[i];//将这些数进行相与,最终得到a
}
}
int* retArr = (int*)malloc(sizeof(int)*2);
retArr[0] = onlyOne;
retArr[1] = onlyOne ^ res;
*returnSize = 2;
return retArr;
}
主要就是int rightOne = res & (~res +1);//取出最右1
这行代码进行操作,可直接取出最右1
题三:数组中数字出现的次数 II
题目解读:该题同样也为上一题的进阶版,只有一个出现一次,其余都出现三次,好在这题没有要求时间和空间复杂度,能力暂时还有限,思考了很久也没想出特别优的算法,最后通过先排序再寻找的方法解决了。
代码(C语言版):
int singleNumber(int* nums, int numsSize){
//先排序
for(int i = 0; i<numsSize-1;i++){
int min = i;
for(int j = i+1; j<numsSize;j++){
if(nums[j]<nums[min]){
min = j;
}
}
int temp = nums[i];
temp = nums[min];
nums[min] = nums[i];
nums[i] = temp;
}
int k = 0;
for(k = 0;k<numsSize-2;){
if(nums[k]==nums[k+1]&&nums[k]==nums[k+2]){
k +=3;
}
else{
break;
}
}
return nums[k];
}
代码解读:首先排序,排序方法自选,这里我就随便选了个选择排序,排完序后,数组都有序,从头开始遍历,每次索引+3,当遇到连续的三个数不相等时便的得出出现一次的数。
题四:消失的两个数字
题目解读:该题同时也为上面题目的进进阶版,一数组包含1-n所有数,除了两个数不在,求出这两个数,同时还有时间和空间复杂度的要求。
这里提供了三种算法解答该题
方法一
显然也可以通过异或的方法求解
代码(C语言版):
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* missingTwo(int* nums, int numsSize, int* returnSize){
int res = 0;
for(int i = 1;i<=numsSize+2;i++){
res ^= i;
}
for(int i = 0;i<numsSize;i++){
res ^= nums[i];
}
//res = a ^ b
//a != b,即a ^ b !=0,即res二进制中必有一位为1
int rightOne = res & (~res +1);//取出最右1
//为1的那一位两个数必定一个为0,一个为1
int onlyOne = 0;
for(int i = 0;i<numsSize;i++){
if((nums[i]&rightOne)==0){//找出所有那一位为1的数
onlyOne ^= nums[i];//将这些数进行相与
}
}
for(int i = 1;i<=numsSize+2;i++){
if((i&rightOne)==0){//找出所有那一位为1的数
onlyOne ^= i;//将这些数进行相与
}
}
int* retArr = (int*)malloc(sizeof(int)*2);
retArr[0] = onlyOne;
retArr[1] = onlyOne ^ res;
*returnSize = 2;
return retArr;
}
代码解读:得到
a^b
的结果和知道1所在的位之后,将缺失元素的数组和完整的数组中那一位为1的所有数进行异或便可得出a,再与a^b
异或得到b.
方法二
因为该数组中的数是从1开始的而且近乎连续,我们可以就把他们分别当做数组的索引来看
代码(C语言版):
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* missingTwo(int* nums, int numsSize, int* returnSize){
//生成数组空间
int* arr =(int*)calloc(numsSize +3,sizeof(int));
int* ans = malloc(sizeof(int) * 2), ansSize = 0;
*returnSize = 2;
//将对应的数放到对应的索引位置上
for (int i = 0; i < numsSize; i++)
arr[nums[i]]++;
for (int i = 1; i < numsSize+3 ; i++)
{
//遍历数组将索引位置上为0的放到返回数组中去
if (arr[i] == 0)
ans[ansSize++] = i;
if(ansSize==2)
break;
}
return ans;
}
代码解读:生成一个数组空间,遍历原数组,每取到一个数就把数组中与该数值相等的索引位置上的值+1(初始都为0),例如,第一个数为5,索引为5的位置上的值变为1,因为没有重复数,所以遍历完之后,数组上的值除了0就是1,为0的就是缺失的数,将其返回就好了。
方法三
利用求和以及两数平均数解答
代码(C语言版):
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* missingTwo(int* nums, int numsSize, int* returnSize){
int res1 = 0;
for(int i = 1;i<=numsSize+2;i++){
res1 += i;
}
for(int i = 0;i<numsSize;i++){
res1 -= nums[i];
}
//假设两个数分别为a和b,得到a+b
int sumAvg = res1/2;
int a = 0,b = 0;
for(int i = 1;i<=numsSize+2;i++){
if(i<=sumAvg){
a += i;
}
}
for(int i = 0;i<numsSize;i++){
if(nums[i]<=sumAvg){
a -= nums[i];
}
}
int* retArr = (int*)malloc(sizeof(int)*2);
retArr[0] = a;
retArr[1] = res1-a;
*returnSize = 2;
return retArr;
}
代码解读:首先利用求和和作差得到a+b,求这两个数的平均数
sumAvg
,可以知道这两个数必然一个大于sumAvg
,另一个小于sumAvg
,于是我们便可以通过sumAvg
将原数组分为两组,大于sumAvg
的一组,小于sumAvg
的一组,这样便可用同样的求和作差的方法分别求出a和b了。
补充
交换两个数的方法可以通过异或操作完成,但前提条件为这两个数必须不相等
int a=2,b=5;
//已知a!=b,交换两个数
a = a ^ b;
b = a ^ b;
b = a ^ b;
//交换完成