数据结构之map和set

本文详细介绍了Java中的数据结构Map和Set,包括它们的特点、接口使用、常见子类的添加问题以及特殊情况如保存Null值的说明。重点讨论了Set作为披着Set外衣的Map,用于去重处理,以及Map接口中的键值对操作,如put()方法的添加与修改功能。同时,给出了Map在解决实际问题中的应用,如寻找只出现一次的数字、复制带随机指针的链表、单词规律匹配和宝石计数等。
摘要由CSDN通过智能技术生成

数据结构之map和set

主要的数据结构:

二分搜索树:在Java标准库中对应TreeMap(基于红黑树-二分平衡搜索树RBTree的实现)/TreeSet

哈希表:在Java标准库中对应HashMap(基于哈希表的实现)/HashSet

一、Set集合和Map结合的特点

set:其实就是披着Set外衣的Map,也是Collection接口的子接口,一次保存一个元素,和List集合最大的区别在于:

Set集合保存的元素不能重复,List可以重复,Collection接口一次只能保存一个元素

经常使用Set集合来进行去重处理

这个两个结合一般是用来进行查找操作

Map接口和Collection没有任何关系

Map接口就是最顶层的父接口,表示保存的是一对键值对对象

接口示意图
在这里插入图片描述

二、Map接口和Set集合的基础使用

Set集合常用方法

方法含义
boolean add(E e)添加一个元素,若要元素不存在则添加成功,返回True;若添加失败,返回False
boolean contains(Object o)判断一个元素o是否在当前set存在
boolean remove(Object o)在Set集合中删除指定元素,若该元素不存在,删除失败返回false;否则删除该元素,返回True

在Set集合中没有提供修改的方法,若需要修改元素,只能先把要修改的元素删除,在添加新元素

遍历Set集合:使用for-each循环

public class SetTest {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();
        System.out.println(set.add(1));
        System.out.println(set.add(1));
        System.out.println(set.add(2));
        System.out.println(set.add(3));
        System.out.println(set.contains(3));
        System.out.println(set.contains(4));
        System.out.println(set.remove(2));
        for (int i : set) {
            System.out.print(i + " ");
        }
    }
}

输出:
true
false
true
true
true
false
true
1 3 

Map接口的常用方法:保存的是一对键值对元素 <key,value>

方法含义
V put(K key, V value)添加一对键值对
boolean containsKey(Object key)判断元素在当前Map的key中是否存在
boolean containsValue(Object value)判断元素在当前Map的value中是否存在
Set<Map.Entry<K, V>> entrySet();返回Map中所有的键值对对象Map.Entry[]对象
Set keySet();返回当前Map接口中所有的key值集合,因为是不重复的,所以使用了Set集合
Collection values();返回当前Map接口中所有的value值集合,因为是可以重复的,所以使用collection接口接收
V get(Object key);根据相应的key值取得相应的value值,若Key不存在,返回null
default V getOrDefault(Object key, V defaultValue)根据相应的key值取得相应的value值,若key不存在,返回默认值defaultVal
V remove(Object key);删除指定k值的一对键值对,返回value值

Map中键值对的类型是: Map.Entry ,它是一个对象,其中包含两个方法:

entry.getKey(): 获取当前键值对的Key

entry.getValue(): 获取当前键值对的value

Map集合的遍历

public static void main(String[] args) {
    // key不重复,value可以重复
    Map<Integer,Integer> map = new HashMap<>();
    // 1 = 10
    map.put(1,10);
    map.put(2,20);
    // value可以重复,用一个key会得到一个value
    map.put(3,10);
    System.out.println(map.containsKey(3));
    System.out.println(map.containsValue(40));
    // Map.Entry[],遍历
    for (Map.Entry<Integer,Integer> entry : map.entrySet()) {
        System.out.println(entry.getKey() + " = " + entry.getValue());
    }
}

Map的put()方法说明

即是新增又是修改

添加一个新的键值对:

若key不存在,就新增一个Map.Entry对象,保存到Map中。若Key已存在,修改原来的value为新的value,返回修改前的value值

Map接口中,元素添加的顺序和保存的顺序没有必然联系

