##哈希表刷题总结
基本介绍 哈希表(Hash Table,也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做哈希函数,存放记录的数组称做哈希表。
一个通俗的例子是,为了查找电话簿中某人的号码,可以创建一个按照人名首字母顺序排列的表(即建立人名 xx 到首字母 F(x)F(x) 的一个函数关系),在首字母为 WW 的表中查找 “王” 姓的电话号码,显然比直接查找就要快得多。这里使用人名作为关键字,“取首字母” 是这个例子中哈希函数的函数法则 F()F(),存放首字母的表对应哈希表。关键字和函数法则理论上可以任意确定。
哈希表是使用 O(1)O(1) 时间进行数据的插入删除和查找,但是哈希表不保证表中数据的有序性,这样在哈希表中查找最大数据或者最小数据的时间是 O(N)O(N) 实现。哈希表不支持需要元素间任何排序信息的操作,例如findMin、findMax或者打印输出排序号的整张表。
设计哈希映射 哈希表是一个在不同语言中都有的通用数据结构。例如,Python 中的 dict 和 Java 中的 Hashmap。哈希表的特性是可以根据给出的 key 快速访问 value。
设计哈希表有两个问题需要去解决: 1). 如何设计哈希方法? 和 2).如何避免哈希碰撞? 。
1). 如何设计哈希方法?:哈希方法会将键值映射到某块存储空间,一个好的哈希方法应该将不同的键值 均匀 地分布在存储空间中。
2). 如何避免哈希碰撞?:哈希方法要将大量的键值映射到一个有限的空间里,这样就有可能会将不同的键值映射到同一个存储空间,这种情况称为 ‘哈希碰撞’ 。哈希碰撞是不可避免的,但可以用策略来解决哈希碰撞。
1.有多少小于当前数字的数字 ####一、暴力求解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int [] smallerNumbersThanCurrent(int [] nums) { int length = nums.length; int newtime[] = new int [length]; for (int i=0 ;i<length;i++){ int time = 0 ; for (int h=0 ;h<length;h++){ if (nums[i]>nums[h]){ time++; } } newtime[i] = time; } return newtime; } }
二、计数排序 三、快速排序 四、排序与映射结合 2.第一个只出现一次的字符 3.1哈希表的遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public char firstUniqChar (String s) { HashMap<Character,Boolean> dic = new HashMap<>(); char [] string = s.toCharArray(); for (char c:string){ dic.put(c,!dic.containsKey(c)); } for (char c:string){ if (dic.get(c)) return c; } return ' ' ; } }
3.2有序hash表的遍历 哈希表是 去重 的,即哈希表中键值对数量 ≤ 字符串 s 的长度。因此,相比于方法一,方法二减少了第二轮遍历的循环次数。当字符串很长(重复字符很多)时,方法二则效率更高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public char firstUniqChar (String s) { Map<Character,Boolean> dic = new LinkedHashMap<>(); char [] string = s.toCharArray(); for (char c:string){ dic.put(c,!dic.containsKey(c)); } for (Map.Entry<Character,Boolean> d : dic.entrySet()){ if (d.getValue()) return d.getKey(); } return ' ' ; } }
3.3补充知识:Java中的Character类 Character 类用于对单个字符进行操作。
Character 类在对象中包装一个基本类型 char 的值(类似于Integer与int的关系)
1 2 3 4 5 6 7 char ch = 'a' ; char uniChar = '\u039A' ; char [] charArray ={ 'a' , 'b' , 'c' , 'd' , 'e' };
然而,在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型的情况。为了解决这个问题,Java语言为内置数据类型char提供了包装类Character类 。
Character类提供了一系列方法来操纵字符。你可以使用Character的构造方法创建一个Character类对象,例如:
在某些情况下,Java编译器会自动创建一个Character对象。
例如,将一个char类型的参数传递给需要一个Character类型参数的方法时,那么编译器会自动地将char类型参数转换为Character对象。 这种特征称为装箱,反过来称为拆箱。
1 2 3 4 5 6 Character ch = 'a' ; char c = test('x' );
###3.只出现一次的字符
####3.1哈希表两次遍历
使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int singleNumber (int [] nums) { HashMap<Integer,Boolean> dic = new HashMap<>(); for (int i:nums){ dic.put(i,!dic.containsKey(i)); } for (int i:nums){ if (dic.get(i)) return i; } return -1 ; } }
3.2位运算 1 2 3 4 5 6 7 8 9 10 class Solution { public int singleNumber (int [] nums) { int single = 0 ; for (int num : nums) { single ^= num; } return single; } }
3.3集合运算 使用集合存储数组中出现的所有数字,并计算数组中的元素之和。由于集合保证元素无重复,因此计算集合中的所有元素之和的两倍,即为每个元素出现两次的情况下的元素之和。由于数组中只有一个元素出现一次,其余元素都出现两次,因此用集合中的元素之和的两倍减去数组中的元素之和,剩下的数就是数组中只出现一次的数字。
3.4集合放入与删除 使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。
4.岛屿的周长 ####方法一:迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int islandPerimeter (int [][] grid) { int land=0 ; int border=0 ; int j = grid.length; int k = grid[0 ].length; for (int i=0 ;i<j;i++){ for (int h=0 ;h<k;h++){ if (grid[i][h]==1 ){ land++; if (i<grid.length-1 &&grid[i+1 ][h]==1 ){ border++; } if (h<grid[0 ].length-1 &&grid[i][h+1 ]==1 ){ border++; } } } } return 4 *land-2 *border; } }
方法二:DFS深度优先搜索 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public int dfsfunction (int i,int j,int [][]grid) { if (i<0 ||i>=grid.length||j<0 ||j>=grid[0 ].length){ return 1 ; } if (grid[i][j]==0 ){ return 1 ; } if (grid[i][j]==2 ){ return 0 ; } grid[i][j]=2 ; return dfsfunction(i-1 ,j,grid)+dfsfunction(i,j-1 ,grid)+dfsfunction(i+1 ,j,grid)+dfsfunction(i,j+1 ,grid); } public int islandPerimeter (int [][] grid) { int row = grid.length; int col = grid[0 ].length; for (int i=0 ;i<row;i++){ for (int j=0 ;j<col;j++){ if (grid[i][j]==1 ){ return dfsfunction(i,j,grid); } } } return 0 ; } }
官方题解1:迭代—-利用算子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { static int [] dx = {0 , 1 , 0 , -1 }; static int [] dy = {1 , 0 , -1 , 0 }; public int islandPerimeter (int [][] grid) { int n = grid.length, m = grid[0 ].length; int ans = 0 ; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < m; ++j) { if (grid[i][j] == 1 ) { int cnt = 0 ; for (int k = 0 ; k < 4 ; ++k) { int tx = i + dx[k]; int ty = j + dy[k]; if (tx < 0 || tx >= n || ty < 0 || ty >= m || grid[tx][ty] == 0 ) { cnt += 1 ; } } ans += cnt; } } } return ans; } }
####官方题解2:DFS—-利用算子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { constexpr static int dx[4 ] = {0 , 1 , 0 , -1 }; constexpr static int dy[4 ] = {1 , 0 , -1 , 0 }; public : int dfs (int x, int y, vector<vector<int >> &grid, int n, int m) { if (x < 0 || x >= n || y < 0 || y >= m || grid[x][y] == 0 ) { return 1 ; } if (grid[x][y] == 2 ) { return 0 ; } grid[x][y] = 2 ; int res = 0 ; for (int i = 0 ; i < 4 ; ++i) { int tx = x + dx[i]; int ty = y + dy[i]; res += dfs(tx, ty, grid, n, m); } return res; } int islandPerimeter (vector<vector<int >> &grid) { int n = grid.size(), m = grid[0 ].size(); int ans = 0 ; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < m; ++j) { if (grid[i][j] == 1 ) { ans += dfs(i, j, grid, n, m); } } } return ans; } };
这个题放在散列表题目下,个人觉得是因为可以使用散列表去记录方格的访问状态
key用来放方格点,value用true或者false来代表方格是否被访问过。
5.岛屿的数量 ###6.岛屿的最大面积
7.查找常用字符 总体来说,还是hash表的思路,重点在于用一个数组创建索引,用另外一个数组更新值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class Solution { public List<String> commonChars (String[] A) { int [] minfreq = new int [26 ]; Arrays.fill(minfreq,Integer.MAX_VALUE); for (String word:A){ int [] freq = new int [26 ]; int length = word.length(); for (int i=0 ;i<length;i++){ char ch =word.charAt(i); ++freq[ch-'a' ]; } for (int i=0 ;i<26 ;i++){ minfreq[i] = Math.min(minfreq[i],freq[i]); } } List<String> ans = new ArrayList<String>(); for (int i=0 ;i<26 ;i++){ for (int j=0 ;j<minfreq[i];j++){ ans.add(String.valueOf((char )(i+'a' ))); } } return ans; } }
8.唯一元素的和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int sumOfUnique (int [] nums) { HashMap<Integer,Boolean> dic = new HashMap<Integer,Boolean>(); int length = nums.length; for (int i:nums){ dic.put(i,!dic.containsKey(i)); } int sum=0 ; for (int i:nums){ if (dic.get(i)){ sum+=i; } } return sum; } }