n重复数组,是指数组中的数字都出现n次;

唯一m重复异类数,是指存在唯一一个没出现n次,只出现了m次的数;

这里我简记它为nX+my问题,求解y,其中m < n,数组中都是整数;

3X+y问题

一直没有精力刷leetcode,今天查问题无意中看到了leetcode 137:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次;找出那个只出现了一次的元素。要求时间复杂度O(n),空间复杂度O(1);

没有在第一时间想到思路,于是花时间研究了一下;

这是一个3X+y的问题;先给出最优解:

        a, b = 0, 0
for num in nums:
a = (a ^ num) & ~b
b = (b ^ num) & ~a
return a

在2X+y问题中,方法是使用异或操作一遍全数组,最终结果就是那个异类数,但从未深入想过为什么异或会对2X+y问题有效,因而也就无法想通为什么直接异或会对3X+y问题无效;

再看一遍异或的特点:

0 ^ 0 = 0
1 ^ 0 = 1
0 ^ 1 = 1
1 ^ 1 = 0

仔细看看,这是因为异或可以让一个信息位(前面的那个1bit数),在接收到有效信息量(后面的那个1bit数,值为1时为有效)的时候,产生二态变化;

因而如果要解决3X+y问题,就需要有一个可以产生三态变化的操作符,单纯的异或是肯定不行的,因为要对一个信息位保存三种状态,至少需要两个信息位,因而在原本的数字变量空间上操作是不够的;

假定这个三态异或操作用符号^3表示;然后用两个信息位(b, a)保存状态,于是三态异或在接收到有效信息量(1)时的运算特点如下(接收到无效信息量0时不产生变化):

(0, 0)  ^3  1 =  (0, 1)
(0, 1) ^3 1 = (1, 0)
(1, 0) ^3 1 = (0, 0)

这个三态变化过程,其实是正常的两位二进制数进位加法的一个修改,修改处在于,对于二进制10,再加1时,直接跳过11回到00;

因而这个状态变化过程可描述为:

  1. 两位二进制数的低位a,在高位b为0时,进行二态变化;在高位b为1时,归0;
  2. 两位二进制数的高位b,在低位a归0时,进行二态变化;在低位a变为1时,保持为0;

可见a与b的变化规律相似,都要求对方为0时自己进行二态变化,对方为1时自己为0,于是上面的最优解的计算过程就很容易写出来了:

        a = (a ^ num) & ~b
b = (b ^ num) & ~a

3X+2y问题

等等,为什么是return a,而不是return b呢?

首先,上面这段代码里的a、b各是一个数字,它位的每个二进制位组合起来,保存了对整个数组^3操作过程中每个二进制位的状态变化;

因为这个^3操作过程中,低位a在第一状态有效,因而要求3X+y问题,需要return a;

因而,如果题目改为:给定一个非空整数数组,除了某个元素只出现两次以外,其余每个元素均出现了三次;找出那个只出现了两次的元素。

求解这个3X+2y问题,答案也就出来了,计算过程同上,而因为高位b在第二状态有效,因而return b即可;

4X+2y问题

再发散思维,改题目:给定一个非空整数数组,除了某个元素只出现两次以外,其余每个元素均出现了四次;找出那个只出现了两次的元素。

沿着上面的思路,需要做一个四态异或操作符^4,同样需要两个信息位进行状态存储,它对于有效信息(1)的运算特点如下:

(0, 0) ^4  1 = (0, 1)
(0, 1) ^4 1 = (1, 0)
(1, 0) ^4 1 = (1, 1)
(1, 1) ^4 1 = (0, 0)

这就是标准的两位二进制数加法,状态变化过程可描述为:

  1. 两位二进制数的低位a,正常进行二态变化;
  2. 两位二进制数的高位b,在低位a进位的时候进位二态变化;

于是计算过程可写为:

        old_a = a
a = (a ^ num)
b = b ^ ((old_a ^ a) & ~a)

其中(old_a ^ a) & ~a,意义为a由1变为0;

