【算法-LeetCode】136. 只出现一次的数字(数组;异或运算;indexOf)

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

文章起笔:2021年11月7日13:35:43

问题描述及示例

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

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

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/single-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的题解

我的题解1(indexOf和lastIndexOf)

第一反应想到的就是利用数组对象的 indexOflastIndexOf 方法。遍历 nums 数组,然后通过 indexOf 方法确定当前遍历的元素第一次出现的位置,同时通过 lastIndexOf 方法确定当前遍历的元素最后一次出现的位置,如果这两个位置的索引值一致,说明了当前遍历元素就是数组中的唯一元素,即我们要找的值,直接将其返回即可。

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
  for(let i = 0; i < nums.length; i++) {
    if(nums.indexOf(nums[i]) === nums.lastIndexOf(nums[i])) {
      return nums[i];
    }
  }
};


提交记录
执行结果:通过
61 / 61 个通过测试用例
执行用时:664 ms, 在所有 JavaScript 提交中击败了7.85%的用户
内存消耗:39.8 MB, 在所有 JavaScript 提交中击败了99.00%的用户
时间:2021/11/07 13:49

但是可以看到上面的解法的时间性能不是很好。我觉得原因在于每次遍历一个数组元素,利用 indexOf 查找元素时都要从头开始匹配(同理,lastIndexOf 也是如此),这样无疑会浪费一些不必要的时间。于是我就想着在遍历过程中将那些不符合条件的元素设置为一个特殊的标志量 null 以代表该元素已经被查找过一次了。当下一次遇到 null 时,就可以直接跳过了。

理论优化:

var singleNumber = function(nums) {
  for(let i = 0; i < nums.length; i++) {
    // 如果当前遍历元素为null,那就直接跳过
    if(nums[i] === null) {
      continue;
    }
    // 寻找当前元素最后一次在数组中出现的位置
    let lastPosition = nums.lastIndexOf(nums[i]);
    // 如果第一次出现的位置和最后一次出现的位置相等的话,就直接返回结果。注意,这里不用
    // 再使用indexOf来寻找当前元素第一次出现的位置了,因为nums[i]之前的元素一定都是null
    if(i === lastPosition) {
      return nums[i];
    }
    // 如果当前元素不是我们要找的那个唯一元素,
    // 那么就将当前位置的元素和lastPosition上的元素都置为null
    nums[i] = null;
    nums[lastPosition] = null;
  }
};


提交记录
执行结果:通过
61 / 61 个通过测试用例
执行用时:468 ms, 在所有 JavaScript 提交中击败了11.56%的用户
内存消耗:40.5 MB, 在所有 JavaScript 提交中击败了51.42%的用户
时间:2021/11/07 13:59

虽然时间表现好了一点,但是似乎没有太大的提升啊……

我的题解2(异或运算)

上面的解法还是多了一些不必要的遍历操作,所以在时间表现上就不大好。于是我又想起了之前在看阮一峰老师的一篇有关异或运算的博客时好像有提到过异或操作在判断重复元素方面的应用:

参考:异或运算 XOR 教程 - 阮一峰的网络日志

于是根据上面的说法,我写出了下面的程序:

/**
 * @param {number[]} nums
 * @return {number}
 */
var singleNumber = function(nums) {
  // 利用reduce函数,让nums中的元素做累积的异或运算,
  // 根据异或运算的特点,最后累积的结果就是那个唯一的元素
  return nums.reduce((acc, cur) => acc ^ cur);
};


提交记录
执行结果:通过
61 / 61 个通过测试用例
执行用时:72 ms, 在所有 JavaScript 提交中击败了83.42%的用户
内存消耗:40.2 MB, 在所有 JavaScript 提交中击败了79.35%的用户
时间:2021/11/07 13:42	

上面的程序就相当于:

nums[0] ^ nums[1] ^ nums[2] ^ ... ^ nums[n-1] ^ nums[n]   

其中,n 相当于是 nums.length - 1

有关 reduce() 函数的用法,可以参看下方【有关参考】处的相关文档。

可以看到,上面的程序的性能有了比较大的提升,因为只需要遍历一次数组就可以了。

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

【更新结束】

更新:2021年11月7日14:25:56

参考:只出现一次的数字 - 只出现一次的数字 - 力扣(LeetCode)

【更新结束】

有关参考

更新:2021年11月7日13:43:40
参考:异或运算 XOR 教程 - 阮一峰的网络日志
参考:Array.prototype.reduce() - JavaScript | MDN

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值