编程题-只出现一次的数

题目:

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

解题一(双层for循环超时):

定义两个指针left用于查找第一个元素,right用于查找第二个元素,对left和right进行双层循环找出在数组中只出现过一次的元素,时间复杂度超时。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        //双层遍历left用于循环查找第一个元素,right用于循环查找第二个元素
        int length = nums.size();
        int left = 0;
        int right = 1;
        while(right!=length){
            for(int i =right;i<length;i++){
                if(nums[i]==nums[left]){
                    left++;
                    break;
                }
            right = left+1;
        }
        }
        return left;
    }
};

解题二(一次循环):

用vector容器代替内层循环体结构,仅通过一次遍历可以查找到只出现一次的数,vector容器中find(vec.begin(),vec.end(),nums[i])函数可以查找当前容器是否存在相同的函数,vec.erase()函数用于删除在容器中指定存在的元素。利用vector的函数接口替代第二层循环体的作用,如下为笔者代码:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int length = nums.size();
        vector<int> vec;
        for(int i=0;i<length;i++){
            if(vec.empty()){
                vec.push_back(nums[i]);
                continue;
            }
            auto it = find(vec.begin(), vec.end(), nums[i]);
            if(it == vec.end()){
                vec.push_back(nums[i]);
            }
            else{
                vec.erase(remove(vec.begin(), vec.end(), nums[i]), vec.end());
            }
        }
        int result = vec[0];
        return result;
    }
};

解题三(位运算):

如果不考虑时间复杂度和空间复杂度的限制,本题可以有很多种的解法。使用集合存储数字,遍历数组中的每个数字,如果集合中没有该数字,则将数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字;使用哈希表存储每个数字和该数字出现的次数,遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。上述解法都额外使用 了O(n)的空间,其中n是数组长度。

使用位运算,可以做到线性时间复杂度和常数空间复杂度。使用异或运算有以下三个性质:

1、任何数和0做异或运算,结果仍然是原来的数。

2、任何数和其自身做异或运算,结果是0。

3、异或运算满足交换律和结合律。

假设数组中有2m+1个数,其中有m个数各出现两次,一个数出现一次,根据异或运算性质3,数组中的全部元素的异或运算结果总是可以写成如下形式:

根据性质2和性质1,上述可以简化和计算得到如下结果:

因此,数组中的全部元素的异或运算结果即为数组中只出现一次的数字,如下代码为:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (auto e: nums) ret ^= e;
        return ret;
    }
};

笔者小记:

使用位运算,可以做到线性时间复杂度和常数空间复杂度。使用异或运算有以下三个性质:

1、任何数和0做异或运算,结果仍然是原来的数。

2、任何数和其自身做异或运算,结果是0。

3、异或运算满足交换律和结合律。

其中“^”符号为异或运算,可以有效替代一些有规则的计算操作。

除此之外,笔者在补充一些位运算的操作符号:

1、按位与运算:对两个操作数的每一位进行“与”运算。两个对应位都为 1 时,结果才为 1,否则为 0

int a = 5; // 二进制: 0101

int b = 3; // 二进制: 0011

int result = a & b; // 结果: 0001,即 1

2、按位或运算:两个对应位中有一个为 1,结果就是 1,只有两个对应位都为 0 时,结果才为 0

int a = 5; // 二进制: 0101

int b = 3; // 二进制: 0011

int result = a | b; // 结果: 0111,即 7

3、按位异或运算:对应位相同则结果为 0,不同则结果为 1

int a = 5; // 二进制: 0101

int b = 3; // 二进制: 0011

int result = a ^ b; // 结果: 0110,即 6

4、按位取反运算:即将 1 变成 0,将 0 变成 1

int a = 5; // 二进制: 0101

int result = ~a; // 结果: 1010 (按位反转) 在 32 位系统上结果为 -6(补码表示)

5、左移:左移时,空出的位会用 0 填充。左移操作相当于将数值乘以 2 的指定次幂。

int a = 5; // 二进制: 0101

int result = a << 1; // 左移 1 位,结果: 1010,即 10

6、右移:右移时,空出的位会根据符号位来填充(对于有符号数,通常填充符号位;对于无符号数,填充 0)。右移操作相当于将数值除以 2 的指定次幂。

int a = 5; // 二进制: 0101

int result = a >> 1; // 右移 1 位,结果: 0010,即 2

赋值运算符与位运算符的组合

你也可以将位运算与赋值运算符组合,简化写法。这些组合操作符包括:

  • &=: 按位与并赋值
  • |=: 按位或并赋值
  • ^=: 按位异或并赋值
  • <<=: 左移并赋值
  • >>=: 右移并赋值
int a = 5; // 二进制: 0101
int b = 3; // 二进制: 0011
a &= b; // a = a & b,即 5 & 3 -> 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值