由于这个b的计算核心算子是异或,异或与否合用有危险,不把(old_a ^ a)引进来会出现bug;

因为要求的y出现2次,所以需要找第二状态时的有效位,return b;

4X+3y问题

状态数同时为4,计算过程同4X+2y,返回时找第三状态时的有效位,因而也return b;

5X+4y问题

题目修改为:给定一个非空整数数组,除了某个元素只出现4次以外,其余每个元素均出现了5次;找出那个只出现了4次的元素。

状态数上升到5,需要做一个五态异或操作符^5,需要3个信息位(c, b, a)做状态存储,对有效信息(1)的运算特点如下:

(0, 0, 0) ^5  1 = (0, 0, 1)
(0, 0, 1) ^5 1 = (0, 1, 0)
(0, 1, 0) ^5 1 = (0, 1, 1)
(0, 1, 1) ^5 1 = (1, 0, 0)
(1, 0, 0) ^5 1 = (0, 0, 0)

状态变化过程可描述为:

  1. 三位二进制数的低位a,在高位c为0时,进行二态变化;在高位c为1时,归0;
  2. 三位二进制数的中位b,在高位c为0时,低位a进位的时候进位二态变化;在高位c为1时,归0;
  3. 三位二进制数的高位c,在低位a和中位b同时归0时,进行二态变化;

计算过程可写为:

        old_a = a
a = (a ^ num) & ~c
b = (b ^ ((old_a ^ a) & ~a)) & ~c
c = (c ^ num) & ~b & ~a

由于y出现的次数是4,高位c在状态4时有效,因而返回c;

-----------------------------

测试代码:

def X3_y1(nums):
a, b = 0, 0
for num in nums:
a = (a ^ num) & ~b
b = (b ^ num) & ~a
return a def X3_y2(nums):
a, b = 0, 0
for num in nums:
a = (a ^ num) & ~b
b = (b ^ num) & ~a
return b def X4_y2(nums):
a, b = 0, 0
for num in nums:
old_a = a
a = (a ^ num)
b = b ^ ((old_a ^ a) & ~a)
return b def X4_y3(nums):
return X4_y2(nums) def X5_y4(nums):
a, b, c = 0, 0, 0
for num in nums:
old_a = a
a = (a ^ num) & ~c
b = (b ^ ((old_a ^ a) & ~a)) & ~c
c = (c ^ num) & ~b & ~a
return c print (X3_y1([2, 3, 2, 2])) # 3
print (X3_y2([2, 3, 2, 3, 2])) # 3 print (X4_y2([2, 2, 3, 2, 3, 2])) # 3
print (X4_y3([2, 2, 3, 2, 3, 3, 4, 3, 4, 4, 2])) #4
print (X4_y3([5, 5, 8, 5, 8, 8, 5])) # 8 print (X5_y4([5, 5, 8, 5, 8, 8, 5, 8, 5])) # 8

执行结果:

3
3
3
4
8
8

