【LeetCode】只出现一次的数字

简介: 【LeetCode】只出现一次的数字

👉只出现一次的数字I👈


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

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?



示例 1:

输入: [2,2,1]

输出: 1



示例 2:

输入: [4,1,2,1,2]

输出: 4


思路:因为只有一个数字是出现一次的,所以我们可以利用异或的性质来解决这道题。直接一个for循环变量,将数组中的元素都进行异或操作,最后得到的结果就是只出现一次的数字。


异或的性质

  • 任何数和 0 做异或运算,结果仍然是原来的数,即 n ^ 0 = n 。
  • 任何数和其自身做异或运算,结果是 0,即 n ^ n = 0 。
  • 异或运算满足交换律和结合律,即 a ^ b ^ a = b ^ a ^ a = b ^ (a ^ a) = b ^ 0 = b。


int singleNumber(int* nums, int numsSize)
{
  int i = 0;
  int ret = 0;
  for (i = 0; i < numsSize; i++)
  {
    ret ^= nums[i];
  }
  return ret;
}

35f897a5ed3c439586a5b9e760ee8e4c.png


分析:该算法的时间复杂度为 O(N),空间复杂度为 O(1),是这道题的最优解。


👉只出现一次的数字II👈


给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。


示例 1:


输入:nums = [2,2,3,2]

输出:3



示例 2:


输入:nums = [0,1,0,1,0,1,99]

输出:99



提示:


1 <= nums.length <= 3 * 10^4

-2^31 <= nums[i] <= 2^31 - 1

nums 中,除某个元素仅出现 一次外,其余每个元素都恰出现三次


思路:如下图所示,考虑数字的二进制形式,对于出现三次的数字,各 二进制位 出现的次数都是 33 的倍数。因此,统计所有数字的各二进制位中 11 的出现次数,并对 33 求余,结果则为只出现一次的数字。

6460827917a34e4d8f87860891b75232.jpg


方法一


int singleNumber(int* nums, int numsSize) 
{
  int i = 0;
  int j = 0;
  int counts[32] = { 0 }; 
  //32表示32个比特位 数组counts是用于存放数组元素补码对应位上的和
  //外层循环一次,完成一个元素对应位的相加
  for (i = 0; i < numsSize; i++)
  {
    for (j = 0; j < 32; j++)
    {
      counts[j] += nums[i] & 1;//将二进制数字加到对应位上
      nums[i] >>= 1;//右移一位,将
    }
  }
  for (i = 0; i < 32; i++)
  {
    counts[i] %= 3;//模除3后,数组counts存放的就是只出现一次的数字的二进制
  }
  unsigned int ret = 0;
  for (i = 0; i < 32; i++)
  {
    ret <<= 1;//先左移是为了有对应关系:i=1,ret左移一次
    //如果ret <<= 1;语句放在后面,那么i=2,ret就左移两次了
    ret |= counts[31 - i];
    //因为左移,所以现将最高位的数字拿出来先,如果左移32次就能回到最高位上了
  }
    return ret;
}

6406f5cbc2224accb57b260376538c28.png


方法二


int singleNumber(int* nums, int numsSize) 
{
  int i = 0;
  int j = 0;
  int ret = 0;
  for (i = 0; i < 32; i++)//控制左移和右移的位数
  {
    int total = 0;
    for (j = 0; j < numsSize; j++)
    {
      total += ((nums[j] >> i) & 1);//补码对应位的和
    }
    if (total % 3)//如果不满足判断条件说明第i位为0,记补码最右边那位为第0位
    {
      //如果满足判断条件,就将第i位改为1
      ret |= (1u << i);//1u表示的是无符号整数1
    }
  }
  return ret;
}


分析:方法一和方法二的时间复杂度和空间复杂度都分别是 O(N) 和 O(1),但是总的来说,方法二相比于方法一更优,也更好理解。


补充知识


如果我想将一个数字的某一个二进制位上的数字改为0或1,怎么才能实现这个操作呢?接下来就为大家讲解。


代码示例:


#include <stdio.h>
int main()
{
  int a = 10;
  int n = 0;
  scanf("%d", &n);
  //把a的第n位置改为1
  a = a | (1 << (n - 1));
  //假设n为3
  //00000000000000000000000000001010  10的补码
  //00000000000000000000000000000100  1 << (n-1)的补码
  //按位或得
  //00000000000000000000000000001110  14的补码
  //成功将a的第三位的数字改为1
  printf("a=%d\n", a);
  //把a的第n位置改为0
  a = a & ~(1 << (n - 1));
  //因为上面将a的第三位数字改为1
  //所以a的补码为
  //00000000000000000000000000001110  14的补码
  //00000000000000000000000000000100  1 << (n-1)的补码
  //11111111111111111111111111111011  ~(1 << (n-1))的补码
  //按位与得
  //00000000000000000000000000001010 10的补码
  //成功将a的第三位的数字改为0
  printf("a=%d\n", a);
  return 0;
}

