【leetcode】哈希表

本章节来自
参考:代码随想录

总结

两数之和 就不能使用双指针法,因为1.两数之和要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。

如果1.两数之和 (opens new window)要求返回的是数值的话,就可以使用双指针法了。


N Sum 问题(N>=2)
参考:一个函数秒杀 2Sum 3Sum 4Sum 问题

采用递归方式,base case是2Sum

/* 注意:调用这个函数之前一定要先给 nums 排序 */
vector<vector<int>> nSumTarget(
    vector<int>& nums, int n, int start, int target) {

    int sz = nums.size();
    vector<vector<int>> res;
    // 至少是 2Sum,且数组大小不应该小于 n
    if (n < 2 || sz < n) return res;
    // 2Sum 是 base case
    if (n == 2) {
        // 双指针那一套操作
        int lo = start, hi = sz - 1;
        while (lo < hi) {
            int sum = nums[lo] + nums[hi];
            int left = nums[lo], right = nums[hi];
            if (sum < target) {
                while (lo < hi && nums[lo] == left) lo++;
            } else if (sum > target) {
                while (lo < hi && nums[hi] == right) hi--;
            } else {
                res.push_back({left, right});
                while (lo < hi && nums[lo] == left) lo++;
                while (lo < hi && nums[hi] == right) hi--;
            }
        }
    } else {
        // n > 2 时,递归计算 (n-1)Sum 的结果
        for (int i = start; i < sz; i++) {
            vector<vector<int>> 
                sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
            for (vector<int>& arr : sub) {
                // (n-1)Sum 加上 nums[i] 就是 nSum
                arr.push_back(nums[i]);
                res.push_back(arr);
            }
            while (i < sz - 1 && nums[i] == nums[i + 1]) i++;
        }
    }
    return res;
}

简单

242. 有效的字母异位词

参考:242. 有效的字母异位词 - 力扣(LeetCode)

把数组当哈希表

class Solution {
    public boolean isAnagram(String s, String t) {
    	if (s.length() != t.length()) {
            return false;
        }
        int[] record=new int[26]; // 小写字母有26个
        // 先把字母相对'a'的位置映射到record数组中,先++再--
        // 如果最后数组有不为0的则返回false
        for(char c:s.toCharArray()){
            record[c-'a']++;
        }
        for(char c:t.toCharArray()){
            record[c-'a']--;
        }
        for(int item:record){
            if(item!=0){
                return false;
            }
        }
        return true;
    }
}

// 排序
class Solution {
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        char[] str1 = s.toCharArray();
        char[] str2 = t.toCharArray();
        Arrays.sort(str1);
        Arrays.sort(str2);
        return Arrays.equals(str1, str2);
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/valid-anagram/solution/you-xiao-de-zi-mu-yi-wei-ci-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

349. 两个数组的交集

参考:349. 两个数组的交集 - 力扣(LeetCode)

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> set1=new HashSet<>();
        Set<Integer> resultSet=new HashSet<>();
        //遍历num1
        for(int item:nums1){
            set1.add(item);
        }
        //遍历num2的过程中判断哈希表中是否存在该元素
        for(int item:nums2){
            if(set1.contains(item)){
                resultSet.add(item);
            }
        }
        //将resultSet转为数组
        int[] resultArr=new int[resultSet.size()];
        int i=0;
        for(int item:resultSet){
            resultArr[i++]=item;
        }

        return resultArr;
    }
}

1. 两数之和

参考:1. 两数之和 - 力扣(LeetCode)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] result=new int[2];
        Map<Integer,Integer> map=new HashMap<>();
        // 先构造map,并把nums映射到其中
        for(int i=0;i<nums.length;i++){
            map.put(nums[i],i);
        }

        for(int i=0;i<nums.length;i++){
            int temp=target-nums[i]; // 两数之差
            // 如果temp在map中且不是nums[i]本身
            if(map.containsKey(temp)&& map.get(temp)!=i){
                result[0]=i;
                result[1]=map.get(temp);
                return result;
            }
        }
        return result;
    }
}

202. 快乐数

参考:202. 快乐数 - 力扣(LeetCode)
参考:代码随想录
题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!
使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。

class Solution {
    public boolean isHappy(int n) {
        Set<Integer> set=new HashSet<>();
        while(true){
            int sum=getSum(n);
            if(sum==1){
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if(set.contains(sum)){
                return false;
            }else{
                set.add(sum);
                n=sum;  
            }      
            
        }
    }
    // 取数值各个位上的单数之和
    public int getSum(int n){
        int sum=0;
        while(n>0){
            sum+=(n % 10) * (n % 10);; // n%10取最高位
            n/=10; // 降低一位
        }
        return sum;
    }
}

383. 赎金信

参考:383. 赎金信 - 力扣(LeetCode)

参考:代码随想录# 383. 赎金信

依然是数组在哈希法中的应用

一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        //记录magazine字符串中每个字符的出现次数
        int[] a=new int[26]; // 最多有26个小写字母
        for(int i=0;i<magazine.length();i++){
            // 算出字符与'a'相对位置 'a'为0,'b'为1,以此类推
            int index=magazine.charAt(i)-'a'; 
            a[index]++;
        }
        for(int i=0;i<ransomNote.length();i++){
            int index=ransomNote.charAt(i)-'a';
            //对于ransomNote中的每一个字符都在数组中查找
            //找到相应位减一,否则找不到返回false
            if(a[index]>0){
                a[index]--;
            }else{
                return false;
            }

        }
        return true;
    }
}

