通过简单的两数相加体会hashmap的好处

引入题目:两数相加

* 给定一个整数数组 nums 和一个目标值 target
* 请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
* 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

来源:LeetCode

第一种方法:暴力法

通过遍历数组的其余部分来寻找它所对应的目标元素

/**
 * 暴力法
 * 时间复杂度:O(n^2)
 * 空间复杂度:O(1)
 *
 * @param nums
 * @param target
 * @return
 */
public static int[] twoSum1(int[] nums, int target) {
    for (int i = 0; i < nums.length; i++) {
        for (int j = i + 1; j < nums.length; j++) {
            //因为 nums[0] + nums[1] = 2 + 7 = 9
            if ((nums[i] + nums[j]) == target) {
                //所以返回 [0, 1]
                return new int[]{i, j};
            }
        }
    }

    throw new IllegalArgumentException("No two sum solution");
}

第二种方法:两遍哈希表

通过以空间换取速度的方式。在第一次迭代中,我们将每个元素的值和它的索引添加到表中。然后,在第二次迭代中,我们将检查每个元素所对应的目标元素(target - nums[i])是否存在于表中。

/**
 * 两遍哈希表
 * 时间复杂度:O(n)
 * 空间复杂度:O(n)
 * @param nums
 * @param target
 * @return
 */
public static int[] twoSum2(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    //将每个元素的值和索引添加到表中
    for (int i = 0; i < nums.length; i++) {
        map.put(nums[i], i);
    }
    for (int i = 0; i < nums.length; i++) {
        //检查每个元素所对应的目标元素(target - nums[i])是否存在于表中
        int complement = target - nums[i];
        if (map.containsKey(complement) && map.get(complement) != i) {
            return new int[]{i, map.get(complement)};
        }
    }
    throw new IllegalArgumentException("No two sum solution");
}

方法三:一遍哈希表

在进行迭代并将元素插入到表中的同时,我们还会回过头来检查表中是否已经存在当前元素所对应的目标元素。如果它存在,那我们已经找到了对应解,并立即将其返回。

/**
 * 一遍哈希表
 * @param nums
 * @param target
 * @return
 */
public static int[] twoSum3(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        //如果hashmap里存在对应的目标元素,立即存进结果集合返回
        if (map.containsKey(complement)) {
            return new int[] { map.get(complement), i };
        }
        map.put(nums[i], i);
    }
    throw new IllegalArgumentException("No two sum solution");
}

通过运行,不难发现,用HashMap的方式速度要快的多。这是因为HashMap集合底层是哈希表,查询的速度特别的快。

JDK1.8之前底层数据结构采用的是:数组+单向链表
JDK1.8之后底层数据结构采用的是:数组+单向链表|红黑树(链表的长度超过8):提高查询的速度

HashMap相关知识:

Map集合

java.util.Map<k,v>集合,是夫妻对儿集合 Map<K,V> ,定义了双列集合规范,每次存储一对儿元素

通过 可以找 对应的值 ,K代表键(Key)的类型,V代表值(Value)的类型。

Map集合的特点

  • Map集合是一个双列集合,一个元素包含两个值(一个key,一个value)
  • Map集合中的元素,key和value的数据类型可以相同,也可以不同
  • Map集合中的元素,key是唯一的,value是可以重复的
  • Map集合中的元素,key和value是一一对应

Map常用子类

HashMap集合、LinkedHashMap集合。

tips:Map接口中的集合都有两个泛型变量,在使用时要为两个泛型变量赋于数据类型。

HashMap集合

java.util.HashMap<k,v>集合 implements Map<k,v>接口
特点:

  • HashMap集合底层是哈希表:查询的速度特别的快

    ​JDK1.8之前:数组+单向链表

    ​JDK1.8之后:数组+单向链表|红黑树(链表的长度超过8):提高查询的速度

  • hashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致

  • 由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法

LinkedHashMap集合

