LeetCode [454. 四数相加 II]

题目:给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n

nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

  • 示例 1:

    输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
    输出:2
    解释:

    两个元组如下:
    1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
    2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
    
  • 示例 2:

    输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
    输出:1

思路

//分组哈希
class Solution {public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {//创建一个哈希表,key用来存放a+b的值,value存放a+b这个值出现的次数Map<Integer,Integer> map = new HashMap<Integer,Integer>();//定义一个计数器int res = 0;//循环遍历数组1和数组2,得到所有的nums1[i] + nums2[i](各个不同的a + b)for(int a : nums1){for(int b:nums2){map.put(a + b,map.getOrDefault(a + b,0) + 1);}}//循环遍历数组3和数组4,得到所有的nums3[i] + nums4[i](各个不同的c + d)for(int c : nums3){for(int d : nums4){//若出现- (c + d) = a + b,即0 - (c + d) = a + b => a + b + c + d = 0,//即nums1[i] + nums2[i] + nums3[i] + nums4[i] = 0//例:a + b = 2,对应的次数为3次,当- (c + d) = 2,说明此时符合条件,//则计数器加上此时a + b对应的次数if(map.containsKey(-(c + d))){res += map.get(- c- d);}}}return res;}
}

扩展

  • HashMap getOrDefault() 方法
  1. getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。

    getOrDefault() 方法的语法为:

    hashmap.getOrDefault(Object key, V defaultValue)
    

LeetCode [383. 赎金信]

题目:给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

  • 示例 1:

    输入:ransomNote = “a”, magazine = “b”
    输出:false

  • 示例 2:

    输入:ransomNote = “aa”, magazine = “ab”
    输出:false

  • 示例 3:

    输入:ransomNote = “aa”, magazine = “aab”
    输出:true

思路

//哈希统计字符数
class Solution {public boolean canConstruct(String ransomNote, String magazine) {//先判断ransomNote字符串长度是否大于magazine字符串长度,大于的话说明magazine的字符是无法组成ransomNote的if(ransomNote.length() > magazine.length()){return false;}//定义一个26长度的数组,英文字母有26个int[] res = new int[26];//遍历magazine,通过toCharArray()方法将字符串转换为字符数组for(char c : magazine.toCharArray()){//比如c这一轮为'g','g'- 'a'为唯一的标识,进行++运算是为了计数,'g'有多少res[c - 'a']++;}//遍历ransomNote,通过toCharArray()方法将字符串转换为字符数组for(char c : ransomNote.toCharArray()){//比如c这一轮为'g','g'- 'a'为唯一的标识,进行--运算是为了判断ransomNote中'g'的数量//是否大于(--后小于0)magazine中'g'的数量,如果大于就返回falseres[c - 'a']--;if(res[c - 'a'] < 0){return false;}}return true;}
}

LeetCode [15. 三数之和]

题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

  • 示例 1:

    输入:nums = [-1,0,1,2,-1,-4]
    输出:[[-1,-1,2],[-1,0,1]]
    解释:

    nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
    nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
    nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
    不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
    注意,输出的顺序和三元组的顺序并不重要。
    
  • 示例 2:

    输入:nums = [0,1,1]
    输出:[]
    解释:唯一可能的三元组和不为 0 。

  • 示例 3:

    输入:nums = [0,0,0]
    输出:[[0,0,0]]
    解释:唯一可能的三元组和为 0 。

思路

//排序 + 双指针
class Solution {public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> res = new ArrayList<List<Integer>>();//先对数组进行排序Arrays.sort(nums);for(int i = 0;i < nums.length;i++){//排序之后如果第一个元素已经大于零,//无论如何组合都不可能凑成三元组,直接返回结果就可以了if(nums[i] > 0){return res;}/**if (nums[i] == nums[i + 1]) {continue;}*这样写会把三元组中出现重复元素的情况直接pass掉了。 *例如{-1, -1 ,2} 这组数据,当遍历到第一个-1的时候,判断下一个也是-1,那这组数据就pass了。 *我们要做的是不能有重复的三元组,但三元组内的元素是可以重复的!*///对第一个元组进行去重/**这么写就是当前使用nums[i],我们判断前一位是不是一样的元素,*在看 {-1, -1 ,2} 这组数据,当遍历到第一个 -1 的时候,只要前一位没有-1*那么 {-1, -1 ,2} 这组数据一样可以收录到结果集里。*/if(i > 0 && nums[i] == nums[i - 1]){continue;}//设置两个指针,指针 left 定义在i+1的位置上,定义指针 right 在数组结尾的位置上。int left = i + 1;int right = nums.length - 1;//如果left<=right,就不是三元组而是二元组了while(left < right){//定义三数之和int count = nums[i] + nums[left] + nums[right];//如果和大于0,由于是排序后的数组(从小到大),所以向0靠近就需要right--(左移)if(count > 0){right--;//如果和小于0,由于是排序后的数组(从小到大),所以向0靠近就需要left++(右移)}else if(count < 0){left++;//如果和等于于0,满足条件,将此时的数组添加进List}else{/*去重复逻辑如果放在这里,{0,0,0}的情况,*可能直接导致 right<=left 了,从而漏掉了{0,0,0}这种三元组*/List<Integer> list = new ArrayList<Integer>();list.add(nums[i]);list.add(nums[left]);list.add(nums[right]);res.add(list);//下面这个方法是简化版// res.add(Arrays.asList(nums[i], nums[left], nums[right]));//接下来需要进行left和right对应数据的去重/**比如数组{0,-1,-1,-1,1,1,1},*此时nums[i] = 0,nums[left] = -1(第一个),nums[right] = 1(最后一个),*当left++,right--的时候,结果还是*nums[i] = 0,nums[left] = -1(第二个),nums[right] = 1(倒数第二个),*结果已经是重复的了*///当left < right并且此时left对应的数和它后一位相等的话,就持续++(右移)while(left < right && nums[left] == nums[left + 1]){left++;}//当left < right并且此时right对应的数和它前一位相等的话,就持续--(左移)while(left < right && nums[right] == nums[right - 1]){right--;}//找到答案时,双指针同时收缩,直到不符合循环条件right--;left++;}}}return res;}
}

扩展:Arrays.asList();

  • Arrays.asList()

    1. 该方法是将数组转化成List集合的方法。Arrays.asList()将数组转换为集合后,底层其实还是数组

      String[] myArray = { "Apple", "Banana", "Orange" };
      List<String> myList = Arrays.asList(myArray);
      //上面两个语句等价于下面一条语句
      List<String> myList = Arrays.asList("Apple","Banana", "Orange");
      
    2. 传递的数组必须是对象数组,而不是基本类型。

      int[] myArray = { 1, 2, 3 };
      List myList = Arrays.asList(myArray);
      System.out.println(myList.size());//1
      System.out.println(myList.get(0));//数组地址值
      System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
      int[] array=(int[]) myList.get(0);
      System.out.println(array[0]);//1
      

      当传入一个原生数据类型数组时,Arrays.asList() 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List的唯一元素就是这个数组,这也就解释了上面的代码。

      使用包装类型数组就可以解决这个问题。

      Integer[] myArray = { 1, 2, 3 };
      
    3. 使用集合的修改方法:add()remove()clear()会抛出异常。

      List myList = Arrays.asList(1, 2, 3);
      myList.add(4);//运行时报错:UnsupportedOperationException
      myList.remove(1);//运行时报错:UnsupportedOperationException
      myList.clear();//运行时报错:UnsupportedOperationException
      

      Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。

      List myList = Arrays.asList(1, 2, 3);
      System.out.println(myList.getClass());//class java.util.Arrays$ArrayList
      

      下面是java.util.Arrays$ArrayList的简易源码,我们可以看到这个类重写的方法有哪些。

        private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable{...@Overridepublic E get(int index) {...}@Overridepublic E set(int index, E element) {...}@Overridepublic int indexOf(Object o) {...}@Overridepublic boolean contains(Object o) {...}@Overridepublic void forEach(Consumer<? super E> action) {...}@Overridepublic void replaceAll(UnaryOperator<E> operator) {...}@Overridepublic void sort(Comparator<? super E> c) {...}}
      

      再看一下java.util.AbstractListremove()方法,这样我们就明白为什么会抛出UnsupportedOperationException

