Map接口
Map是一个接口类,没有继承自Collection,该类中存储的是<K,V>
键值对,并且K是不重复的,而V可以重复。
常用方法
添加元素
添加一对元素到Map接口中,使用put
方法,其中key不能重复。如果当前key存在,会更新对应的value值。
若要保证Map接口元素元素的添加顺序和保存顺序相同,使用LinkedHashMap
。
注意, put方法身兼两职:添加 + 更新
若Key不存在,将新的键值对插入Map集合中;若K存在,更新对应的value,返回更新前的value值。
查询元素
Map接口中的查询,调用get
方法,根据Key值查询value值,若不存在返回null。
还有一个常用方法getOrDefault
,根据key值查询value值,若value不存在,返回默认值。
Map接口的遍历
在Java中,继承了Iterable
接口的子类可以直接使用for-each循环来遍历。而Map没有继承这个接口,所以当需要遍历Map接口时,我们要将Map转为Set(Set继承了Iterable接口)。Map保存的是一对元素,在内部保存的是一个个不重复的Entry
对象,通过entrySet()
这个方法将一对元素转为Entry对象,转完之后Set就保存了一个个不重复的Entry对象。此时通过entry.getKey()
和entry.getValue()
方法取得entry对象的Key和Value值。
注意:实现Map接口的常用类有
TreeMap
和HashMap
,在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常,value可以为空。但是HashMap的key和value都可以为空。
常用Map子类的关系
TreeMap | HashMap | |
---|---|---|
内部数据结构 | 红黑树 | 哈希表 |
key和value是否允许为null | key不能为null,value可以为null,key必须具备可比较的性质或传入比较器对象 | key和value都可以为null |
是否有序 | 对于key有序,这个大小关系由Comparable或者比较器对象来决定 | 无序 |
是否线程安全 | 不安全 | 不安全 |
Set接口
Set接口是Collection的子接口,保存了单个不重复的元素。其实Set接口的本质就是Map,Set是将元素保存在了Map的Key上,因此Set是不重复元素的集合。
常用方法
添加元素
Set中使用add
方法添加元素,当添加的元素不同时,返回true,相同时,返回false。
查询是否包含
contains()
方法。
注意,实现Set接口的常用类有
TreeSet
和HashSet
,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础上维护了一个双向链表来记录元素的插入次序。 TreeSet中不能插入null的key,HashSet可以。 Set最大的功能就是对集合中的元素进行去重。
Map和Set的OJ练习
宝石与石头
1、描述:
给你一个字符串 jewels
代表石头中宝石的类型,另有一个字符串 stones
代表你拥有的石头。 stones
中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。字母区分大小写,因此 “a” 和 “A” 是不同类型的石头。
2、思路:
先把jewels中的每个字符串保存在Set中,拿着这个集合去扫描Stones,如果jewels中的字符在Stones中,则是宝石,否则就是石头。
3、代码实现
public class Num771_NumJewelsInStones {
public int numJewelsInStones(String jewels, String stones) {
//使用set保存每个宝石的类型
Set<Character> set = new HashSet<>();
for (int i = 0; i < jewels.length(); i++) {
set.add(jewels.charAt(i));
}
//扫描石头字符,所谓的宝石其实就是石头字符中出现的宝石字符
int nums = 0;
for (int i = 0; i < stones.length(); i++) {
char c = stones.charAt(i);
if(set.contains(c)){
nums ++;
}
}
return nums;
}
}
坏键盘打字
1、描述: 旧键盘上坏了几个键,于是在敲一段文字的时候,对应的字符就不会出现。现在给出应该输入的一段文字、以及实际被输入的文字,请你列出肯定坏掉的那些键。
2、代码实现
public class NewCode_BadKeys {
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
while (in.hasNextLine()) { // 注意 while 处理多个 case
String expected = in.nextLine().toUpperCase();
String actual = in.nextLine().toUpperCase();
//使用set集合存储实际的字符
Set<Character> set = new HashSet<>();
for(int i = 0;i<actual.length();i++){
set.add(actual.charAt(i));
}
//2、拿着实际存在的字符扫描期望的字符串找到坏键
//所谓的坏键就是期望中存在但set中不存在的字符
Set<Character> badkey= new LinkedHashSet<>();
for(int i = 0;i<expected.length();i++){
char c = expected.charAt(i);
if(!set.contains(c)){
badkey.add(c);
}
}
//最终将badkey中字符输出
for(char c : badkey){
System.out.print(c);
}
//换行进行下一次输入
System.out.println();
}
}
}
复制带随机指针的链表
1、描述: 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。返回复制链表的头节点。
2、思路: 这个题非常有意思,如果只是单纯的复制一个链表,只需要定义一个虚拟头节点然后尾插元素即可,但这个链表带有了随机的指针,也就是说当前节点不仅会指向下一个节点,还会随机指向其他任意位置的节点。如何才能深拷贝这个带有随机指针的链表呢?做法也很巧妙,使用Map集合。找到新旧链表之间节点的映射关系以及随机指针的映射关系。
3、代码实现
public Node copyRandomList(Node head) {
Map<Node,Node> nodesMap = new HashMap<>();
//遍历原链表,构建新链表,保存在map接口中,将新旧链表的相同节点作为一对映射关系
Node tmp = head;
while(tmp != null){
Node node = new Node(tmp.val);
nodesMap.put(tmp,node);
tmp = tmp.next;
}
//再遍历每个节点,通过原链表构建新链表的next和random引用
tmp = head;
while(tmp != null){
//构建新链表的next
nodesMap.get(tmp).next = nodesMap.get(tmp.next);
//构建random
nodesMap.get(tmp).random = nodesMap.get(tmp.random);
tmp = tmp.next;
}
//最终通过原链表的头找到一一映射关系来得到新链表的头
return nodesMap.get(head);
}
继续加油努力!!!