leetCode - - - 哈希表

目录

[1.模拟行走机器人(LeetCode 874)](#1.模拟行走机器人(LeetCode 874))

[2.数组的度(LeetCode 697)](#2.数组的度(LeetCode 697))

[3.子域名访问次数(LeetCode 811)](#3.子域名访问次数(LeetCode 811))

[4.字母异位词分组(LeetCode 49)](#4.字母异位词分组(LeetCode 49))

5.小结

1.常见的哈希表实现

2.遍历Map


1.模拟行走机器人(LeetCode 874)

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

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

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

机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,并继续执行下一个命令。

返回机器人距离原点的 最大欧式距离平方 。(即,如果距离为 5 ,则返回 25

注意:

  • 北方表示 +Y 方向。
  • 东方表示 +X 方向。
  • 南方表示 -Y 方向。
  • 西方表示 -X 方向。
  • 原点 [0,0] 可能会有障碍物。

示例 1:

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

        // 初始化结果
        int result = 0;
        
        // 从原点( 0 ,0 )位置开始出发
        int x = 0, y = 0;
        
        // 机器人前进的方向
        // 初始方向:正北
        int direction = 0;

        // 定义四个方向
        int[][] direxy = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

        // 障碍物有多个,所以需要有一个障碍物坐标点集合
        // 使用 集合 Set 存储障碍物的坐标,用来检查下一步是否受阻
        // 机器人每试图走一个位置,就用此位置与障碍物集合列表里的坐标进行比较,看是否刚好是障碍物坐标点
        HashSet<String> obstaclesSet = new HashSet<String>();

        // 存储障碍物坐标点
        for (int[] obs : obstacles) {

            // 以坐标的形式存储
            obstaclesSet.add(obs[0] + "," + obs[1]);

        }

        // 机器人开始行走
        for (int com : commands) {

            // -2:向左转 90 度
            if (com == -2) {
                direction = (direction == 0) ? 3 : direction - 1;

            // -1:向右转 90 度
            } else if (com == -1) {
                direction = (direction == 3) ? 0 : direction + 1;
            
            // 1 <= x <= 9 :向前移动 x 个单位长度
            } else {
                // 没有遇到障碍物,一直往前走 com 个单位长度
                while (com-- > 0 && !obstaclesSet.contains((x + direxy[direction][0]) + "," + (y + direxy[direction][1]))) {
                    x += direxy[direction][0];
                    y += direxy[direction][1];
                }

                // 机器人停下来后开始计算最大欧式距离的平方
                // 更新结果
                result = Math.max(result, x * x + y * y);
            }
        }
        return result;
    }
}

知识点:

  1. 二维数组的使用

定义:int[] obs 表示一个一维数组,每个 obs 是 obstacles 数组的元素。obstacles 是一个二维数组,其中的每个元素都是表示障碍物坐标的数组。

访问元素:通过 obs[0] 和 obs[1] 可以访问障碍物的 x 和 y 坐标。

  1. 集合(HashSet)的使用

定义:HashSet 是一个集合类,它不允许重复的元素,并且提供了高效的查找、添加和删除操作。

用法:在这里,obstaclesSet 被用来存储障碍物的坐标,以便快速查找。这种存储方式提高了查找效率,尤其是在处理大量障碍物数据时。

  1. 字符串的连接

操作:obs[0] + "," + obs[1] 将 x 和 y 坐标值转换为字符串格式 "x,y"。这种格式使得坐标可以被轻松地存储和比较。

应用:将坐标以字符串形式存储到 HashSet 中,便于进行后续的查找和操作。

  1. 增强的 for 循环

定义:for (int[] obs : obstacles) 是 Java 的增强型 for 循环(或称为 foreach 循环),它用于遍历数组或集合中的每一个元素。

用法:这使得遍历 obstacles 数组中的每个障碍物坐标变得简单和直观。

  1. 坐标系统

概念:x 和 y 坐标用于表示二维空间中的位置。在代码中,obs[0] 和 obs[1] 分别代表 x 和 y 坐标值。


2.数组的度(LeetCode 697)

给定一个非空且只包含非负数的整数数组 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 。

解题思路

1.哈希表:使用 HashMap 存储每个元素的出现次数及其最早和最后出现的位置。

2.数组遍历:通过遍历数组来填充哈希表的信息。

3.度量最大出现次数:在哈希表中查找出现次数最多的元素。(第一次遍历)

4.计算最短子数组长度:遍历哈希表,找到包含出现次数最多元素的最短子数组。(第二次遍历)

知识点运用

Map.Entry 是 Map 接口中的一个嵌套接口,表示 Map 中的一个键值对。

1.主要方法:

getKey():获取 Map.Entry 的键。

getValue():获取 Map.Entry 的值。

setValue(V value):设置 Map.Entry 的值。

2.遍历 Map:最常用的方式是通过 entrySet() 方法,该方法返回 Map 中所有键值对的集合(Set<Map.Entry<K, V>>)。

java 复制代码
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 2);

for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + ": " + value);
}

