赞
踩
Map
和Set
是一种专门用来进行搜索的容器(或数据结构);
前面常见的搜索方式
:遍历,二分查找;该种方式适合于静态搜索(搜索过程中不会再进行插入和删除操作了)
而Map
和Set
是一种适合动态查找的集合容器;
一般把搜索的数据称为关键字(
Key
),与关键字相对应的称为值(Value
),将其称之为Key-value
的键值对,因此,存在以下两种模型:
纯 K 模型 :
Set
中采用的就是纯 K 这种模型,该模型主要用来判断关键字Key
是否在集合中;
例如: 快速查找某个名字是否在通讯录中,或查找一个英文单词是否在字典中;
K-V 模型 :
Map
中采用的就是 Key-Value 这种模型,该模型需要根据指定的Key
来查找对应的Value
值,并且K
一定是唯一的,不能重复;
例如:梁山好汉的江湖绰号,每个好汉都有自己的江湖绰号(及时雨-宋江);
由上可以看出:Map实质是一个接口,没有继承自 Collection,底层是通过TreeMap、HashMap两个实现类来实现的
;
Map.Entry<K, V>
是Map
内部实现的用来存放<key, value>
键值对映射关系的内部类,该内部类中主要提供了<key,value>
的获取,value
的设置以及Key
的比较方式;
注意:Map.Entry<K,V> 并没有提供设置 Key 的方法
方法 | 说明 |
---|---|
V put(K key, V value) | 设置 key 对应的 value |
V getOrDefault(Object key, V defaultValue) | 返回 key 对应的 value,key 不存在,返回默认值 |
V get(Object key) | 返回 key 对应的 value |
V remove(Object key) | 删除 key 对应的映射关系 |
Set keySet() | 返回所有 key 的不重复集合 |
Collection values() | 返回所有 value 的可重复集合 |
Set<Map.Entry<K, V>> entrySet() | 返回所有的 key-value 映射关系 |
boolean containsKey(Object key) | 判断是否包含 key |
boolean containsValue(Object value) | 判断是否包含 value |
注意区别:
boolean containsKey(Object key)
方法时间复杂度:O(log2N)
;
boolean containsValue(Object value)
方法的时间复杂度:O(N)
;
TreeMap相关知识:
TreeMap
底层是红黑树;
红黑树
:红黑树是一棵二叉搜索树
和一些条件限制
组成的结构;
二叉搜索树:二叉搜索树又称为二叉排序树,也可以是一棵空树,若它的左子树不为空,则左子树上所有结点的值都小于根结点的值;若它的右子树不为空,则右子树上所有结点的值都大于根结点的值
它的左右子树也分别为二叉搜索树;
二叉搜索树
条件限制:
红黑树近似为一棵平衡的二叉搜索树
;
特性约束
:
(1) 结点的颜色不是红色就是黑色;
(2)根结点的颜色必须是黑色;
(3) 不能有连在一起的红色结点;
(4)每条路径中黑色结点的个数必须相同;
(5) “叶子结点”(指的是空引用或空结点
)必须是黑色的;
红黑树:
TreeMap 的使用:
public class TestMap { public static void method1() { Map<String, String> m = new TreeMap<>(); //设置 Key 对应的 value m.put("orange", "橙子"); m.put("apple", "苹果"); m.put("banana", "香蕉"); System.out.println(m.size()); // 3 System.out.println(m.get("apple")); //key存在,返回与key对应的value System.out.println(m.get("pear")); //key不存在,返回null System.out.println(m.getOrDefault("pear", "梨")); //key不存在,返回默认值 // TreeMap 中的 key 不能为空---抛空指针异常 //m.put(null,"null"); // TreeMap 中的 value可以为空 m.put("grape", null); System.out.println(m.size()); //4 //key不可以重复,当key不存在时,插入键值对 //key存在,则用新的 value修改之前与 key 对应的 value System.out.println(m.get("orange")); System.out.println(m.put("orange", "橘子")); //返回旧value值 System.out.println(m.get("orange")); //返回新的value值--->橘子 //value可以重复 m.put("unknown", "苹果"); System.out.println(m.size()); // 5 //containsKey,检测是否包含key---->O(log2N) if (m.containsKey("unknown")) { m.remove("unknown"); } else { System.out.println("unknown不存在"); } //containsValue,检测是否包含value--->O(N) if (m.containsValue("橘子")) { System.out.println("橘子存在"); } else { System.out.println("橘子不存在"); } //拿到m中所有的key---->keySet //Set的类型必须与Map一致 //结果默认情况是按照字典序排列 Set<String> set = m.keySet(); for (String s : set) { System.out.print(s + " "); } System.out.println(); //apple banana grape orange(有序--默认字典序) System.out.println("================="); //拿到m中所有的value---->values Collection<String> list = m.values(); for(String v:list){ System.out.print(v + " "); } System.out.println(); System.out.println("================"); //获取所有的键值对 -->entrySet Set<Map.Entry<String, String>> kv=m.entrySet(); for(Map.Entry<String ,String> e:kv){ System.out.println(e.getKey()+" :"+e.getValue()); } System.out.println(); }
打印结果:
注意:
(1) key 必须是能够比较的,否则抛出类型转换异常;---->解决方法:实现 comparable
接口
(2) 打印的 key
是有序的排列;
(3)在 TreeMap
中插入键值对时,key
不能为空,否则就会抛 NullPointerException
异常,但是value
可以为空;
m.put(null,"null");
HashMap 的使用:
public static void method2() { Map<String, String> m = new HashMap<>(); //设置 Key 对应的 value m.put("orange", "橙子"); m.put("apple", "苹果"); m.put("banana", "香蕉"); System.out.println(m.size()); // 3 System.out.println(m.get("apple")); //key存在,返回与key对应的value System.out.println(m.get("pear")); //key不存在,返回null System.out.println(m.getOrDefault("pear", "梨")); //key不存在,返回默认值 // HashMap中的key可以为空 m.put(null,"null"); // HashMap 中的value可以为空 m.put("grape", null); System.out.println(m.size()); //5 //key不可以重复,当key不存在时,插入键值对 //key存在,则用新的 value修改之前与 key 对应的 value System.out.println(m.get("orange")); System.out.println(m.put("orange", "橘子")); //返回旧value值 System.out.println(m.get("orange")); //返回新的value值--->橘子 //value可以重复 m.put("unknown", "苹果"); System.out.println(m.size()); // 6 //containsKey,检测是否包含key---->O(log2N)树的高度 if (m.containsKey("unknown")) { m.remove("unknown"); } else { System.out.println("unknown不存在"); } //containsValue,检测是否包含value--->O(N) if (m.containsValue("橘子")) { System.out.println("橘子存在"); } else { System.out.println("橘子不存在"); } //拿到m中所有的key---->keySet //Set的类型必须与Map一致 //结果不一定有序 Set<String> set = m.keySet(); for (String s : set) { System.out.print(s + " "); } System.out.println(); //orange banana null apple grape (不一定是有序的) System.out.println("================="); //拿到m中所有的value---->values Collection<String> list = m.values(); for(String v:list){ System.out.print(v + " "); } System.out.println(); System.out.println("================"); //获取所有的键值对 -->entrySet Set<Map.Entry<String, String>> kv=m.entrySet(); for(Map.Entry<String ,String> e:kv){ System.out.println(e.getKey()+" :"+e.getValue()); } System.out.println(); }
打印结果:
注意:
(1)HashMap
中的 key
可以为空;
(2)结果不一定有序;
Map
是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap
或者HashMap
;
Map
中存放键值对的Key
是唯一的,value
是可以重复的;
Map
中的 Key
可以全部分离出来,存储到 Set
中来进行访问(因为Key不能重复) ;
Map
中键值对的Key
不能直接修改,value
可以修改,如果要修改key
,只能先将该key
删除掉,然后再来进行重新插入;
由上可以看出:Set实质是一个接口,继承自 collection ,底层是通过TreeSet、HashSet两个实现类来实现的
;
方法 | 说明 |
---|---|
boolean add(E e) | 添加元素,但重复元素不会被添加成功 |
void clear() | 清空集合 |
boolean contains(Object o) | 判断 o 是否在集合中 |
Iterator iterator() | 返回迭代器 |
boolean containsAll(Collection<?> c) | 集合c中的元素是否在set中全部存在 |
boolean remove(Object o) | 删除集合中的 o |
int size() | 返回set中元素的个数 |
boolean isEmpty() | 检测set是否为空,为空返回true,否则返回false |
Object[] toArray() | 将set中的元素转换为数组返回 |
boolean addAll(Collection<?extends E> c) | 将集合c中的元素添加到set中,可以达到去重的效果 |
TreeSet使用:
import java.util.TreeSet; import java.util.Iterator; import java.util.Set; public class TestSet { public static void main(String[] args) { Set<String> s = new TreeSet<>(); // add(key): 如果key不存在,则插入,返回ture // 如果key存在,返回false s.add("apple"); s.add("orange"); s.add("peach"); s.add("banana"); System.out.println(s.size()); System.out.println(s); //[apple, banana, orange, peach] s.add("apple"); System.out.println(s);//添加元素,重复元素不会被添加成功,结果仍是[apple, banana, orange, peach] // add(key): key如果是空,抛出空指针异常 //s.add(null); // contains(key): 如果key存在,返回true,否则返回false System.out.println(s.contains("apple"));//true System.out.println(s.contains("watermelon"));//false // remove(key): key存在,删除成功返回true // key不存在,删除失败返回false // key为空,抛出空指针异常 s.remove("apple"); System.out.println(s); //[banana, orange, peach] s.remove("watermelon"); System.out.println(s); // 抛出空指针异常 //s.remove(null); //迭代器遍历 Iterator<String> it = s.iterator(); while(it.hasNext()){ System.out.print(it.next() + " "); } System.out.println(); } }
结果打印:
注意:
(1) TreeSet
打印结果是有序的;
(2)不能插入空 key
;
HashSet 的使用:
public class TestSet { public static void main(String[] args) { Set<String> s = new HashSet<>(); // add(key): 如果key不存在,则插入,返回ture // 如果key存在,返回false s.add("apple"); s.add("orange"); s.add("peach"); s.add("banana"); System.out.println(s.size()); //4 System.out.println(s); //[orange, banana, apple, peach] s.add("apple"); System.out.println(s);//添加元素,重复元素不会被添加成功,结果仍是[orange, banana, apple, peach] //key可以为空 s.add(null); System.out.println(s);//[orange, banana, null, apple, peach] System.out.println(s.size()); //5 // contains(key): 如果key存在,返回true,否则返回false System.out.println(s.contains("apple"));//true System.out.println(s.contains("watermelon"));//false // remove(key): key存在,删除成功返回true // key不存在,删除失败返回false s.remove("apple"); System.out.println(s); //[orange, banana,null, peach] s.remove("watermelon"); System.out.println(s);//[orange, banana,null, peach] s.remove(null); System.out.println(s);//[orange, banana, peach] Iterator<String> it = s.iterator(); while(it.hasNext()){ System.out.print(it.next() + " "); } System.out.println(); } }
结果输出:
注意:
(1)HashSet
打印结果是无序的;
(2) 可以插入null
;
Set
中只存储了key
,并且要求 key
唯一;Set
的底层是使用 Map
来实现的,其使用 key
与Object
的一个默认对象作为键值对插入到 Map
中;Java源代码中:
Set
最大的功能就是对集合中的元素进行去重;Set
接口的常用类有 TreeSet
和 HashSet
;Set
中的Key
不能修改,如果要修改,先将原来的删除掉,然后再重新插入;Set
中不能插入null
的key
;Map
统计数组中每个元素出现的次数,因为不用考虑是否有序,所以采用 HashMap
;代码实现:
class Solution { public int singleNumber(int[] nums) { //HashMap统计每个元素出现的次数 HashMap<Integer,Integer> m = new HashMap<>(); for(int i = 0;i<nums.length;i++){ m.put(nums[i],m.getOrDefault(nums[i],0)+1); } //遍历找只出现一次的元素 for(Map.Entry<Integer,Integer> entry:m.entrySet()){ if(entry.getValue() == 1) return entry.getKey(); } return 0; } }
cur
创建一个新结点newNode
;HashMap
中插入---->put
;m.get(cur).next=m.get(cur.next)
m.get(cur).random=m.get(cur.random)
;代码实现:
class Solution { public Node copyRandomList(Node head) { //链表为空时 if(head==null){ return null; } Map<Node,Node> m=new HashMap<>(); Node cur=head; while(cur!=null){ //插入相同值域的结点 m.put(cur,new Node(cur.val)); cur=cur.next; } cur=head; while(cur!=null){ //将插入的新结点链接起来 m.get(cur).next=m.get(cur.next); //给插入结点的随机指针域赋值 m.get(cur).random=m.get(cur.random); cur=cur.next; } return m.get(head); } }
leetcode:宝石与石头
思路
:
代码实现:
class Solution { public int numJewelsInStones(String jewels, String stones) { //统计石头出现的次数 Map<Character,Integer> m=new HashMap<>(); for(int i=0;i<stones.length();i++){ Character ch=stones.charAt(i); m.put(ch,m.getOrDefault(ch,0)+1); } //统计宝石的个数 int count=0; for(int i=0;i<jewels.length();i++){ Character ch=jewels.charAt(i); count+=m.getOrDefault(ch,0); } return count; } }
牛客网:旧键盘打字
思路
:
对于IO类型的题目,必须要包含:
(1)创建一个
Main
类;
(2)在写一个main
方法;
(3)将所需要的包导入;
(4)循环接收数据的输入
本题目中:
in
)、输出(out
)的数据,并将其转换为大写字符;set
中;---->不考虑有序,采用HashSet
;set
中插入,插入成功则表明是坏键(set
具有去重的功能),将其删除;代码实现:
//导入需要的包 import java.util.Scanner; import java.util.HashSet; import java.util.Set; //写一个Main类 public class Main{ //写一个main方法 public static void main(String[] args){ Scanner sc=new Scanner(System.in); //循环接收用户输入的String,将其转换成大写 //循环接收输出的String,将其转换成大写 while(sc.hasNext()){ String in=sc.nextLine().toUpperCase(); String out=sc.nextLine().toUpperCase(); //将out结果存放在HashSet中 Set<Character> s=new HashSet<>(); for(int i=0;i<out.length();i++){ s.add(out.charAt(i)); } //将in中的每个字符往set中插入,插入成功---就是坏键,输出 for(int i=0;i<in.length();i++){ if(s.add(in.charAt(i))){ System.out.print(in.charAt(i)); } } System.out.println(); } } }
leetcode:前K个高频单词
思路
:
借助Map
和优先级队列
Map
统计每个单词出现的次数;K
个元素来建小堆(需要比较器),将剩余的N-K
个元素与堆顶元素进行比较,大于堆顶的元素往前移;代码实现:
class Less implements Comparator<Map.Entry<String,Integer>>{ public int compare(Map.Entry<String,Integer> o1, Map.Entry<String,Integer> o2){ if(o1.getValue()>o2.getValue()){ return 1; } if(o1.getValue()==o2.getValue() && o2.getKey().compareTo(o1.getKey())>0){ return 1; } if(o2.getValue()==o2.getValue() && o1.getKey().compareTo(o2.getKey())==0){ return 0; } return -1; } } class Solution { public List<String> topKFrequent(String[] words, int k) { //统计每个单词出现的次数---HashMap Map<String,Integer> m=new HashMap<>(); for(int i=0;i<words.length;i++){ m.put(words[i],m.getOrDefault(words[i],0)+1); } //TOP-K //用前K个建小堆 Less cmp=new Less(); PriorityQueue<Map.Entry<String,Integer>> p=new PriorityQueue<>(cmp); //entrySet获取键值对(单词,次数) Set<Map.Entry<String,Integer>> s=m.entrySet(); int i=0; for(Map.Entry<String,Integer> kv:s){ if(i<k){ p.offer(kv); i++; }else{ //用N-K个与堆顶元素比较,找最大的; if(cmp.compare(kv,p.peek())>=0){ p.poll(); p.offer(kv); } } } LinkedList<String> list=new LinkedList<>(); for(i=0;i<k;i++){ list.addFirst(p.poll().getKey()); } return list; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。