首页 > 编程语言 >力扣只出现一次的数字系列总结(C++)

力扣只出现一次的数字系列总结(C++)

时间:2023-01-26 15:07:17浏览次数:61  
标签:总结 数字 nums int C++ 力扣 异或 LeetCode



tags: LeetCode C++ DSA

写在前面

最近用到的异或运算越来越多了, 而其中又以只出现一次的数字为经典题型, 下面总结总结一下只出现一次的数字系列. 代码均为C++.

前置知识

逻辑表达式

下面这些结论都可以自己写一个真值表推导得出.

符号

运算

性质

$\bar\ $

逻辑非

-

力扣只出现一次的数字系列总结(C++)_c++

逻辑与

力扣只出现一次的数字系列总结(C++)_逻辑与_02, 力扣只出现一次的数字系列总结(C++)_leetcode_03

力扣只出现一次的数字系列总结(C++)_真值表_04, 力扣只出现一次的数字系列总结(C++)_真值表_05,

力扣只出现一次的数字系列总结(C++)_leetcode_06

逻辑或

力扣只出现一次的数字系列总结(C++)_真值表_07, 力扣只出现一次的数字系列总结(C++)_逻辑与_08,

力扣只出现一次的数字系列总结(C++)_真值表_09, 力扣只出现一次的数字系列总结(C++)_逻辑与_10,

力扣只出现一次的数字系列总结(C++)_leetcode_11

逻辑异或

力扣只出现一次的数字系列总结(C++)_真值表_12,

一些之后会用到的二级结论有:

  1. 力扣只出现一次的数字系列总结(C++)_leetcode_13;
  2. 力扣只出现一次的数字系列总结(C++)_真值表_14, 力扣只出现一次的数字系列总结(C++)_c++_15;
  3. 力扣只出现一次的数字系列总结(C++)_leetcode_16, 力扣只出现一次的数字系列总结(C++)_真值表_17;
  4. 德摩根律: 力扣只出现一次的数字系列总结(C++)_c++_18, 力扣只出现一次的数字系列总结(C++)_c++_19;
  5. 德摩根律(三元): 力扣只出现一次的数字系列总结(C++)_真值表_20, 力扣只出现一次的数字系列总结(C++)_真值表_21.

真值表转化为逻辑表达式, 就是找使结果值为1的行, 然后将自变量为0的记为非, 为1的不变, 用与运算连接成组后, 将上述的每组(代表每行)用或运算连接. (没学过数电, 纯属个人语言表达)

基本题型

​136. 只出现一次的数字 - 力扣(LeetCode)​​;

利用异或的同值消除性质, 直接异或即可, 最快的方法.

class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans{};
for(int num:nums)ans^=num;
return ans;
}
};

异或的高级用法

异或分组

这两题是一样的, 都是通过位运算分组的方法来做.

class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int xorsum{};
for(auto num:nums)xorsum^=num;
int lsb=xorsum==INT_MIN?xorsum:xorsum&(-xorsum);
int type1{};
for(int num:nums)
if (lsb&num)
type1^=num;
return {type1, xorsum^type1};
}
};

​面试题 17.19. 消失的两个数字 - 力扣(LeetCode)​​;

这部分内容可以看我之前的总结: ​​力扣面试题寻找消失的两个数字多解法总结_zorchp的博客-CSDN博客​​;

位运算的混合用法:逻辑表达式

​137. 只出现一次的数字 II - 力扣(LeetCode)​​;

下面是接近​​O(n)​​的做法, 原因是采用了一个32位的逐位遍历.

class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans{};
//32个位
for (int i{};i<32;++i){
int total{};
for (int num:nums)
total+=(num>>i)&1;
if (total%3)
ans|=(1<<i);
}
return ans;
}
};

​O(n)​​做法需要一定的逻辑表达式知识(数字电路), 通过真值表得到逻辑表达式, 这里还应用了一个技巧, 就是直接通过现有的Y值更新X, 参考了题解[^2].

​逻辑电路角度详细分析该题思路,可推广至通解 - 只出现一次的数字 II - 力扣(LeetCode)​​;

下面我用题解中的思路重新走一遍推导过程, 加深一下理解.