解题代码:

java 复制代码
class Solution {
    public int findShortestSubArray(int[] nums) {
    // 初始化长度为一个大数值,方便后续更新
        int minLength = Integer.MAX_VALUE;

        // Map存储每个元素的出现次数及其最早和最晚出现位置
        Map<Integer, int[]> map = new HashMap<>();

        // 遍历数组,统计每个元素的信息
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {
                int[] value = map.get(nums[i]);
                value[0]++; // 增加出现次数
                value[2] = i; // 更新最后出现的位置
            } else {
                // 第一次出现该元素
                map.put(nums[i], new int[]{1, i, i});
            }
        }

        // 计算数组中出现次数最多的元素
        int maxTimes = 0;
        for (int[] value : map.values()) {
            maxTimes = Math.max(maxTimes, value[0]);
        }

        // 再次遍历map来找到最短长度
        for (Map.Entry<Integer, int[]> entry : map.entrySet()) {
            int[] value = entry.getValue();
            if (value[0] == maxTimes) {
                minLength = Math.min(minLength, value[2] - value[1] + 1);
            }
        }

        return minLength;
    }
}

3.子域名访问次数(LeetCode 811)

网站域名 "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 次。
java 复制代码
class Solution {
    public List<String> subdomainVisits(String[] cpdomains) {

        // 使用哈希表记录每个子域名的计数
        Map<String, Integer> map = new HashMap<>();

        // 遍历数组
        for (String s : cpdomains) {

            // 获取当前字符串的长度
            int n = s.length();
            
            // 每个字符串都是由数字 + 域名组成
            // 先去获取这个字符串中的数字
            // 从 0 开始,向后扫描到空格位置
            int idx = 0;

            // 从前向后扫描到空格位置
            while (idx < n && s.charAt(idx) != ' ') idx++;

            // 截取出数字来
            // tips: substring() 方法返回字符串的子字符串
            // beginIndex -- 起始索引(包括), 索引从 0 开始
            // endIndex -- 结束索引(不包括)
            int cnt = Integer.parseInt(s.substring(0, idx));

            // 从后往前处理域名部分,直到处理完毕
            int start = idx + 1; 
            
            // 从后往前处理域名部分
            idx = n - 1;

            // 直到处理完毕
            while (idx >= start) {

                // 每个域名由多个子域名组成
                // 通过 . 来截取
                while (idx >= start && s.charAt(idx) != '.') idx--;

                // 获取当前子域名
                String cur = s.substring(idx + 1);

                // 更新这个子域名的计数
                map.put(cur, map.getOrDefault(cur, 0) + cnt);

                // idx 继续向前移动
                idx--;
            }
        }

        // 从哈希表中构造出数组答案来
        List<String> ans = new ArrayList<>();

        // keySet() 方法返回映射中所有 key 组成的 Set 视图
        for (String key : map.keySet()) {

            // key 是域名,map.get(key) 获取 value
            ans.add(map.get(key) + " " + key);

        }

        // 返回结果
        return ans;
    }
}

区别keySet() 和 entrySet()

keySet():

返回 Map 中所有键的集合 (Set<K>).

适合只需要处理键的情况。例如,只需要检查键是否存在或者只需要获取键的列表。

entrySet():

返回 Map 中所有键值对的集合 (Set<Map.Entry<K, V>>).

更适合同时需要访问键和值的情况。通过 Map.Entry<K, V> 对象可以直接访问键和值,提高效率

java 复制代码
for (String key : map.keySet()) {
    System.out.println(key + " -> " + map.get(key));
}