// 官方
class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        int[] cnt = new int[26];
        for (char c : magazine.toCharArray()) {
            cnt[c - 'a']++;
        }
        for (char c : ransomNote.toCharArray()) {
            cnt[c - 'a']--;
            if(cnt[c - 'a'] < 0) {
                return false;
            }
        }
        return true;
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/ransom-note/solution/shu-jin-xin-by-leetcode-solution-ji8a/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

中等

454. 四数相加 II

参考:454. 四数相加 II - 力扣(LeetCode)

就相当于两数之和等于0,第一个数是前两个数组之和,第二个数是后两个数组之和

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer, Integer> map = new HashMap<>();
        int result=0;
        int temp=0;
         
        // 统计两个数组中的元素之和,同时统计出现的次数,放入map
        // key为元素之和,value为出现的次数
        for(int i:nums1){
            for(int j:nums2){
                temp=i+j;
                if(map.containsKey(temp)){
                    map.put(temp,map.get(temp)+1);
                }else{
                    map.put(temp,1);
                }
            }
        }

        //统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时加上出现的次数
        for(int i:nums3){
            for(int j:nums4){
                temp=0-i-j;
                if(map.containsKey(temp)){
                    result += map.get(temp);
                }
            }
        }
        return result;        
    }
}

15. 三数之和

参考:15. 三数之和 - 力扣(LeetCode)

哈希表法

参考:由浅入深-递进四解法<叫我小白就好>【三数之和】认真是一种态度 - 三数之和 - 力扣(LeetCode) 写得非常好

参考:三数和,哈希表 - 三数之和 - 力扣(LeetCode)

// map法
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result=new ArrayList<List<Integer>>();
        int n=nums.length;
        if(n<3) return result;
        Arrays.sort(nums); // 先排序 满足从左到右遍历时,得到的数字a<=b<=c
        Map<Integer,Integer> map=new HashMap<>(); 
        // 把数组映射到集合中
        for(int i=0;i<n;i++){
            map.put(nums[i],i);
        }
        // i:[0,n-1)  i<n也可以,就是后面几次是无效遍历,下同
        for(int i=0;i<n-2;i++){
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                break;
            }
            // 第一个数去重 比如i=1,nums[0]=num[1]时可跳过,即连续两个数及以上相同的可以跳过
            if(i>0 && nums[i]==nums[i-1]) {
                continue;
            }          
            // j:[0,n)     
            for(int j=i+1;j<n-1;j++){
                // 第二个数去重
                if(j>i+1&&nums[j]==nums[j-1]){
                    continue;
                }
                int c=0-nums[i]-nums[j]; // 第三个数
                // map.get(c); // 有则返回下标,没有则返回null
                // a<=b<=c且map中有第三个数且第三个数的下标应该大于j
                 if (map.get(c) != null && map.get(c) > j) { 
                    ArrayList list=new ArrayList<Integer>();
                    list.add(nums[i]);
                    list.add(nums[j]);
                    list.add(c);
                    result.add(list);
                }
            }
        }
        return result;
    }
}


// set法  [1,2,-2,-1]无法通过
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result=new ArrayList<List<Integer>>();
        int n=nums.length;
        if(n<3) return result;
        Arrays.sort(nums); // 先排序 满足从左到右遍历时,得到的数字a<=b<=c
        HashSet<Integer> set=new HashSet<Integer>(); 
        // 把数组映射到集合中
        for(int item:nums){
            set.add(item);
        }
        int target=0;// 目标值
        for(int i=0;i<n-2;i++){
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                break;
            }
            // 第一个数去重 比如i=1,nums[0]=num[1]时可跳过,即连续两个数及以上相同的可以跳过
            if(i>0 && nums[i]==nums[i-1]) {
                continue;
            }               
            target=0-nums[i];
            for(int j=i+1;j<n-1;j++){
                // 第二个数去重
                if(j>i+1&&nums[j]==nums[j-1]){
                    continue;
                }
                int c=target-nums[j]; // 第三个数
                // a<=b<=c且set中有第三个数
                if(set.contains(c)){  
                	// 如果nums有重复的数,j=那个数时这里就取大于等于号,否则为大于号,比较麻烦,所以直接用map。   
                    if(c>=nums[j]) {      
                    	ArrayList list=new ArrayList<Integer>();
                    	list.add(nums[i]);
                    	list.add(nums[j]);
                    	list.add(c);
                    	result.add(list);
                    }   
                }
            }
        }
        return result;
    }
}