第一时间应该想到的是找到一种逻辑操作,可以满足 111=01∗1∗1=0 且 01=10=10∗1=1∗0=1 ,其中 *∗ 为这种新逻辑操作符。根据这个,我们可以想到
出现0次为0,出现1次为1,出现2次的值无所谓,出现3次就又回到0,也就是说,我们一共需要记录3种状态:0次,1次,2次,之后次数都是这三种状态的循环。其实这也就是一个模三运算。
记录两个状态需要的是一位二进制0/1,那么记录三种状态需要的是至少两位二进制,可以是00, 01, 10, 11,这里我们只需要选其中任意三个状态即可,例如:00,01,10,分别代表0次1次2次。
用00代表0次,01代表出现1次是因为刚好对应数字原本那位上0代表0次,1代表1次,这样可以方便写程序,不这么选也可以,但最后你自己要有个映射规则。至于2次的编码无所谓,10和11都可以,反正最后答案也不要它,只是个中间状态,辅助我们计算的。
那么对于输入数字的每一位二进制位,都可以用这三种状态表示。如果再输入一个数字,对于每一位上,我们的操作可以化为:
新输入的是0(即00),三种状态都维持不变,力扣只出现一次的数字系列总结(C++)_leetcode_22
新输入的是1(即01),力扣只出现一次的数字系列总结(C++)_真值表_23

设当前状态为XY,输入为Z,那么我们可以分别为X和Y列出真值表

00

0

0

0

01

0

0

1

10

0

1

0

00

1

0

1

01

1

1

0

10

1

0

0

很容易得到针对力扣只出现一次的数字系列总结(C++)_逻辑与_28力扣只出现一次的数字系列总结(C++)_c++_29的逻辑表达式为:
力扣只出现一次的数字系列总结(C++)_逻辑与_30
有了这两个式子, 其实就已经能得到结果了:

class Solution {
public:
int singleNumber(vector<int>& nums) {
int X{}, Y{}, Yn{};
for(int Z: nums){
Yn=~X&~Y&Z|~X&Y&~Z;
X=X&~Y&~Z|~X&Y&Z;
Y=Yn;
}
return Y;
}
};

但是这样有点眼花缭乱了, 下面更新一下力扣只出现一次的数字系列总结(C++)_c++_29的表达式: (用到了上面提到的二级结论)
力扣只出现一次的数字系列总结(C++)_c++_32

力扣只出现一次的数字系列总结(C++)_c++_33

于是代码也可以相应简化:

class Solution {
public:
int singleNumber(vector<int>& nums) {
int X{}, Y{}, Yn{};
for(int Z: nums){
Yn=~X&(Y^Z);
X=(X^Y)&~(Y^Z);
Y=Yn;
}
return Y;
}
};

想想是不是可以用力扣只出现一次的数字系列总结(C++)_c++_29来更新力扣只出现一次的数字系列总结(C++)_逻辑与_28呢?

首先可以注意到, 用力扣只出现一次的数字系列总结(C++)_c++_29代替力扣只出现一次的数字系列总结(C++)_逻辑表达式_37, 代入真值表, 可以得到:

力扣只出现一次的数字系列总结(C++)_c++_29

00

0

0

01

0

0

10

0

1

01

1

0

00

1

1

10

1

0

上面这个表可能还看不出来, 交换一下力扣只出现一次的数字系列总结(C++)_c++_41力扣只出现一次的数字系列总结(C++)_c++_29, 那么真值表可以写成:

力扣只出现一次的数字系列总结(C++)_c++_29

00

0

0

10

0

0

01

0

1

10

1

0

00

1

1

01

1

0

再来看看原始的真值表(忽略了力扣只出现一次的数字系列总结(C++)_逻辑与_28)

00

0

0

01

0

1

10

0

0

00

1

1

01

1

0

10

1

0

发现了什么? 这两个表反映出的对应关系是一样的!

这就意味着通过力扣只出现一次的数字系列总结(C++)_c++_29的计算公式完全可以得到力扣只出现一次的数字系列总结(C++)_逻辑与_28的计算公式, 引用题解的说法, 这两个更新公式是同构的. 也就是说, 力扣只出现一次的数字系列总结(C++)_逻辑与_28的表达式可以记为: (将刚才的交换力扣只出现一次的数字系列总结(C++)_c++_41力扣只出现一次的数字系列总结(C++)_c++_29操作体现在变量中)
力扣只出现一次的数字系列总结(C++)_leetcode_55

多么优雅的对称形式.

事实上就是函数同一种对应关系的不同字母表示, 即:
力扣只出现一次的数字系列总结(C++)_逻辑与_56

于是代码可以更简洁地写成:

class Solution {
public:
int singleNumber(vector<int>& nums) {
int X{}, Y{};
for(int Z: nums){
Y=~X&(Y^Z);
X=~Y&(X^Z);
}
return Y;
}
};

