[Java][算法 哈希]Day 01---LeetCode 热题 100---01~03

LeetCode 热题 100---01~03   ------->哈希

第一题  两数之和

思路

最直接的理解就是   找出两个数的和等于目标数   这两个数可以相同 但是不能是同一个数字(从数组上理解就是内存上不是同一位置)

解法一:暴力法

暴力解万物 按照需求  我们需要将数组的任意不同位置的数两两相加 再去判断是否等于目标数target 那么很显然 利用for循环的嵌套 第一层for循环从头遍历到尾 表示第一个数字 ;第二个数从第一个数的后一位置开始遍历到尾部,表示第二个数字

因为题目中明确说明了每种输入只会对应一个答案  所以找到之后就可以直接返回了

那么对应的代码就是

class Solution {public int[] twoSum(int[] nums, int target) {int n = nums.length;for (int i = 0; i < n; ++i) {for (int j = i + 1; j < n; ++j) {if (nums[i] + nums[j] == target) {return new int[]{i, j};}}}return new int[0];}
}

时间复杂度为O(N^2)

解法二:双指针法

上述时间复杂度之所以高 是因为我们查找第二个数采用的也是循环 就导致了循环的嵌套,如果想降低时间复杂度,那么就需要降低第二个数的查找时间

其实这个思路也比较简单 就是我们先将数组进行排序 保证从小到大/从大到小排序(这里我们排从小到大),那么 我们就可以最开始从数组最左侧A和最右侧B的两个数据开始相加得到sum  如果sum>target 那么说明我们需要讲两个加数变小,已知一个加数A已经是最小 那么只能让B往前走一位从而减小数据(当然 如果倒数第二个数据和最后一个数据等大  自然得出来的结果还是会大于target  但是不妨碍我们继续判断),反之  如果sum<target 那么就需要增大加数,只有加数A能够增大 所以就需要将加数A向右移动一位,依此类推,直到找到数据

class Solution {public int[] twoSum(int[] nums, int target) {ArrayList<Integer> list=new ArrayList<>();for (int num : nums) {list.add(num);}int[] dest = Arrays.copyOf(nums, nums.length);Arrays.sort(dest);int slow=0;int fast=nums.length-1;while(slow!=fast){int sum=dest[slow]+dest[fast];if(sum==target){int i = list.indexOf(dest[slow]);int j = list.lastIndexOf(dest[fast]);return new int[]{i,j};}else if(sum<target){slow++;}else if(sum>target){fast--;}}return new int[]{};}
}

可以看到  时间复杂度确实变小了  但是变化不多

解法三:哈希

这才是这个题目的出题本意  使用Hash来进行判断

同解法二,我们需要的是减少第二个数字的查询时间,我们可以将每个数存入Hash表中,然后通过target-A=B来得到B 然后判断在Hash表中是否存在B即可  因为Hash的缘故 第二个数据被查询的时间减少了

因为要找寻的是下表  我们利用Map数据结构 数据作为Key  下标作为Value  这样我们就可以通过key来找到下标

那么我们遍历一遍数组 作为第一个数A 然后通过containsKey(T   Key)方法来判断是否存在第二个数据B  如果存在 就直接通过get方法获得B的下标返回即可

如果不存在 就将该数放到Map中  之所以先判断后放入 是防止先放入之后  会出现自己和自己相加等于target的情况

class Solution {public int[] twoSum(int[] nums, int target) {Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();for (int i = 0; i < nums.length; ++i) {if (hashtable.containsKey(target - nums[i])) {return new int[]{hashtable.get(target - nums[i]), i};}hashtable.put(nums[i], i);}return new int[0];}
}

第二题  字母异位词分组

思路  

首先  字母异位词就是指  对一个单词重新排列顺序后 得到一个新的单词  在这个题目中  可以理解为  给你一个字符串数组  判断该数组中哪些元素是由同一些字母组成的 

例如示例一   eat  和 ate  和tea 三个元素都是由  a  e   t 组合而成 所以他们三个归为一个数组中

如此一来 我们只需要想办法  将各个方式组成的元素 用不重复方法标识出来即可 

最好的方法就是统计字母次数  

解法一:编码-分类

我们对每一个元素遍历  然后利用  每个单词-‘a’ 得到ASCII码差值  对应一个int[26] 数组arr中的每一个数据,简单理解就是a对应arr[0]  b对应arr[1]......以此类推  最后  由相同的字母组成的单词所得到的arr数据肯定相同  然后我们将arr转化为字符串String 作为标识的key  value采用List<String> 将同一类的单词都存入到这个Map<String,List<String>> map=new HashMap<>()中即可

