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个数字出现了1次的情况。

代码

异或版:

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

k进制版:

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        def decimal_to_k(num: int, k: int) -> tuple:
            if num < 0:
                is_negative = 1
                num *= -1
            else:
                is_negative = 2
            ans = []
            while num >= k:
                ans.append(num % k)
                num //= k
            ans.append(num)
            return (is_negative, ans[::-1])


        def k_to_decimal(k_nums: tuple, k: int) -> int:
            is_negative, knum = k_nums[0], k_nums[1]
            ans = 0
            for index, digit in enumerate(knum[::-1]):
                ans += (k ** index * digit)
            return ans * (-1 if is_negative == 1 else 1)


        def occur_once(nums: list, k: int) -> int:
            prev_sign, prev = decimal_to_k(nums[0], k)
            for num in nums[1:]:
                cur_sign, cur_k = decimal_to_k(num, k)
                # num
                min_length = min(len(prev), len(cur_k))
                new_prev = []
                for index in range(1, min_length + 1):
                    new_prev.append((cur_k[-index] + prev[-index]) % k)
                new_prev.reverse()
                new_prev = cur_k[:(len(cur_k) - min_length)] + new_prev
                new_prev = prev[:(len(prev) - min_length)] + new_prev
                prev = new_prev
                # sign
                prev_sign = (prev_sign + cur_sign) % k
            return k_to_decimal((prev_sign, prev), k)
        
        return occur_once(nums, 2)
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页