      public E remove(int index) {throw new UnsupportedOperationException();
      }
      
    4. 如何正确的将数组转换为ArrayList

      1. 自己动手实现

        //JDK1.5+
        static <T> List<T> arrayToList(final T[] array) {final List<T> l = new ArrayList<T>(array.length);for (final T s : array) {l.add(s);}return (l);
        }
        
        Integer [] myArray = { 1, 2, 3 };
        System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
        
      2. 最简便的方法(推荐)

        List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
        
      3. 使用 Java8 的Stream(推荐)

        Integer [] myArray = { 1, 2, 3 };
        List myList = Arrays.stream(myArray).collect(Collectors.toList());
        //基本类型也可以实现转换(依赖boxed的装箱操作)
        int [] myArray2 = { 1, 2, 3 };
        List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
        
      4. 使用 Guava(推荐)

        对于不可变集合,你可以使用ImmutableList类及其of()copyOf()工厂方法:(参数不能为空)

        List<String> il = ImmutableList.of("string", "elements");  // from varargs
        List<String> il = ImmutableList.copyOf(aStringArray);      // from array
        

        对于可变集合,你可以使用Lists类及其newArrayList()工厂方法:

        List<String> l1 = Lists.newArrayList(anotherListOrCollection);    // from collection
        List<String> l2 = Lists.newArrayList(aStringArray);               // from array
        List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
        
      5. 使用 Apache Commons Collections

        List<String> list = new ArrayList<String>();
        CollectionUtils.addAll(list, str);
        

LeetCode [18. 四数之和]

题目:给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

你可以按 任意顺序 返回答案 。

  • 示例 1:

    输入:nums = [1,0,-1,0,-2,2], target = 0
    输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
    
  • 示例 2:

    输入:nums = [2,2,2,2,2], target = 8
    输出:[[2,2,2,2]]
    

思路

四数之和与前面三数之和的思路几乎是一样的,如果前面的三数之和会做了的话,这里其实就是在前面的基础上多添加一个遍历的指针而已。四数之和的双指针解法是两层for循环nums[i] + nums[j]为确定值,依然是循环内有left和right下标作为双指针,找出nums[i] + nums[j] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。

//排序 + 双指针
class Solution {public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> res = new ArrayList<List<Integer>>();//先对数组进行排序Arrays.sort(nums);for(int i = 0;i < nums.length;i++){//排序之后如果第一个元素已经大于零,且大于target//无论如何组合都不可能凑成四元组,直接返回结果就可以了if(nums[i] > 0 && nums[i] > target){return res;}//对第一个元组进行去重if(i > 0 && nums[i - 1] == nums[i]){continue;}for(int j = i + 1;j < nums.length;j++){//对第二个元组进行去重if(j > i + 1 && nums[j - 1] == nums[j]){continue;}//设置两个指针,指针 left 定义在j+1的位置上,定义指针 right 在数组结尾的位置上。int left = j + 1;int right = nums.length - 1;//如果left<=right,就不是四元组而是三元组了while(left < right){//定义四数之和int sum = nums[i] + nums[j] + nums[left] + nums[right];//如何和大于target,由于是排序后的数组(从小到大),所以向target靠近就需要right--(左移)if(sum > target){right--;//如何和小于target,由于是排序后的数组(从小到大),所以向target靠近就需要left++(右移)   }else if(sum < target){left++;//如果和等于于target,满足条件,将此时的数组添加进List}else{res.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));//接下来需要进行left和right对应数据的去重//当left < right并且此时left对应的数和它后一位相等的话,就持续++(右移)while(left < right && nums[left] == nums[left + 1]){left++;}//当left < right并且此时right对应的数和它前一位相等的话,就持续--(左移)while(left < right && nums[right - 1] == nums[right]){right--;}//找到答案时,双指针同时收缩,直到不符合循环条件left++;right--;}}}}return res;}
}

扩展:Collection子接口:List接口;Set接口概述

Collection子接口:List接口

1. 存储的数据特点:

存储序有序的、可重复的数据。

  • 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
  • List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  • JDK AP中List接口的实现类常用的有:ArrayList、LinkedList和 Vector.

2. 常用方法:

List除了从 Collection集合继承的方法外,List集合里添加了一些根据索引来操作集合元素的方法。

