哈希表(散列表)
一、哈希表
1.总结一下,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
2.当数据量比较少时,可以考虑使用数组。直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。
二、有效的字母异位词
1、有效的字母异位词(力扣242)
//1.t是s的异位词等价于「两个字符串排序后相等」
public static boolean isAnagram(String s, String t) {
if (s.length() != t.length()){
return false;
}
char[] c1 = s.toCharArray();
char[] c2 = t.toCharArray();
Arrays.sort(c1);
Arrays.sort(c2);
return Arrays.equals(c1,c2);
}
//2.输入字符串不包含 unicode 字符
public static boolean isAnagram(String s, String t) {
if (s.length() != t.length()){
return false;
}
int[] arr = new int[26];
for (int i = 0; i < s.length(); i++) {
arr[s.charAt(i) - 'a'] ++;
}
for (int i = 0; i < t.length(); i++) {
arr[t.charAt(i) - 'a'] --;
if (arr[t.charAt(i) - 'a'] < 0){
return false;
}
}
return true;
}
如果输入字符串包含 unicode 字符,那就只能使用哈希表了, JAVA的char类型是支持unicode的
//3.输入字符串包含 unicode 字符
public static boolean isAnagram(String s, String t) {
if (s.length() != t.length()){
return false;
}
Map<Character,Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
map.put(s.charAt(i),map.getOrDefault(s.charAt(i),0) + 1);
}
for (int i = 0; i < t.length(); i++) {
map.put(t.charAt(i),map.getOrDefault(t.charAt(i),0) - 1);
if (map.get(t.charAt(i)) < 0){
return false;
}
}
return true;
}
2、赎金信(力扣383)
//1.和242很像,没啥难度
public static boolean canConstruct(String ransomNote, String magazine) {
if (ransomNote.length() > magazine.length()){
return false;
}
int[] arr = new int[26];
for (int i = 0; i < magazine.length(); i++) {
arr[magazine.charAt(i) - 'a'] ++;
}
for (int i = 0; i < ransomNote.length(); i++) {
arr[ransomNote.charAt(i) - 'a'] --;
if (arr[ransomNote.charAt(i) - 'a'] < 0){
return false;
}
}
return true;
}
//2.哈希map
public static boolean canConstruct(String ransomNote, String magazine) {
if (ransomNote.length() > magazine.length()) {
return false;
}
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < magazine.length(); i++) {
map.put(magazine.charAt(i), map.getOrDefault(magazine.charAt(i), 0) + 1);
}
for (int i = 0; i < ransomNote.length(); i++) {
map.put(ransomNote.charAt(i),map.getOrDefault(ransomNote.charAt(i),0) - 1);
if (map.get(ransomNote.charAt(i)) < 0){
return false;
}
}
return true;
}
3、字母异位词分组(力扣49)
//1.哈希map
public List<List<String>> groupAnagrams(String[] strs) {
//考虑清楚用什么来表示键和值
Map<String,ArrayList<String>> map = new HashMap<>();
for (String s : strs){
char[] c = s.toCharArray();
Arrays.sort(c);
String key = String.valueOf(c);
if (!map.containsKey(key)){
map.put(key,new ArrayList<>());
}
map.get(key).add(s);
}
//map.values() 返回的是collection,直接是集合
return new ArrayList<>(map.values());
}
4、找到字符串中所有字母异位词(力扣438)
//1.自己想的
public static List<Integer> findAnagrams(String s, String p) {
if (s.length() < p.length()){
return new ArrayList<>();
}
List<Integer> list = new ArrayList<>();
char[] c1 = p.toCharArray();
Arrays.sort(c1);
for (int i = 0; i <= s.length() - p.length(); i++) {
char[] c2 = s.substring(i,i + p.length()).toCharArray();
Arrays.sort(c2);
if (Arrays.equals(c1,c2)){
list.add(i);
}
}
return list;
}
//2.滑动窗口
public static List<Integer> findAnagrams(String s, String p) {
int sLen = s.length(), pLen = p.length();
if (sLen < pLen) {
return new ArrayList<>();
}
//分别统计滑动窗口和p中的字符的数量
int[] sCount = new int[26];
int[] pCount = new int[26];
List<Integer> list = new ArrayList<>();
for (int i = 0; i < pLen; i++) {
sCount[s.charAt(i) - 'a']++;
pCount[p.charAt(i) - 'a']++;
}
if (Arrays.equals(sCount, pCount)) {
list.add(0);
}
//滑动窗口向右移动,依次比较
for (int i = 0; i < sLen - pLen; i++) {
sCount[s.charAt(i) - 'a'] --;
sCount[s.charAt(i + pLen) - 'a'] ++;
if (Arrays.equals(sCount,pCount)){
list.add(i + 1);
}
}
return list;
}
三、两个数组的交集
1、两个数组的交集(力扣349)
//1.利用list和set
public static int[] intersection(int[] nums1, int[] nums2) {
List<Integer> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();
for (Integer n1 : nums1) {
set.add(n1);
}
for (Integer n2 : nums2) {
if (set.contains(n2)) {
list.add(n2);
set.remove(n2);
}
}
int[] ans = new int[list.size()];
//直接利用list的get方法
for (int i = 0; i < list.size(); i++) {
ans[i] = list.get(i);
}
return ans;
}
//2.利用两个set
public static int[] intersection1(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<>();
Set<Integer> set2 = new HashSet<>();
for (int n1 : nums1) {
set1.add(n1);
}
for (int n2 : nums2) {
if (set1.contains(n2)) {
set2.add(n2);
}
}
int[] ans = new int[set2.size()];
int index = 0;
for (int i : set2) {
ans[index++] = i;
}
return ans;
}
//3.暴力解法(最先想到的解法)
public static int[] intersection2(int[] nums1, int[] nums2) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
if (nums1[i] == nums2[j]) {
set.add(nums1[i]);
}
}
}
int[] ans = new int[set.size()];
int index = 0;
for (int i : set) {
ans[index++] = i;
}
return ans;
}
//4.双指针
public static int[] intersection3(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
Set<Integer> set = new HashSet<>();
int i = 0;
int j = 0;
while (i < nums1.length && j < nums2.length) {
if (nums1[i] == nums2[j]) {
set.add(nums1[i]);
i++;
j++;
} else if (nums1[i] < nums2[j]) {
i++;
} else if (nums1[i] > nums2[j]) {
j++;
}
}
int[] ans = new int[set.size()];
int index = 0;
for (int value : set) {
ans[index++] = value;
}
return ans;
}
//5.二分法
public static int[] intersection4(int[] nums1, int[] nums2) {
Set<Integer> set = new HashSet<>();
Arrays.sort(nums2);
for (int target : nums1) {
if (binarySearch(nums2, target)) {
set.add(target);
}
}
int index = 0;
int[] ans = new int[set.size()];
for (int value : set) {
ans[index++] = value;
}
return ans;
}
public static boolean binarySearch(int[] num, int target) {
int left = 0;
int right = num.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (num[mid] == target) {
return true;
} else if (num[mid] < target) {
left = mid + 1;
} else if (num[mid] > target) {
right = mid - 1;
}
}
return false;
}
2、两个数组的交集 II(力扣350)
//2.排序+双指针 前提:两个数组是排好序的
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int i = 0,j = 0;
int len1 = nums1.length,len2 = nums2.length;
List<Integer> list = new ArrayList<>();
while(i < len1 && j < len2){
if(nums1[i] == nums2[j]){
list.add(nums1[i]);
i ++;j ++;
}else if(nums1[i] < nums2[j]){
i ++;
}else{
j ++;
}
}
int size = list.size();
int[] ans = new int[size];
for(int index = 0;index < size; index++){
ans[index] = list.get(index);
}
return ans;
}
三、其他的哈希表题
1、快乐数(力扣202)
//1.普通方法
//两种情况:1 最终是1 2. 陷入循环
public static boolean isHappy(int n) {
Set<Integer> set = new HashSet<>();
//如果n不是1,而且没有陷入循环,就不停迭代
while (n != 1 && !set.contains(n)){
set.add(n);
n = getSum(n);
}
return n == 1;
}
//获取一个数的各个位上的数字的平方和
public static int getSum(int n){
int sum = 0;
while (n > 0){
int num = n % 10;
sum += num * num;
n /= 10;
}
return sum;
}
//2.快慢指针
//两种情况:如果成环,且不是1,返回false
public static boolean isHappy(int n) {
int slow = n;
int fast = getSum(n);
while (fast != 1 && fast != slow){
//慢指针每次走一步
slow = getSum(fast);
//快指针每次走两步
fast = getSum(getSum(slow));
}
return fast == 1;
}
2、两数之和(力扣1)
//1.暴力解法
public static int[] twoSum(int[] nums, int target) {
int[] res = new int[2]
for(int i = 0;i < nums.length - 1; i ++){
for(int j = i + 1;j < nums.length;j ++){
if(nums[i] + nums[j] == target){
res[0] = i;
res[1] = j;
break;
}
}
}
return res;
}
//2.哈希map
public static int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i < nums.length;i ++){
if (map.containsKey(target - nums[i])){
res[0] = map.get(target - nums[i]);
res[1] = i;
break;
}
map.put(nums[i],i);
}
return res;
}
3、四数相加 II(力扣454)
//1.暴力
//四个数组两两组合,等效成两个数组之和
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int len = nums1.length;
Map<Integer,Integer> map = new HashMap<>();
for(int i = 0;i < len;i ++){
for(int j = 0;j < len; j ++){
map.put(nums1[i] + nums2[j],map.getOrDefault(nums1[i] + nums2[j],0) + 1);
}
}
int count = 0;
for(int i = 0;i < len;i ++){
for(int j = 0;j < len; j ++){
if(map.containsKey(-(nums3[i] + nums4[j]))){
count += map.get(-(nums3[i] + nums4[j]));
}
}
}
return count;
}
时间复杂度O(n2),空间复杂度O(n2)
4、三数之和(力扣15)
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(nums);
int len = nums.length;
for (int i = 0; i < len - 2; i++) {
if (nums[i] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1, right = len - 1;
int target = -nums[i];
while (left < right) {
if (nums[left] + nums[right] == target) {
list.add(Arrays.asList(nums[i],nums[left],nums[right]));
left++;
right--;
while (left < right) {
if (nums[left] == nums[left - 1]) {
left++;
}
}
while (left < right) {
if (nums[right] == nums[right + 1]) {
right--;
}
}
} else if (nums[left] + nums[right] < target) {
left++;
} else {
right--;
}
}
}
return list;
}
5、四数之和(力扣18)
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list = new ArrayList<>();
if(nums== null || nums.length < 4){
return list;
}
Arrays.sort(nums);
int len = nums.length;
for(int i = 0;i < len - 3;i ++){
if(i > 0 && nums[i] == nums[i - 1]){
continue;
}
for(int j = i + 1;j < len - 2;j ++){
if(j > i + 1 && nums[j] == nums[j - 1]){
continue;
}
int left = j + 1,right = len - 1;
int ans = target - nums[i] - nums[j];
while(left < right){
if(nums[left] + nums[right] == ans){
list.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
left ++;
right --;
while(left < right && nums[left] == nums[left - 1]){
left ++;
}
while(left < right && nums[right] == nums[right + 1]){
right --;
}
}else if(nums[left] + nums[right] < ans){
left ++;
}else{
right --;
}
}
}
}
return list;
}