Java深入理解及巩固 Map & Set 集合

Map和Set接口关系

在这里插入图片描述

Map

概念

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

常用方法

方法解释
V get(Object key)返回 key 对应的 value
V getOrDefault(Object key, V defaultValue)返回 key 对应的 value,key 不存在,返回默认值
V put(K key, V value)设置 key 对应的 value
V remove(Object key)删除 key 对应的映射关系
Set< K> keySet()返回所有 key 的不重复集合
Collection< V> values()返回所有 value 的可重复集合
Set<Map.Entry<K, V>> entrySet()返回所有的 key-value 映射关系
boolean containsKey(Object key)判断是否包含 key
boolean containsValue(Object value)判断是否包含 value

代码演示

        //两种创建方式
        HashMap<String,Integer> hashMap = new HashMap<>();
        Map<String,Integer> map = new HashMap<>();

        //put
        map.put("xing",1);
        map.put("fei",2);
        map.put("fan",3);
        System.out.println(map);//{xing=1, fan=3, fei=2}

        //get
        int a = map.get("xing");
        System.out.println(a);  //1

        //get
        int b = map.getOrDefault("xing",88);
        int c = map.getOrDefault("lala",88);
        System.out.println(b);//1
        System.out.println(c);//88

        //remove
        map.remove("fan");
        System.out.println(map);//{xing=1, fei=2}
        map.put("fan",3);

        //keySet
        Set<String> set = map.keySet();
        System.out.println(set);//[xing, fan, fei]

        //values
        Collection<Integer> val = map.values();
        System.out.println(val);//[1, 3, 2]

        //entrySet
        Set<Map.Entry<String,Integer>> set2 = map.entrySet();
        for(Map.Entry<String,Integer> entry : set2) {
            System.out.print(entry.getKey() + "=" + entry.getValue() + "  ");//xing=1  fan=3  fei=2
        }
        System.out.println();

        //containsKey
        boolean d = map.containsKey("xing");
        boolean e = map.containsKey("lala");
        System.out.println(d);//true
        System.out.println(e);//false

        // containsValue
        boolean f = map.containsValue(2);
        boolean g = map.containsValue(5);
        System.out.println(f);//true
        System.out.println(g);//false

注意事项

  1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap
  2. Map中存放键值对的Key是唯一的,value是可以重复的
  3. 在Map中插入键值对时,key和value都可以为空
  4. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
  5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
  6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行
    重新插入。
  7. TreeMap和HashMap的区别
Map底层结构TreeMapHashMap
底层结构红黑树哈希桶
插入/删除/查找时间复杂度O(logn)O(1)
是否有序关于Key有序无序
线程安全不安全不安全
插入/删除/查找区别需要进行元素比较通过哈希函数计算哈希地址
比较与覆写key必须能够比较,否则会抛出ClassCastException异常自定义类型需要覆写equals和hashCode方法
应用场景需要Key有序场景下Key是否有序不关心,需要更高的时间性能

Set

概念

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

常用方法

方法解释
boolean add(E e)添加元素,但重复元素不会被添加成功
void clear()清空集合
boolean contains(Object o)判断 o 是否在集合中
Iterator< E> iterator()返回迭代器
boolean remove(Object o)删除集合中的 o
int size()返回set中元素的个数
boolean isEmpty()检测set是否为空,空返回true,否则返回false
Object[] toArray()将set中的元素转换为数组返回
boolean containsAll(Collection<?> c)集合c中的元素是否在set中全部存在,是返回true,否则返回false
boolean addAll(Collection<? extendsE> c)将集合c中的元素添加到set中,可以达到去重的效果

注意事项

  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。
  8. TreeSet和HashSet的区别
Set底层结构TreeSetHashSet
底层结构红黑树哈希桶
插入/删除/查找时间复杂度O(logn)O(1)
是否有序关于Key有序不一定有序
线程安全不安全不安全
插入/删除/查找区别按照红黑树的特性来进行插入和删除1. 先计算key哈希地址 2. 然后进行插入和删除
比较与覆写key必须能够比较,否则会抛出ClassCastException异常自定义类型需要覆写equals和hashCode方法
应用场景需要Key有序场景下Key是否有序不关心,需要更高的时间性能

代码演示

        Set<Integer> set = new HashSet<>();

        //add
        set.add(1);
        set.add(2);
        set.add(3);
        System.out.println(set);//[1, 2, 3]

        //clear
        set.clear();
        System.out.println(set);//[]
        set.add(1);
        set.add(2);
        set.add(3);

        //contains
        boolean a = set.contains(2);
        boolean b = set.contains(4);
        System.out.println(a);//true
        System.out.println(b);//false

        //iterator
        Iterator<Integer> it = set.iterator();
        while(it.hasNext()){
            System.out.print(it.next() + " ");//1 2 3
        }
        System.out.println();

        //remove
        System.out.println(set.remove(1));//true
        System.out.println(set.remove(4));//false
        set.add(1);

        //size
        System.out.println(set.size());//3

        //isEmpty
        System.out.println(set.isEmpty());//false

        //toArray
        Object[] arr = set.toArray();
        System.out.println(Arrays.toString(arr));//[1, 2, 3]

        //containsAll
        Set<Integer> set2 = new HashSet<>();
        set.add(3);
        set2.add(4);
        System.out.println(set.containsAll(set2));//false

        //addAll
        set.addAll(set2);
        System.out.println(set);//[1, 2, 3, 4]

巩固练习

(一)找出第一个重复的数据

list当中存放的数据为10万个,找出第一个重复的数据

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 10_0000; i++) {
            list.add(random.nextInt(10_0000));
        }
        System.out.println(list);
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < list.size(); i++) {
            if(set.contains(list.get(i))) {
                System.out.println(list.get(i));
                break;
            }else {
                set.add(list.get(i));
            }
        }
    }