  • void add(int index, Object ele):在index位置插入ele元素
  • boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
  • Object get(int index):获取指定index位置的元素
  • int indexOf(Object obj):返回obj在集合中首次出现的位置
  • int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
  • Object remove(int index):移除指定index位置的元素,并返回此元素
  • Object set(int index, Object ele):设置指定index位置的元素为ele
  • List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合

总结:

  • 增:add(Object obj)
  • 删:remove(int index) / remove(Object obj)
  • 改:set(int index, Object ele)
  • 查:get(int index)
  • 插:add(int index, Object ele)
  • 长度:size()
  • 遍历: ① Iterator迭代器方式 ② foreach(增强for循环) ③ 普通的循环

代码示例:

@Test
public void test2(){ArrayList list = new ArrayList();list.add(123);list.add(456);list.add("AA");list.add(new Person("Tom",12));list.add(456);//int indexOf(Object obj):返回obj在集合中首次出现的位置。如果不存在,返回-1.int index = list.indexOf(4567);System.out.println(index);//int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置。如果不存在,返回-1.System.out.println(list.lastIndexOf(456));//Object remove(int index):移除指定index位置的元素,并返回此元素Object obj = list.remove(0);System.out.println(obj);System.out.println(list);//Object set(int index, Object ele):设置指定index位置的元素为elelist.set(1,"CC");System.out.println(list);//List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开区间的子集合List subList = list.subList(2, 4);System.out.println(subList);System.out.println(list);
}@Test
public void test1(){ArrayList list = new ArrayList();list.add(123);list.add(456);list.add("AA");list.add(new Person("Tom",12));list.add(456);System.out.println(list);//void add(int index, Object ele):在index位置插入ele元素list.add(1,"BB");System.out.println(list);//boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来List list1 = Arrays.asList(1, 2, 3);list.addAll(list1);//        list.add(list1);System.out.println(list.size());//9//Object get(int index):获取指定index位置的元素System.out.println(list.get(0));}

3. 常用实现类:

3. 常用实现类:
|----Collection接口:单列集合,用来存储一个一个的对象|----List接口:存储序的、可重复的数据。  -->“动态”数组,替换原的数组|----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储|----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储|----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储

3.1 ArrayList

  • ArrayList是List接口的典型实现类、主要实现类
  • 本质上,ArrayList是对象引用的一个”变长”数组
  • Array Listi的JDK 1.8之前与之后的实现区别?
    • JDK 1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组
    • JDK 1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组
  • Arrays.asList(...)方法返回的List集合,既不是 ArrayList实例,也不是Vector实例。Arrays.asList(...)返回值是一个固定长度的List集合

代码示例:

@Test
public void test1() {Collection coll = new ArrayList();coll.add(123);coll.add(345);coll.add(new User("Tom", 34));coll.add(new User("Tom"));coll.add(false);//iterator()遍历ArrayList集合Iterator iterator = coll.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}
}

3.2 linkedList

  • 对与对于频繁的插入和删除元素操作,建议使用LinkedList类,效率更高
  • 新增方法:
    • void addFirst(Object obj)
    • void addLast(Object obj)
    • Object getFirst()
    • Object getlast)()
    • Object removeFirst()
    • Object removeLast()
  • Linkedlist:双向链表,内部没有声明数组,而是定义了Node类型的frst和last,用于记录首末元素。同时,定义内部类Node,作为 Linkedlist中保存数据的基本结构。Node除了保存数据,还定义了两个变量:
    • prev:变量记录前一个元素的位置
    • next:变量记录下一个元素的位置

代码示例:

@Test
public void test3(){LinkedList linkedList = new LinkedList();linkedList.add(123);linkedList.add(345);linkedList.add(2342);linkedList.add("DDD");linkedList.add("AAA");Iterator iterator = linkedList.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}
}

4. 源码分析(难点)

4.1 ArrayList的源码分析:

4.1.1 JDK 7.0情况下

ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
  • 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
  • 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)

4.1.2 JDK 8.0中ArrayList的变化:

ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
...

后续的添加和扩容操作与JDK 7.0 无异。

4.1.3 小结:

