leetcode-136. 只出现一次的数字

题目

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

说明:

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

示例 1:

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

示例 2:

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

解题思路

总觉得自己会了,但是优化算法想不出来……说明还是不怎么会啊

对于这道题,一个很常规的思路就是所有元素异或,然后剩下的元素就是所求的元素。但是为什么异或可以呢?

异或是把十进制数字转化为二进制,然后按位异或。异或保证了出现偶数次的位数都为0,出现奇数次的位数为1,所以对所有数字异或后,出现偶数次的数字,用异或后,每位都变为0,只有出现了1次的数字保留了下来。

那么,如果其他数字都出现了奇数次,应该怎么办呢?

实际上,用上面的思路也很容易想到了,假设其他数字都出现了k次,那么我们只要能用一种运算,保证每位上出现了k次的数字都变为0,就可以找到只出现1次的数字了。

把所有数字都转成k及以下的进制,然后对每一位求和,再对k取模,最终剩下的数字就是出现1次的数字。

不能用k以上的进制,因为这样没法区分该位的结果。比如,输入[5, 4, 1, 1, 5, 1, 5], k = 3,如果直接用10进制,对个位数相加得到22,取模后得到1,不知道该位应该是1还是4还是7

为了最省空间,使用k进制最好

注意要对负数做额外处理,因为python中负数比较特殊,所以额外写了一个符号位

为什么符号和数字可以分开计算?
注意到,在计算的时候,可以把符号和数字分开算。这是因为我们计算的时候,实际上对每一位都是取模的,所以忽略了进位带来的改变。因此数字可以单独计算

为什么符号也可以直接相加?
对于符号来说,实际上只有正和负两种情况。用什么数字做代表都无所谓,关键在于取模相加。

以上思路可以推广到任意次数,即假设其他数字出现了k次,只有1个数字出现了非k次的情况。

代码

异或版:

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        ans = 0
        for num in nums:
            ans ^= num
        return ans

k进制版:

class Solution:
    def decimal_to_k(self, num: int, k: int) -> tuple:
        """
        Change num to k-base, return digits in a string
        
        Returns:
            sign: int, 1 for positive, -1 for negative
            res: str, the left is the least digit
        """
        if num == 0:
            return '0', 1
        res = ''
        sign = 1 if num > 0 else -1
        num *= sign
        while num > 0:
            res += str(num % k)
            num //= k
        return res, sign

    def basek_xor(self, num1: str, num2: str, k: int) -> int:
        """
        Xor by mod k, from left to right, each digit is (num1[i] + num2[j]) % k, with no carry
        """
        res = ''
        for i in range(min(len(num1), len(num2))):
            digit1, digit2 = int(num1[i]), int(num2[i])
            res += str((digit1 + digit2) % k)
        i += 1
        if i < len(num1):
            res += num1[i:]
        if i < len(num2):
            res += num2[i:]
        return res

    def k_to_decimal(self, num: str, sign: int, k: int) -> int:
        """
        base-k to decimal

        Args:
            num: str, left -> right is least -> most
            sign: either 1 (positive) or k - 1 (negative)
        """
        res = 0
        for each_digit in num[::-1]:
            res = res * k + int(each_digit)
        sign = -1 if sign == k - 1 else 1
        return res * sign

    def helper(self, nums: List[int], k: int) -> int:
        """
        Use k-base to find the single number
        """
        # base-k
        res, res_sign = '0', 0
        for each_num in nums:
            each_num_base_k, sign = self.decimal_to_k(each_num, k)
            res = self.basek_xor(res, each_num_base_k, k)
            res_sign = (sign + res_sign) % k
        return self.k_to_decimal(res, res_sign, k)

    def singleNumber(self, nums: List[int]) -> int:
        return self.helper(nums, 2)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值