for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " -> " + entry.getValue());
}

4.字母异位词分组(LeetCode 49)

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

字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

示例 1:

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

示例 2:

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

做题思路:

1.遍历所有字符串,计算每个字符串的字符频次。

2.将频次转换为唯一的键,并使用哈希表将具有相同键的字符串分组。

3.返回哈希表中所有分组的值,即字母异位词的分组结果

较为严谨的解法:不确定每个字符串是否可以有重复的字符,这里计算了每个字符出现的频次

java 复制代码
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String,List<String>> map=new HashMap<String,List<String>>();
        for(String str:strs){
            // count 代表每个小写字母出现的频次
            int[] count=new int[26];

            // 利用 for 循环,统计 str 当中每个字母出现的频次
            for(int i=0;i<str.length();i++){
                count[str.charAt(i) -'a']++;
            }
            StringBuffer sb=new StringBuffer();

            // // 将每个出现次数大于 0 的字母和出现次数按顺序拼接成字符串,作为哈希表的键
            for(int i=0;i<26;i++){
                if(count[i]!=0){
                    // 先记录字母,比如记录了字母 k
                    sb.append((char)(i+'a'));
                     // 再记录次数,比如记录了次数 9
                    sb.append(count[i]);
                }
            }
            // 转换为字符串的形式,比如为 a1c9q7
            String key=sb.toString();
            // 在哈希表 map 当中找出这个 key 对应的字符串 str 来
            // 1、如果有这个 key,那么这个 key 对应的 数组 会新增一个 str 进去
            // 2、如果没有这个 key,那么会初始化一个数组,用来新增这个 str
            List<String> list=map.getOrDefault(key,new ArrayList<String>());
            list.add(str);
            map.put(key,list);
        }

        //return new ArrayList<>(map.values());
        return  new ArrayList<List<String>>(map.values()); 
    }
}

力扣给的通过率最高的解法:默认每个字符串中不会出现重复的字母

java 复制代码
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
         HashMap<String, List<String>> hashMap = new HashMap<>();
        for (String str : strs) {
            char[] charArray = str.toCharArray();
            Arrays.sort(charArray);
            String key = new String(charArray);
            List<String> list = hashMap.getOrDefault(key, new ArrayList<String>());
            list.add(str);
            hashMap.put(key, list);
        }
        return  new ArrayList<>(hashMap.values());
    }
}

5.小结

1.常见的哈希表实现

HashMap:

用于存储键值对,每个键(key)与一个值(value)相关联。

java 复制代码
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
int value = map.get("apple");

使用:使用 HashMap 统计元素出现的次数

java 复制代码
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
    map.put(num, countMap.getOrDefault(num, 0) + 1);
}

HashSet:

适用于需要存储唯一元素的场景(类似于 HashMap 的键部分)

java 复制代码
Set<String> set = new HashSet<>();
set.add("apple");
boolean exists = set.contains("apple"); // true

使用:通过 HashSet 快速查找元素是否出现过

java 复制代码
Set<Integer> set = new HashSet<>();
for (int num : nums) {
    if (!set.add(num)) {
        // 如果返回 false,说明 num 已经存在
    }

2.遍历Map

entrySet() 是最常用的方法,适合遍历 Map 中的键值对。

keySet() 和 values() 方法适合只需要键或值时使用。

java 复制代码
 // 遍历 entrySet()
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println("Key: " + key + ", Value: " + value);
        }

// 遍历 keySet()
for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println("Key: " + key + ", Value: " + value);
}

// 遍历 values()
for (Integer value : map.values()) {
    System.out.println("Value: " + value);
}
相关推荐
转世成为计算机大神15 分钟前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
搬砖的小码农_Sky16 分钟前
C语言:数组
c语言·数据结构
qq_3273427336 分钟前
Java实现离线身份证号码OCR识别
java·开发语言
Swift社区1 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman2 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
阿龟在奔跑2 小时前
引用类型的局部变量线程安全问题分析——以多线程对方法局部变量List类型对象实例的add、remove操作为例
java·jvm·安全·list
飞滕人生TYF2 小时前
m个数 生成n个数的所有组合 详解
java·递归
先鱼鲨生2 小时前
数据结构——栈、队列
数据结构
一念之坤2 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
代码小鑫2 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计