JDK 7.0中的ArrayList的对象的创建类似于单例的饿汉式,而JDK 8.0中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

4.2 LinkedList的源码分析:

LinkedList list = new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。//其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}
}

4.3 Vector的源码分析:

  • Vector是一个古老的集合,JDK 1.0就有了。大多数操作与ArrayList相同,区别在于Vector是线程安全的
  • 在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免选择使用。
  • JDK 7.0和JDK 8.0中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
  • 在扩容方面,默认扩容为原来的数组长度的2倍。

5. 存储的元素的要求:

添加的对象,所在的类要重写equals()方法

6. 面试题

请问 ArrayList/LinkedList/Vector的异同?谈谈你的理解?ArrayList底层是什么?扩容机制? Vector和 ArrayList的最大区别?

  • ArrayList和 Linkedlist的异同:

    二者都线程不安全,相比线程安全的 Vector,ArrayList执行效率高。 此外,ArrayList是实现了基于动态数组的数据结构,Linkedlist基于链表的数据结构。对于随机访问get和set,ArrayList觉得优于Linkedlist,因为Linkedlist要移动指针。对于新增和删除操作add(特指插入)和 remove,Linkedlist比较占优势,因为 ArrayList要移动数据。

  • ArrayList和 Vector的区别:

    Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比 ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。Vector还有一个子类Stack.

Collection子接口:Set接口概述

  • Set接口是Collection的子接口,set接口没有提供额外的方法
  • Set集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set集合中,则添加操作失败。(多用于过滤操作,去掉重复数据)
  • Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法

1.存储的数据特点:

用于存放无序的、不可重复的元素

以HashSet为例说明:

  1. 无序性:不等于随机性。存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。
  2. 不可重复性:保证添加的元素照equals()判断时,不能返回true.即:相同的元素只能添加一个。

2. 元素添加过程:(以HashSet为例)

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断

数组此位置上是否已经有元素:

  • 如果此位置上没有其他元素,则元素a添加成功。 —>情况1
  • 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
    • 如果hash值不相同,则元素a添加成功。—>情况2
    • 如果hash值相同,进而需要调用元素a所在类的equals()方法:
      • equals()返回true,元素a添加失败
      • equals()返回false,则元素a添加成功。—>情况3

对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。

JDK 7.0 :元素a放到数组中,指向原来的元素。

JDK 8.0 :原来的元素在数组中,指向元素a

总结:七上八下

HashSet底层:数组+链表的结构。(JDK 7.0以前)

3. 常用方法

Set接口中没额外定义新的方法,使用的都是Collection中声明过的方法。

3.1 重写hashCode()的基本方法

  • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值。
  • 当两个对象的 equals() 方法比较返回true时,这两个对象的 hashCode() 方法的返回值也应相等。
  • 对象中用作 equals() 方法比较的Field,都应该用来计算hashCode值。

3.2 重写 equals() 方法基本原则

  • 以自定义的 Customer类为例,何时需要重写 equals()
  • 当一个类有自己特有的“逻辑相等”概念,当改写equals()的时候,总是要改写 hash Code(),根据一个类的 equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据 Object.hashCode() 方法,它们仅仅是两个对象。
  • 因此,违反了相等的对象必须具有相等的散列码.
  • 结论:复写equals方法的时候一般都需要同时复写 hashCode 方法。通常参与计算 hashCode的对象的属性也应该参与到 equals() 中进行计算。

3.3 Eclipse/IDEA工具里hashCode()重写

以Eclipse/DEA为例,在自定义类中可以调用工具自动重写 equals()hashCode()

问题:为什么用 Eclipse/IDEA复写 hash Code方法,有31这个数字?

  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
  • 并且31只占用5bits,相乘造成数据溢出的概率较小。
  • 31可以由i*31==(<<5)-1来表示,现在很多虚拟机里面都有做相关优化。(提高算法效率)
  • 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除!(减少冲突)

代码示例:

@Override
public boolean equals(Object o) {System.out.println("User equals()....");if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;if (age != user.age) return false;return name != null ? name.equals(user.name) : user.name == null;
}@Override
public int hashCode() { //return name.hashCode() + age;int result = name != null ? name.hashCode() : 0;result = 31 * result + age;return result;
}

