写在前面

这类题目之前还真没接触过,没什么好的思路。不过看要求又是一道考虑时间复杂度的问题,需要掂量(考虑)一下自己的算法有没有达到要求。根据自己目前的能力写出来的算法还真没做到O(n)的时间复杂度,应该是O(n^2),不过居然通过了lintcode测试,而且是100% 数据通过测试,费解ing。
查阅资料发现主元素问题是《编程之美》上的原题,而且lintcode上还有主元素 II主元素 III问题,以及“引导”本人跳转过来的lintcode第3题:统计数字。
这里就只分析记录主元素问题,其他问题另行记录。

正文

题目

给定一个整型数组,找出主元素,它在数组中的出现次数严格大于数组元素个数的二分之一。

样例
给出数组[1,1,1,1,2,2,2],返回 1

挑战
要求时间复杂度为O(n),空间复杂度为O(1)

题目分析

作为lintcode上的难度为容易的题目,却没有什么好的思路,也是汗颜。不过没关系,有问题自己先努力解决之,再向前辈们请教取经。站在巨人的肩膀上,汲取养分,以便更快地成长。

方法1

方法1分析

这是我想的到的一种时间复杂度为O(n^2)的方法,空间复杂度倒是保持了O(1)。应该来说,是最朴素的方法了。本来是想使用新技能,用完发现不适合用在这里,结果效率还这么差,结果居然还通过了测试!
方法是将数组中某一元素与所有的元素进行比较(当然比较方法有多种),然后做统计,在数组中的出现次数严格大于数组元素个数的二分之一的元素自然就是主元素。但是这样需要进行内外两次循环,时间复杂度O(n^2)。
比较方法可以是相等比较,可以求差值为0。目标不外乎比较两个值是否相等。这里本人采用了异或方式(看书get的新技能,本来觉得蛮好玩的,现在发现好像也没什么突破性进步,无奈ing):因为任何整数和自己本身的异或为0。姑且安慰自己说异或方式效率更好吧=.=。

方法1代码

