算法-哈希表、集合、映射

1、两数之和(简单)

题目地址:https://leetcode.cn/problems/two-sum/description/

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?

解答

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

2、模拟行走机器人(中等)

题目地址:https://leetcode.cn/problems/walking-robot-simulation/

机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands

  • -2 :向左转 90
  • -1 :向右转 90
  • 1 <= x <= 9 :向前移动 x 个单位长度

在网格上有一些格子被视为障碍物 obstacles 。第 i 个障碍物位于网格点 obstacles[i] = (xi, yi)

机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,但仍然可以继续尝试进行该路线的其余部分。

返回从原点到机器人所有经过的路径点(坐标为整数)的最大欧式距离的平方。(即,如果距离为 5 ,则返回 25

注意:

  • 北表示 +Y 方向。
  • 东表示 +X 方向。
  • 南表示 -Y 方向。
  • 西表示 -X 方向。

示例 1:

输入:commands = [4,-1,3], obstacles = []
输出:25
解释:
机器人开始位于 (0, 0):
1. 向北移动 4 个单位,到达 (0, 4)
2. 右转
3. 向东移动 3 个单位,到达 (3, 4)
距离原点最远的是 (3, 4) ,距离为 32 + 42 = 25

示例 2:

输入:commands = [4,-1,4,-2,4], obstacles = [[2,4]]
输出:65
解释:机器人开始位于 (0, 0):
1. 向北移动 4 个单位,到达 (0, 4)
2. 右转
3. 向东移动 1 个单位,然后被位于 (2, 4) 的障碍物阻挡,机器人停在 (1, 4)
4. 左转
5. 向北走 4 个单位,到达 (1, 8)
距离原点最远的是 (1, 8) ,距离为 12 + 82 = 65

提示:

  • 1 <= commands.length <= 104
  • commands[i] is one of the values in the list [-2,-1,1,2,3,4,5,6,7,8,9].
  • 0 <= obstacles.length <= 104
  • -3 * 104 <= xi, yi <= 3 * 104
  • 答案保证小于 231

解答

class Solution {
    public int robotSim(int[] commands, int[][] obstacles) {
        Map<String, String> map = new HashMap();
        String connector = "-";
        int ans = 0;
        for(int[] obstacle : obstacles){
            String key = obstacle[0] + connector + obstacle[1];
            if(!map.containsKey(key)){
                map.put(key, key);
            }
        }
        int[][] arr = {{0,1},{1,0},{0,-1},{-1,0}};
        int x = 0, y = 0;
        int tempX = 0, tempY = 0;
        int index = 0;
        for(int command : commands){
            if(command == -1){
                index = (index + 1) % 4;
                continue;
            }
            if(command == -2){
                index = (index + 3) % 4;
                continue;
            }
            for(int i = 0; i < command; i++){
                tempX = x + arr[index][0];
                tempY = y + arr[index][1];
                String key = tempX + connector + tempY;
                if(map.containsKey(key)){
                    break;
                }
                x = tempX;
                y = tempY;
                ans = Math.max(ans, x*x + y*y);
            }
        }
        return ans;
    }
}

3、字母异位词分组(中等)

题目地址:https://leetcode.cn/problems/group-anagrams/

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。

示例 1:

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

示例 2:

输入: strs = [""]
输出: [[""]]

示例 3:

输入: strs = ["a"]
输出: [["a"]]

提示:

  • 1 <= strs.length <= 104
  • 0 <= strs[i].length <= 100
  • strs[i] 仅包含小写字母

解答

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> map = new HashMap();
        for(String str :strs){
            String key = geneKey(str);
            if(map.containsKey(key)){
                map.get(key).add(str);
            }else{
                List<String> list = new ArrayList();
                list.add(str);
                map.put(key, list);
            }
        }
        List<List<String>> ans = new ArrayList();
        for(List<String> list : map.values()){
            ans.add(list);
        } 
        return ans;
    }

    private String geneKey(String str){
        char[] ch = str.toCharArray();
        Arrays.sort(ch);
        String key = "";
        for(char c : ch){
            key += c;
        }
        return key;
    }
}

4、串联所有单词(困难)

题目地址:https://leetcode.cn/problems/substring-with-concatenation-of-all-words/

给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同

s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef""abefcd""cdabef""cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联字串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

提示:

  • 1 <= s.length <= 104
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i]s 由小写英文字母组成

解答

​ 先化繁为简,首先 words 中单词的长度是相等的,那么 words 中字符的长度是一定的,假设words中字符的长度为wordsCountLength,那么只需要从s的第一个字符开始向后循环,每次取出长度为wordsCountLength的字符串,去判断该字符串是否与words匹配即可,如果匹配,把下标放入集合,最终返回即可。

