文章目录

集合框架的概述

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)。

数组在存储多个数据方面的特点

  • 一旦初始化以后,其长度就确定了。
  • 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。
  • 比如:String[] arr;int[] arr1;Object[] arr2;

数组在存储多个数据方面的缺点

一旦初始化以后,其长度就不可修改。

数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。

获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用

数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

集合框架

Java 集合可分为 Collection 和 Map 两种体系

  • Collection接口:单列集合,用来存储一个一个的对象

    • List接口:存储有序的、可重复的数据。 -->“动态”数组链表

      • ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
      • LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
      • Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
    • Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”无序确定互异
      • HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
      • LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历,对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
      • TreeSet:可以按照添加对象的指定属性,进行排序。
  • Map接口:双列集合,用来存储一对(key - value)一对的数据 -->高中函数:y = f(x)

    • HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value

      • LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。

      原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。

    • TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序底层使用红黑树

    • Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value

      • Properties:常用来处理配置文件。key和value都是String类型

Collection接口中的方法的使用

add(Object e)

将元素e添加到集合coll中

    coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());

size()

获取添加的元素的个数

        System.out.println(coll.size());

addAll(Collection coll1)

将coll1集合中的元素添加到当前的集合中

    Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1); System.out.println(coll.size());//6
System.out.println(coll);

clear()

清空集合元素

 coll.clear();

isEmpty()

判断当前集合是否为空

        System.out.println(coll.isEmpty());

contains(Object obj)

判断当前集合中是否包含obj,我们在判断时会调用obj对象所在类的equals(),需要充重写equals

boolean contains = coll.contains(123);
System.out.println(contains);

containsAll(Collection coll1)

判断形参coll1中的所有元素是否都存在于当前集合中

Collection coll1 = Arrays.asList(123,4567);
System.out.println(coll.containsAll(coll1));

remove(Object obj)

从当前集合中移除obj元素。返回true移除成功,返回false移除失败

	Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false); coll.remove(1234);
System.out.println(coll); coll.remove(new Person("Jerry",20));
System.out.println(coll);

removeAll(Collection coll1)

差集:从当前集合中移除coll1中所有的元素,也需要重写equals。

		Collection coll1 = Arrays.asList(123,456);
coll.removeAll(coll1);
System.out.println(coll);

retainAll(Collection coll1)

交集:获取当前集合和coll1集合的交集,并返回给当前集合

Collection coll1 = Arrays.asList(123,456,789);
coll.retainAll(coll1);
System.out.println(coll);

equals(Object obj)

要想返回true,需要当前集合和形参集合的元素都相同。

		Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false); Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add(123);
coll1.add(new Person("Jerry",20));
coll1.add(new String("Tom"));
coll1.add(false); System.out.println(coll.equals(coll1));

hashCode()

返回当前对象的哈希值

System.out.println(coll.hashCode());

数组 —>集合:调用Arrays类的静态方法asList()

 		List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list); List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1 List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2

集合 —>数组:toArray()

 Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}

iterator()

返回Iterator接口的实例,用于遍历集合元素。

 iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}

结论

向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals().

Iterator迭代器接口

Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素。

迭代器的执行原理

iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}

hasNest()先判断下一个位置地址是否存在,如果存在,再调用next()来获取值,避免空指针。

两种错误使用方式

错误方式一

        Iterator iterator = coll.iterator();
while((iterator.next()) != null){//先判断了下一个是不是null,此时指针已经下移
System.out.println(iterator.next());//又输出了下一个,则上一个没有被输出就跳过了
}

这种方式第一会跳着输出,第二可能会出现空指针。

错误方式二

        while (coll.iterator().hasNext()){//每次都会创建一个新的迭代器,其指针都会指向第一个元素
System.out.println(coll.iterator().next());
}

则这样将一直循环输出第一个元素

remove方法

public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false); //删除集合中"Tom"
Iterator iterator = coll.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
if("Tom".equals(obj)){
iterator.remove();
}
}
//遍历集合
iterator = coll.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

ForEach遍历集合或数组