class Solution {public List<List<String>> groupAnagrams(String[] strs) {List<List<String>> endList=new ArrayList<>();//最终返回的ListMap<String,List<String>> map=new HashMap<>();
// 遍历整个字符串数组进行操作for (String str : strs) {String code=getCode(str);  // 求出每个元素对应的code  作为标识key
// 判断该key是否存在 如果存在 就放到对应的List中 反之 如不存在 就创造一个新的key并new一个新List将该元素放入if(map.get(code)!=null){  map.get(code).add(str);}else{map.put(code,new ArrayList<>());map.get(code).add(str);}}
//最后遍历整个Map  将value取出来即可map.forEach((x,y)->endList.add(y));return endList;}public static String getCode(String str){char[] charArray = str.toCharArray();int [] arr=new int[26];for (char c : charArray) {arr[c-'a']++;}return Arrays.toString(arr);}
}

解法二:排序

如果两个单词是属于字母异位词,那么两者的字母组成肯定是相同的,如果字母组成是相同的,那么两者对内部单词进行同样的排序方式得到的结果也肯定是一样的,所以,我们需要对每个元素单词内部进行排序,然后将结果一样的放到一起即可

其实这种方法和上述的编码分类思想差不多,解法一是我们利用字母数量进行一个编码,我们解法二其实就是将排序后的结果作为标识编码来进行区分

class Solution {public static List<List<String>> groupAnagrams(String[] strs) {List<List<String>> endList=new ArrayList<>();Map<String,List<String>> map=new HashMap<String, List<String>>();for(String x:strs) {char[] arr=x.toCharArray();Arrays.sort(arr);String end=new String(arr);if(map.containsKey(end)) {map.get(end).add(x);}else {map.put(end,new ArrayList<String>());map.get(end).add(x);}}map.forEach((x,y)->{endList.add(y);});return endList;}
}

第三题 最长连续序列

思路

判断有无连续的序列,简单的方式就是遍历一遍,然后遍历每个数的时候,判断下一个数字是否等于前一个数字加一,等于的计数器+1,反之则归零

需要注意的是,需要考虑空数组,数组中存在相同元素的情况

解法一: 自己写的

我们自己的写法就是按照上述思路的遍历想法解题

class Solution {public int longestConsecutive(int[] nums) {if(nums.length==0) return 0; //空数组直接返回LinkedHashSet<Integer> temp = new LinkedHashSet<>();ArrayList<Integer> list=new ArrayList<>();
//我们需要set来去重  但是因为set本身是无序的  为了方便后续的比较后一位是否等于前一位加一
//就需要该集合是有序的  所以我们采用LinkedHashSet这种结构for(int x=0;x<nums.length;x++){temp.add(nums[x]);}
//去重后用List存储  方便转数组for (Integer i : temp) {list.add(i);}Integer[] array = list.toArray(new Integer[0]);int[] arr=new int[array.length];for (int i = 0; i < array.length; i++) {arr[i]=array[i];}int max=0;int number=0;Arrays.sort(arr);for (int i = 1; i < arr.length; i++) {
//遍历数组 判断前一位和后一位是否连续  连续+1  反之归零if(arr[i]==arr[i-1]+1){number++;}else{if(number>max){max=number;}number=0;}}if(number>max){max=number;}return max+1;}
}

可能会有疑问  为啥中间需要用List集合来转存一下 而不是直接Set集合temp转数组arr呢?其实也是可以的  对比两者 内存消耗和时间消耗其实差不多  temp直接转Array在某些特殊情况中会比List转Array是稍微多消耗一些资源的  所以哪怕第一段代码需要额外开销来转存到List中 但是单纯的开销空间来创造一个List和遍历集合消耗也不大

class Solution {public int longestConsecutive(int[] nums) {if(nums.length==0) return 0;LinkedHashSet<Integer> temp = new LinkedHashSet<>();for(int x=0;x<nums.length;x++){temp.add(nums[x]);}Integer[] array = temp.toArray(new Integer[0]);int[] arr=new int[array.length];for (int i = 0; i < array.length; i++) {arr[i]=array[i];}int max=0;int number=0;Arrays.sort(arr);for (int i = 1; i < arr.length; i++) {if(arr[i]==arr[i-1]+1){number++;}else{if(number>max){max=number;}number=0;}}if(number>max){max=number;}return max+1;}
}

解法二:官方解法---Hash法

官方解法还是很巧妙的,我们采取遍历的方式来找,很容易重复遍历判断相同序列

由于我们要枚举的数 x 一定是在数组中不存在前驱数 x−1 的,不然按照上面的分析我们会从 x−1x-1x−1 开始尝试匹配,因此我们每次在哈希表中检查是否存在 x−1即能判断是否需要跳过了。

增加了判断跳过的逻辑之后,时间复杂度是多少呢?外层循环需要 O(n) 的时间复杂度,只有当一个数是连续序列的第一个数的情况下才会进入内层循环,然后在内层循环中匹配连续序列中的数,因此数组中的每个数只会进入内层循环一次。根据上述分析可知,总时间复杂度为 O(n)符合题目要求。

class Solution {public int longestConsecutive(int[] nums) {Set<Integer> num_set = new HashSet<Integer>();for (int num : nums) {num_set.add(num);}int longestStreak = 0;for (int num : num_set) {if (!num_set.contains(num - 1)) {int currentNum = num;int currentStreak = 1;while (num_set.contains(currentNum + 1)) {currentNum += 1;currentStreak += 1;}longestStreak = Math.max(longestStreak, currentStreak);}}return longestStreak;}
}

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

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

相关文章

MATLAB知识点:使用逻辑值修改或删除矩阵元素

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章 3.4.4 逻辑运算 3.4.4.3 使用逻辑值修改或删…

【数据分享】1929-2023年全球站点的逐年平均风速(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全球气象站…

使用Python进行数据的描述性分析,用少量的描述性指标来概括大量的原始数据

在进行数据分析时&#xff0c;当研究者得到的数据量很小时&#xff0c;可以通过直接观察原始数据来获得所有的信息。但是&#xff0c;当得到的数据量很大时&#xff0c;就必须借助各种描述性指标来完成对数据的描述工作。用少量的描述性指标来概括大量的原始数据&#xff0c;对…

ubuntu原始套接字多线程负载均衡

原始套接字多线程负载均衡是一种在网络编程中常见的技术&#xff0c;特别是在高性能网络应用或网络安全工具中。这种技术允许应用程序在多个线程之间有效地分配和处理网络流量&#xff0c;提高系统的并发性能。以下是关于原始套接字多线程负载均衡技术的一些介绍&#xff1a; …

《经过》-何方?

若将生命比作一段旅程&#xff0c;唯有走过&#xff0c;方知全貌。在这旅途中&#xff0c;我们每个人都会历经怀疑与信仰&#xff0c;等待与离别……究竟何种生活&#xff0c;才是我们所追求的&#xff1f; 记得在23年的十月&#xff0c;我与朋友驱车归家。我和朋友开车回家&am…

【linux开发工具】vim详解

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 “学如逆水行舟&#xff0…

架构整洁之道-软件架构-测试边界、整洁的嵌入式架构、实现细节

6 软件架构 6.14 测试边界 和程序代码一样&#xff0c;测试代码也是系统的一部分。甚至&#xff0c;测试代码有时在系统架构中的地位还要比其他部分更独特一些。 测试也是一种系统组件。 从架构的角度来讲&#xff0c;所有的测试都是一样的。不论它们是小型的TDD测试&#xff…

放假--寒假自学版 day1(补2.5)

fread 函数&#xff1a; 今日练习 C语言面试题5道~ 1. static 有什么用途&#xff1f;&#xff08;请至少说明两种&#xff09; 1) 限制变量的作用域 2) 设置变量的存储域 2. 引用与指针有什么区别&#xff1f; 1) 引用必须被初始化&#xff0c;指针不必。 2) 引用初始…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 2月9日,星期五

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年2月9日 星期五 农历腊月三十 除夕 1、 三部门&#xff1a;各地不得挤占、挪用、截留、滞留优抚对象补助经费。 2、 校外培训《条例》出炉&#xff1a;明确在职教师、教研人员不得从事校外培训活动。 3、 2024年“全面降…

uTools工具使用

之前发现一款非常有用的小工具&#xff0c;叫uTools&#xff0c;该软件集成了比如进制转换、json格式化、markdown、翻译、取色等等集插件大成&#xff0c;插件市场提供了很多开源插件工具。可以帮助开发人员节省了寻找各种处理工具的时间&#xff0c;非常推荐。 1、软件官方下…

Open CASCADE学习|点和曲线的相互转化

目录 1、把曲线离散成点 1.1按数量离散 1.2按长度离散 1.3按弦高离散 2、由点合成曲线 2.1B样条插值 2.2B样条近似 1、把曲线离散成点 计算机图形学中绘制曲线&#xff0c;无论是绘制参数曲线还是非参数曲线&#xff0c;都需要先将参数曲线进行离散化&#xff0c;通过离…

单片机学习笔记---DS1302实时时钟工作原理

目录 DS1302介绍 学会读芯片手册&#xff08;DS1302芯片手册&#xff09; 封装 引脚定义 电源部分 时钟部分 通信部分 总结列表 内部结构图 电源控制部分 时钟控制部分 寄存器部分 访问部分 寄存器部分的详细定义 命令字 时序的定义 单字节读 单字节写 提前预…