​ 那么就剩余怎么判断一个字符串和words匹配了,由于words中单词长度一样,那么就可以将字符串按照单词的长度也分成一个个单词,为了判断方便,就可以用哈希表,将单词作为key,单词数量作为value,只需要判断两个map里面的key是一样的,且key中的value相等即可,如果判断成功,则为匹配,不成功,则为不匹配。

​ 易错点:这里有一个易错点,使用集合时,使用的都是包装类型,例如 Integer,判断值相等,要使用原子类型int,否则有可能判断错误,为什么说有可能呢,因为在java中,-128到127是放在缓存中的,因此就算是使用Integer包装,也是同一个Integer,值也相等,但是超过这个范围,使用Integer包装的值就不等了。

class Solution {
    public List<Integer> findSubstring(String s, String[] words) {
        List<Integer> list = new ArrayList();
        int wordsCountLength = words.length * words[0].length();
        Map<String, Integer> wordsMap = new HashMap();
        for(String word : words){
            int count = wordsMap.containsKey(word) ? wordsMap.get(word) + 1 : 1;
            wordsMap.put(word, count);
        }
        for(int i=0; i+wordsCountLength <= s.length(); i++){
            String str = s.substring(i, i+wordsCountLength);
            
            if(containsWords(str, wordsMap, words[0].length())){
                
                list.add(i);
            }
        }
        return list;
    }

    private boolean containsWords(String str, Map<String, Integer> wordsMap, int wordLength){
        Map<String, Integer> map = new HashMap();
        for(int i=0; i< str.length(); i=i+wordLength){
            String key = str.substring(i, i+wordLength);
            int count = map.containsKey(key) ? map.get(key) + 1 : 1;
            map.put(key, count);
        }
        return containsWords(wordsMap, map);
    }

    private boolean containsWords(Map<String, Integer> wordsMap, Map<String, Integer> strWordsMap){
        if(wordsMap.size() != strWordsMap.size()){
            return false;
        }
        for(String key : wordsMap.keySet()){
            if(!strWordsMap.containsKey(key) || strWordsMap.get(key).intValue() != wordsMap.get(key).intValue()){
                return false;
            }
        }
        return true;
    }

}

5、LRU 缓存(中等)

题目地址:https://leetcode.cn/problems/lru-cache/description/

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。

实现 LRUCache 类:

  • LRUCache(int capacity)正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 getput 必须以 O(1) 的平均时间复杂度运行。

示例:

输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1);    // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);    // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);    // 返回 -1 (未找到)
lRUCache.get(3);    // 返回 3
lRUCache.get(4);    // 返回 4

提示:

  • 1 <= capacity <= 3000
  • 0 <= key <= 10000
  • 0 <= value <= 105
  • 最多调用 2 * 105getput

解答

class LRUCache {

    ListNode headNode;
    ListNode endNode;
    int capacity;
    int count;
    Map<Integer, ListNode> map;

    public LRUCache(int capacity) {
        headNode = new ListNode();
        endNode = new ListNode();
        headNode.next = endNode;
        endNode.pre = headNode;
        this.capacity = capacity;
        count = 0;
        map = new HashMap();
    }
    
    public int get(int key) {
        if(map.containsKey(key)){
            moveNode(map.get(key));
            return map.get(key).val;
        }
        return -1;
    }
    
    public void put(int key, int value) {
        if(map.containsKey(key)){
            ListNode listNode = map.get(key);
            listNode.val = value;
            moveNode(listNode);
        }else{
            if(count == capacity){
                map.remove(endNode.pre.key);
                delNode(endNode.pre);
            }else{
                count++;
            }
            ListNode listNode = new ListNode(key, value);
            map.put(key, listNode);
            addHeadNode(listNode);
        }
    }

    private void moveNode(ListNode listNode){
        delNode(listNode);
        addHeadNode(listNode);
    }

    private void delNode(ListNode listNode){
        listNode.pre.next  = listNode.next;
        listNode.next.pre = listNode.pre;
    }

    private void addHeadNode(ListNode listNode){
        headNode.next.pre = listNode;
        listNode.next = headNode.next;
        headNode.next = listNode;
        listNode.pre = headNode;
    }
}

class ListNode{
    public ListNode next;
    public ListNode pre;
    public int key;
    public int val;

    public ListNode(int key, int val){
        this.key = key;
        this.val = val;
    }