public static void main(String[] args) {
    Map<String,Integer > map = new HashMap<>();
    map.put("王五",10);
    map.put("李四",12);
    map.put("孙武",13);
    map.put("张三",14);
    System.out.println(map.put("王五",22));
    //取出所有的key
    Set<String> keys = map.keySet();
    System.out.println("当前所有的key为");
    for (String str : keys){
        System.out.print(str + " ");
    }
    System.out.println();
    //取出所有的value
    Collection<Integer> values = map.values();
    for (Integer age : values){
        System.out.print(age + " ");
    }
}
输出:
10
当前所有的key为
李四 张三 孙武 王五 
12 14 13 22 //乱序

三、Map接口常见子类的添加问题

  1. Map接口中元素的添加顺序和元素的保存顺序没有必然的联系
  2. HashMap中保存的元素顺序由hash函数来决定
  3. TreeMap中保存的元素顺序由TreeMap中的comparTo方法决定
  4. 要使用TreeMap保存元素,该类要么实现Comparable接口要么传入一个比较器
public static void main(String[] args) {
    Map<String,Integer > map = new HashMap<>();
    map.put("王五",10);
    map.put("李四",12);
    map.put("孙武",13);
    map.put("张三",14);
    System.out.println(map.toString());
}
输出:
{李四=12, 张三=14, 孙武=13, 王五=10} //乱序

TreeMap不覆写ComparTo方法的结果 ----报错
HashMap不会报错-----这两个用的是不同的底层结构

public class MapTest {
    public static void main(String[] args) {
        Map<ZhangSan,String> map = new TreeMap<>();
        map.put(new ZhangSan(),"test");
    }
}
class ZhangSan {}

报错:
jcl.ZhangSan cannot be cast to java.lang.Comparable

四、Map保存Null值的说明

HashMap是可以保存Null值的,HashMap的key和value都能为null,但是key为null的时候,有且只有一个

TreeMap中key不能为空,value可以为空

HashMap

public static void main(String[] args) {
    Map<String ,String > map = new HashMap<>();
    map.put(null,null);
    map.put(null,"test");
    System.out.println(map.toString());
}
输出:
{null=test}

看一个代码

public static void main(String[] args) {
    Map<String,String> map = new TreeMap<>();
    try {
        map.put(null,null);
    }catch (NullPointerException e) {
        System.out.println("key为空");
    }
    map.put("张三",null);
    System.out.println(map);
}
输出:
key为空
{张三=null}

五、练习题

只出现一次的数字

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

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

思路:利用Map集合保存每个元素和出现的频次,然后遍历map,道道频次为一的那个元素。

public int singleNumber(int[] nums) {
    // 1.先扫描原数组,将每个元素以及出现的频次保存到Map中
    Map<Integer,Integer> map = new HashMap<>();
    for (int i : nums) {
        map.put(i,map.getOrDefault(i,0) + 1);
    }
    // 2.遍历这个map,找到频次为1的那个元素
    int ret = 0;
    for (Map.Entry<Integer,Integer> entry : map.entrySet()) {
        if (entry.getValue().equals(1)) {
            ret = entry.getKey();
        }
    }
    return ret;
}
出现一次的数字 II

给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 **三次 。**请你找出并返回那个只出现了一次的元素。

输入:nums = [2,2,3,2]
输出:3

这道题和上面的题目一样,代码都不用换,只要找到频次为1的元素就可以了

复制带随机指针的链表

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

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

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

返回复制链表的头节点。

在这里插入图片描述

思路:利用Map集合-它保存的是key和value的映射关系

  1. 遍历原链表构造新链表,在Map集合中维护原链表和新链表的映射关系
  2. 再次遍历原链表,通过映射关系维护新链表的next和random
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) {
        //1.遍历原链表,构造Map集合维护原链表结点和新链表结点的映射关系
        //原1 = 新1
        Map<Node,Node> map = new HashMap<>();
        for (Node x = head; x != null;x = x.next){
            Node node = new Node(x.val);
            map.put(x,node);
        }
        //2.再次遍历原链表,通过映射关系维护新链表的next和random
        for (Node x = head; x != null;x = x.next ){
            //新的结点的next = 原结点的next
            map.get(x).next = map.get(x.next);
            //新节点的random = 原节点的random
            map.get(x).random = map.get(x.random);
        }
        //原链表的头结点对应新链表的头结点
        return map.get(head);
    }
}
单词规律

