数据结构与算法-- 数组中出现次数超过一半的数字(时间复杂度的讨论)

时间效率

  • 互联网想对时间效率格外的敏感,所以我们总是在需求迭代一定程度后去做优化。而且我们解决问题的时候,时间效率往往是一个考查的重点。因此我们平时编码过程中就必须不断的优化效率,追求完美的态度与能力。
    • 首先是编码习惯,例如java中字符串操作我们不建议使用String的+ 运算符来完成。这样会产生非常多的String临时变量,造成空间与实际的浪费,更好的方式是用StringBuilder的Append方法完成字符串的拼接。
    • 其次,即使是同一个算法用循环和递归两种思路实现时间效率也是不一样的。递归的本质是吧一个大的复杂问题拆解成两个或者多个小的简单问题,如果小问题中互相重叠,那么直接用递归实现,代码会更加简单,但是时间效率非常差,这时候,我们可以用递归的思路来分析,但是用数组(一维数组或者二维数组)来保存中间结果基于循环实现。绝大部分动态规划算法的分析和代码实现都是分这两个步骤。
    • 再次,代码时间效率还体现在我们数据结构的掌握程度。
    • 最后以如下提来举例:

数组中出现次数超过一半的数字

  • 数组中有一个数字出现的次数超过数组长度的一半,找出这个数字,例如:输入一个长度为10 的数组{1,2,3,3,3,3,3,3,4,5,6,7}。由于数组3在数组中出现了6次,超过数组长度的一半因此输出3

  • 解法一:看到题目的第一反应就是讲数组排序统计,自然很容易得出每个数字的次数。题目给出的数组没说排序

    • 先排序。排序的时间复杂度O(nlogn)
    • 接着统计O(n)
    • 最直观的算法通常不是最优解,我们接着找更优的解法
  • 解法二:题意中是一个特殊的数组,数组中有一个数字出现次数超过数组长度的一半。排序后,不管其中重复的数字怎么排,其中位于数组中间位置的数字比如就是出现超过一半的那个数字。

    • 结论是我们需要找的就是统计学中的中位数。即长度为n 的数组,找出第n/2 大的数字,这个有成熟的O(N)算法得到数组中第K个大的数
  • 分析:

    • 我们在之前的排序算法文章:数据结构与算法–排序算法总结(动图演示) 对各种排序算法有详细的解释。根据快排的思路,我们先选择数组中一个随机的数字,然后调整数组中数字顺序,使得左边小,右边大。如果这个数字的下标正好是n/2,那么得到我们的结果
    • 如果下标大于n/2,那么中位数在 左边,则同样的算法用在左边的数列上
    • 如果下标小于n/2,那么中位数在右边,则同样的算法用在作右边的数列上
    • 典型的递归
  • 经过如上分析有如下代码:

/*** 如果数组中超过数组一半的数都是同一个数,找出那个数字* @author liaojiamin* @Date:Created in 17:21 2021/5/27*/
public class MoreThenHalfNum {public static void main(String[] args) {int[] arrayData = {4,4,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6};System.out.println(findMiddleNum(arrayData, 0, arrayData.length -1));}/***  方法一:二分查找思想找出 数组中间大小的那个数字*  如果该数字在数组中的个数超过数组的一半,那么中间大小的数字必然是这个数字* */public static int findMiddleNum(int[] arrayData, int left, int right){if(arrayData == null || arrayData.length <= 1){return -1;}Integer middle = (left + right)/2;if(left < right){int index = swapArray(arrayData, left, right);if(index == middle){return arrayData[middle];}quickSort(arrayData, left, index - 1);quickSort(arrayData, index + 1, right);}return arrayData[middle];}/*** 挖坑法二分查找* */public static int swapArray(int[] arrayData, Integer left, Integer right){if(left < right){int position = arrayData[left];while (left < right){while (right > left && arrayData[right] > position){right --;}if(left < right){arrayData[left] = arrayData[right];left ++;}while (left < right && arrayData[left] < position){left ++;}if(left < right){arrayData[right] = arrayData[left];right--;}}arrayData[left] = position;}return left;}
}
  • 以上解法时间复杂度能达到O(n),但是还是有点复杂的,可能你写不出来,是否有更简单的方法?

  • 解法三:依然分析数组的特性,超过一半的存有量,也就是他比他的数字出现的次数的总和还要多,

    • 那么我们只需要统计个数就能得到了
    • 只进行一次遍历数组,保存两个值,一个数组中数字,一个他的次数
    • 如果遍历到下一个数据的时候,还是这个数,那么次数加 +1,否则次数 -1
    • 如果次数达到0 ,那么情况保存的这个数,在接着遍历依然用以上逻辑
    • 由于我们要找的数据出现的次数比其他数字总和还要多,那么肯定最后一个保存下来的数据必然是我们要的那个出现此处过半的数据。
  • 根据如上分析有如下代码:

/*** 如果数组中超过数组一半的数都是同一个数,找出那个数字* @author liaojiamin* @Date:Created in 17:21 2021/5/27*/
public class MoreThenHalfNum {public static void main(String[] args) {int[] arrayData = {4,4,4,4,4,4,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6};System.out.println(findThenHalfNum(arrayData));}/*** 方法二:依次遍历数组中每一个数字,将遇到的数字保存到 k 对象中,如果依次遇到的数字中与k中相同统计+1*  否则 -1,如果统计为0 情况k对象,依次对每个数字操作,最终保留下来的比如是数量最多的那个数字* */public static int findThenHalfNum(int[] arrayData){if(arrayData == null || arrayData.length <= 1){return -1;}int findNum = -1;int findNumCount = -1;for (int i = 0; i < arrayData.length; i++) {if(findNum == -1){findNum = arrayData[i];findNumCount = 1;}else {if(findNumCount == 0){findNum = arrayData[i];findNumCount =1;}else {if(findNum == arrayData[i]){findNumCount ++;}else {findNumCount --;}}}}return findNum;}}
  • 如上第二第三都是最优解,但是第二中修改了数组,第三种没有修改数组,可以依据具体情况来决定方案。