(二)重复的数字全部都去重

list当中有10万个数据,把重复的数字全部都去重

public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 10_0000; i++) {
            list.add(random.nextInt(10_0000));
        }
        System.out.println(list);
        Set<Integer> set = new HashSet<>();
        //方式一:
        for (int i = 0; i < list.size(); i++) {
            set.add(list.get(i));
        }
        System.out.println(set);
        //方式二:
        set.addAll(list);
        System.out.println(set);
    }

(三)统计重复数字及出现的次数

将10万个数据,统计重复数字及出现的次数

    public static void main(String[] args) {
        Random random = new Random();
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10_0000; i++) {
            list.add(random.nextInt(10_0000));
        }
        Map<Integer,Integer> map = new HashMap<>();
        for (int i = 0; i < 10_0000; i++) {
            int tmp = list.get(i);
            if(map.containsKey(tmp)) {
                map.put(tmp,map.get(tmp)+1);
            }else {
                map.put(tmp,1);
            }
        }
        for (Map.Entry<Integer,Integer> entry : map.entrySet()) {
            if(entry.getValue() > 1) {
                System.out.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }

(四)只出现一次的数字

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

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1] 输出: 1 示例 2:

输入: [4,1,2,1,2] 输出: 4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/single-number


class Solution {
    public int singleNumber(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for(int i = 0; i < nums.length; i++) {
            if(set.contains(nums[i])) {
                set.remove(nums[i]);
            }else {
                set.add(nums[i]);
            }
        }
        for(int i : set) {
            return i;
        }
        return -1;
    }
}
class Solution {
    public int singleNumber(int[] nums) {
        int res = nums[0];
        for(int i = 1; i < nums.length; i++) {
            res = res ^ nums[i];
        }
        return res;
    }
}

(五)复制带随机指针的链表

给你一个长度为 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:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]] 示例 2:

输入:head = [[1,1],[2,1]] 输出:[[1,1],[2,1]] 示例 3:

输入:head = [[3,null],[3,0],[3,null]] 输出:[[3,null],[3,0],[3,null]] 示例 4:

输入:head = [] 输出:[] 解释:给定的链表为空(空指针),因此返回 null。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/copy-list-with-random-pointer

/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        Map<Node,Node> map = new HashMap<>();
        Node cur = head;
        while(cur != null) {
            Node tmp = new Node(cur.val);
            map.put(cur,tmp);
            cur = cur.next;
        }
        cur = head;
        while(cur != null) {
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        return map.get(head);
    }
}

(六)宝石与石头

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

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

示例 1:

输入: J = “aA”, S = “aAAbbbb” 输出: 3 示例 2:

输入: J = “z”, S = “ZZ” 输出: 0

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jewels-and-stones

class Solution {
    public int numJewelsInStones(String jewels, String stones) {
        Set<Character> set = new HashSet<>();
        for(int i = 0; i < jewels.length(); i++) {
            set.add(jewels.charAt(i));
        }
        int count = 0;
        for(int i = 0; i < stones.length(); i++) {
            if(set.contains(stones.charAt(i))) {
                count++;
            }
        }
        return count;
    }
}

(七)旧键盘

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

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

输出描述: 按照发现顺序,在一行中输出坏掉的键。其中英文字母只输出大写,每个坏键只输出一次。题目保证至少有1个坏键。 示例1 输入
7_This_is_a_test
_hs_s_a_es 输出 7TI

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

import java.util.*;

public class Main {
    
    public static void main(String[] args) {
        Scanner cin = new Scanner(System.in);
        String a = cin.nextLine().toUpperCase();
        String b = cin.nextLine().toUpperCase();
        Set<Character> set = new HashSet<>();
        Set<Character> con = new HashSet<>();
        for(char c : b.toCharArray()) {
            set.add(c);
        }
        for(char c : a.toCharArray()) {
            if(!set.contains(c) && !con.contains(c)) {
                con.add(c);
                System.out.print(c);
            }
        }
    }
    
}

(八)前K个高频单词

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

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

示例 1:

输入: [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2 输出: [“i”,
“love”] 解析: “i” 和 “love” 为出现次数最多的两个单词,均为2次。
注意,按字母顺序 “i” 在 “love” 之前。

示例 2:

输入: [“the”, “day”, “is”, “sunny”, “the”, “the”, “the”, “sunny”, “is”,
“is”], k = 4 输出: [“the”, “is”, “sunny”, “day”] 解析: “the”, “is”,
“sunny” 和 “day” 是出现次数最多的四个单词,
出现次数依次为 4, 3, 2 和 1 次。

注意:

假定 k 总为有效值, 1 ≤ k ≤ 集合元素数。 输入的单词均由小写字母组成。

扩展练习:

尝试以 O(n log k) 时间复杂度和 O(n) 空间复杂度解决

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/top-k-frequent-words

class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        Map<String,Integer> map = new HashMap<>();
        for(String s : words) {
            if(map.containsKey(s)) {
                map.put(s,map.get(s)+1);
            }else {
                map.put(s,1);
            }
        }
        PriorityQueue<String> queue = new PriorityQueue<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                if(map.get(o1).equals(map.get(o2))) {
                    return o2.compareTo(o1);
                }else {
                    return map.get(o1) - map.get(o2);
                }
            }
        });
        for(String s : map.keySet()) {
            queue.offer(s);
            if(queue.size() > k) {
                queue.poll();
            }
        }
        List<String> list = new ArrayList<>(k);
        while (queue.size() > 0) {
            list.add(queue.poll());
        }
        Collections.reverse(list);
        return list;
    }
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值