[宝宝也能看懂的 leetcode 活动][30-Day LeetCoding Challenge] 第一天

Hi 你们好,我是张小猪。欢迎来到『宝宝也能看懂』系列特别篇 - 30-Day LeetCoding Challengegit

这是一个 leetcode 官方的小活动,地址在这里(若是被重定向回中文站了,去掉域名里的 -cn 便可)。活动内容很简单,从 4 月 1 号开始,天天会选出一道题(国内时间中午 12 点更新),在 24 小时内完成便可得到一点小奖励。虽然奖励彷佛也没什么用,不过做为一个官方的打卡活动,小猪仍是来打一下卡吧,正好做为天天下班回家后的娱乐。github

这里是 4 月 1 号的题,也是题目列表中的第 136 题 -- 『只出现一次的数字』算法

题目描述

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

说明:segmentfault

你的算法应该具备线性时间复杂度。 你能够不使用额外空间来实现吗?数组

示例 1:数据结构

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

示例 2:spa

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

官方难度

EASYcode

解决思路

这道题彷佛没什么好讲的,大概是做为活动的开篇,预热一下,吸引围观,激励用户参与。blog

下面给出几个方案,供小伙伴们参考。

方案 1

其实这是个凑数的方案(哈哈哈,没想到吧)。小猪相信不会有小伙伴们这么去写的,毕竟直接就到 O(n^2) 了,实在是太浪费了。权且做为最最基础的实现吧。

const singleNumber = nums => {
  const arr = [];
  for (const n of nums) {
    const idx = arr.indexOf(n);
    idx === -1 ? arr.push(n) : arr.splice(idx, 1);
  }
  return arr.pop();
};

方案 2

Set 代替了方案 1 中的数组,使得查询和删除都快了不少。不过代码仍是太多了,而且也得基于额外的数据结构和空间,并不推荐。

const singleNumber = nums => {
  const set = new Set();
  for (const n of nums) {
    set.has(n) ? set.delete(n) : set.add(n);
  }
  for (const x of set.keys()) return x;
};

方案 3

用位操做代替了前两种方案里对于额外数据结构和空间的依赖。小猪以为这个方案算是这道题的标准实现吧,才不会告诉你前两种方案是为了写文章才凑出来的呢 hia hia hia O(∩_∩)O

稍微解释一下异或这个位操做吧,它的行为逻辑能够理解为两个值不一样则为 true,相同则为 false。以下表:

0 ^ 0 === 0
0 ^ 1 === 1
1 ^ 0 === 1
1 ^ 1 === 0

那么基于这个运算逻辑,咱们能够发现,对于两个相同的数值进行异或运算,那么结果必定是 0。再看看咱们题目中的数据,正好是成对的数字加上一个单独的数字。这时候再加上异或这个运算能够知足交换律和结合律,因而咱们那些成对的数字运算完后正好为 0,而 0 与一个数字进行异或预算即是这个数字自己。

基于这个思路,咱们能够获得以下的代码:

const singleNumber = nums => {
  let ret = 0;
  for (const n of nums) ret ^= n;
  return ret;
};

固然,都写成这样了,不如咱们就直接一行吧:

const singleNumber = nums => nums.reduce((prev, cur) => prev ^ cur, 0);

拓展

关于异或操做的一些实际应用场景,这里再补充几个栗子吧。

交换两个数

let a = 10;
let b = 20;
a = a ^ b;
b = b ^ a;
a = b ^ a;
console.log(a, b) // 20, 10

稍微解释一下过程吧:

  1. 第一次异或操做获得了两个数字不一样的位为 1,相同的位为 0,并保存进了变量 a
  2. 第二次异或操做至关因而把 b 中两个数不一样的位取反,相同的位保持不变,因而 b 就变成了 a,而后保存进变量 b 中。
  3. 第三次异或操做继续把如今 b 中两个数不一样的位取反,相同的位保持不变。注意,这时候的 b 中的值实际上是 a,因此运算后获得的值是 b,而后保存进变量 a 里。
  4. 最终 a 就进了 b,b 就进了 a

两个数相加

let a = 10;
let b = 20;
while (b !== 0) {
  const a2 = a ^ b;
  const b2 = (a & b) << 1;
  a = a2;
  b = b2;
}
console.log(a) // 30

同理,不过这里面还用到了与操做和左移操做。原理其实和加法是同样的,就是按位相加,而后须要进位的地方就进位。不过在二进制中,可能的状况会比较少。具体以下:

  1. 经过异或运算找出按位相加后不会产生进位的地方。
  2. 经过与运算找出按位相加后会产生进位的地方,并作进位。
  3. 把上述两个值保存后继续执行相同的计算,直到加数减小为 0,那么被加数就变成了最初的两数之和。

总结

这是 『30-Day LeetCoding Challenge』 的第一题,没有太多能够说的,因而就稍微拓展了一点关于异或操做的内容。但愿能够帮到有须要的小伙伴。

若是以为不错的话,记得『三连』哦。小猪爱大家哟~ >.<

相关连接

qrcode_green.jpeg

相关文章
相关标签/搜索