上一篇:数据结构与算法-- 八皇后问题(两种实现方案)
下一篇:数据结构与算法–将数组排成最小的数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我是如何一步步的在并行编程中将lock锁次数降到最低实现无锁编程

在并行编程中&#xff0c;经常会遇到多线程间操作共享集合的问题&#xff0c;很多时候大家都很难逃避这个问题做到一种无锁编程状态&#xff0c;你也知道一旦给共享集合套上lock之后&#xff0c;并发和伸缩能力往往会造成很大影响&#xff0c;这篇就来谈谈如何尽可能的减少lock…

常用Arthas命令

jad反编译 检查线上代码是否修改成功&#xff0c;例如修改interface后看Jar包是否引入新的&#xff0c;或者代码是否最新的。 jad com.zhenai.counseling.business.provider.facade.supremecourse.RedeemRecordFacadeImpl //反编译只展示源码 jad --source-only com.zhenai.c…

关于分布式锁的面试题都在这里了

「我今天班儿都没上&#xff0c;就为了赶紧把这篇文章分布式锁早点写完。我真的不能再贴心了。」「边喝茶边构思&#xff0c;你们可不要白嫖了&#xff01;三连来一遍&#xff1f;」引言为什么要学习分布式锁&#xff1f;最简单的理由就是作为一个社招程序员&#xff0c;面试的…

数据结构与算法--将数组排成最小的数

将数组排成最小的数 题目&#xff1a;输入一个正整数的数组&#xff0c;将数组中所有数字拼接在一起排列成一个新的数&#xff0c;打印能拼接出来的所有数字中最小的一个&#xff0c; 案例&#xff1a;输入数组{12,4&#xff0c;55}&#xff0c;则能打印出最小的数组是&#x…

Git 15周年:当年的分道扬镳,成就了今天的开源传奇

4 月 7 日&#xff0c;全球最主流的版本控制系统 —— Git 迎来 15 周年纪念日&#xff0c;项目主要维护者 Junio C Hamano&#xff08;滨野 纯&#xff09; 先生发邮件庆祝了这一日子。我们知道&#xff0c;所有的软件项目在整个生命周期中都要经过不断迭代&#xff0c;在一个…

数据结构与算法--丑数

找出排在第n位大的丑数 丑数&#xff1a;我们将只包含质因子 2,3,5的数称为丑数&#xff08;ugly Number&#xff09;。求按从小到大的熟悉怒排列的低1500 个丑数。例如6,8 都是丑数&#xff0c;但是14 不是丑数&#xff0c;因为他包含质因子7。1 是基础丑数 解法一&#xff1…

数据结构与算法--第一个只出现一次的字符

第一个只出现一次的字符 题目&#xff1a;在字符串中找出第一个只出现一次的字符&#xff0c;比如输入“wersdfxvsdfwer”&#xff0c;则输出x。 方法一&#xff1a; 还是老规矩&#xff0c;初始想法是从头遍历每一个字符&#xff0c;每遍历一个字符都与后面n-1个字符比较如果…

使用 docker 编译运行 abp 项目

在前面的两篇文章中&#xff0c;介绍了如何在华为鲲鹏架构及其Euler系统上运行dotnet core, 使用docker运行了默认的mvc模板项目&#xff0c;这篇文章继续介绍在docker中运行更复杂的dotnet core项目&#xff0c;这里以业内鼎鼎大名的abp vnext框架&#xff0c;版本 2.6 为例。…

数据结构与算法--数组中的逆序对

题目&#xff1a;在数组中的两个数字如果签名一个数字大于后面的数组&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 案例&#xff1a;输入数组{7,5&#xff0c;6,4}中一共有5个逆序对分别是{7,6}&#xff0c;{7,5}&#x…

用了这么多年的泛型,你对它到底有多了解?

现代程序员写代码没有人敢说自己没用过泛型&#xff0c;这个泛型模板T可以被任何你想要的类型替代&#xff0c;确实很魔法很神奇&#xff0c;很多人也习以为常了&#xff0c;但就是这么有趣的泛型T底层到底是怎么帮你实现的&#xff0c;不知道有多少人清楚底层玩法&#xff0c;…

数据结构与算法--两个链表中第一个公共节点

链表中第一个公共节点 公节点定义&#xff1a;同一个节点在两个链表中&#xff0c;并不是节点值相同题目&#xff1a;输入两个节点&#xff0c;找出他们的第一个公共节点&#xff0c;节点定义如需 /*** 链表元素节点** author liaojiamin* Date:Created in 12:17 2021/3/5*/ …

ASP.NET Core技术研究-全面认识Web服务器Kestrel

因为IIS不支持跨平台的原因&#xff0c;我们在升级到ASP.NET Core后&#xff0c;会接触到一个新的Web服务器Kestrel。相信大家刚接触这个Kestrel时&#xff0c;会有各种各样的疑问。今天我们全面认识一下ASP.NET Core的默认Web服务器Kestrel。一、初识Kestrel首先&#xff0c;K…

数据结构与算法--二叉堆(最大堆,最小堆)实现及原理

二叉堆&#xff08;最大堆&#xff0c;最小堆&#xff09;实现及原理 二叉堆与二叉查找树一样&#xff0c;堆也有两个性质&#xff0c;即结构性质和堆性质。和AVL树一样&#xff0c;对堆的一次操作必须到堆的所有性质都被满足才能终止&#xff0c;也就是我们每次对堆的操作都必…