剑指offer面试题56:数组中数字出现的次数(golang实现)

题目描述

该类题目书中共有两题,以下为题目描述:

  1. 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
    Leetcode56-I:数组中数字出现的次数
  2. 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
    Leetcode56-II:数组中数字出现的次数 II

算法分析

  1. 因为题目限制了O(n)的时间复杂度,所以排序或者map去重不能用来解决本题。考虑到任何一个数字异或自身得到的是0,可以通过异或的方式去掉数组中重复的数字。
    具体思路:异或数组中所有数字,最终得到的结果xorRes等于两个singleNumber异或的值,所以可以通过按位与求出xorRes中右边第一个等于1的二进制位。根据该二进制位将原数组分组,在该二进制位上等于1的为一组,等于0的为另一组。易知重复的数字一定会在同一组,不同的数字一定会在不同的组,因此可以对两个组分别异或求和,最终得到的就是两个singleNumber。
  2. 本题因为相同的数字都有三个,所以不能通过异或运算解决。因为只有一个singleNumber,所以统计数字的各二进制位1的个数,用一个长为32的数组保存( 1 < n u m s [ i ] < 2 31 1 < nums[i] < 2^{31} 1<nums[i]<231)。最终对数组的每一个元素模3,再将二进制位拼起来,得到的就是唯一的singleNumber。
    本题可以用有限状态自动机[1]简化时间复杂度和空间复杂度。
  • 首先确定状态,对每一个二进制位来说,数字1的个数共有三种状态:0、1、2(模3),所以可以用两位二进制位表示状态:00、01、10。

  • 状态转移规则
    低位用one表示,高位用two表示,二进制位的值用bit表示,
    统计one:
    if two == 1,one只能为0
    if two == 0,if bit == 0,则one不变
    if two == 1,if bit == 1,则one取反
    ⇒ \Rightarrow if two == 1,则one不变
    if two == 0,one ^ bit
    ⇒ \Rightarrow one = one ^ bit & ~two
    统计two(此时one已经更新):
    if one == 1,two只能为0
    if one == 0,if bit == 0,说明无进位,two不变
    if one == 0,if bit == 1,说明有进位,two取反
    发现计算two的公式其实和one是一样的:two ^ bit & ~one

  • 对于32个二进制位,状态转移规则都是一样的,因此可以直接计算32位数。

复杂度分析

问题规模为数组元素个数n

  1. 时间复杂度:O(n),需要遍历数组
    空间复杂度:O(1)
  2. 时间复杂度:O(n),需要遍历数组
    空间复杂度:O(1)

Golang代码如下

//	题一
func singleNumbers(nums []int) []int {
	xorRes, count := 0, 1
	x, y := 0, 0
	for _, v := range nums {
		xorRes ^= v
	}
	for xorRes & count == 0 {
		count <<= 1
	}
	for _, v := range nums {
		if v & count != 0 {
			x ^= v
		} else {
			y ^= v
		}
	}
	return []int{x, y}
}

//	题二
func singleNumber(nums []int) int {
	ones, twos := 0, 0
	for _, v := range nums {
		ones = ones ^ v & ^twos
		twos = twos ^ v & ^ones
	}
	return ones
}

参考

  1. 数组中数字出现的次数 II(位运算 + 有限状态自动机,清晰图解)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值