4. 常用实现类:

 |----Collection接口:单列集合,用来存储一个一个的对象|----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”|----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet.|----TreeSet:可以按照添加对象的指定属性,进行排序。

4.1 HashSet

  • Hashset是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。
  • HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。
  • HashSet具有以下特点:
    • 不能保证元素的排列顺序
    • HashSet不是线程安全的
    • 集合元素可以是nul
  • HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
  • 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”

代码示例:

@Test
//HashSet使用
public void test1(){Set set = new HashSet();set.add(454);set.add(213);set.add(111);set.add(123);set.add(23);set.add("AAA");set.add("EEE");set.add(new User("Tom",34));set.add(new User("Jarry",74));Iterator iterator = set.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}
}

4.2 LinkedHashSet

  • LinkedhashSet是HashSet的子类
  • LinkedhashSet根据元素的hashCode值来决定元素的存储位置但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  • LinkedhashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
  • LinkedhashSet不允许集合元素重复。

图示:

代码示例:

@Test
//LinkedHashSet使用
public void test2(){Set set = new LinkedHashSet();set.add(454);set.add(213);set.add(111);set.add(123);set.add(23);set.add("AAA");set.add("EEE");set.add(new User("Tom",34));set.add(new User("Jarry",74));Iterator iterator = set.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}
}

4.3 TreeSet

  • Treeset是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
  • TreeSet底层使用红黑树结构存储数据
  • 新增的方法如下:(了解)
    • Comparator comparator()
    • Object first()
    • Object last()
    • Object lower(object e)
    • Object higher(object e)
    • SortedSet subSet(fromElement, toElement)
    • SortedSet headSet(toElement)
    • SortedSet tailSet(fromElement)
  • TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。

红黑树图示:

红黑树的特点:有序,查询效率比List快

详细介绍:www.cnblogs.com/LiaHon/p/11…

代码示例:

@Test
public void test1(){Set treeSet = new TreeSet();treeSet.add(new User("Tom",34));treeSet.add(new User("Jarry",23));treeSet.add(new User("mars",38));treeSet.add(new User("Jane",56));treeSet.add(new User("Jane",60));treeSet.add(new User("Bruce",58));Iterator iterator = treeSet.iterator();while (iterator.hasNext()){System.out.println(iterator.next());}
}

5. 存储对象所在类的要求:

5.1HashSet/LinkedHashSet:

  • 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
  • 要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码

重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

5.2 TreeSet:

  1. 自然排序中,比较两个对象是否相同的标准为:compareTo() 返回0.不再是 equals()
  2. 定制排序中,比较两个对象是否相同的标准为:compare() 返回0.不再是 equals()

6. TreeSet的使用

