四种方式带你层层递进解剖算法---hash表不一定适合寻找重复数据
一、题目描述
> 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
二、思路分析
- 算法(Algorithm)指的是解题的方案,是一系列解决问题的明确动作。所以说算法没有语言区分,只要我们的方案是完整的任何语言都可以实现它。我是C++出身但是从事Java多年,下面将是通过java来实现算法
考察点
- 任何算法基本上都可以通过暴力枚举来解决,但那仅仅是理论上。解决问题不仅要考虑理论最终还得取决于硬件和时间的支持。所以我们面对一个问题首先得确定方案。想要确定方案就得知道问题的痛点或者说问题的考点在哪里
- 此题是要找出重复的数字,想要找出重复的数字就得有一个对比的操作,想要有一个对比的操作就得将旧数据存放在一定规则的区域中。关于规则的区域这就引入了哈希表(HashTable)。
三、代码+解析
初版
public int findRepeatNumber(int[] nums) {
//构建hash表 。 Java中Map天生的Hash表
Map<integer, object=""> map = new HashMap<>();
for (int num : nums) {
//已经在hash表中存在的说明数据重复
if (map.containsKey(num)) {
return num;
}
//没有重复的数据需要添加到hash表中
map.put(num, num);
}
return 0;
}

- 此题是leetcode中简单类型的题目。既然是刷题首先得找简单的找找自信。正好也确定下自己的刷题风格。结果很明显是没有问题也是一次性通过。
- 虽然题目简单但是我们不能仅仅满足于完成。回过头来想想我们这样做有啥缺点是不是还有进步的空间呢?
首次升级
升级点
- 在上面的那个版本中我们借助于hash表来实现数据的存储从而进行数据的比对是否重复。这里因为引入了hash表而hash表就需要在内存中开辟空间这就导致了我们的程序在内存上开辟的比较大。会随着数组的重复性后偏导致我们的hash表内存越来越大,极端情况下我们的hash表中的元素和数组中的元素趋近于相等。
- 其次是每次都需要从hash中获取数据和数组中的数据进行对比。我们知道hash表尤其是Java中的Map的实现在获取数据是需要先根据hashcode值定位到hash槽,然后在从槽头开始遍历链表或者是树进行数据寻找。这个过程虽然已经很快了但是和数组直接寻址法相比就弱爆了。
- 基于上面两个痛点,我决定取消Hash表的引入。上面说了本题的考点是Hash表,但是并不意味着必须使用Hash表来实现是最优的。所谓条条大路通罗马实现是有很多种的,善于利用周遭的环境是我们人类的本能。
优化落地
- 既然是查找重复数据如果是有序的数组的话只需要逐个对相邻的两个进行比较就可以了。因为有序状态的数组每个元素会起着隔离的效果,这样就避免的Hash表的存在在内存上肯定比Hash表低的,而且上面也提到了数组的寻址比
HashMap快的多,所以在速度上应该也会快很多的
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) {
return nums[i];
}
}
return 0;
}

- 上图中左边是Hash表的方式运行效果,右图是相邻比较的运行效果。两者在执行时间和内存消耗上不是在同一个量级的。提升了两倍之多
再次升级
升级点
- 上面排序后相邻位置比较运行的结果我觉得还是挺满意的,但是在代码的是实现上有个边界的问题。而且我们需要逐个进行比较,逐个比较在时间上应该是比较耗时的。
- 基于上面逐个比较,笔者这里再次进行优化。将进行跳位比较

- 跳位就避免了逐个比较,将比较的次数控制下来。
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
int index =i;
while (index != nums[index]) {
index = nums[index];
}
if (index != i) {
return index;
}
}
return 0;
}

- 效果对比看一下,两者基本没有区别。在执行速度上基本一致。内存消耗上后者应该比前者高一点的,可能是leetcode统计内存没有那么细致再结合运行期间不稳定因素所以执行出来的结果虽然是后者高但是实际上笔者这里认为逐位相邻比较才是最优的。
- 本次的升级实际上是失败的,充其量就是逐位相邻比较的一种变形。但是本次的变形却引入另外一个概念---跳位交换
最终升级
升级点
其实仔细思考下为什么跳位寻址比较没有逐位相邻比较有什么显著的提升呢。说到底是因为我们已经排好顺序了在已经排好的顺序中我们跳位进行比对是没有起到太大的作用的。
这里笔者又查阅了官方的推荐解法--原地交换。这里的【原地交换】和笔者提出的【跳位寻址比较】不谋而合。下面我将翻译下官网的推荐解法(官网是真的强大)
这里官网推荐的代码就不贴了。大家可以直接在官网题解中找到原地交换讲解 。 但是笔者尝试了很多次都没有题解中说的100% 。 可能语言的差异所以他的实现并不支持java的。笔者这里对他进行稍微的带动
public int findRepeatNumber(int[] nums) {
for (int i = 0; i < nums.length; i++) {
while (i != nums[i]) {
if (nums[i] == nums[nums[i]]) {
return nums[i];
}
nums[i] = nums[i] ^ nums[nums[i]];
nums[nums[i]] = nums[nums[i]]^nums[i];
nums[i] = nums[i] ^ nums[nums[i]];
}
}
return 0;
}
- 官网中引入了一个临时变量用于交换数据暂存。这里内存就会一直被占用。理论上内存也不会太受影响的。但是结果在java中运行却不是那么完美
- 笔者这里将通过异或的方式实现数据的交换。运行结果相对会高点 。这里笔者在此提醒下leetcode每次运行因为大环境的问题并不能准确反映性能的问题
- 下面是笔者在leetcode连续运行三次的效果图

