当前位置:   article > 正文

Set 和 Map 的使用_setmap

setmap

试想一个这样的场景,如果我们将学生的学号和姓名存放在一个二维数组中,如果我们要通过某个学生的学号来查找学生的姓名,通过遍历数组我们可以得到结果,但是的如果数据量很大,那么查找的过程就会非常的费劲,所以 Java 中提供了这样的两个类(其实是接口)Set 和 Map ,Map和set是一种专门用来进行搜索的容器或者数据结构。

目录

为什么要有 Set 和 Map 两个模型?

Set 和 Map

Map

常用方法

注意事项

Set

常用方法

注意事项

练习题

力扣136. 只出现一次的数字

力扣138. 复制带随机指针的链表

力扣771. 宝石与石头

牛客网. 旧键盘

力扣692. 前K个高频单词


为什么要有 Set 和 Map 两个模型?

实际上我们有时候只需要记录一个数据存不存在(例如我们要查找张三这个学生存不存在),而一些时候我们要通过这个数据找到它所映射的东西(例如我们想找到2021111111这个学号对应的学生信息)。

一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以上述两个模型可以表示为:

1. 纯 key 模型,比如:快速的查找某个名字在不在通讯录中。在 Java 中对应的容器为 Set。

2. Key-Value 模型,比如:统计文件中每个单词出现的次数就可用这样的键值对表示<单词,单词出现的次数>。在 Java 中对应的容器为 Map。

Set 和 Map

我们前面说了 Set 和 Map 实际上是两个接口,需要类来实现这个接口才可以使用,Java 中实现 Set 接口的是 TreeSet 和 HashSet ,实现 Map 的是 HashMap 和 TreeMap 。

下面给出集合类的继承关系图(不知道怎么去水印将就着看一下,里面有很重要的信息)

TreeSet 和 HashSet,HashMap 和 TreeMap底层的实现逻辑不同,在将来的实现中会讲解。

Map

Map 是带有泛型参数的 具体是 Map<K, V> (K,V是代表类型,如果是内置类型要将其主动换成对应的包装类)

Map 是一个接口,该接口没有继承自Collection,它存储的是结构的键值对,并且 K 一定是唯一的,不能重复。

常用方法

1. 新建 Map 实例

  1. Map<String, String> map = new HashMap<>();
  2. Map<String, String> map = new TreeMap<>();

选择上面其中的一种即可,建议选择 HashMap 。

2. put 方法插入键值对

Map 内部的元素之间的先后顺序和插入顺序关系不大

当 put 的时候发现 key 已经存在, 此时就会覆盖原有的 value

  1. Map<String, String> map = new HashMap<>();
  2. // 1. 使用 put 方法插入键值对
  3. // Map 内部的元素之间的先后顺序和插入顺序关系不大~
  4. // 当 put 的时候发现 key 已经存在, 此时就会覆盖原有的 value
  5. map.put("及时雨", "宋江");
  6. map.put("黑旋风", "李逵");
  7. map.put("行者", "武松");
  8. map.put("及时雨", "宋公明");
  9. System.out.println(map);

运行结果如下:

3. get 方法, 根据 key 获取 value

如果 key 不存在, get 返回 null

还可以使用 getOrDefault 来根据 key 获取 value

如果 key 不存在, getOrDefault 返回一个默认值, 只是返回默认值并不会添加新的键值对。

  1. Map<String, String> map = new HashMap<>();
  2. map.put("行者", "武松");
  3. // 2. 使用 get 方法, 根据 key 获取 value
  4. // 如果 key 不存在, get 返回 null
  5. // 还可以使用 getOrDefault 来根据 key 获取 value
  6. // 如果 key 不存在, getOrDefault 返回一个默认值, 并不会添加新的键值对
  7. String value = map.get("行者");
  8. System.out.println(value);
  9. value = map.get("小李广");
  10. System.out.println(value);
  11. value = map.getOrDefault("小李广", "花荣");
  12. System.out.println(value);
  13. System.out.println(map);

结果为:

4. 使用 isEmpty 判定是否为空

5. 使用 size 获取到键值对的个数

6. 使用 clear 清空所有的键值对

7. 遍历 Map

Map 设计出来不是为了遍历,但是可以使用特殊手段遍历,遍历 Map 是比较复杂的, 需要把 Map 转换成 Set 再遍历。

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