双指针法

来自官方题解:排序 + 双指针(其实只用了一个右指针,左指针用了for循环代替)

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result=new ArrayList<List<Integer>>();
        int n=nums.length;
        if(n<3) return result;
        Arrays.sort(nums); // 先排序 满足从左到右遍历时,得到的数字a<=b<=c
        
        for(int first=0;first<n;first++){
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[first] > 0) {
                break;
            }
            // 第一个数去重 比如i=1,nums[0]=num[1]时可跳过,即连续两个数及以上相同的可以跳过
            if(first>0 && nums[first]==nums[first-1]) {
                continue;
            }
            int third=n-1;      
            int target=-nums[first];// 目标值         
            for(int second=first+1;second<n;second++){
                // 第二个数去重
                if(second>first+1 && nums[second]==nums[second-1]) {
                    continue;
                }
                // 右指针向左移动
                while(second<third && nums[second]+nums[third] > target){
                    -- third;
                }
                if(second==third){
                    break;
                }
                if(nums[second]+nums[third] == target){
                	// 直接从数组中创建List
                    result.add(Arrays.asList(nums[first],nums[second],nums[third]));
                }
            }
        }
        return result;
    }
}

while 双指针 效果更好

import java.util.*;

class Solution {
    public static void main(String[] args) {
        int[] nums = { -1, 0, 1, 2, -1, -4 };
        System.out.println(threeSum(nums));
    }

    public static List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        int n = nums.length;
        if (n < 3)
            return result;
        Arrays.sort(nums); // 先排序 满足从左到右遍历时,得到的数字a<=b<=c

        for (int i = 0; i < n - 2; i++) {
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                break;
            }
            // 第一个数去重 比如i=1,nums[0]=num[1]时可跳过,即连续两个数及以上相同的可以跳过
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = n - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    right--;
                } else if (sum < 0) {
                    left++;
                } else {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    // 去重 当前的数与下一个数相等时跳一步,直到指向相同数中的最后一个数
                    while (left < right && nums[left] == nums[left + 1])
                        left++;
                    while (left < right && nums[right] == nums[right - 1])
                        right--;
                    // 1.while中有相同的,此时left指向最后一个相同的数,再left++,指向下一个不同的数
                    // 2.while中没有,left++,也指向下一个不同的数
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
}

18. 四数之和

参考:18. 四数之和 - 力扣(LeetCode)

参考:此题解可通用于5数之和、6数之和…:注释详细,思路清晰 - 四数之和 - 力扣(LeetCode)

双指针法

for+右指针

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        int n = nums.length;
        if (n < 4) return result;
        Arrays.sort(nums); // 先排序 满足从左到右遍历时,得到的数字a<=b<=c<=d

        for (int first = 0; first < n-3; first++) {
            // 第一个数去重 比如i=1,nums[0]=num[1]时可跳过,即连续两个数及以上相同的可以跳过
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            for (int second = first + 1; second < n-2; second++) {
                // 第二个数去重
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                for (int third = second + 1; third < n-1; third++) {
                    // 第三个数去重
                    if (third > second + 1 && nums[third] == nums[third - 1]) {
                        continue;
                    }
                    int four = n - 1; // 右指针指向最后一个位置
                    // 右指针向左移动
                    while (third < four && nums[first] + nums[second] + nums[third]+nums[four] > target) {
                        --four;
                    }
                    if (four == third) {
                        break;
                    }
                    if (nums[first] + nums[second] + nums[third] + nums[four] == target) {
                        result.add(Arrays.asList(nums[first], nums[second], nums[third], nums[four]));
                    }
                }
            }
        }
        return result;
    }
}

while 双指针

import java.util.*;

class Solution {
    public static void main(String[] args) {
        int[] nums = { 1, -2, -5, -4, -3, 3, 3, 5 };
        System.out.println(fourSum(nums, -11));
    }

    public static List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        int n = nums.length;
        if (n < 4)
            return result;
        Arrays.sort(nums); // 先排序 满足从左到右遍历时,得到的数字a<=b<=c<=d

        for (int i = 0; i < n - 3; i++) {
            // 第一个数去重 比如i=1,nums[0]=num[1]时可跳过,即连续两个数及以上相同的可以跳过
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < n - 2; j++) {
                // 第二个数去重
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = n - 1;
                while (left < right) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        // 去重 当前的数与下一个数相等时跳一步,直到指向相同数中的最后一个数
                        while (left < right && nums[left] == nums[left + 1])
                            left++;
                        while (left < right && nums[right] == nums[right - 1])
                            right--;
                        // 1.while中有相同的,此时left指向最后一个相同的数,再left++,指向下一个不同的数
                        // 2.while中没有,left++,也指向下一个不同的数
                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }

}

困难

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值