public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.add(new String("Tom"));
coll.add(false); //for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器。
for(Object obj : coll){
System.out.println(obj);
}
}
 public void test2(){
int[] arr = new int[]{1,2,3,4,5,6};
//for(数组元素的类型 局部变量 : 数组对象)
for(int i : arr){
System.out.println(i);
}
}

Test

  public void test3(){

        String[] arr = new String[]{"MM","MM","MM"};
//方式一:普通for赋值
for(int i = 0;i < arr.length;i++){
arr[i] = "GG";
} //方式二:增强for循环
for(String s : arr){
s = "GG";
} for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
}

如果是方式一,则输出结果为GG,因为是直接改的数组的元素。如果是方式二,则输出结果为MM,因为是把数组中的元素取出来赋值给s,修改的只是s,数组中的值并没有改变。

ArrayList源码分析

jdk7

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)

jdk8

ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组

list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]后续的添加和扩容操作与jdk 7 无异。

小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

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;
} }

Vector的源码分析

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。

在扩容方面,默认扩容为原来的数组长度的2倍

List接口中的常用方法

void add(int index, Object ele)

在指定位置插入

        ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
list.add(new Person("Tom",12));
list.add(456);

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));

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):移除指定index位置的元素,并返回此元素

    Object obj = list.remove(0);
System.out.println(obj);
System.out.println(list);

Object set(int index, Object ele)

设置指定index位置的元素为ele

    list.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);

总结:常用方法

增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:

  1. Iterator迭代器方式
  2. 增强for循环
  3. 普通的循环

Set接口

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

Set:存储无序的、不可重复的数据

以HashSet为例说明:

  1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。

  2. 不可重复性:保证添加的元素按照**equals()**判断时,不能返回true.即:相同的元素只能添加一个。

添加元素的过程:以HashSet为例:

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
数组此位置上是否已经有元素:
情况1:如果此位置上没有其他元素,则元素a添加成功。(1)
情况2 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。(2)
如果hash值相同,进而需要调用元素a所在类的equals()方法:
equals()返回true,元素a添加失败
equals()返回false,则元素a添加成功。(3)

对于添加成功的2和3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下

HashSet底层:数组+链表的结构。

要求:

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

重写两个方法的小技巧:

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

LinkedHashSet的使用

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率高于HashSet

    public void test2(){
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129); Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

TreeSet

1.向TreeSet中添加的数据,要求是相同类的对象。
2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)
3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

TreeSet底层是红黑树

public class User implements Comparable{
private String name;
private int age; public User() {
} public User(String name, int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
} @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;
} //按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
} }
} 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());
} }

定制排序

    public void test2(){
Comparator com = new Comparator() {
//按照年龄从小到大排列
@Override
public 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);//带参构造器,传入一个comparator
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());
}
} }

Map接口

Map结构的理解

  • Map中的key:无序的、不可重复的,使用Set存储所有的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
  • Map中的value:无序的、可重复的,使用Collection存储所有的value —>value所在的类要重写equals()
  • 一个键值对:key-value构成了一个Entry对象。
  • Map中的entry:无序的、不可重复的,使用Set存储所有的entry

HashMap的底层实现原理

jdk7为例说明

  • HashMap map = new HashMap():
  • 在实例化以后,底层创建了长度是16的一维数组Entry[] table。
  • …可能已经执行过多次put…
  • map.put(key1,value1):
  • 首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
  • 如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
  • 如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
    • 如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
    • 如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
      • 如果equals()返回false:此时key1-value1添加成功。----情况3
      • 如果equals()返回true:使用value1替换value2。
        补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
  • 在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。

jdk8 相较于jdk7在底层实现方面的不同

  1. new HashMap():底层没有创建一个长度为16的数组
  2. jdk 8底层的数组是:Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树
  5. 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
  6. 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储

HashMap源码中的重要常量

  •  **`DEFAULT_INITIAL_CAPACITY`** : HashMap的默认容量,16
  •  **`DEFAULT_LOAD_FACTOR:HashMap`**的默认加载因子:0.75
  •  **`threshold`**:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
  •  **`TREEIFY_THRESHOLD`**:Bucket中链表长度大于该默认值,转化为红黑树:8
  •  **`MIN_TREEIFY_CAPACITY`**:桶中的Node被树化时最小的hash表容量:64