import java.util.ArrayList;public class Solution {/*** @param nums: a list of integers* @return: find a majority number*/public int majorityNumber(ArrayList<Integer> nums) {// write your codeint num = nums.size();int halfNum = (int) Math.ceil(num / 2.0);int count = 0;for (int i = 0; i < num; i++) {for (int j = 0; j < num; j++) {if ((nums.get(i) ^ nums.get(j)) == 0) {count++;if (count >= halfNum) {return nums.get(i);}}}}return -1;}
}

此处的问题有一个小问题,是在循环结束的时候,如果没有正确执行返回循环体中的数字,返回最后的-1,并不能表示什么意义,反而会在输入的数组没有主元素且数组中有负值的时候因为返回-1而造成迷惑。

tips

这里涉及一些ArrayList的操作:创建,初始化,添加元素和获取元素,可以参考后面给出的链接。两个链接是同一篇文章,importNew[2]引用的CSDN某位细致入微的作者写的文章[1],不过importNew的其他文章也都很出色,推荐一看。首推原作者的博客,珍惜作者的努力。

总之方案1算是很low的了,期待后面有什么好的思路改掉其中的bug吧。此处仅作记录,不建议使用。

其他方案

这里只做介绍,说一些目前大概有哪些思路,不作具体实现。

先排序,后求解

先对整数数组排序,然后根据排序后的规律定位主元素,并求解。定位方法有两种:一是顺序查找并做统计,超过半数的为主元素;二是直接在排序的基础上定位中位数,此时中位数就是主元素。很明显后者更优。
本方案主要在于前面的排序工作,需要O(nlogN)~O(n^2)的时间复杂度(大小写N是一个意思,只是为了避免logn分辨不清)。
顺序统计自然没什么说的,设置count变量,然后相同的数字+1;不同的数字跳过,然后继续下一个元素。

主元素->中位数

因为已经进行了排序操作,因此数组保持有序状态。并且主元素的特点是该元素在数组中的出现次数严格大于数组元素个数的二分之一,因此数组中位数必然是主元素。
示意图如下:

数组总数为奇数,那么中间的数自然就是中位数,也就是主元素;如果为偶数,中间的两个数任取其一都可以(例如如果总数为6,要达到主元素的要求,主元素数目必然>=4)。

分治法

将数组一分为二,然后左右数组与原题目要求相同,之后采用相同的方法。
更详细方法和代码,请自行查阅,此处只提供一种思路。

编程之美算法

思路分析

真的是只有这里才能称得上是算法。
思路也很简单:因为主元素的数量比其他元素的数量和都多,因此数组中任意两个不相等的数字相互抵消,最终剩余的数字必然是主元素,当然剩余的主元素个数可能不止为1。
两两抵消的时候又分为两种情况:一是主元素和非主元素抵消,二是非主元素和非主元素抵消。如果是主元素和非主元素抵消,结果是主元素数量-1,非主元素数量-1,仍然满足主元素在数组中的出现次数严格大于数组元素个数的二分之一这一条件。如果是非主元素和和非主元素抵消,结果是非主元素总数-2,主元素总数不变,主元素数目更是严格大于数组元素个数的二分之一.。
具体方式是先取nums[0]为主元素,同时设置计数变量count=1,然后与num[1]比较:如果相同则count+1;如果不同则count-1。同时判断count是否为0:为0则取下一个元素设置为主元素,继续后续比较;如果不为0则继续比较。
方法相当巧妙,个人认为中间难理解的部分是count=0的时候取下一元素设为主元素,其实理解后还是蛮简单的,而且算法改进的关键点也在这里。
现在举例介绍思路如下:
例1:

2,3,1,1,2,1,1

第一个元素和第二个元素均不是主元素,那么count的变化,就是count=1然后count-1,结果是count=0,需要从第三个元素重新开始,重复上述操作。而这一变化对应的是从数组中消除了两个非主元素。
例2:

1,1,2,3,1,1,2

同样的如果是主元素和非主元素相抵消,如1,1,2,3相抵消,对最终结果也没什么影响,到了3后的1自然还是重新设置count=1,相当于对1,1,2进行主元素查找。
后面在小结部分有进一步思考分析。

个人代码

代码为个人根据上述思路自己编写的第一版,比较粗糙。如有错误还请留言讨论。

import java.util.ArrayList;public class Solution {/*** @param nums: a list of integers* @return: find a majority number*/public int majorityNumber(ArrayList<Integer> nums) {// write your codeint majorityNumber = nums.get(0);int count = 1;for (int i = 1; i < nums.size(); i++) {if (majorityNumber == nums.get(i)) {count++;} else {count--;}if (count == 0) {// 主元素被取出,下次比较的值应该是在此主元素的下一个// 因此此处i++表示主元素是本次比较值的下一个,加上for循环中的i++,可以回归到之前的原始文体i++;// 判断i是否越界,方便后面拓展到其他的主元素求解。// 其实,>1/2的主元素是不需要判断的。后面的>1/k需要判断if (i >= nums.size()) {break;}// 重新设置主元素majorityNumber = nums.get(i);count = 1;}}return majorityNumber;}
}

很容易看出此算法的时间复杂度为O(n),复杂度O(1)。
此处代码只是简单的实现了上面的思路,并没有优化代码。分析算法可以知道算法的转折之处在于nums.get(i)与备选主元素的判断和count=0时的判断操作。因此优化代码之后,可以先列出新版本的流程图:

由流程图可以看出,新算法的整体思路还是类似的,不同之处在于count=0的时候的处理方式。
梳理之后整体思路如下:首先初始化主元素和count(计数、赋初始值与否都无所谓),然后进入流程图操作。数组中元素逐个与备选主元素比较:如果与备选主元素相同则count+1;如果不同且count值不为0,则进行count-1,如果此次运算后count=0也不立即将备选主元素的值重新赋值,而是进入下一次循环;如果比较元素与主元素不同,且主元素count=0,意味着,备选主元素抵消完了,此时再进行备选主元素更新操作。
这样,所有的数组中的元素都可以通过统一模型进行操作,不需要单独的判断进行备选主元素更新操作,代码更加简洁。

然后新版本的代码如下:

import java.util.ArrayList;public class Solution {/*** @param nums:*            a list of integers* @return: find a majority number*/public int majorityNumber(ArrayList<Integer> nums) {// write your codeint majorityNumber = 0;int count = 0;for (int i = 0; i < nums.size(); i++) {if (majorityNumber == nums.get(i)) {count++;} else if (count == 0) {majorityNumber = nums.get(i);count++;} else {count--;}}return majorityNumber;}
}

至此,主元素问题解决。

小结

编程之美提供的思路确实精妙,时间、空间开销都很小,为之赞叹。而排序算法后再定位中位数的方法,时间在排序时花销较大,瓶颈是排序算法的好坏。

其实仔细想想,编程之美的算法可以看做是第一种方法的精简版本。
本来是需要数组中的每一个元素与全部元素比较,并且进行统计。这种方式大而全,适合所有有类似结构或思路的问题,但是没有很好地结合本问题的特点:主元素出现的次数严格大于数组大小的二分之一,因而毫无效率可言。最优解的方法是选择两两抵消,保证最后剩余的元素肯定是主元素。算法本质其实也是每次两个元素进行比较,不过是两个元素同时在前进,然后分为两类,要么是主元素,要么不是主元素,然后主元素和非主元素相互抵消,同时“舍弃”一个元素。

相应的,如果是1/3、…、1/k?对于O(n)的时间复杂度,O(k)的空间复杂度,就很好理解了。类比1/2的主元素求解,同时保存k-1个数值不同的备选主元素,后面的值进行比较的时候,保证某一的备选主元素+1或者同时舍弃k个不同的主元素。

换个思路,如果考虑count为0的时候,重置主元素然后重复操作的话,又有一种重新开始,问题回归到初始状态的感觉。解题思路给人一种可以使用最简单的基础知识相互结合求解复杂问题的感觉,从细微之处着手解决大问题。

天下难事必作于易。

引申

问题拓展成1/3、…、1/k,问题依然类似,但又有些不同。

相似的是,原理基本一致。

同时保存k-1个备选主元素,然后进行比较。不同之处在于1/2的时候,剩余的最后一个必然是主元素。但是此时1/3的时候可能剩余两个数,1/k的时候可能剩余k-1个数。考虑最差的比较情况:如果每次都是主元素与另外k-1个非主元素进行抵消(没有非主元素相互抵消的情况),并且主元素数量刚刚满足主元素的条件(比如1/2的时候,5个元素中主元素有3个,下面的例子中1/3的时候,11个元素中主元素有4个)。举例如下,主元素占1/3的时候,11个元素:

1,1,1,1,2,2,3,3,4,4,4

按照抵消模型,可以作图如下:

不同的是最终主元素的获取。

这时候,如果按照最终的count值得到主元素,结果会是4,明显错误,因此要以两个备选主元素为指标进行统计,统计后的count最大值对应的才是主元素。

后面的主元素II和主元素III都可以根据上面的流程图进行扩充。因此算法对应的流程图不再赘述。

主元素II

题目

给定一个整型数组,找到主元素,它在数组中的出现次数严格大于数组元素个数的三分之一。

样例
给出数组[1,2,1,2,1,3,3],返回 1

挑战
要求时间复杂度为O(n),空间复杂度为O(1)

主元素II代码

import java.util.ArrayList;public class Solution {/*** @param nums: a list of integers* @return: The majority number that occurs more than 1/3*/public int majorityNumber(ArrayList<Integer> nums) {// write your codeint majorityNumber1 = 0;int majorityNumber2 = 0;int count1 = 0;int count2 = 0;for (int i = 0; i < nums.size(); i++) {if (majorityNumber1 == nums.get(i)) {count1++;} else if (majorityNumber2 == nums.get(i)) {majorityNumber2 = nums.get(i);count2++;} else if (count1 == 0) {majorityNumber1 = nums.get(i);count1++;} else if (count2 == 0) {majorityNumber2 = nums.get(i);count2++;} else {count1--;count2--;}}count1 = count2 = 0;for (int i = 0; i < nums.size(); i++) {if (majorityNumber1 == nums.get(i)) {count1++;} else if (majorityNumber2 == nums.get(i)) {count2++;}}return count1 > count2 ? majorityNumber1 : majorityNumber2;}
}

主元素III

题目

给定一个整型数组,找到主元素,它在数组中的出现次数严格大于数组元素个数的1/k。

样例
给出数组[1,2,1,2,1,3,3],返回 1

挑战
要求时间复杂度为O(n),空间复杂度为O(1)

主元素III代码

注意此处代码中breakcontinue的应用,以及标志位的配合。

import java.util.ArrayList;public class Solution {/*** @param nums: A list of integers* @param k: As described* @return: The majority number*/public int majorityNumber(ArrayList<Integer> nums, int k) {// write your code// 初始化// java默认int初始化为0,可以不进行下面的赋值操作int[] majorityNumber = new int[k - 1];int[] count = new int[k - 1];boolean hasUsed = false;for (int i = 0; i < k - 1; i++) {majorityNumber[i] = 0;count[i] = 0;}// 还选比较操作。一定要有continuefor (int i = 0; i < nums.size(); i++) {hasUsed = false;for (int j = 0; j < k - 1; j++) {if (majorityNumber[j] == nums.get(i)) {count[j]++;hasUsed = true;break;}}if (hasUsed) {continue;}for (int j = 0; j < k - 1; j++) {if (count[j] == 0) {majorityNumber[j] = nums.get(i);count[j]++;hasUsed = true;break;}}if (hasUsed) {continue;}for (int j = 0; j < k - 1; j++) {if (count[j] != 0) {count[j]--;}}}// 重置count,统计k-1个备选主元素的数量,找出真正的主元素for (int i = 0; i < k - 1; i++) {count[i] = 0;}for (int i = 0; i < nums.size(); i++) {for (int j = 0; j < k - 1; j++) {if (majorityNumber[j] == nums.get(i)) {count[j]++;}}}// 找出count的最大值int index = 0;int temp = 0;for (int i = 0; i < k - 1; i++) {if (temp < count[i]) {temp = count[i];index = i;}}return majorityNumber[index];}
}

参考网址:

  • [1] http://blog.csdn.net/u013256816/article/details/50916648
  • [2] http://www.importnew.com/19233.html

主元素、主元素II、主元素III相关推荐

  1. 由113号元素鉨114号元素夫115号元素镆元素汞银金等元素构成的超导体

    由113号元素鉨114号元素夫115号元素镆元素汞银金等元素构成的超导体 由粒子加速器制造出113号元素鉨114号元素夫115号元素镆,它们的质量比是1:1.5:3,混入高温2000摄氏度的汞,银,金 ...

  2. 下列不属于html5语义元素,HTML5 新的语义元素

    HTML5 提供了新的语义元素来明确一个Web页面的不同部分: HTML5中新的语义元素 HTML5 元素 标签定义文档中的节(section.区段).比如章节.页眉.页脚或文档中的其他部分. 根据W ...

  3. 让元素固定_原神雷元素不如火元素吗?阵容搭配与圣遗物强化攻略

    一.本篇文章要回答的问题 雷元素不如火元素吗?雷元素主C队伍该如何搭配?圣遗物该如何选择和强化?元素充能效率是鸡肋吗?刻晴该如何养成? 二.为什么感觉火元素强而雷元素弱? 首先要从增幅反应和剧变反应讲 ...

  4. CSS基础-09-布局(定位 position、浮动float,元素对其、图像对其、文本对齐、元素内内容对齐,元素堆叠)

    文章目录 1 定位 (position) 1.1 static 定位 1.2 fixed 定位 1.3 relative 定位 1.4 absolute 定位 1.5 sticky 定位 2. 浮动( ...

  5. 以域变量rear和length分别指示循环队列中队尾元素的位置和内含元素的个数。给出队满条件和相应的如对和出队算法。

    以域变量rear和length分别指示循环队列中队尾 元素的位置和内含元素的个数.给出队满条件和相应的如对和出队算法. /* 以域变量rear和length分别指示循环队列中队尾 元素的位置和内含元素 ...

  6. c语言求比平均值大的元素,c语言 10个元素的数组求平均值并输出比平均值大的数...

    [c语言]编写程序,求数组a中前n个元素的最大值max.最小值min及平均值avg. 是前n个元素么?main(){inti=n,sum=0;max=a[0];min=a[0];for(i=0;ia[ ...

  7. oracle联合主键 索引,关于复合主键查询时使用索引研究

    当数据库创建表时,每个表只能有一个主键,但是如果想让多个列都成为主键时,就要用到复合主键. 一.主键唯一约束 我们知道当某列为主键时,Oracle会自动将此列创建唯一约束.也就是说不允许有相同的值出现 ...

  8. MyBatis主键回填和自定义主键

    MyBatis主键回填和自定义主键 1. 主键回填 JDBC中的Statement对象在执行插入的SQL后,可以通过getGeneratedKeys方法获得数据库生成的主键,这样便能达到获取主键的功能 ...

  9. ui布局设计元素:ui布局设计元素有哪些?

    ui布局设计元素:ui布局设计元素有哪些?这一直是我比较喜欢研究的话题,今天正好朋友正好问起.所以就在这里来把自己在行业里的总结写下来,给大家参考一下.说实话,做ui设计师以来,一直有很多朋友问我相关 ...

  10. 浅谈设置父元素透明度不影响子元素透明度

    浅谈设置父元素透明度不影响子元素透明度 筱葭 2017-03-15 10:50:00  11970  收藏 2 展开 设置父元素opacity:0.5,子元素不设置opacity,子元素会受到父元素o ...

最新文章

  1. ORACLE空值漫谈1
  2. 关于Kotlin语法的异议
  3. 使用TestNG-xslt+ant来美化selenium测试报告
  4. java 正则表达式提取价格
  5. 支持本地 持久化的 单机版 consul 服务
  6. Docker精华问答 | Consul是什么?
  7. 微观经济学如何计算机会成本,【微观经济学】机会成本
  8. 大数据之-Hadoop3.x_MapReduce_MapJoin案例完成---大数据之hadoop3.x工作笔记0134
  9. 几款Linux系统漏洞扫描、评估工具简介
  10. [Remoting]在.NET環境實作Flex 3 Remoting - (2) Flex Builder 環境設定
  11. chattr与lsattr命令
  12. 套接字的作用与一般使用_05 | 使用套接字进行读写
  13. pjsip在windows上编译
  14. C++ 错误根源分析
  15. Markdown记录
  16. H264里面描述符的意思--------x264学习笔记ByMK
  17. VulnHub 靶场--October
  18. 【微信小程序】扫描二维码/条形码,并获取信息
  19. 笔记本 原因代码: 0x500ff 关机类型: 关闭电源_图吧垃圾佬关于笔记本电池更换的几项建议...
  20. air macbook 风扇响_MacBook装Windows 7后温度过高/风扇过响的解决办法

热门文章

  1. GameFramework框架——使用过程中遇到的问题
  2. 蓝的成长记——追逐DBA(20):何故缘起,建库护航 (二次发布-练习使用markdown编辑)
  3. 标题:关于high-speed Charting Control配置过程中一些问题(ChartCtrl新手向)vs2019+win10-2020.12.11
  4. via自定义搜索引擎代码_VIA浏览器自定义logo设置
  5. 喜马拉雅xm文件转换为mp3?
  6. Authorization头的作用
  7. Mysql学习总结十二:系统变量、用户变量、定义条件和处理程序
  8. Unity下实现弹簧骨骼(Spring Bone)
  9. Openssl移植到ARM开发板
  10. Docker的安装与基本使用(一)---使用Docker的前提条件和三要素