剑指offer 专项突破版 4、只出现一次的数字

题目链接

  • 思路一

    用一个哈希表记录数字出现的次数,然后再次遍历,返回次数为1的数字,唯一值得注意的就是对于map的getOrDefault方法的运用

    时空复杂度:假设nums长度为n,那么哈希表长度为n/3+1,各遍历一次,所以时间复杂度为O(n),由于哈希表容量为n/3+1,所以空间复杂度为O(n)

      public int singleNumber(int[] nums) {
    
          HashMap<Integer, Integer> numTimes = new HashMap<>(nums.length / 3);
    
          for (int num : nums) {
              numTimes.put(num, numTimes.getOrDefault(num, 0) + 1);
          }
    
          for (Map.Entry<Integer, Integer> entry : numTimes.entrySet()) {
              if (1 == entry.getValue())
                  return entry.getKey();
          }
    
          return -1;
      }
    
  • 思路二

    首先我们用一个长度为32的int数组numTimes来存储数组中所有的数字的二进制下每一位的和,比如nums为[2,2,1,2] 那么int数组就为[0,0,0······3,1]

    因为所有的数字都出现了三次,如果没有那个出现一次的数字,那么numTimes中的每一个数字都可以整除3,但是因为有一个出现了一次的数字,那么如果对于numTimes中的某一个数字n,如果可以整除3,那么对于出现一次的数字k在这一位一定是0,如果不能整除,那么k的这一位一定是1

    时空复杂度:上方for循环为O(32n)可以看作O(n),下方for循环为O(1),所以整体时间复杂度为O(n),额外空间开辟了一个长度为32的int数组,可以看作O(1)

    public int singleNumber(int[] nums) {
        
        int[] bitNums = new int[32];
        int result = 0;
    
        //把数组按位存储到bitNum中
        for (int num : nums) {
            //获取num每一位的值并填充
            for (int i = 0; i < 32; i++) {
                bitNums[31 - i] += num & 1;
                num >>= 1;
            }
        }
    
        for (int bitNum : bitNums) {
            result = (result << 1) + bitNum % 3;
        }
        return result;
    }
    

    需要解释一下这里
    bitNums[31 - i] += num & 1;
    我们在下面遍历数组的时候是从前往后遍历的,我们每遍历一次result都要左移一次,所以bitNum数组应该把高位放到低索引

补充
  • 这个题目有一个简化版本:一个数只出现一次,其余数字出现两次,这时把所有数组元素异或一次即可
  • 这个题目可以引申,即某个数字出现m次,其余数字都出现n次,都可以用此种做法,前提是m不能被n整除
  • 注意一个区别,此前我们用i&i-1实现了把某个数字的最右侧的1变成0,但是这里不能用,因为我们要准确的知道某个数字的二进制下哪一位是1,哪一位是0

Go版本

  • 思路一
func singleNumber(nums []int) int {
	numMap := map[int]int{}

	for _, num := range nums {
		numMap[num]++
	}

	for k, v := range numMap {
		if v == 1 {
			return k
		}
	}
	return -1
}

注意的是初始化map的方式和利用map计数时可以不判断是否存在,因为不存在会返回0,所以直接++即可

  • 思路二
func singleNumber(nums []int) int {
	result := int32(0)

	for i := 0; i < 32; i++ {
		total := 0
		for _, num := range nums {
			total += (num >> i) & 1
		}
		if 0 != total%3 {
			result |= int32(1 << i)
		}
	}
	return int(result)
}

注意这里在go需要用int32,否则符号位的解析会出现问题,再一个是这个思路,外层循环是第i位的情况,内存循环是在遍历每个数字,这样的话一个嵌套循环就可以了,不需要再用一个数组去记录每一位的情况,最后再去判断。下面的思路相较于思路二就比较麻烦了

func singleNumber(nums []int) int {
	var bitArray [32]int

	for _, num := range nums {
		for i := 0; i < 32; i++ {
			bitArray[i] += ((num >> i) & 1)
		}
	}

	var result int32
	for index, bit := range bitArray {
        if bit % 3 != 0{
		    result |= int32(1 << index)
        }
	}
	return int(result)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值