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

相关文章
|
分布式计算 DataWorks 关系型数据库
DataWorks数据源问题之脏数据如何解决
DataWorks数据源是指DataWorks中配置的用于数据集成的外部数据源;本合集将讲解如何在DataWorks中配置和管理数据源,以及处理数据源连接和集成过程中的问题。
302 2
|
7月前
|
人工智能 安全 Anolis
打造更 AI 的操作系统 《龙蜥+超级探访》第三期走进浪潮信息
且看龙蜥社区如何联合浪潮信息向更高层次的操作系统智能化迈进?
打造更 AI 的操作系统 《龙蜥+超级探访》第三期走进浪潮信息
|
10月前
|
缓存 监控 Shell
如何使用 HBase Shell 进行数据的实时监控和备份?
如何使用 HBase Shell 进行数据的实时监控和备份?
181 5
|
人工智能 搜索推荐 物联网
移动应用开发的未来趋势:从跨平台到AI集成
在数字化浪潮的推动下,移动应用已成为我们日常生活不可或缺的一部分。本文将探讨移动应用开发领域的最新进展,特别是跨平台框架和人工智能技术的融合如何塑造这一行业。通过分析当前技术栈、工具和最佳实践,我们将揭示未来移动应用开发的趋势,并讨论这些变化对开发者、企业和最终用户的意义。
288 1
|
11月前
|
运维 监控 安全
物联网卡:物联网卡为什么不能使用在手机上
物联网卡(IoT SIM卡)通常是为物联网设备设计的,这些设备包括但不限于智能家居设备、可穿戴设备、工业监控设备等。它们与用于智能手机的SIM卡有所不同,主要是因为设计目标、功能限制、资费结构以及网络接入策略上的差异。以下是物联网卡不能直接在手机上使用的主要原因:
|
人工智能 自动驾驶 大数据
北京市门头沟区与阿里云正式签约!
北京市门头沟区与阿里云正式签约!
237 1
北京市门头沟区与阿里云正式签约!
|
存储 C++ 索引
【C++STL基础入门】深入浅出string类的比较(compare)、复制(copy)
【C++STL基础入门】深入浅出string类的比较(compare)、复制(copy)
763 1
|
人工智能 自然语言处理
Meta新模型NLLB获Nature盛赞,200种濒危语言高质量翻译,不让任何语言掉队
【6月更文挑战第24天】Meta的NLLB模型在Nature上受赞誉,能高质量翻译200种语言,包括濒危语言,助力文化交流与保护。该模型通过创新技术克服低资源语言挑战,推动跨语言理解,但同时也引发对语言多样性的讨论。[[1](https://www.nature.com/articles/s41586-024-07335-x)]
262 1
|
SQL 缓存 大数据
优化数据库性能的五大策略
传统的数据库性能优化常常集中在SQL查询优化和索引设计上,然而,在当今大数据时代,优化数据库性能需要综合考虑更多因素。本文将介绍五大策略,从硬件资源利用、数据模型设计、查询优化、缓存策略到数据库配置调整,为您提供全面的数据库性能优化方案。
|
存储 缓存 Linux
linux 自动定时清理缓存
linux 自动定时清理缓存
474 0

热门文章

最新文章