剑指Offer 56-II.数组中数字出现的次数II

题目描述

在这里插入图片描述

思路一:哈希表

若在没有时空间复杂度的情况下,一个简单的思路是直接使用哈希表记录数组中不同数字出现的次数:

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        hashtable=dict()
        for i in nums:
            if i in hashtable.keys():
                hashtable[i]+=1
            else:
                hashtable[i]=1
        for k in hashtable.keys():
            if hashtable[k]==1:
                return k

这一算法虽然是极其简单有效,但在时空复杂度上是糟糕的。(面试备选方案)

思路二:排序+查找

一个同样简单的思路是将数组进行排序,在将数组从小到大进行排序后,由于相同的数字总会在排序后共同出现,因而会被十分简单地区分出来:

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        def QuickSort(nums:List[int],left:int,right:int)->List[int]:
            if left>=right:
                return 
            l,r,key=left,right,nums[left]
            while l<r:
                while l<r and nums[r]>=key:
                    r-=1
                nums[l]=nums[r]
                while l<r and nums[l]<key:
                    l+=1
                nums[r]=nums[l]
            nums[l]=key
            QuickSort(nums,left,l-1)
            QuickSort(nums,l+1,right)
            return nums
        nums=QuickSort(nums,0,len(nums)-1)
        flag=1
        for i in range(1,len(nums)-1):
            if nums[i]==nums[i-1]:
                flag=0
            else:
                if flag:
                    return nums[i-1]
                else:
                    flag=1
        return nums[-1]

思路三:位运算

可以联想到,当数组中如果其他数字只出现两次,而只有一个数字出现一次时,我们可以采用对数组中所有的数字异或轻松得到问题的结果,更深层次地,我们思考这样可行的原因,由于位运算异或操作在位值相同时为0,不同时则为1,这可以转换为对每一位上二进制位相加模2取余,相同的两个数在这一操作过程中不会影响最终的结果,因而能取得最终的仅出现一次的数字。
那么迁移到这个问题上来,我们是否可以对二进制数字按位相加后模3取余即可得到那个唯一仅出现一次的数字呢?答案显然是可以的:

class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        def Tripe(x,y):
            tripebit=1
            ans=0
            while x and y:
                ans+=((x%3+y%3)%3)*tripebit
                tripebit*=3
                x=x//3
                y=y//3
            ans+=(x+y)*tripebit
            return ans
        return functools.reduce(Tripe,nums)

思路四:有限状态机

这一思路的解法是在充分明白思路三前提后的算法优化。回顾思路三的位运算操作,我们实际上只是将数组中所有的数字按位相加后取余,那么我们是否可以不用把比特位显示的写出每一位的操作,而是把他们整体进行考虑,把数字按位累加的过程分成所有当前位组成的数字和所有进位组成的数字。那么这一过程可以理解为一种有限状态机,在每一位上,会有位加法运算后的当前位与进位,由于运算是针对模三取余的,因而共有三种状态:
图片取自力扣题解区用户Krahets提供解法
那么用one表示所有的当前位组成的数字,two表示所有进位表示的组成的数字,则根据图示的状态跳转过程,我们可以写出如下的状态跳转规则:

if two == 0:
  if n == 0:
    one = one
  if n == 1:
    one = ~one
if two == 1:
    one = 0

作者:jyd
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/solution/mian-shi-ti-56-ii-shu-zu-zhong-shu-zi-chu-xian-d-4/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
if two == 0:
    one = one ^ n
if two == 1:
    one = 0

把上述规则整合如下:

one = one ^ n & ~two
two = two ^ n & ~one

进而我们有形成如下的状态转移机:

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

作者:jyd
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/solution/mian-shi-ti-56-ii-shu-zu-zhong-shu-zi-chu-xian-d-4/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值