LinkedHashMap的底层实现原理

HashMap中的内部类:Node

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}

LinkedHashMap中的内部类:Entry

static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}

before、after用于记录添加元素的先后顺序

Map中定义的方法

Object put(Object key,Object value)

将指定key-value添加到(或修改)当前map对象中

void putAll(Map m)

将m中的所有key-value对存放到当前map中

Object remove(Object key)

移除指定key的key-value对,并返回value

void clear()

清空当前map中的所有数据

Object get(Object key)

获取指定key对应的value

boolean containsKey(Object key)

是否包含指定的key

boolean containsValue(Object value)

是否包含指定的value

int size()

返回map中key-value对的个数

boolean isEmpty()

判断当前map是否为空

boolean equals(Object obj)

判断当前map和参数对象obj是否相等

Set keySet()

返回所有key构成的Set集合

Collection values()

返回所有value构成的Collection集合

Set entrySet()

返回所有key-value对构成的Set集合

总结:常用方法

  • 添加:put(Object key,Object value)
  • 删除:remove(Object key)
  • 修改:put(Object key,Object value)
  • 查询:get(Object key)
  • 长度:size()
  • 遍历:keySet() / values() / entrySet()

TreeMap

向TreeMap中添加key-value,要求key必须是由同一个类创建的对象,因为要按照key进行排序:自然排序 、定制排序.

自然排序

 public void test1(){
TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18); map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100); Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue()); }
}

定制排序

 public void test2(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public 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());
}
throw new RuntimeException("输入的类型不匹配!");
}
});
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18); map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100); Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue()); }
}

Properties

  • Properties 类是 Hashtable 的子类,该对象用于处理属性文件
  • 由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型
  • 存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
public class PropertiesTest {

    //Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) {
FileInputStream fis = null;
try {
Properties pros = new Properties(); fis = new FileInputStream("jdbc.properties");
pros.load(fis);//加载流对应的文件 String name = pros.getProperty("name");
String password = pros.getProperty("password"); System.out.println("name = " + name + ", password = " + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
} }
} }
}

最新文章

  1. currentStyle与getComputedStyle应用
  2. sqlserver2012更改文件组
  3. BZOJ4590——[Shoi2015]自动刷题机
  4. canvas beginPath()
  5. iOS直播直播,头都大了
  6. Toolkit.getImage获取图片
  7. MFC 密码框
  8. cmd 登录oracle
  9. 【练习】手工生成awr报告
  10. bzoj 1877 [SDOI2009]晨跑(最小费用最大流)
  11. jquery 几点注意事项
  12. web代理进行跨域访问
  13. HPU--1221 Fibonacci数列
  14. jQuery的区别:$().click()和$(document).on(&#39;click&#39;,&#39;要选择的元素&#39;,function(){})的不同
  15. Git篇(基础)
  16. Kafka(二)CentOS7.5搭建Kafka2.11-1.1.0集群与简单测试
  17. 用户密码管理和 su 命令
  18. C#重点内容之:事件(Event)
  19. 关于gridview改变行内容事件需要点击别的行或控件才能执行
  20. angualrJs实现图片上传功能

热门文章

  1. 谱聚类算法—Matlab代码
  2. 【论文笔记】Leveraging Post-click Feedback for Content Recommendations
  3. CF999C Alphabetic Removals 题解
  4. 【LeetCode】1060. Missing Element in Sorted Array 解题报告 (C++)
  5. 【LeetCode】384. Shuffle an Array 解题报告(Python & C++)
  6. 【LeetCode】865. Smallest Subtree with all the Deepest Nodes 解题报告(Python & C++)
  7. DP? (hdu3944)
  8. GCD (hdu 5726)
  9. datatables scrollX设置水平滚动无效问题
  10. Spurious Local Minima are Common in Two-Layer ReLU Neural Networks