四、总结
- 不能仅仅依赖leetcode的运行结果作为衡量程序好坏的依据。笔者这里只是从个人的角度出发区分出程序的优劣
- 虽然leetcode不能作为唯一标准,但是多次运行的结果可以做一个参考价值。
- 算法的实现并不是一层不变的。我们学习算法是基础面对实际的问题还是得在算法的基础上进行扩展,结合实际的场景触发才是最正确的选择
> 最后还得送各位兄弟一句话,关注、点赞、收藏不能忘。万一哪天你找不到我了呢
</integer,>
四种方式带你层层递进解剖算法---hash表不一定适合寻找重复数据的更多相关文章
- 详解vue 路由跳转四种方式 (带参数)
详解vue 路由跳转四种方式 (带参数):https://www.jb51.net/article/160401.htm 1. router-link ? 1 2 3 4 5 6 7 8 9 10 ...
- Java实现文件复制的四种方式
背景:有很多的Java初学者对于文件复制的操作总是搞不懂,下面我将用4中方式实现指定文件的复制. 实现方式一:使用FileInputStream/FileOutputStream字节流进行文件的复制操 ...
- iOS 登陆的实现四种方式
iOS 登陆的实现四种方式 一. 网页加载: http://www.cnblogs.com/tekkaman/archive/2013/02/21/2920218.ht ml [iOS登陆的实现] A ...
- 实现web数据同步的四种方式
http://www.admin10000.com/document/6067.html 实现web数据同步的四种方式 1.nfs实现web数据共享 2.rsync +inotify实现web数据同步 ...
- 关于this绑定的四种方式
一.前言 我们每天都在书写着有关于this的javascript代码,似懂非懂地在用着.前阵子在看了<你不知道的JavaScript上卷>之后,也算是被扫盲了一边关于this绑定的四种方式 ...
- linux下实现web数据同步的四种方式(性能比较)
实现web数据同步的四种方式 ======================================= 1.nfs实现web数据共享2.rsync +inotify实现web数据同步3.rsyn ...
- C#批量插入数据到Sqlserver中的四种方式
我的新书ASP.NET MVC企业级实战预计明年2月份出版,感谢大家关注! 本篇,我将来讲解一下在Sqlserver中批量插入数据. 先创建一个用来测试的数据库和表,为了让插入数据更快,表中主键采用的 ...
- 【Java EE 学习 80 下】【调用WebService服务的四种方式】【WebService中的注解】
不考虑第三方框架,如果只使用JDK提供的API,那么可以使用三种方式调用WebService服务:另外还可以使用Ajax调用WebService服务. 预备工作:开启WebService服务,使用jd ...
- ASP.NET MVC之下拉框绑定四种方式(十)
前言 上两节我们讲了文件上传的问题,关于这个上传的问题还未结束,我也在花时间做做分割大文件处理以及显示进度的问题,到时完成的话再发表,为了不耽误学习MVC其他内容的计划,我们今天开始好好讲讲关于MVC ...
随机推荐
- python-3-2
一 切片 1.切片是list取值的一种方式 列子: nums = ['a','b','c','d','e','f','h','g','k','l','kk','nn','ee'] 取b和c元素出来 p ...
- sqli-labs系列——第四关
less4 第四关的sql语句是这样的: select * from user where id=("$id"); ?id=1")–+回显正常 order by 4报错, ...
- 敏捷史话(十三):我被 Facebook 解雇了——Kent Beck
2011年,Kent Beck 加入了 Facebook .那时候的他已年过半百,几十年的经验让他自认为非常了解软件行业.在 Facebook 的新手训练营期间,Kent 开始意识到,Facebook ...
- A. 1.划分数列
A . 1. 划 分 数 列 A. 1.划分数列 A.1.划分数列 氵水沝淼㵘解析 先预处理,然后公式推(详见代码) Code #include <bits/stdc++.h> using ...
- [图论]最短路径问题 :Floyed-Warshall
最短路径问题 目录 最短路径问题 Description Input Output Sample Input Sample Output 解析 了解Floyed算法 Floyed算法的核心思想: 代码 ...
- java面试-什么是GC root
一.什么是垃圾 内存中已经不再被使用到的空间就是垃圾 二.要进行垃圾回收,如何判断一个对象是否可以被回收? 引用计数法 很难解决对象之间的循环引用问题 枚举根节点做可达性分析 通过一系列名为" ...
- Gateway的限流重试机制详解
前言 想要源码地址的可以加上此微信:Lemon877164954 前面给大家介绍了Spring Cloud Gateway的入门教程,这篇给大家探讨下Spring Cloud Gateway的一些其 ...
- Web协议详解与抓包实战,高效解决网络难题
无论你是前后端工程师,还是运维测试,如果想面试更高的职位,或者要站在更高的角度去理解技术业务架构,并能在问题出现时快速.高效地解决问题,Web 协议一定是你绕不过去的一道坎. 旨在帮助你对各种常用 W ...
- 还在用KPI做管理研发团队?试试黄勇的OKR实战笔记
OKR是一种融入了人性的科学管理框架,承诺的事情就要努力去做到.深层次来看,OKR便恰恰体现了这样一种"承诺"精神. OKR绝不是一款简单的目标管理工具,用好它,你便能体会到管理的 ...
- Unity2D项目-平台、解谜、战斗! 0.2 序言:团队在线协作方案、基线控制
各位看官老爷们,这里是RuaiRuai工作室,一个做单机游戏的兴趣作坊. 本文跟大家聊一下笔者团队中所使用的在线协作的诸多工具,以及使用这些工具的目的和所记录的内容,希望这些内容在大家团队工作中有所帮 ...