分别是1. 两数之和,49. 字母异位词分组,128. 最长连续序列这三道题。
两数之和这道题,说起来就像是四级词汇书里的第一个词——abandon。每次刷 Hot100,都是从它开始,翻来覆去,熟得不能再熟。
最直接的解法当然是暴力两层循环,时间复杂度 O(n²),这里就不展开了,毕竟大家都会。
真正值得说的是核心思路:遍历数组时,对当前元素num,我们其实只需要知道一件事——target - num是否已经出现过。这时候就该 HashSet 登场了,它的contains方法能帮我们以 O(1) 的时间快速判断一个数是否存在,整体复杂度直接降到 O(n)。
class Solution { public int[] twoSum(int[] nums, int target) { int n=nums.length; Set a=new HashSet(); for(int i=0;i<n;i++){ a.add(nums[i]); } for(int i=0;i<n;i++){ if(a.contains(target-nums[i])){ for(int j=0;j<n;j++){ if(nums[j]==target-nums[i]&&i!=j){ return new int[]{i,j}; } } } } return new int[0]; } }字母异位词分组这题,刷了好几遍,这次再刷发现还是忘了一些细节,正好记下来。
第一个误区:一开始想着用二重链表来存,但仔细一想,链表本身没有 key 的概念,我们恰恰需要一个"key"来把变位词归到一组——排序后相同的字符串就是天然的 key。这时候就该想到 HashMap,所以选对数据结构真的很重要。
第二个误区:String 本身不能直接排序,得先转成 char 数组才能排。
第三个误区:char 数组转回 String 要用new String(...)。
第四个就是 HashMap 的基本用法别搞混:get是按键取值,put是新建或更新键值对。
总结一下:排序后的字符串做 key,HashMap 做分组容器,char 数组负责排序中转,三步走完这题就清晰了。
class Solution { public List<List<String>> groupAnagrams(String[] strs) { int n=strs.length; Map<String,ArrayList<String>> m=new HashMap<>(); for(String ss:strs){ char [] ch=ss.toCharArray(); Arrays.sort(ch); String newss=new String(ch); if(!m.containsKey(newss)){ m.put(newss,new ArrayList<>()); } m.get(newss).add(ss); } return new ArrayList<List<String>>(m.values()); } }128. 最长连续序列
第一个误区:一开始想着用 HashMap,其实 Set 就够了。我们只需要判断某个数存不存在,不需要键值对映射,Set 完全能胜任。
第二个是核心:不是对每个数都往两边扩,而是先判断num - 1是否存在——如果不存在,说明num就是某段连续序列的起点,从它开始往后数就行;如果存在,说明它不是起点,直接跳过。这样只从起点往后数,每个数最多被访问一次,整体 O(n),避免重复计算。
第三个细节:数组转 Set 其实就是遍历每个数,一个个add进去,以及在遍历中,不要去遍历数组,不然你会发现超时了,需要去遍历set。
总结:Set 存数做 O(1) 查询,只从连续序列的起点往后数,数组遍历入 Set——三步理清,这题就不绕了。
class Solution { public int longestConsecutive(int[] nums) { int n=nums.length; Set<Integer> a=new HashSet<Integer>(); for(int num:nums){ a.add(num); } int maxlen=0; for(int num:a){ if(!a.contains(num-1)){ int curlen=1; int b=num; while(a.contains(b+1)){ b++; curlen++; } maxlen=Math.max(maxlen,curlen); } } return maxlen; } }