    public ListNode(){

    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

6、子域名访问统计(中等)

题目地址:https://leetcode.cn/problems/subdomain-visit-count/description/

网站域名 "discuss.leetcode.com" 由多个子域名组成。顶级域名为 "com" ,二级域名为 "leetcode.com" ,最低一级为 "discuss.leetcode.com" 。当访问域名 "discuss.leetcode.com" 时,同时也会隐式访问其父域名 "leetcode.com" 以及 "com"

计数配对域名 是遵循 "rep d1.d2.d3""rep d1.d2" 格式的一个域名表示,其中 rep 表示访问域名的次数,d1.d2.d3 为域名本身。

  • 例如,"9001 discuss.leetcode.com" 就是一个 计数配对域名 ,表示 discuss.leetcode.com 被访问了 9001 次。

给你一个 计数配对域名 组成的数组 cpdomains ,解析得到输入中每个子域名对应的 计数配对域名 ,并以数组形式返回。可以按 任意顺序 返回答案。

示例 1:

输入:cpdomains = ["9001 discuss.leetcode.com"]
输出:["9001 leetcode.com","9001 discuss.leetcode.com","9001 com"]
解释:例子中仅包含一个网站域名:"discuss.leetcode.com"。
按照前文描述,子域名 "leetcode.com" 和 "com" 都会被访问,所以它们都被访问了 9001 次。

示例 2:

输入:cpdomains = ["900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"]
输出:["901 mail.com","50 yahoo.com","900 google.mail.com","5 wiki.org","5 org","1 intel.mail.com","951 com"]
解释:按照前文描述,会访问 "google.mail.com" 900 次,"yahoo.com" 50 次,"intel.mail.com" 1 次,"wiki.org" 5 次。
而对于父域名,会访问 "mail.com" 900 + 1 = 901 次,"com" 900 + 50 + 1 = 951 次,和 "org" 5 次。

提示:

  • 1 <= cpdomain.length <= 100
  • 1 <= cpdomain[i].length <= 100
  • cpdomain[i] 会遵循 "repi d1i.d2i.d3i""repi d1i.d2i" 格式
  • repi 是范围 [1, 104] 内的一个整数
  • d1id2id3i 由小写英文字母组成

解答

class Solution {
    private Map<String, Integer> map;
    public List<String> subdomainVisits(String[] cpdomains) {
        map = new HashMap();
        String numberSplit = " ";
        for(String cpdomain : cpdomains){
            String[] arr = cpdomain.split(numberSplit);
            int number = Integer.parseInt(arr[0]);
            setNumber(arr[1], number);
        }
        List<String> list = new ArrayList();
        for(String key : map.keySet()){
            list.add(map.get(key) + " " + key);
        }
        return list;
    }


    private void setNumber(String key, int number){
        int value = map.containsKey(key) ? map.get(key) + number : number;
        map.put(key, value);
        if(key.indexOf(".") > -1){
            setNumber(key.substring(key.indexOf(".")+1), number);
        }
    }
}

7、数组的度(简单)

题目地址:https://leetcode.cn/problems/degree-of-an-array/

给定一个非空且只包含非负数的整数数组 nums,数组的 的定义是指数组里任一元素出现频数的最大值。

你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。

示例 1:

输入:nums = [1,2,2,3,1]
输出:2
解释:
输入数组的度是 2 ,因为元素 1 和 2 的出现频数最大,均为 2 。
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组 [2, 2] 的长度为 2 ,所以返回 2 。

示例 2:

输入:nums = [1,2,2,3,1,4,2]
输出:6
解释:
数组的度是 3 ,因为元素 2 重复出现 3 次。
所以 [2,2,3,1,4,2] 是最短子数组,因此返回 6 。

提示:

  • nums.length150,000 范围内。
  • nums[i] 是一个在 049,999 范围内的整数。

解答

class Solution {
    public int findShortestSubArray(int[] nums) {
        Map<Integer, Integer> indexMap = new HashMap();
        Map<Integer, Integer> countMap = new HashMap();
        int endIndex = 0;
        int numberCount = 0;
        for(int i=0; i<nums.length; i++){
            // 计算当前值的count
            int count = countMap.containsKey(nums[i]) ? countMap.get(nums[i]) + 1 : 1;
            // 获取当前值第一次出现的下标
            int firstIndex = indexMap.containsKey(nums[i]) ? indexMap.get(nums[i]) : i;
            indexMap.put(nums[i], firstIndex);
            countMap.put(nums[i], count);
            // 如果当前值的数量最大 或者 与之前最大值相等,且最短,替换最后出现的下标以及总数
            if(numberCount < count || numberCount == count && i-indexMap.get(nums[i]) < endIndex-indexMap.get(nums[endIndex])){
                numberCount = count;
                endIndex = i;
            }
        }
        return endIndex-indexMap.get(nums[endIndex]) + 1;
    }
}
posted @ 2022-12-06 21:37  李聪龙  阅读(59)  评论(0编辑  收藏  举报