给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。

输入: pattern = “abba”, str = “dog cat cat dog”
输出: true

输入: pattern = “aaaa”, str = “dog cat cat dog”
输出: false

思路:这种题目是模式匹配问题,都可以采用Map映射结构解决

图下图所示,将其翻译成数字保存到Map集合中。
在这里插入图片描述

public boolean wordPattern(String pattern, String s) {
    //按照空格分隔
    String[] str = s.split(" ");
    //两个字符长度不一样,肯定不是匹配的
    if (str.length != pattern.length()) {
        // pattern = "abba" str = "dog cat cat"
        return false;
    }
    // 建立pattern和str关于数字的映射关系
    int count = 1;
    Map<String,Integer> map1 = new HashMap<>();
    StringBuilder sb1 = new StringBuilder();
    for (String temp : str) {
        if (map1.containsKey(temp)) {
            sb1.append(map1.get(temp));
        }else {
            map1.put(temp,count);
            sb1.append(count ++);
        }
    }
    count = 1;
    Map<Character,Integer> map2 = new HashMap<>();
    StringBuilder sb2 = new StringBuilder();
    // pattern = "abba"
    for (int i = 0; i < pattern.length(); i++) {
        char c = pattern.charAt(i);
        if (map2.containsKey(c)) {
            sb2.append(map2.get(c));
        }else {
            map2.put(c,count);
            sb2.append(count ++);
        }
    }
    // 1221   1221
    return sb1.toString().equals(sb2.toString());
}
宝石与石头

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

字母区分大小写,因此 “a” 和 “A” 是不同类型的石头。

输入:jewels = “aA”, stones = “aAAbbbb”
输出:3

思路:使用Set集合保存jewels中的字符

扫描stones字符串,判断出现的每个字符是否在set中已经存在,若存在就说明是个宝石

public int numJewelsInStones(String jewels, String stones) {
    // 使用Set集合保存jewels中的字符
    Set<Character> set = new HashSet<>();
    for (int i = 0; i < jewels.length(); i++) {
        set.add(jewels.charAt(i));
    }
    // 扫描stones字符串,判断出现的每个字符是否在set中已经存在,若存在就说明是个宝石
    int ret = 0;
    for (int i = 0; i < stones.length(); i++) {
        if (set.contains(stones.charAt(i))) {
            // 是个宝石
            ret ++;
        }
    }
    return ret;
}
旧键盘

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

输入描述:
输入在2行中分别给出应该输入的文字、以及实际被输入的文字。每段文字是不超过80个字符的串,由字母A-Z(包括大、小写)、数字0-9、
以及下划线“_”(代表空格)组成。题目保证2个字符串均非空。
输出描述:
按照发现顺序,在一行中输出坏掉的键。其中英文字母只输出大写,每个坏键只输出一次。题目保证至少有1个坏键。

在这里插入图片描述

思路:因为Set和Map保存元素的顺序和添加顺序无关,所以实际的字符串去扫描期望的字符串,才能保证按照发现顺序输出

public class BadKey {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String expectedStr = null;
        String actualStr = null;
        while (scanner.hasNextLine()) {
            expectedStr = scanner.nextLine();
            actualStr = scanner.nextLine();
        }
        // 输出的字母需要大写,都转为大写处理
        expectedStr = expectedStr.toUpperCase();
        actualStr = actualStr.toUpperCase();
        // 遍历实际输入的字符串,存入到Set中
        Set<Character> set = new HashSet<>();
        for (int i = 0; i < actualStr.length(); i++) {
            set.add(actualStr.charAt(i));
        }
        // 拿着实际输入的set去遍历期望输入的字符串
        // 所谓的坏键就是期望中有,而实际中不存在的字符
        // 因为期望中存在而实际不存在的字符有可能有多个,因此输出时需要过滤重复元素
        Set<Character> single = new HashSet<>();
        for (int i = 0; i < expectedStr.length(); i++) {
            char c = expectedStr.charAt(i);
            // 坏键就是期望中存在,但是实际不存在
            if (!set.contains(c)) {
                if (single.add(c)) {
                    System.out.print(c);
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值