给你一个整数数组 nums
。如果任一值在数组中出现 至少两次 ,返回 true
;如果数组中每个元素互不相同,返回 false
。
示例 1:
输入:nums = [1,2,3,1]
输出:true
示例 2:
输入:nums = [1,2,3,4]
输出:false
示例 3:
输入:nums = [1,1,1,3,3,4,3,2,4,2]
输出:true
考虑到Set集合不能存储相同的内容,故可以使用HashSet来完成。
复习(篇幅很大,结果在最后边)
List接口
概述
有序的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。
Collection将集合划分为两大类:
- List集合
- Set集合
List接口的特点
- 有序【存储有序】
- 可重复
- 可以存储 null值
- 部分子集合线程安全,部分不安全 例如 ArrayList 和 Vector
- 有索引,针对每个元素能够方便地查询和修改
- 判断元素是否重复依赖于equals方法
- a. 如果元素是系统类,不需要重写equals方法
- b. 如果是自定义类,就需要我们按需求重写 equals方法
List接口的常用方法
增加
//在指定 index 索引处理插入元素 element
void add(int index, E element)
//在指定 index 索引处理插入集合元素 c
boolean addAll(int index, Collection<? extends E> c)
删除
E remove(int index) //删除指定索引 index 处的元素
修改
//修改指定索引 index 处的元素为 element
E set(int index, E element)
遍历
//for循环遍历集合中的每一个元素
for(int i=0;i<list.size();i++){
System.out.println(list.get(i))
}
//通过列表迭代器遍历集合中的每一个元素
ListIterator<E> listIterator()
//通过列表迭代器从指定索引处开始正向或者逆向遍历集合中的元素
ListIterator<E> listIterator(int index)
List接口的五种遍历方式
-
toArray
-
Iterator
-
foreach
-
普通for
-
ListIterator
List接口去除重复元素
- 方式一:创建一个新的集合去除重复元素再使用地址传递
- 方式二:在原集合的基础上使用选择排序思想去除重复元素
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("李四");
list.add("李四");
list.add("王五");
for (int i = 0; i < list.size(); i++) {
for (int j = i + 1; j < list.size(); j++) {
if (list.get(i).equals(list.get(j))) {
list.remove(j);
j--;
}
}
}
并发修改异常的处理
异常名称:并发修改异常 java.util.ConcurrentModificationException
产生原因:在使用迭代器迭代的同时使用原集合对元素做了修改
解决办法:
- 使用 toArray 方法
- 使用 普通 for 遍历
- 使用 ListIterator 遍历集合并且使用 列表迭代器修改元素
ArrayList
概述
List
接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括null
在内的所有元素。除了实现List
接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于Vector
类,除了此类是不同步的。)
特点
- 底层数据结构是数组
- 增加和删除的效率低,查询和修改的效率高
- 能够存储 null 值
- 线程不安全,效率高 可以通过 Collections.synchronizedList();变安全
- 有索引,能够方便检索
- 元素可重复,我们自己可以通过 选择排序去重复
- 不可以排序,但是可以通过 Collections.sort();方法排序
注:ArrayList中常用的方法全部来自于 父类 Collection
,List
,Object
.这里不再做详细叙述。
Vector
Vector
类可以实现可增长的对象数组。与数组一样,它包含可以使用整数索引进行访问的组件。但是,Vector
的大小可以根据需要增大或缩小,以适应创建Vector
后进行添加或移除项的操作。
特点
- 底层数据结构是数组
- 有索引,能够方便检索
- 增加和删除的效率低,查询和修改的效率高
- 线程安全,效率低
- 能够存储 null 值
- 元素可重复【我们自己可以通过选择排序思想去除重复元素】
- 不可以排序,但是可以通过 Collections.sort();方法排序
常用方法
增加
//添加元素 obj 到集合中
public synchronized void addElement(E obj)
//在指定索引 index 处插入元素 obj
public synchronized void insertElementAt(E obj, int index)
删除
//移除指定索引 index 处的元素
public synchronized void removeElementAt(int index)
//移除所有元素
public synchronized void removeAllElements()
修改
//修改指定索引 index 的元素为 obj
public synchronized void setElementAt(E obj, int index)
遍历
//for循环遍历集合中的所有元素
public synchronized E elementAt(int index) + size()
//for循环遍历集合中的所有元素
public synchronized Enumeration<E> elements()
获取
//获取集合中的第一个元素
public synchronized E firstElement()
//获取集合中的最后一个元素
public synchronized E lastElement()
//获取指定索引 index 的元素
public synchronized E elementAt(int index)
相关面试题
ArrayList和Vector的区别?
1) Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。
2) 当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。
Stack
概述
Stack 类表示后进先出(LIFO)的对象堆栈。它通过五个操作对类 Vector 进行了扩展 ,允许将向量视为堆栈。它提供了通常的 push 和 pop 操作,以及取堆栈顶点的 peek 方法、测试堆栈是否为空的 empty 方法、在堆栈中查找项并确定到堆栈顶距离的 search 方法。
特点
- 基于栈结构的集合,先进后出
Stack
类是Vector
类的子类,所以该类也是线程安全的,效率低,建议使用Deque
接口的实现类
常用方法
//将元素压入栈底
E push(E item)
//将元素从栈结构中弹出,并作为此函数的值返回该对象,此方法会影响栈结构的大小
E pop()
//查看堆栈顶部的对象,但不从栈中移除它。
E peek()
//测试栈是否为空。
boolean empty()
//返回对象在栈中的位置,以 1 为基数。
int search(Object o)
注:如果栈中元素为空,再尝试弹栈,将会抛出 EmptyStackException 异常, 而不是 NoSuchElementException
示例代码如下:
Stack<String> stack = new Stack<>();
// 压栈
stack.push("A");
stack.push("B");
stack.push("C");
while (!stack.isEmpty()) {
System.out.println("栈顶元素:" + stack.peek());
// 弹栈
System.out.println("弹出栈顶元素:" + stack.pop());
}
Queue
概述
在处理元素前用于保存元素的 collection。除了基本的 Collection 操作外,队列还提供其他的插入、提取和检查操作。每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或false,具体取决于操作)。插入操作的后一种形式是用于专门为有容量限制的 Queue 实现设计的;在大多数实现中,插入操作不会失败。
特点
- 该接口是队列接口的根接口,先进先出
- 该接口提供队列相关两种形式的方法,一种抛出异常(操作失败时),另一种返回一个特殊值(
null
或false
,具体取决于操作)。插入操作的后一种形式是用于专门为有容量限制的Queue
实现设计的;在大多数实现中,插入操作不会失败。
常用方法
抛出异常 | 返回特殊值 | |
---|---|---|
插入 | add(e) | offer(e) |
移除 | remove() | poll() |
检查 | element() | peek() |
Queue<String> queue = new ArrayDeque<>();
queue.add("A");
queue.add("B");
queue.add("C");
while (!queue.isEmpty()) {
System.out.println(queue.remove());
}
// 或者
queue.offer("A");
queue.offer("B");
queue.offer("C");
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
Deque
概述
一个线性 collection,支持在两端插入和移除元素。名称 deque 是“double ended queue(双端队列)”的缩写,通常读为“deck”。大多数 Deque 实现对于它们能够包含的元素数没有固定限制,但此接口既支持有容量限制的双端队列,也支持没有固定大小限制的双端队列。
特点
- Deque是一个Queue的子接口,是一个双端队列,支持在两端插入和移除元素
- deque支持索引值直接存取。
- Deque头部和尾部添加或移除元素都非常快速。但是在中部安插元素或移除元素比较费时。
- 插入、删除、获取操作支持两种形式:快速失败和返回null或true/false。
- 不推荐插入null元素,null作为特定返回值表示队列为空。
常用方法
第一个元素(头部) | 最后一个元素(尾部) | |||
抛出异常 | 特殊值 | 抛出异常 | 特殊值 | |
插入 | addFirst(e) | offerFirst(e) | addList(e) | offerLast(e) |
移除 | removeFirst(e) | pollFirst(e) | removeLast(e) | pollLast(e) |
检addFirst(e)查 | getFirst(e) | peekFirst(e) | getLast(e) | peekLast(e) |
双向队列操作
插入元素
- addFirst(): 向队头插入元素,如果元素为null,则发生空指针异常
- addLast(): 向队尾插入元素,如果为空,则发生空指针异常
- offerFirst(): 向队头插入元素,如果插入成功返回true,否则返回false
- offerLast(): 向队尾插入元素,如果插入成功返回true,否则返回false
移除元素
- removeFirst(): 返回并移除队头元素,如果该元素是null,则发生NoSuchElementException
- removeLast(): 返回并移除队尾元素,如果该元素是null,则发生NoSuchElementException
- pollFirst(): 返回并移除队头元素,如果队列无元素,则返回null
- pollLast(): 返回并移除队尾元素,如果队列无元素,则返回null
获取元素
- getFirst(): 获取队头元素但不移除,如果队列无元素,则发生NoSuchElementException
- getLast(): 获取队尾元素但不移除,如果队列无元素,则发生NoSuchElementException
- peekFirst(): 获取队头元素但不移除,如果队列无元素,则返回null
- peekLast(): 获取队尾元素但不移除,如果队列无元素,则返回null
栈操作
- pop(): 弹出栈中元素,也就是返回并移除队头元素,等价于removeFirst(),如果队列无元素,则发生NoSuchElementException
- push(): 向栈中压入元素,也就是向队头增加元素,等价于addFirst(),如果元素为null,则发生NoSuchElementException,如果栈空间受到限制,则发生IllegalStateException
引用场景
- 满足FIFO场景时
- 满足LIFO场景时,曾经在解析XML按标签时使用过栈这种数据结构,但是却选择Stack类,如果在进行栈选型时,更推荐使用Deque类,应为Stack是线程同步
Set接口的框架结构:无序的,不可重复的数据
|----->HashSet:作为Set的主要实现类,线程不安全可以存放null值
* |----->LinkedHashSet:作为HashSet的子类,遍历时按照添加时的顺序遍历
* |----->TreeSet:底层用二叉树储存数据,要求TreeSet里面的元素是同一个类型的
* 1,Set接口没有定义额外的新方法
* 2,要求向Set添加数据,他所在的类一定重写hashCode()和equals()方法
* 重写的hashCode()和equals()方法要保持一致性,以实现对象相等规则,即,相等的对象必须有相等的散列码(哈希值)
* 重写两个方法的小技巧:对象中所用的equals()方法比较的Field,都应用来计算hashCode
*Set:无序的,不可重复的数据
* 一:以HashSet说明
* 1:无序:不等同于随机性,存储的数据在底层数组中的位置并不是按照数组索引的顺序进行添加的,有一个方法是hashCode,
* 根据hash值的值确定所在的位置
* 2:不可重复:保证添加的元素按照equals方法判断时,不能返回true。相同元素只能添加一个
*①类中重写了equals方法(没重写equals方法),但是没有重写hashCode方法时,添加时候new的类的内容可以相同,都显示
*②类中重写了equals方法(没重写equals方法),并且重写hashCode方法时,添加时就不能有相同的,有的话只显示一个
* 二:添加元素时,以HashSet为例:
* 向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的hash值,此hash值通过某种算法,计算出在HashSet
* 底层数组中所在的位置(索引);
* 判断该位置是否有元素,若此位置没有其他元素,则元素a直接添加成功;
* 若此位置有其他元素b(或者以链表形式存在的多个元素)时,首先比较a与b的hash值,
* 如果hash值不相同,则添加成功,
* 若hash值相同,则调用元素a所在类的equals()方法,
* 若返回true,添加失败,
* 若返回false,添加成功,
*对于添加成功的情况2和情况3而言:元素a与已经存在索引位置上的元素以链表的方式存储,
* 对于jdk7而言,元素a放到数组中,指向原来的元素
* 对于jdk8而言,原来的元素放到数值中,指向元素a(七上八下)
*
*注:HashSet底层是数组+链表
考虑到Set集合不能存储相同的内容,故可以使用HashSet来完成。
思路:
- 首先创建一个HashSet集合;
- 接着遍历数组,将数组的每个元素加入到集合中;
- 因为HashSet不能存放重复的元素,只需要比较插入完后的Set集合的长度与数组的长度是否相等即可,若相等,就是没有相同元素,返回false,若Set集合的长度小于数组,则说明有重复的,返回true;
Java代码示例:
class Solution {
public boolean containsDuplicate(int[] array) {
HashSet<Integer> set=new HashSet<Integer>();
for(int i =0;i<array.length;i++){
set.add(array[i]);
}
if(set.size()==array.length){
return false;
}
return true;
}
}