[算法] 举一反三之n重复数组中找唯一m重复异类数的更多相关文章

  1. C语言 选择排序算法原理和实现 从数组中 找出最小的元素然后交换位置

    #include <stdio.h> int main(void) { /* 选择排序算法 原理:从数组中 找出最小的元素然后交换位置: */ int a[10] = {9,5,10,7, ...

  2. 前端与算法 leetcode 26. 删除排序数组中的重复项

    目录 # 前端与算法 leetcode 26. 删除排序数组中的重复项 题目描述 概要 提示 解析 算法 # 前端与算法 leetcode 26. 删除排序数组中的重复项 题目描述 26. 删除排序数 ...

  3. 在数组中找几个数的和等于某个数[LeetCode]

    首先明确一点,这个方面的问题设计到的知识点是数组的查找的问题.对于类似的这样的查找操作的具体办法就是三种解决方法: 1.暴力算法,多个for循环,很高的时间复杂度 2.先排序,然后左右夹逼,但是这样会 ...

  4. 3sum(从数组中找出三个数的和为0)

    Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all un ...

  5. leetcode-26.删除重复数组中的重复项

    leetcode-26.删除重复数组中的重复项 题意 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度. 不要使用额外的数组空间,你必须在原地修改输入数 ...

  6. 用C#写一个函数,在一个数组中找出随意几个值相加等于一个值 与迭代器对比

    算法!用C#写一个函数,在一个数组中找出随意几个值相加等于一个值比如,数组{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}  要找出那些数相加等 ...

  7. Leetcode33--->Search in Rotated Sorted Array(在旋转数组中找出给定的target值的位置)

    题目: 给定一个旋转数组,但是你不知道旋转位置,在旋转数组中找出给定target值出现的位置:你可以假设在数组中没有重复值出现 举例: (i.e., 0 1 2 4 5 6 7 might becom ...

  8. 【算法】求多个数组中的交集(Java语言实现)

    简介: 最近在工作中遇到一个问题,需要离线比较两张Mongodb表的差异:大小差异,相同的个数. 所以,我将导出的bson文件转成了json文件(2G以上),一条记录正好是一行. 问题: 因此我将以上 ...

  9. 刷题之给定一个整数数组 nums 和一个目标值 taget,请你在该数组中找出和为目标值的那 两个 整数

    今天下午,看了一会github,想刷个题呢,就翻出来了刷点题提高自己的实际中的解决问题的能力,在面试的过程中,我们发现,其实很多时候,面试官 给我们的题,其实也是有一定的随机性的,所以我们要多刷更多的 ...

随机推荐

  1. 请求库之selenium模块

    本片导航: 介绍及安装 基本使用 选择器 等待元素被加载 元素交互操作 其他及练习   一.介绍 1.简单概述 selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无 ...

  2. __x__(22)0907第四天__ 垂直外边距重叠

    外边距重叠, 也叫“外边距合并”,指的是,当两个外边距相遇时,它们将形成一个外边距. 合并后的外边距的高度,等于两个发生合并的外边距的高度中的较大者...在布局时,易造成混淆. 1. 上下元素 垂直外 ...

  3. mobile_竖向滑屏

    竖向滑屏 元素最终事件 = 元素初始位置 + 手指滑动距离 移动端,"手指按下","手指移动" 两个事件即可(且不需要嵌套),有需要时才使用 "手指离 ...

  4. 简化equals()方法的重写

    实例说明 在定义类时,属性可以是基本类型也可以是引用类型.当重写equals()方法时一会要用“==”来比较基本类型,一会要用equals()比较引用类型,这样代码看着有些混乱.为此推荐使用Commo ...

  5. Python基础之容器1----字符串和列表

    一.编码: 1.编码只是梳理 2.编码涉及的函数: 3.实例: 字符串内存图 二.字符串 1.定义:由一系列字符组成的不可变序列容器,存储的是字符的编码值. 不可变:指字符串一旦定义,其内存地址就已经 ...

  6. Ehcache 3.7文档—基础篇—GettingStarted

    为了使用Ehcache,你需要配置CacheManager和Cache,有两种方式可以配置java编程配置或者XML文件配置 一. 通过java编程配置 CacheManager cacheManag ...

  7. EF Oracle TNS 连接

    <oracle.manageddataaccess.client> <version number="*"> <settings> <se ...

  8. 小甲鱼零基础python课后题 P22 021函数:递归是神马

    0.递归在编程上的形式是如何表现的呢? 答:在编程上,递归表现为函数调用本身这么一个行为. 1.递归必须满足哪两个基本条件? 答:1函数调用自己. 2有正确的返回条件 2.思考一下,按照递归的特性,在 ...

  9. Django数据库,在原有表中添加新字段

    1.在你要添加新字段的app的 models.py 文件中添加需要新增的字段(这里新增的是dress字段): from django.db import models # Create your mo ...

  10. WAN口和LAN 口有什么区别

    WAN口不能够用来连接电脑. LAN(1.2.3.4)口只能够用来连接电脑. 拓展资料 路由器(Router),是连接因特网中各局域网.广域网的设备,它会根据信道的情况自动选择和设定路由,以最佳路径, ...