[前端]_一起刷leetcode 只出现一次的数字

432 阅读3分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

题目:

剑指 Offer II 004. 只出现一次的数字

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。 请你找出并返回那个只出现了一次的元素。

  示例 1:

输入: nums = [2,2,3,2]
输出: 3

示例 2:

输入: nums = [0,1,0,1,0,1,100]
输出: 100

 

提示:

  • 1 <= nums.length <= 3 * 104
  • -231 <= nums[i] <= 231 - 1
  • nums 中,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次

 

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

方法1思路:

  • Map记录每个元素出现的次数
  • 遍历Map找到value值为1的元素

方法1实现:

var singleNumber = function (nums) {
  let map = new Map();

  // 遍历数组,记录每个元素出现的次数
  for (let i = 0; i < nums.length; i++) {
    map.set(nums[i], (map.get(nums[i]) || 0) + 1);
  }

  // 从我们的map中找到value值为1的元素
  for (let [key] of map) {
    if (map.get(key) === 1) {
      return key;
    }
  }
};

方法2思路

  • 采用二进制,这样子在32位中每个位置的值只有0和1
  • 遍历求每个位置的和, 能被3整除的说明出现了3次,不能被整除的说明出现了1次
  • 定义一个变量result,记录每个位置被3整除后的结果 0 || 1
  • 从最后一位开始,每一轮求解完更新结果的当前位置
  • 遍历结束,拿到的结果就是我们要找的出现一次的值

方法2实现

var singleNumber = function (nums) {
  let result = 0;
  const n = nums.length;

  // 二进制是32位的, 判断每一位的值
  for (let i = 0; i < 32; i++) {
    // 累加每一位的值总和
    let total = 0;
    for (let j = 0; j < n; j++) {
      // (nums[j] >> i) & 1 => 拿到当前位的值
      total += (nums[j] >> i) & 1;
    }

    // 如果当前位不能被3整除,说明出现1次的元素在当前位有值
    if (total % 3 !== 0) {
      // 把当前位推到结果中去
      result += (total % 3) << i;
    }
  }

  return result;
};

总结

方法1是求解最简单的方式,当然这道题目中进阶要求我们使用常量空间去解决问题。像数组特别长的时候我们的map空间会溢出。所以这时候我们采取位运算来解决问题。其中位运算里面有几个知识点:

  1. nums[j] >> i 把当前的元素右移i位, 可以理解为Math.floor(nums[j] / Math.pow(2, i))。 简单来讲就是当前值除以2的i次方,向下取整。

  2. (nums[j] >> i) & 1 左边部分上一步已经解释过了,这一步把左边部分称为nn & 1 可以求出n是奇数还是偶数, 当n为奇数时 n & 1 = 1, n为偶数时 n & 1 = 0.

  3. result += (total % 3) << i 我们的遍历是从最后一位开始的,相当于二进制中每次朝原本的数组中unshift一个数字。比如 10 => 110 => 1110; 只不过这道题我们要返回的结果是10进制的,所以直接用+=;相当于 2 + 4 + 8 + 16

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。