现在来解释一下上面的代码,

Map.Entry<String, String> 是一个类型,这个类型是 Map 的一个内部类

map.entrySet() 是将 map 里面存放的键值对转换为 Set 类型(将原来的 <K > ---> < V>序列表转换为 <K, V>)

下面给出示意图(内存布局不是这样的):

至于为什么 entry.getKey() 和 entry.getValue() 可以访问到 Set 类型的 key 和 value ,这是因为 Set 的底层是使用 Map 来实现的。

8. 还可以单独的获取到所有的 key 和所有的 value

  1. for (String key : map.keySet()) {
  2. System.out.println(key);
  3. }
  4. for (String value : map.values()) {
  5. System.out.println(value);
  6. }

注意事项

1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类 TreeMap 或者 HashMap。

2. Map 中存放键值对的Key是唯一的,value是可以重复的。

3. 在 Map 中插入键值对时,key 不能为空,否则就会抛 NullPointerException 异常,但是 value 可以为空。

4. Map 中的 Key 可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。

5. Map 中的 value 可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。

6. Map 中键值对的 Key 不能直接修改,value可以修改,如果要修改 key,只能先将该 key 删除掉,然后再来进行重新插入。

Set

Set 与 Map 主要的不同有两点:Set 是继承自 Collection 的接口,Set 中只存储了 Key。

常用方法

由于 Set 继承自 Collection ,所以好多方法都是 Collection 中的,这里只做简单介绍。

1. 新建 Set 实例

  1. Set<String> set = new HashSet<>();
  2. Set<String> set = new TreeSet<>();

选择上面其中的一种即可,建议选择 TreeSet 。

2. 使用 add 插入元素

如果插入重复的 key, 实际只保存一份,借助 Set 的这个特性, 就可以用于 "去重"。

3. 使用 contains 方法判定元素是否存在

boolean ret = set.contains("java");

4. 使用 remove 删除元素

5. 使用 isEmpty 来判定空

6. 使用 size 获取元素个数

7. 使用 clear 清空元素

8. 遍历

a) 可直接使用 for-each 遍历,由上面的 Java 集合类继承关系图可以看到,Set 继承了 Iterable 接口,所以可以使用 for - each

  1. for (String key : set) {
  2. System.out.println(key);
  3. }

b) 使用迭代器来遍历

  1. Iterator<String> it = set.iterator();
  2. while (it.hasNext()) {
  3. System.out.println(it.next());
  4. }

不论使用 a ,b 那种都可以,编译器会将 a 转化 b 的形式。

注意事项

1. Set 是继承自 Collection 的一个接口类。

2. Set 中只存储了 key,并且要求 key 一定要唯一。

3. Set 的底层是使用 Map 来实现的,其使用 key 与 Object 的一个默认对象作为键值对插入到 Map 中的。

4. Set 最大的功能就是对集合中的元素进行去重。

5. 实现 Set 接口的常用类有 TreeSet 和 HashSet ,还有一个 LinkedHashSet ,LinkedHashSet 是在 HashSet 的基础上维护了一个双向链表来记录元素的插入次序。

6. Set 中的 Key 不能修改,如果要修改,先将原来的删除掉,然后再重新插入。

7. Set中不能插入 null 的 key。

练习题

力扣136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

  1. public int singleNumber(int[] nums) {
  2. Map<Integer, Integer> map = new HashMap<>();
  3. for (int x : nums) {
  4. int val = map.getOrDefault(x, 0);
  5. map.put(x, val + 1);
  6. }
  7. for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
  8. if (entry.getValue() == 1) {
  9. return entry.getKey();
  10. }
  11. }
  12. return 0;
  13. }

力扣138. 复制带随机指针的链表

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。

  1. public Node copyRandomList(Node head) {
  2. if (head == null) {
  3. return null;
  4. }
  5. Map<Node, Node> map = new HashMap<>();
  6. for (Node cur = head; cur != null; cur = cur.next) {
  7. map.put(cur, new Node(cur.val));
  8. }
  9. for (Node cur = head; cur != null; cur = cur.next) {
  10. map.get(cur).next = map.get(cur.next);
  11. map.get(cur).random = map.get(cur.random);
  12. }
  13. return map.get(head);
  14. }

力扣771. 宝石与石头

给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。