dc8879718a8f4017bd86c18de18ecf65.png


总结:


如果想将数字 n 的补码的第 n 位上的数字改为1,可以借助n = n | (1 << (n - 1))或者n |= 1 << (n-1)

如果想将数字 n 的补码的第 n 位上的数字改为0,可以借助n = n & ~(1 << (n - 1))或者` a &= ~(1 << (n - 1))


博主个人觉得这个补充知识非常的重要,如果你不知道这个小的知识点,有可能就做不出【只出现一次的数组II】这道题了。


👉只出现一次的数字III👈


给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按任意顺序返回答案。


进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?


示例 1:


输入:nums = [1,2,1,3,2,5]


输出:[3,5]

解释:[5, 3] 也是有效的答案。



示例 2:

输入:nums = [-1,0]

输出:[-1,0]



` 提示:

2 <= nums.length <= 3 * 10^4

-2^31 <= nums[i] <= 2^31 - 1

除两个只出现一次的整数外,nums 中的其他数字都出现两次



思路:第一步:遍历数组,把所有元素都进行异或操作得到 ret。因为数组中两个数字都只出现了一次,所以 ret 一定不为0,也就表明 ret 的补码至少有一个二进制位上的数字为1。第二步:再利用 while 循环找出 ret 的补码哪一个二进制位上的数字为1,记该位置为pos。第三步:将数组的每个元素都右移pos次,然后跟1进行按位与操作,判断结果是否为1。结果为1的为一组,结果为0的为另一种,那么两个只出现一次的数字就被分在两个组里了。再将这两组数字进行按位异或操作就能得到只出现一次的数字 num1 和 num2了。

代码示例:


int* singleNumber(int* nums, int numsSize, int* returnSize) 
{
  int ret = 0;
  int i = 0;
  int* result = (int*)malloc(sizeof(int) * 2);
  if (result == NULL)
  {
    *returnSize = 0;
    return result;
  }
  for (i = 0; i < numsSize; i++)
  {
    ret ^= nums[i];
  }
  int pos = 0;
  while (((ret >> pos) & 1) == 0)
  {
    pos++;
  }
  int num1 = 0;
  int num2 = 0;
  for (i = 0; i < numsSize; i++)
  {
    if ((nums[i] >> pos) & 1)
    {
      num1 ^= nums[i];
    }
    else
      num2 ^= nums[i];
  }
  result[0] = num1;
  result[1] = num2;
  *returnSize = 2;
  return result;
}

7098b4cc23db4db09927f330e498802a.png


👉总结👈


LeetCode刷题网站上的【只出现一次的数字】这三道题涉及很多位运算,可以说是相当的经典。相信大家看完这篇博客都会有所收获。那就麻烦大家给个三连支持一下!谢谢大家啦!!!💖💝❣️

相关文章
|
7月前
|
算法
leetcode-136:只出现一次的数字
leetcode-136:只出现一次的数字
36 0
|
2月前
|
算法
Leetcode(只出现一次的数字)
这篇文章介绍了如何使用位异或运算法则解决LeetCode上的一个编程问题,即在数组中找出只出现一次的数字,而其他数字都出现两次。
14 0
|
算法 Java
大厂算法题目-单链表删除数字
大厂算法题目-单链表删除数字
大厂算法题目-单链表删除数字
|
7月前
|
算法
【力扣】136. 只出现一次的数字
【力扣】136. 只出现一次的数字
|
7月前
leetcode:476. 数字的补数
leetcode:476. 数字的补数
115 0
|
7月前
|
算法
leetcode-260:只出现一次的数字 III
leetcode-260:只出现一次的数字 III
41 0
|
算法 Java C语言
leetcode之只出现一次的数字
今天为大家分享的是关于在数组中找到只出现一次数字的系列题目,我将使用c跟Java来实现,希望我的分享能够帮助到大家。
|
算法 C++ Python
每日算法系列【LeetCode 面试题 17.05】字母与数字
每日算法系列【LeetCode 面试题 17.05】字母与数字
|
机器学习/深度学习 存储 算法
算法打卡Day23_leetcode _136. 只出现一次的数字
算法打卡Day23_leetcode _136. 只出现一次的数字
算法打卡Day23_leetcode _136. 只出现一次的数字
|
Python
LeetCode 260. 只出现一次的数字 III
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
107 0