6.1 使用说明:

  1. 向TreeSet中添加的数据,要求是相同类的对象。
  2. 两种排序方式:自然排序(实现Comparable接口 和 定制排序(Comparator)

6.2 常用的排序方式:

方式一:自然排序

  • 自然排序:TreeSet会调用集合元素的 compareTo(object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列
  • 如果试图把一个对象添加到Treeset时,则该对象的类必须实现Comparable接口。
    • 实现Comparable的类必须实现 compareTo(Object obj) 方法,两个对象即通过compareTo(Object obj) 方法的返回值来比较大小
  • Comparable的典型实现:
    • BigDecimal、BigInteger以及所有的数值型对应的包装类:按它们对应的数值大小进行比较
    • Character:按字符的unic!ode值来进行比较
    • Boolean:true对应的包装类实例大于fase对应的包装类实例
    • String:按字符串中字符的unicode值进行比较
    • Date、Time:后边的时间、日期比前面的时间、日期大
  • 向TreeSet中添加元素时,只有第一个元素无须比较 compareTo() 方法,后面添加的所有元素都会调用 compareTo() 方法进行比较。
  • 因为只有相同类的两个实例才会比较大小,所以向 TreeSet中添加的应该是同一个类的对象。 对于TreeSet集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值。
  • 当需要把一个对象放入TreeSet中,重写该对象对应的equals()方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过equals()方法比较返回true,则通过 compareTo(object ob) 方法比较应返回0。否则,让人难以理解。
@Test
public void test1(){TreeSet set = new TreeSet();//失败:不能添加不同类的对象//        set.add(123);//        set.add(456);//        set.add("AA");//        set.add(new User("Tom",12));//举例一://        set.add(34);//        set.add(-34);//        set.add(43);//        set.add(11);//        set.add(8);//举例二:set.add(new User("Tom",12));set.add(new User("Jerry",32));set.add(new User("Jim",2));set.add(new User("Mike",65));set.add(new User("Jack",33));set.add(new User("Jack",56));Iterator iterator = set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}}

方式二:定制排序

  • TreeSet的自然排序要求元素所属的类实现Comparable接口,如果元素所属的类没有实现 Comparable接口,或不希望按照升序(默认情况)的方式排列元素或希望按照其它属性大小进行排序,则考虑使用定制排序。定制排序,通过 Comparator接口来实现。需要重写 compare(T o1,T o2)方法。
  • 利用 int compare(T o1,T o2) 方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
  • 要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。
  • 此时,仍然只能向Treeset中添加类型相同的对象。否则发生 ClassCastException 异常
  • 使用定制排序判断两个元素相等的标准是:通过 Comparator比较两个元素返回了0
@Test
public void test2(){Comparator com = new Comparator() {//照年龄从小到大排列@Overridepublic int compare(Object o1, Object o2) {if(o1 instanceof User && o2 instanceof User){User u1 = (User)o1;User u2 = (User)o2;return Integer.compare(u1.getAge(),u2.getAge());}else{throw new RuntimeException("输入的数据类型不匹配");}}};TreeSet set = new TreeSet(com);set.add(new User("Tom",12));set.add(new User("Jerry",32));set.add(new User("Jim",2));set.add(new User("Mike",65));set.add(new User("Mary",33));set.add(new User("Jack",33));set.add(new User("Jack",56));Iterator iterator = set.iterator();while(iterator.hasNext()){System.out.println(iterator.next());}
}

总结

  • 一般来说哈希表都是用来快速判断一个元素是否出现集合里

    对于哈希表,要知道哈希函数哈希碰撞在哈希表中的作用.

    哈希函数是把传入的key映射到符号表的索引上。

    哈希碰撞处理有多个key映射到相同索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。

    接下来是常见的三种哈希结构:

    • 数组
    • set(集合)
    • map(映射)

数组作为哈希表

在242.有效的字母异位词中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的!

这道题目包含小写字母,那么使用数组来做哈希最合适不过。

在[383.赎金信]中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组!

本题和242.有效的字母异位词 很像,242.有效的字母异位词 是求 字符串a 和 字符串b 是否可以相互组成,在383.赎金信 中是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。

使用map的空间消耗要比数组大一些,因为map要维护红黑树或者符号表,而且还要做哈希函数的运算。所以数组更加简单直接有效!

set作为哈希表

在349. 两个数组的交集中我们给出了什么时候用数组就不行了,需要用set。

这道题目没有限制数值的大小,就无法使用数组来做哈希表了。

主要因为如下两点:

  • 数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。
  • 如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。

所以此时一样的做映射的话,就可以使用set了。

map作为哈希表

在1.两数之和 中map正式登场。

来说一说:使用数组和set来做哈希法的局限。

  • 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
  • set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

map是一种<key, value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。

代码随想录算法训练营第07天 | LeetCode 454.四数相加2,383. 赎金信,15. 三数之和,18. 四数之和,总结相关推荐

  1. 代码随想录算法训练营day6| 454.四数相加II 383.赎金信 15.三数之和 18.四数之和

    代码随想录算法训练营day6| 454.四数相加II 383.赎金信 15.三数之和 18.四数之和 LeetCode 454 四数相加II 题目链接: 454.四数相加II class Soluti ...

  2. 代码随想录算法训练营第6天 | 454. 四数相加 II 383. 赎金信 15. 三数之和 18. 四数之和

    一.Leetcode 454. 四数相加 II 相当于两数相加.但是呢很巧妙的是,卡哥在遍历CD数组时把查哈希表的方法融入了进去.学习一下. 二.Leetcode 383. 赎金信 更简单了,主要是审 ...

  3. 代码随想录算法训练营第七天|454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ● 18. 四数之和

    一.454.四数相加II 力扣 思路:第一眼还没反应过来,真是缺练.在四个数组中分别寻找,可以先把前两个数组的和先存入map中,再计算后两个数组元素的和,看一下相反数在map中出现没有,出现过就res ...

  4. 代码随想录算法训练营第07天 | 454.四数相加II 、383. 赎金信、315. 三数之和 、18. 四数之和

    题目 题目链接,代码 题目链接,代码 题目链接,代码 题目链接,代码 初见思路 454.四数相加II 直接看了解析,想通的话还是比较好理解的.用一个map来记录两个数组的和以及出现次数, key为i+ ...

  5. 代码随想录算法训练营第08天 | LeetCode 344.反转字符串,541. 反转字符串2,剑指Offer 05.替换空格,151.翻转字符串里的单词,剑指Offer58-II.左旋转字符串

    LeetCode [344. 反转字符串] 题目:编写一个函数,其作用是将输入的字符串反转过来.输入字符串以字符数组 s 的形式给出. 不要给另外的数组分配额外的空间,你必须**原地修改输入数组**. ...

  6. 代码随想录算法训练营第六天|454.四数相加II,383. 赎金信,15. 三数之和,18. 四数之和。

    代码随想录算法训练营第六天|454.四数相加II,383. 赎金信,15. 三数之和,18. 四数之和. 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和 454.四数相加I ...

  7. 代码随想录算法训练营day1

    代码随想录算法训练营第一天| 704. 二分查找.27. 移除元素. 704.二分查找 题目链接:leetcode704 Binary search 暴力解法: class Solution {pub ...

  8. 代码随想录算法训练营第七天| 哈希表理论基础 ,454.四数相加II, 383. 赎金信, 15. 三数之和, 18. 四数之和

    代码随想录算法训练营第七天| 哈希表理论基础 ,454.四数相加II, 383. 赎金信, 15. 三数之和, 18. 四数之和 454.四数相加II 建议:本题是 使用map 巧妙解决的问题,好好体 ...

  9. 代码随想录算法训练营第二天 | LeetCode977.有序数组的平方 ,209.长度最小的子数组,59.螺旋矩阵II

    代码随想录算法训练营第二天 | LeetCode977.有序数组的平方 ,209.长度最小的子数组,59.螺旋矩阵II 一. LeetCode977.有序数组的平方 1. 题目链接[LeetCode9 ...

最新文章

  1. Postman最被低估的功能,自动化接口测试效率简直无敌
  2. 如何快速出稿一个优秀APP的构图
  3. *[topcoder]TheTree
  4. NOIp #2010
  5. noip模拟赛 Chtholly Nota Seniorious
  6. java ssh 常用术语
  7. cxf客户端访问方式
  8. 模型提效的另一条路:数据增强
  9. javaWeb -- 虚拟主机以及虚拟目录映射的配置
  10. matlab 高阶(一) —— assignin与evalin
  11. ios plist_iOS属性列表(plist)示例
  12. matlab a1处语法无效,MATLAB常见错误
  13. 假设检验方法-T检验、Z检验、F检验、卡方检验
  14. convexHull实现
  15. Java春招和秋招的区别_国企招聘,春招与秋招有何区别?错过秋招我该怎么办?...
  16. mplayer全参数
  17. oracle收集snop,SNAP收集服务器信息
  18. 安卓苹果手机有效清除微信浏览器内部缓存
  19. win7下开启wifi热点共享网络的方法
  20. Object Removal by Exemplar-Based Inpainting 概括(附源码)

热门文章

  1. centos7 解决硬盘内存爆满
  2. 什么是AI解梦,你了解吗?
  3. 给大家搞个可以唱卡拉OK的软件玩玩(免费的)
  4. C++:STL教程从入门到精通
  5. 番外:win32k.sys什么情况下会挂上PTE
  6. 对不起,让大家久等了,RETURNS!
  7. matlab 动态邻域粒子群,求解TSP问题的动态邻域粒子群优化算法
  8. hdu-5009-Paint Pearls-dp
  9. 如何配置linux服务器
  10. 【codevs2980】买帽子 动态规划