From e761b7065065194f9fe10492dfaa54726ce44864 Mon Sep 17 00:00:00 2001 From: jaredliw Date: Fri, 23 Jul 2021 18:16:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0py=EF=BC=8Cjs=E5=92=8Ccpp?= =?UTF-8?q?=EF=BC=8C=E5=8E=BB=E9=99=A4=E5=A4=9A=E4=BD=99=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../求次数问题/只出现一次的数2.md | 210 +++++++++++++++--- 1 file changed, 185 insertions(+), 25 deletions(-) diff --git a/animation-simulation/求次数问题/只出现一次的数2.md b/animation-simulation/求次数问题/只出现一次的数2.md index 31b47e1..81cfa8f 100644 --- a/animation-simulation/求次数问题/只出现一次的数2.md +++ b/animation-simulation/求次数问题/只出现一次的数2.md @@ -28,88 +28,248 @@ 1.通过遍历数组获取所有元素的和以及 HashSet 内元素的和。 -2.(SumSet * 3 - SumNum)/ 2即可,除以 2 是因为我们减去之后得到的是 2 倍的目标元素。 +2.(SumSet * 3 - SumNum)/ 2 即可,除以 2 是因为我们减去之后得到的是 2 倍的目标元素。 注:这个题目中需要注意溢出的情况 。 #### 题目代码 +Java Code: + ```java class Solution { public int singleNumber(int[] nums) { HashSet set = new HashSet<>(); - long sumset = 0; - long sumnum = 0; + long setsum = 0; + long numsum = 0; for (int x : nums) { //所有元素的和 - sumnum += x; - if (set.contains(x)) { - continue; - } - //HashSet元素和 - sumset += x; + numsum += x; + if (!set.contains(x)) { + //HashSet元素和 + setsum += x; + } set.add(x); } //返回只出现一次的数 - return (int)((3 * sumset - sumnum) / 2); + return (int)((3 * setsum - numsum) / 2); } } ``` +C++ Code: + +```cpp +class Solution { +public: + int singleNumber(vector& nums) { + unordered_set set; + long setsum = 0; + long numsum = 0; + for (int x : nums) { + //所有元素的和 + numsum += x; + if (set.find(x) == set.end()) { + //HashSet内元素的和 + setsum += x; + } + set.insert(x); + } + //返回值 + return (3 * setsum - numsum) / 2; + } +}; +``` + +JS Code: + +```javascript +var singleNumber = function(nums) { + let set = new Set(); + let setsum = 0; + let numsum = 0; + for (let x of nums) { + //所有元素的和 + numsum += x; + if (!set.has(x)) { + setsum += x; + } + //HashSet内元素的和 + set.add(x); + } + //返回值 + return (3 * setsum - numsum) / 2; +}; +``` + +Python Code: + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + return (3 * sum(set(nums)) - sum(nums)) // 2 +``` + 这个题目用 HashMap 和排序查找肯定也是可以的,大家可以自己写一下,另外我们在第一题中有个利用异或求解的方法,但是这个题目是出现三次,我们则不能利用直接异或来求解,那还有其他方法吗? ### 位运算 #### 解析 -这个方法主要做法是将我们的数的二进制位每一位相加,然后对其每一位的和取余 ,我们看下面的例子。 +这个方法主要做法是将我们的数的二进制位每一位相加,然后对其每一位的和与 3 取余 ,我们看下面的例子。 ![只出现一次的数字2](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/只出现一次的数字2.5p4wxbiegxc0.png) -那么我们为什么要这样做呢?大家想一下,如果其他数都出现 3 次,只有目标数出现 1 次,那么每一位的 1 的个数无非有这2种情况,为 3 的倍数(全为出现三次的数) 或 3 的倍数 +1(包含出现一次的数)。这个 3 的倍数 +1 的情况也就是我们的目标数的那一位。 +那么我们为什么要这样做呢?大家想一下,如果其他数都出现 3 次,只有目标数出现 1 次,那么每一位的 1 的个数无非有这两种情况,为 3 的倍数(全为出现三次的数)或 3 的倍数 +1(包含出现一次的数)。这个 3 的倍数 +1 的情况也就是我们的目标数的那一位。 #### 题目代码 +Java Code: + ```java class Solution { public int singleNumber(int[] nums) { int res = 0; for(int i = 0; i < 32; i++){ int count = 0; - for (int j = 0; j < nums.length; j++) { - //先将数右移,并求出最后一位为 1 的个数 - if ((nums[j] >> i & 1) == 1) { + for (int num: nums) { + //检查第 i 位是否为 1 + if ((num >> i & 1) == 1) { count++; - } + } } - //找到某一位取余为 1 的数,并左移,为了将这一位循环结束后移至原位 if (count % 3 != 0) { + // 将第 i 位设为 1 res = res | 1 << i; } } - return res; + return res; } } ``` -我们来解析一下我们的代码 +C++ Code: -> **<<** 左移动运算符:运算数的各二进位全部左移若干位,由 **<<** 右边的数字指定了移动的位数,高位丢弃,低位补0。 +```cpp +class Solution { +public: + int singleNumber(vector& nums) { + int res = 0; + for(int i = 0; i < 32; i++){ + int count = 0; + for (int num: nums) { + //检查第 i 位是否为 1 + if ((num >> i & 1) == 1) { + count++; + } + } + if (count % 3 != 0) { + // 将第 i 位设为 1 + res = res | 1 << i; + } + } + return res; + } +}; +``` + +JS Code: + +```javascript +var singleNumber = function(nums) { + let res = 0; + for(let i = 0; i < 32; i++){ + let count = 0; + for (let num of nums) { + //检查第 i 位是否为 1 + if ((num >> i & 1) == 1) { + count++; + } + } + if (count % 3 != 0) { + // 将第 i 位设为 1 + res = res | 1 << i; + } + } + return res; +}; +``` + +Python Code: + +```python +class Solution: + def singleNumber(self, nums: List[int]) -> int: + res = 0 + for i in range(32): + count = 0 + for num in nums: + # 检查第 i 位是否为 1 + if (num >> i & 1) == 1: + count += 1 + if count % 3 != 0: + # 将第 i 位设为 1 + res = res | 1 << i + # 这里的做法稍有不同,见下方解释 + if (res >> 31 & 1) == 1: + res = ~(res ^ 4294967295) + return res +``` + + + +我们来解析一下我们的代码: + +> **<<** 左移运算符:运算数的各二进位全部左移若干位,由 **<<** 右边的数字指定了移动的位数,高位丢弃,低位补0。 > -> **>>** 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,**>>** 右边的数字指定了移动的位数 +> **>>** 右移运算符:**>> ** 左边的运算数的各二进位全部右移若干位,**>>** 右边的数字指定了移动的位数。 -另外我们的代码中还包含了 a & 1 和 a | 1 这有什么作用呢?继续看下图 +另外我们的代码中还包含了 a & 1 和 a | 1 这有什么作用呢?继续看下图。 -> **&** 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 +> **&** 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0。 ![只出现一次的数位运算且](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/只出现一次的数位运算且.vq3lcgv0rbk.png) -因为我们 a & 1 中 1 只有最后一位为 1,其余位皆为 0 ,所以我们发现 a & 1的作用就是判断 a 的最后一位是否为 1 ,如果 a 的最后一位为 1 ,a & 1 = 1,否则为 0 。所以我们还可以通过这个公式来判断 a 的奇偶性。 +因为我们 a & 1 中 1 只有最后一位为 1,其余位皆为 0 ,所以我们发现 **a & 1 的作用就是判断 a 的最后一位是否为 1** ,如果 a 的最后一位为 1 ,a & 1 = 1,否则为 0 。所以我们还可以通过这个公式来判断 a 的奇偶性。 > **|** 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 ![或运算](https://cdn.jsdelivr.net/gh/tan45du/tan45du.github.io.photo@master/photo/或运算.6orep3gsrxc0.png) -这个公式的作用就是将我们移位后的 res 的最后一位 0 变为 1。这个 1 也就代表着我们只出现一次元素的某一位。 \ No newline at end of file +这个公式的作用就是**将我们移位后的 res 的最后一位 0 变为 1**。这个 1 也就代表着我们只出现一次元素的某一位。 + + + +> 贡献者[@jaredliw](https://github.com/jaredliw)注: +> +> 这里我想解释一下 python 里的这两行: +> +> ```python +> if (res >> 31 & 1) == 1: +> res = ~(res ^ 4294967295) +> ``` +> +> int 的的符号是由最高位(这题用的是32位)的值决定,1 就是负数,0就是正数。由于 python 的 int 类型理论上是无限大的,这题里的 res 都会被认定为是正数。举个例子,32位的 -4 是这样的: +> +> > 11111111111111111111111111111100 (最高位是 1 )= -4 +> +> python 里的则是这样的: +> +> > ...000000000000 11111111111111111111111111111100 (前面跟着无限个 0,最高位是 0 )= 4294967292 +> +> 怎么办呢? +> +> 我们可以先将 res 的后32位取反(与 4294967295 异或,4294967295 的二进制是 32 个 1),得到: +> +> > ...000000000000 00000000000000000000000000000011(最高位是0)= 3 +> +> 之后再用波浪号按位取反,得到: +> +> > ...111111111111 11111111111111111111111111111100 (前面跟着无限个 1,最高位是 1)= -4 +> +> 大家可以自行验证看看:`(res >> n & 1) == 1` ,n 随便填个大于 31 的数字,之前是 false,之后就变成 true (代表第 33 位,第 34 位,……都转成 1 了)。 +> +> 虽然说这种方法有一种脱裤子放屁的感觉 ,而且`res -= 2 ** 32` 也能办到,但由于涉及到 int 存储的问题(我省略了许多,大家自行度娘哈),我觉得还是有必要知道的。 +