【LeetCode】137. 只出现一次的数字 II

leetcode

 

解题思路

问题要求在整数数组中找到唯一出现一次的元素(其他元素均出现三次),需满足 ​​O(n) 时间​​和 ​​O(1) 空间​​复杂度。两种主流解法:

  1. ​​位状态机(高效解法)​​:
    用 one 和 two 两个变量分别记录当前位出现 ​​1 次​​和 ​​2 次​​的状态。当数字第三次出现时,通过位运算重置状态,最终 one 即为结果。
  2. ​​逐位统计(直观解法)​​:
    统计每位 ​​1 出现的总次数​​,对 ​​3 取余​​。若余数非零,说明目标数字在该位为 1,据此逐位构建结果。

关键步骤

解法一:位状态机(高效)

  1. ​​初始化​​:one = 0two = 0,分别记录出现 1 次和 2 次的位。
  2. ​​遍历数组​​:
    • two 更新:two |= one & num → 将 one 中已存在的位同步到 two(标记出现两次)。
    • one 更新:one ^= num → 记录出现奇数次的位(首次或第三次出现时翻转)。
    • 计算 three = one & two → 标记出现三次的位。
    • 重置:one &= ^threetwo &= ^three → 清除出现三次的位。
  3. ​​返回结果​​:one 即为目标数字。

解法二:逐位统计(通用)

  1. ​​遍历 32 位​​(整数位数)。
  2. ​​统计当前位 1 的总数​​:
    • 对每个数右移 i 位后与 1 相与,累加结果到 cnt
  3. ​​判断目标位​​:
    • 若 cnt % 3 != 0,说明目标在该位为 1,设置 ans 的对应位。
  4. ​​组合结果​​:通过位或操作 ans |= (1 << i) 构建目标数字。

代码实现

解法一:位状态机

func singleNumberStateMachine(nums []int) int {
    var one, two int
    for _, num := range nums {
        two |= one & num   // 同步 one 到 two(出现两次)
        one ^= num         // 更新出现奇数次的位
        three := one & two // 标记出现三次的位
        one &^= three      // 重置 one(清除三次出现的位)
        two &^= three      // 重置 two
    }
    return one
}

解法二:逐位统计

func singleNumberBitCount(nums []int) int {
    var ans int
    for i := 0; i < 32; i++ {
        cnt := 0
        for _, num := range nums {
            cnt += (num >> i) & 1 // 统计第 i 位 1 的总数
        }
        if cnt%3 != 0 { // 目标数字在此位为 1
            ans |= 1 << i // 设置结果的第 i 位
        }
    }
    return ans
}

 


示例测试

func main() {
    nums1 := []int{2, 2, 3, 2}
    nums2 := []int{0, 1, 0, 1, 0, 1, 99}

    fmt.Println("状态机解法:")
    fmt.Println(singleNumberStateMachine(nums1)) // 输出 3
    fmt.Println(singleNumberStateMachine(nums2)) // 输出 99

    fmt.Println("逐位统计解法:")
    fmt.Println(singleNumberBitCount(nums1)) // 输出 3
    fmt.Println(singleNumberBitCount(nums2)) // 输出 99
}

复杂度分析

解法时间复杂度空间复杂度
位状态机 ​​O(n)​​ ​​O(1)​​
逐位统计 ​​O(32n)​​ ​​O(1)​​
  • ​​位状态机​​:仅需单次遍历,效率更高。
  • ​​逐位统计​​:固定 32 次内循环,仍满足线性要求,但常数较大。

关键点总结

    1. ​​位状态机优势​​:
      • ​​单次遍历​​:无需内循环,效率更优。
      • ​​状态转移​​:通过 one 和 two 的位运算实现状态机,清除三次出现的位是核心。
    2. ​​逐位统计优势​​:
      • ​​直观易扩展​​:可推广至其他重复次数(如 5 次、7 次)。
      • ​​负数处理​​:需额外处理符号位(如用 int32 循环),但本题未涉及。
    3. ​​选择建议​​:
      • 追求效率 → ​​位状态机​​。
      • 追求可读性/扩展性 → ​​逐位统计​​。
    4. ​​注意事项​​:
      • 两种方法均满足 ​​O(1) 空间​​,仅用常数变量。
      • 逐位统计中,cnt % 3 等价于 cnt % 3 == 1(目标数字贡献 1 次)。
posted @ 2025-06-20 11:44  云隙之间  阅读(21)  评论(0)    收藏  举报