官方题解中直接将力扣只出现一次的数字系列总结(C++)_c++_57替换成力扣只出现一次的数字系列总结(C++)_c++_58, 然后将真值表直接转换为逻辑表达式来计算, 也能得到上述结果.

位运算的特殊处理

​剑指 Offer II 070. 排序数组中只出现一次的数字 - 力扣(LeetCode)​​​;​​540. 有序数组中的单一元素 - 力扣(LeetCode)​​​;
这道题虽然不是主要用位运算做, 但是其中蕴含的一个思想很经典, 就是用异或操作来忽略对数字奇偶性的讨论:

  • 力扣只出现一次的数字系列总结(C++)_真值表_59, 则力扣只出现一次的数字系列总结(C++)_逻辑表达式_60;
  • 力扣只出现一次的数字系列总结(C++)_真值表_61, 则力扣只出现一次的数字系列总结(C++)_真值表_62.

也即, 奇数与1做异或相当于减1, 偶数与1异或相当于加1.

class Solution {
public:
int singleNonDuplicate(vector<int>& nums) {
int l{},r=nums.size()-1;
while (l<r){
int m=l+(r-l)/2;
if(nums[m]==nums[m^1])l=m+1;
else r=m;
}
return nums[l];
}
};

最后的总结

  1. 如果题目中说了排序数组, 那么大概率去想二分做法;
  2. 只出现一次的两个数字, 通过异或lsb的方式进行分组;
  3. 对于给出范围的题目(例如从1到N的正整数), 可以通过数学方法联立方程找出出现一次的数字;
  4. 原地哈希也是不错的选择, 但是比较难想到.


标签:总结,数字,nums,int,C++,力扣,异或,LeetCode
From: https://blog.51cto.com/u_15366127/6023534

相关文章

  • 总结 CSS 伪类选择器 nth-child
    前言nth-child伪类选择器非常地好用,所以必须得掌握它,能够为我们简化不少的CSS代码。比如选择前n行元素、选择后n行元素、选择奇偶行元素、选择n倍元素等。其语法......
  • 总结:packaging类型值为pom的项目,无法通过package打包按钮生成jar文件,也无法通过mvn in
          把packaging类型值为pom给去掉,重新执行之前的操作,点击package按钮,就能看到生成target文件夹,并且在target文件夹下面也会看到有jar的文件把packagin......
  • C++语言课程设计任务书[2023-01-26]
    C++语言课程设计任务书[2023-01-26]课程设计要求及评分标准:一、教学目标和基本要求本课程全面系统的学习面向对象程序设计的基本概念,基本语法和编程方法。正确理解掌握C......
  • C/C++歌曲信息管理系统[2023-01-26]
    C/C++歌曲信息管理系统[2023-01-26]任务描述(1)设计一个对歌曲信息进行查询、编辑、添加、删除等操作的管理程序。(2)歌曲信息包括歌曲名、词作者、曲作者、演唱者、发......
  • C/C++仲恺农业工程学院计算机系图书管理系统
    C/C++仲恺农业工程学院计算机系图书管理系统对仲恺农业工程学院计算机系的图书进行管理,包括录入、删除、查找、修改、排序等功能。图书应该包括类别、编号、ISBN号、作者......
  • C++《面向对象程序设计》[2023-01-26]
    C++《面向对象程序设计》[2023-01-26]课程设计报告课程名称面向对象程序设计课题名称专业班级学号姓名指导教师2022年12月26日......
  • C++一卡通管理系统[2023-01-26]
    C++一卡通管理系统[2023-01-26]编程题题1:采用面向对象的程序设计方法编写一个一卡通管理系统,要求使用多继承、虚函数、虚基类,要有设定类别、计算消费额等功能。题2......
  • 【C++ OOP 01】封装
    封装封装的意义封装是C++面向对象三大特性之一封装的意义:将属性和行为作为一个整体,表现生活中的事物将属性和行为加以权限控制封装意义一​ 在设计类的时候,属性和......
  • C/C++校友管理系统[2023-01-26]
    C/C++校友管理系统[2023-01-26]问题描述设计一个数智学院校友管理系统,设置管理员、校友两个角色。实现校友注册与管理、学校新闻发布与查看,问卷调查功能。基本功能要求:......
  • C/C++学生成绩管理系统[2023-01-26]
    C/C++学生成绩管理系统[2023-01-26]设某班有n位同学,每位同学的数据包括以下内容:学号(长整型)、姓名(字符串)、数学成绩(整型)、程序设计成绩(整型)。设计程序完成以下功能:新建数据......