java.util.LinkedHashMap<k,v>集合 extends HashMap<k,v>集合
特点:

  • LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序)
  • LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的
  • 通过哈希表结构可以保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法

Map集合的常用方法

public V put(K key, V value) : 把指定的键与指定的值添加到Map集合中。

​ 返回值:V
​ 存储键、值对的时候,key不重复,返回值V是null
​ 存储键、值对的时候,key重复,会使用新的value替换map中重复的value,返回被替换的value值

public V remove(Object key): 把指定的键 所对应的键、值对元素 在Map集合中删除.

​ 返回被删除元素的值。
​ key存在,v返回被删除的值 --Integer赋值给int可以自动拆箱
​ key不存在,v返回null --Integer赋值给int会报错NullPointerException

public V get(Object key): 根据指定的键,在Map集合中获取对应的值。
​ 返回值:
​ key存在,返回对应的value值
​ key不存在,返回null

values(): 获取Map中的所有value

Map集合的第一种遍历方式:通过键找值的方式

Map集合中的方法:
Set<K> keySet(): 返回此映射中包含的键的 Set 视图。

实现步骤:

  1. 使用Map集合中的方法keySet(),把Map集合所有的key取出来,存储到一个Set集合中
  2. 遍历Set集合,获取Map集合中的每一个key
  3. 通过Map集合中的方法get(key),通过key找到value

例一种最快的:

//使用增强for遍历Set集合
for(String key : map.keySet()){
   //通过Map集合中的方法get(key),通过key找到value
   Integer value = map.get(key);
   System.out.println(key+"="+value);
}

Map集合遍历的第二种方式:使用Entry对象遍历

Map.Entry<K,V>:在Map接口中有一个Entry接口。作用:存键、值对(结婚证)

Map集合中的方法:
Set<Map.Entry<K,V>> entrySet():获取到Map对象中所有键、值对 对象的集合(Set集合)

实现步骤:

  1. 使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
  2. 遍历Set集合,获取每一个Entry对象
  3. 使用Entry对象中的方法getKey()和getValue()获取键与值

Map集合不能直接使用迭代器或foreach进行遍历。但是转成Set之后就可以用了

HashMap存储自定义类型键值

(可以是对象,也可以是集合/数组)

Map集合保证key是唯一:对象作为key的元素,必须重写hashCode方法和equals方法,以保证key唯一

比如:

key:Person类型
​ Person类就必须重写hashCode方法和equals方法,以保证key唯一
value:String类型
​ 可以重复

又比如:

key:String类型
​ String类重写hashCode方法和equals方法,可以保证key唯一
value:Person类型
​ value可以重复(同名同年龄的人视为同一个)

Hashtable<K,V>集合

java.util.Hashtable<K,V>集合 implements Map<K,V>接口

  • Hashtable:底层也是一个哈希表,是一个线程安全的集合,是单线程集合,速度慢

  • Hashtable集合,不能存储null值,null键

  • HashMap:底层是一个哈希表,是一个线程不安全的集合,是多线程的集合,速度快

  • HashMap集合(之前学的所有的集合):可以存储null值,null键

  • Hashtable和Vector集合一样,在jdk1.2版本之后被更先进的集合(HashMap,ArrayList)取代了

  • Hashtable的子类Properties依然活跃在历史舞台

Properties集合是一个唯一和IO流相结合的集合

静态方法of(E... elements)

JDK9的新特性:

List接口,Set接口,Map接口: 里边增加了一个静态的方法of,可以给集合一次性添加多个元素

static List<E> of(E... elements)

使用前提:
当集合中存储的元素的个数已经确定了,不在改变时使用
注意:

  1. of方法只适用于List接口,Set接口,Map接口,不适用于接接口的实现类
  2. of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常
  3. Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常

例:

Map<String,Integer> map = Map.of("a",1,"bb",2,"ccc",3);
System.out.println(map);
posted @ 2019-03-16 19:35  shinl00  阅读(715)  评论(0编辑  收藏  举报