J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。

  1. public int numJewelsInStones(String jewels, String stones) {
  2. Set<Character> set = new HashSet<>();
  3. for (int i = 0; i < jewels.length(); i++) {
  4. set.add(jewels.charAt(i));
  5. }
  6. int ret = 0;
  7. for (int i = 0; i < stones.length(); i++) {
  8. if (set.contains(stones.charAt(i)) == true) {
  9. ret++;
  10. }
  11. }
  12. return ret;
  13. }

牛客网. 旧键盘

链接:https://www.nowcoder.com/questionTerminal/f88dafac00c8431fa363cd85a37c2d5e
来源:牛客网

旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现。现在给出应该输入的一段文字、以及实际被输入的文字,请你列出
肯定坏掉的那些键。

输入描述:

输入在2行中分别给出应该输入的文字、以及实际被输入的文字。每段文字是不超过80个字符的串,由字母A-Z(包括大、小写)、数字0-9、
以及下划线“_”(代表空格)组成。题目保证2个字符串均非空。

输出描述:

按照发现顺序,在一行中输出坏掉的键。其中英文字母只输出大写,每个坏键只输出一次。题目保证至少有1个坏键。

  1. import java.util.*;
  2. public class Main {
  3. // 牛客网. 旧键盘
  4. // 链接:https://www.nowcoder.com/questionTerminal/f88dafac00c8431fa363cd85a37c2d5e
  5. //来源:牛客网
  6. //旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现。现在给出应该输入的一段文字、以及实际被输入的文字,请你列出
  7. //肯定坏掉的那些键。
  8. //输入描述:
  9. //输入在2行中分别给出应该输入的文字、以及实际被输入的文字。每段文字是不超过80个字符的串,由字母A-Z(包括大、小写)、数字0-9、
  10. //以及下划线“_”(代表空格)组成。题目保证2个字符串均非空。
  11. //输出描述:
  12. //按照发现顺序,在一行中输出坏掉的键。其中英文字母只输出大写,每个坏键只输出一次。题目保证至少有1个坏键。
  13. public static void main(String[] args) {
  14. Scanner scanner = new Scanner(System.in);
  15. while (scanner.hasNext()) {
  16. String excepted = scanner.next();
  17. String actual = scanner.next();
  18. excepted = excepted.toUpperCase();
  19. actual = actual.toUpperCase();
  20. Set<Character> set = new HashSet<>();
  21. for (int i = 0; i < actual.length(); i++) {
  22. set.add(actual.charAt(i));
  23. }
  24. Set<Character> badKey = new HashSet<>();
  25. StringBuilder stringBuilder = new StringBuilder();
  26. for (int i = 0; i < excepted.length(); i++) {
  27. char curKey = excepted.charAt(i);
  28. if (set.contains(curKey) == true) {
  29. continue;
  30. }
  31. if (badKey.contains(curKey) == true) {
  32. continue;
  33. }else {
  34. badKey.add(curKey);
  35. stringBuilder.append(curKey);
  36. }
  37. }
  38. System.out.println(stringBuilder.toString());
  39. }
  40. }
  41. }

这道题本来想着遍历一遍正常的输入,将它的没个字符和该字符出现的次数存放在 Map 中,然后在遍历实际输出的字符串,在 Map 中查找,将字符对应的次数减一,遍历完后查看 Map 中哪一个 values 的值不为 0 就输出哪一个字符,但是他要求按照发现的顺序,而 Map 没有顺序,所以这个方法就失效了。

力扣692. 前K个高频单词

给一非空的单词列表,返回前 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。

  1. public List<String> topKFrequent(String[] words, int k) {
  2. Map<String, Integer> map = new HashMap<>();
  3. for (int i = 0; i < words.length; i++) {
  4. int val = map.getOrDefault(words[i], 0);
  5. map.put(words[i], val + 1);
  6. }
  7. List<String> list = new ArrayList<>(map.keySet());
  8. Collections.sort(list, new Comparator<String>() {
  9. @Override
  10. public int compare(String o1, String o2) {
  11. if (map.get(o1) == map.get(o2)) {
  12. return o1.compareTo(o2);
  13. }
  14. return map.get(o2) - map.get(o1);
  15. }
  16. });
  17. return list.subList(0, k);
  18. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/570898
推荐阅读
相关标签
  

闽ICP备14008679号