No.1 不要小瞧数组
所谓数组,是有序的元素序列。若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按无序的形式组织起来的一种形式。这些无序排列的同类数据元素的集合称为数组。
1.1 代码实现
public class Array<E> {
// 泛型数组
private E[] data;
// 存储元素数量
private int size;
// 构造函数
public Array() {
this(10);
}
//构造函数
public Array(int capacity) {
data = (E[]) new Object[capacity];
size = 0;
}
// 获取数组存放元素的个数
public int getSize() {
return size;
}
// 获取数组容量
public int getCapacity() {
return data.length;
}
// 判断数组是否为空
public boolean isEmpty() {
return size == 0;
}
// 向数组头部插入元素
public void addFirst(E elem) {
for (int i = size; i >= 0; i--) {
data[i + 1] = data[i];
}
data[0] = elem;
size++;
}
// 向数组末尾添加元素
public void addLast(E elem) {
if (size == data.length) {
resize(data.length * 2);
}
data[size++] = elem;
}
// 向指定位置插入元素
public void add(int index, E elem) {
if (size == data.length) {
resize(data.length * 2);
}
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal insertion position");
}
for (int i = size - 1; i > index - 1; i--) {
data[i + 1] = data[i];
}
data[index] = elem;
size++;
}
// 获取索引对应的元素
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Array Index Overflow");
}
return data[index];
}
// 获取第一个元素
public E getFirst() {
return get(0);
}
// 获取最后一个元素
public E getLast() {
return get(size - 1);
}
// 修改索引对应的元素
public void set(int index, E elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal insertion position");
}
data[index] = elem;
}
// 判断是否存在该元素
public boolean contains(E elem) {
for (int i = size; i > 0; i--) {
if (data[i].equals(elem)) {
return true;
}
}
return false;
}
// 查找元素对应的索引
public int find(E elem) {
for (int i = 0; i < size; i++) {
if (data[i].equals(elem)) {
return i;
}
}
return -1;
}
// 删除指定索引的元素
public E remove(int index) {
E ret = data[index];
for (int i = index; i < size; i++) {
data[i] = data[i + 1];
}
size--;
data[size] = null;
if (size == data.length / 2 && data.length / 2 != 0) {
resize(data.length / 2);
}
return ret;
}
// 从头部删除元素
public E removeFirst() {
return remove(0);
}
// 从尾部删除元素
public E removeLast() {
return remove(size - 1);
}
// 删除指定元素
public void removeElem(E elem) {
int index = find(elem);
if (index != -1) {
remove(index);
}
}
}
1.2 动态申请数组空间
private void resize(int newCapacity){
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < data.length; i++) {
newData[i] = data[i];
}
data = newData;
}
No.2 链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。程序语言或面向对象语言,如C,C++和Java依靠易变工具来生成链表。
2.1 代码实现
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node() {
this(null, null);
}
public Node(E e) {
this(e, null);
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
@Override
public String toString() {
return e.toString();
}
}
// 虚拟头结点
private Node dummyHead;
// 节点数量
private int size;
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
// 获取节点数量
public int getSize() {
return size;
}
// 是否为空
public boolean isEmpty() {
return size == 0;
}
// 头插法添加节点
public void addFirst(E e) {
add(0, e);
}
// 插入节点
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
prev.next = new Node(e, prev.next);
size++;
}
// 尾插法添加节点
public void addLast(E e) {
add(size, e);
}
// 查看头结点的元素
public E getFirst() {
return get(0);
}
// 查看节点元素
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
// 查看尾节点的元素
public E getLast() {
return get(size - 1);
}
// 修改节点的元素
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
// 删除头结点
public E removeFirst() {
return remove(0);
}
// 删除节点
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Remove failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node delNode = prev.next;
prev.next = delNode.next;
E ret = delNode.e;
delNode = null;
size--;
return ret;
}
// 删除尾节点
public E removeLast() {
return remove(size - 1);
}
// 是否存在元素
public boolean contains(E e) {
for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
}
2.2 移除链表元素
思路:首先通过while循环找到那个节点,先对其判空,然后将该节点的前一节点直接指向该节点的下一节点。
public class Solution {
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val) {
head = head.next;
}
if (head == null) {
return null;
}
ListNode listNode = head;
while (listNode.next != null) {
if (listNode.next.val == val) {
listNode.next = listNode.next.next;
} else {
listNode = listNode.next;
}
}
return head;
}
}
2.3 反转链表
思路:循环将后面的节点的下一节点指向前一节点。
public class Solution {
public ListNode reverseList(ListNode head) {
// 定义前一节点
ListNode prev = null;
// 定义当前节点
ListNode cur = head;
while (cur != null) {
// 创建当前节点的下一节点
ListNode next = cur.next;
// 当前节点的下一节点赋值为前一节点
cur.next = prev;
// 前一节点赋值为当前节点
prev = cur;
// 当前节点赋值为下一节点
cur = next;
}
return prev;
}
}
2.4 删除链表中的节点
思路:将下一个节点中的值赋值给当前节点,删除下一节点即可。
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
No.3 栈
栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
3.1 代码实现
3.1.1 接口
public interface Stack<E> {
// 获取栈内元素数量
int getSize();
// 是否为空栈
boolean isEmpty();
// 压栈
void push(E e);
// 弹栈
E pop();
// 获取栈顶元素
E peek();
}
3.1.2 数组实现
public class ArrayStack<E> implements Stack<E> {
Array<E> array;
public ArrayStack(){
array = new Array<>();
}
public ArrayStack(int capacity){
array = new Array<>(capacity);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public void push(E e) {
array.addLast(e);
}
@Override
public E pop() {
return array.removeLast();
}
@Override
public E peek() {
return array.getLast();
}
}
3.1.3 链表实现
public class LinkedListStack<E> implements Stack<E> {
LinkedList<E> linkedList;
public LinkedListStack() {
linkedList = new LinkedList<>();
}
@Override
public int getSize() {
return linkedList.getSize();
}
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public void push(E e) {
linkedList.addLast(e);
}
@Override
public E pop() {
return linkedList.removeLast();
}
@Override
public E peek() {
return linkedList.getLast();
}
}
3.2 括号匹配
思路:判断是否(、[、{ 中的任意一个,如果是,则压栈,判断是否是 )、]、} 中的任意一个,如果是,则弹栈并和对应的相匹配,如果匹配失败,返回 false,如果匹配成功,继续执行,直到栈为空
import java.util.Stack;
public class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) {
return false;
}
char topChar = stack.pop();
if (c == ')' && topChar != '(') {
return false;
}
if (c == ']' && topChar != '[') {
return false;
}
if (c == '}' && topChar != '{') {
return false;
}
}
}
return stack.isEmpty();
}
}
No.4 队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
4.1 代码实现
4.1.1 接口
public interface Queue<E> {
// 获取队列元素数量
int getSize();
// 是否为空队列
boolean isEmpty();
// 入队
void enqueue(E e);
// 出队
E dequeue();
// 获取队首元素
E getFront();
}
4.1.2 数组实现
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue(){
array = new Array<>();
}
public ArrayQueue(int capacity) {
array = new Array<>(capacity);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void enqueue(E e) {
array.addLast(e);
}
@Override
public E dequeue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
}
4.1.2 链表实现
public class LinkedListQueue<E> implements Queue<E> {
private LinkedList<E> linkedList;
public LinkedListQueue(){
linkedList = new LinkedList<>();
}
@Override
public int getSize() {
return linkedList.getSize();
}
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public void enqueue(E e) {
linkedList.addLast(e);
}
@Override
public E dequeue() {
return linkedList.removeFirst();
}
@Override
public E getFront() {
return linkedList.getFirst();
}
}
4.2 循环队列
思路:舍弃一个空间,用来判断是否为空,即尾指针加1模队列长度和头指针相等;
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front, tail;
public LoopQueue() {
this(10);
}
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
}
public int getCapacity() {
return data.length - 1;
}
@Override
public boolean isEmpty() {
return front == tail;
}
@Override
public int getSize() {
return (tail - front + data.length) % data.length;
}
@Override
public void enqueue(E e) {
// 如果尾节点加1模队列长度和头结点相等,则扩容
if ((tail + 1) % data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
}
public E dequeue(){
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}else {
E e = data[front];
front = (front + 1) % data.length;
if (getSize() == getCapacity()/ 4 && getCapacity() / 2 != 0){
resize(getCapacity() / 2);
}
return e;
}
}
@Override
public E getFront() {
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
return data[front];
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < getCapacity(); i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = getSize();
}
}
No.5 二叉搜索树
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
5.1 代码实现
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class BST<E extends Comparable<E>> {
private class Node {
public E e;
public Node left, right;
public Node(E e) {
this.e = e;
this.left = null;
this.right = null;
}
private Node root;
private int size;
}
private Node root;
private int size;
public BST() {
root = null;
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
// 向二分搜索树添加新的元素e
public void add(E e) {
root = addPlus(root, e);
}
// 向以node为根的二分搜索树中插入元素e(递归)
private void add(Node node, E e) {
// 如果e==node.e, 则返回
if (e.equals(node.e)) {
return;
}
// 如果e<node.e 并且node没有左孩子,创建节点作为根节点的左孩子
else if (e.compareTo(node.e) < 0 && node.left == null) {
node.left = new Node(e);
size++;
return;
}
// 如果e>node.e,并且node没有右孩子,创建节点作为根节点的右孩子
else if (e.compareTo(node.e) > 0 && node.right == null) {
node.right = new Node(e);
size++;
return;
}
// 如果e<node.e, 将node.left作为根节点
if (e.compareTo(node.e) < 0) {
add(node.left, e);
} else {
// 如果e>=node.e,将node.right作为根节点
add(node.right, e);
}
}
// 返回插入新节点后二分搜索树的根
private Node addPlus(Node node, E e) {
// 如果根节点为空,创建并返回该节点
if (node == null) {
size++;
return new Node(e);
}
// 如果e<node.e,调用add经node.left作为根节点
if (e.compareTo(node.e) < 0) {
node.left = addPlus(node.left, e);
}
// 如果e>=node.e,调用add经node.left作为根节点
else {
node.right = addPlus(node.right, e);
}
return node;
}
// 从二分搜索树中删除并返回最小值
public E removeMin() {
E ret = min();
removeMin(root);
return ret;
}
// 删除以node为根节点的二分搜索树中的最小节点
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else {
node.left = removeMin(node.left);
return node;
}
}
// 从二分搜索树中删除并返回最大值
public E removeMax() {
E ret = max();
removeMax(root);
return ret;
}
// 删除以node为根节点的二分搜索树中的最大节点
private Node removeMax(Node node) {
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
} else {
node.right = removeMax(node.right);
return node;
}
}
public void remove(E e) {
root = remove(root, e);
}
private Node remove(Node node, E e) {
if (node == null) {
return null;
}
if (e.compareTo(node.e) < 0) {
node.left = remove(node.left, e);
return node;
} else if (e.compareTo(node.e) > 0) {
node.right = remove(node.right, e);
return node;
} else {
if (node.left == null){
// 待删除节点左孩子为空,将右孩子替换成该节点返回
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else if (node.right == null) {
// 待删除节点右孩子为空,将左孩子替换成该节点返回
Node leftNode = node.left;
node.right = null;
size--;
return leftNode;
} else {
// 返回该节点右孩子的最小节点
Node middleNode = min(node.right);
// 将删除了node.right的二叉搜索树赋值给middleNode.right
middleNode.right = removeMin(node.right);
// 将node.left赋值给middleNode.left
middleNode.left = node.left;
node.left = node.right = null;
return middleNode;
}
}
}
// 从二分搜索树中查找并返回最小值
public E min() {
if (isEmpty()) {
throw new IllegalArgumentException("BST is empty!");
}
return min(root).e;
}
// 以node为根节点从二分搜索树中查找并返回最小节点
private Node min(Node node) {
if (node.left == null) {
return node;
} else {
return min(node.left);
}
}
// 从二分搜索树中查找并返回最大值
public E max() {
if (isEmpty()) {
throw new IllegalArgumentException("BST is empty!");
}
return max(root).e;
}
// 以node为根节点从二分搜索树中查找并返回最大节点
private Node max(Node node) {
if (node.right == null) {
return node;
} else {
return max(node.right);
}
}
// 查看二分搜搜树是否包含元素e
public boolean contains(E e) {
return contains(root, e);
}
// 以node为根的二分搜索树中是否包含元素e(递归)
private boolean contains(Node node, E e) {
if (node == null) {
return false;
}
if (e.compareTo(node.e) == 0) {
return true;
} else if (e.compareTo(node.e) < 0) {
return contains(node.left, e);
} else {
return contains(node.right, e);
}
}
// 二分搜索树的前序遍历
public void preOrder() {
preOrder(root);
}
// 以node为根节点的二分搜索树前序遍历(递归)
private void preOrder(Node node) {
if (node != null) {
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
}
// 二分搜索树的中序遍历
public void inOrder() {
inOrder(root);
}
// 以node为根节点的二分搜索树中序遍历(递归)
private void inOrder(Node node) {
if (node != null) {
inOrder(node.left);
System.out.println(node.e);
inOrder(node.right);
}
}
// 二分搜索树的后序遍历
public void postOrder() {
postOrder(root);
}
// 以node为根节点的二分搜索树后序遍历(递归)
private void postOrder(Node node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.println(node.e);
}
}
}
5.2 深度优先遍历
思路:由于队列是先进先出的,所以首先将根节点加入队列,循环将当前节点的左右节点加入队列
public void levelOrder() {
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
Node cur = queue.remove();
System.out.println(cur.e);
if (cur.left != null) {
queue.add(cur.left);
}
if (cur.right != null) {
queue.add(cur.right);
}
}
}
}
No.6 集合
计算机科学中,集合是一组可变数量的数据项(也可能是0个)的组合,这些数据项可能共享某些特征,需要以某种操作方式一起进行操作。一般来讲,这些数据项的类型是相同的,或基类相同(若使用的语言支持继承)。列表(或数组)通常不被认为是集合,因为其大小固定,但事实上它常常在实现中作为某些形式的集合使用。集合的种类包括列表,集,多重集,树和图。枚举类型可以是列表或集。
6.1 代码实现
6.1.1 接口
public interface Set<E> {
void add(E e);
void remove(E e);
boolean contains(E e);
int getSize();
boolean isEmpty();
}
6.1.2 数组实现
public class ArraySet<E extends Comparable<E>> implements Set<E> {
private Array<E> array;
public ArraySet() {
array = new Array<>();
}
@Override
public void add(E e) {
if (!array.contains(e)) {
array.addLast(e);
}
}
@Override
public void remove(E e) {
array.removeElem(e);
}
@Override
public boolean contains(E e) {
return array.contains(e);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
}
6.1.2 链表实现
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node() {
this(null, null);
}
public Node(E e) {
this(e, null);
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
@Override
public String toString() {
return e.toString();
}
}
// 虚拟头结点
private Node dummyHead;
// 节点数量
private int size;
// 构造
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
// 获取节点数量
public int getSize() {
return size;
}
// 是否为空
public boolean isEmpty() {
return size == 0;
}
// 头插法添加节点
public void addFirst(E e) {
add(0, e);
}
// 插入节点
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
prev.next = new Node(e, prev.next);
size++;
}
// 尾插法添加节点
public void addLast(E e) {
add(size, e);
}
// 查看头结点的元素
public E getFirst() {
return get(0);
}
// 查看节点元素
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
// 查看尾节点的元素
public E getLast() {
return get(size - 1);
}
// 修改节点的元素
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
// 删除头结点
public E removeFirst() {
return remove(0);
}
// 删除节点
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Remove failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node delNode = prev.next;
prev.next = delNode.next;
E ret = delNode.e;
delNode = null;
size--;
return ret;
}
// 删除尾节点
public E removeLast() {
return remove(size - 1);
}
// 是否存在元素
public boolean contains(E e) {
for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
stringBuilder.append(cur + "->");
cur = cur.next;
}
stringBuilder.append("NULL");
return stringBuilder.toString();
}
}
6.1.3 二叉树实现
public class BSTSet<E extends Comparable<E>> implements Set<E> {
private BST<E> bst;
public BSTSet() {
bst = new BST<>();
}
@Override
public void add(E e) {
if (!contains(e)) {
bst.add(e);
}
}
@Override
public void remove(E e) {
bst.remove(e);
}
@Override
public boolean contains(E e) {
return bst.contains(e);
}
@Override
public int getSize() {
return bst.getSize();
}
@Override
public boolean isEmpty() {
return false;
}
}
6.2 两个数组的交集
思路:将第一个数组中的不重复放到集合中,遍历第二个数组,如果已经存在于集合中,就将它添加到数组中。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set = new LinkedHashSet<>();
for (int num : nums1) {
set.add(num);
}
ArrayList<Integer> arrayList = new ArrayList<>();
for (int num : nums2) {
if (set.contains(num)) {
arrayList.add(num);
set.remove(num);
}
}
int[] res = new int[arrayList.size()];
for (int i = 0; i < arrayList.size(); i++) {
res[i] = arrayList.get(i);
}
return res;
}
}
6.3 唯一摩尔斯密码词
思路:将每个字符对应的密码词转换成字符串,添加到集合中,判断集合的大小。
class Solution {
public int uniqueMorseRepresentations(String[] words) {
String[] codes = {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."};
Set<String> strings = new LinkedHashSet<>();
for (String word : words) {
StringBuilder res = new StringBuilder();
for (int i = 0; i < word.length(); i++) {
res.append(codes[word.charAt(i) - 'a']);
}
strings.add(res.toString());
}
return strings.size();
}
}
No.7 映射
将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。此接口取代 Dictionary类,后者完全是一个抽象类,而不是一个接口。
7.1 代码实现
7.1.1 接口
public interface Map<K, V> {
void add(K key, V value);
V remove(K key);
boolean contains(K key);
V get(K key);
void set(K key, V newValue);
int getSize();
boolean isEmpty();
}
7.1.2 链式实现
import java.util.ArrayList;
public class LinkedListMap<K, V> implements Map<K, V> {
private class Node {
public K key;
public V value;
public Node next;
public Node() {
this(null, null, null);
}
public Node(K key) {
this(key, null, null);
}
public Node(K key, V value) {
this(key, value, null);
}
public Node(K key, V value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
@Override
public String toString() {
return key.toString() + " : " + value.toString();
}
}
private Node dummyHead;
private int size;
public LinkedListMap() {
dummyHead = new Node();
size = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
private Node getNode(K key) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.key.equals(key))
return cur;
cur = cur.next;
}
return null;
}
@Override
public boolean contains(K key) {
return getNode(key) != null;
}
@Override
public V get(K key) {
Node node = getNode(key);
return node == null ? null : node.value;
}
@Override
public void add(K key, V value) {
Node node = getNode(key);
if (node == null) {
dummyHead.next = new Node(key, value, dummyHead.next);
size++;
} else
node.value = value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(key);
if (node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
@Override
public V remove(K key) {
Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.key.equals(key))
if (prev.next != null) {
Node delNode = prev.next;
prev.next = delNode.next;
size--;
return delNode.value;
}
prev = prev.next;
}
return null;
}
}
7.1.3 二叉树实现
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import java.util.ArrayList;
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {
private class Node {
public K key;
public V value;
public Node left, right;
public Node() {
this(null, null, null, null);
}
public Node(K key) {
this(key, null, null, null);
}
public Node(K key, V value) {
this(key, value, null, null);
}
public Node(K key, V value, Node left) {
this(key, value, left, null);
}
public Node(K key, V value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"key=" + key +
", value=" + value +
", left=" + left +
", right=" + right +
'}';
}
}
private Node root;
private int size;
public BSTMap() {
root = null;
size = 0;
}
@Override
public void add(K key, V value) {
root = add(root, key, value);
}
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else {
node.value = value;
}
return node;
}
private Node min(Node node) {
if (node.left == null) {
return node;
} else {
return min(node.left);
}
}
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else {
node.left = removeMin(node.left);
return node;
}
}
@Override
public V remove(K key) {
Node node = getNode(root, key);
if (node != null){
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key) {
if (node == null) {
return null;
}
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
return node;
} else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
return node;
} else {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
return leftNode;
} else {
Node middleNode = getNode(node.right, key);
middleNode.right = removeMin(node.right);
middleNode.left = node.left;
node.left = node.right = null;
return middleNode;
}
}
}
@Override
public boolean contains(K key) {
return getNode(root, key) != null;
}
@Override
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null) {
throw new IllegalArgumentException(key + "doesn't exist!");
} else {
node.value = newValue;
}
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
private Node getNode(Node node, K key){
if(node == null)
return null;
if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
}
7.2 两个数组的交集 II
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums1){
if (!map.containsKey(num)){
map.put(num, 1);
} else {
map.put(num, map.get(num) + 1);
}
}
ArrayList<Integer> list = new ArrayList<>();
for (int num : nums2) {
if (map.containsKey(num)) {
list.add(num);
map.put(num, map.get(num) - 1);
if (map.get(num) == 0){
map.remove(num);
}
}
}
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
}
No.8 堆
堆是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆是线性数据结构,相当于一维数组,有唯一后继。
堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)
若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。由此,若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
8.1 代码实现
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap() {
data = new Array<>();
}
public MaxHeap(int capacity) {
data = new Array<>(capacity);
}
public MaxHeap(E[] arr) {
data = new Array<>(arr);
for (int i = parent(arr.length - 1); i >= 0; i--) {
siftDown(i);
}
}
public int size() {
return data.getSize();
}
public boolean isEmpty() {
return data.isEmpty();
}
// 返回索引的父节点的索引
private int parent(int index) {
if (index == 0) {
throw new IllegalArgumentException("index-0 doesn't have parent.");
} else {
return (index - 1) / 2;
}
}
// 返回索引的左孩子的索引
private int leftChild(int index) {
return index * 2 + 1;
}
// 返回索引的右孩子的索引
private int rightChild(int index) {
return index * 2 + 2;
}
public void add(E e) {
data.addLast(e);
siftUp(data.getSize() - 1);
}
// 上浮
private void siftUp(int k) {
while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
data.swap(k, parent(k));
k = parent(k);
}
}
// 返回最大值
public E findMax() {
if (size() == 0) {
throw new IllegalArgumentException("Can not findMax when heap is empty.");
}
return data.get(0);
}
// 取出堆中的最大元素
public E extractMax() {
E ret = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
// 下沉
private void siftDown(int k) {
while (leftChild(k) < data.getSize()) {
int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置
if (j + 1 < data.getSize() &&
data.get(j + 1).compareTo(data.get(j)) > 0)
j++;
// data[j] 是 leftChild 和 rightChild 中的最大值
if (data.get(k).compareTo(data.get(j)) >= 0)
break;
data.swap(k, j);
k = j;
}
}
// 返回堆中的最大元素,并将其替换成元素e
public E replace(E e) {
E ret = findMax();
data.set(0, e);
siftDown(0);
return ret;
}
}
8.2 基于最大堆实现优先队列
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
private MaxHeap<E> maxHeap;
public PriorityQueue() {
maxHeap = new MaxHeap<>();
}
@Override
public int getSize() {
return maxHeap.size();
}
@Override
public boolean isEmpty() {
return maxHeap.size() == 0;
}
@Override
public void enqueue(E e) {
maxHeap.add(e);
}
@Override
public E dequeue() {
return maxHeap.extractMax();
}
@Override
public E getFront() {
return maxHeap.findMax();
}
}
8.3 前K个高频元素
思路:首先将元素值作为键,元素的出现的次数作为值存储到映射中,然后循环映射,将前k个映射中的元素添加到堆中,如果后面的元素的频率比已经添加到堆中的元素频率高,删除该元素,将这个元素添加到堆中。
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
public class Sokution {
private class Freq implements Comparable<Freq> {
int e, freq;
public Freq(int e, int freq) {
this.e = e;
this.freq = freq;
}
@Override
public int compareTo(Freq another) {
if (this.freq < another.freq) {
return -1;
} else if (this.freq > another.freq) {
return 1;
} else {
return 0;
}
}
}
public List<Integer> topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
PriorityQueue<Freq> priorityQueue = new PriorityQueue<>();
for (int key : map.keySet()) {
if (priorityQueue.size() < k) {
priorityQueue.add(new Freq(key, map.get(key)));
} else if (map.get(key) > priorityQueue.peek().freq){
priorityQueue.remove();
priorityQueue.add(new Freq(key, map.get(key)));
}
}
LinkedList<Integer> list = new LinkedList();
while (!priorityQueue.isEmpty()) {
list.add(priorityQueue.remove().e);
}
return list;
}
}
No.9 线段树
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
9.1 代码实现
public class SegmentTree<E> {
private E[] tree;
private E[] data;
private Merger<E> merger;
public SegmentTree(E[] arr, Merger<E> merger) {
this.merger = merger;
data = (E[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
tree = (E[]) new Object[4 * arr.length];
bulidSegmentTree(0, 0, data.length - 1);
}
// 在treeIndex的位置创建表示区间[l...r]的线段树
private void bulidSegmentTree(int treeIndex, int l, int r) {
if (l == r) {
tree[treeIndex] = data[l];
return;
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int mid = l + (r - l) / 2;
bulidSegmentTree(leftTreeIndex, l, mid);
bulidSegmentTree(rightTreeIndex, mid + 1, r);
tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
}
public int getSize() {
return data.length;
}
public E get(int index) {
if (index < 0 || index > data.length) {
throw new IllegalArgumentException("Index is illegal.");
}
return data[index];
}
// 计算左孩子的索引
public int leftChild(int index) {
return 2 * index + 1;
}
// 计算右孩子的索引
public int rightChild(int index) {
return 2 * index + 2;
}
// 在tree中查找区间[l...r]
public E query(int queryL, int queryR) {
if (queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length || queryL > queryR) {
throw new IllegalArgumentException("Index is illegal.");
}
return query(0, 0, data.length - 1, queryL, queryR);
}
private E query(int treeIndex, int l, int r, int queryL, int queryR) {
if (l == queryL && r == queryR) {
return tree[treeIndex];
}
int mid = l + (r - l) / 2;
int leftChildIndex = leftChild(treeIndex);
int rightChildIndex = rightChild(treeIndex);
if (queryL >= mid + 1) {
return query(rightChildIndex, mid + 1, r, queryL, queryR);
} else if (queryR <= mid + 1) {
return query(leftChildIndex, l, mid, queryL, queryR);
} else {
E leftResult = query(leftChildIndex, l, mid, queryL, mid);
E rightResult = query(rightChildIndex, mid + 1, r, mid + 1, queryR);
return merger.merge(leftResult, rightResult);
}
}
public void set(int index, E e) {
if (index < 0 || index >= data.length) {
throw new IllegalArgumentException("Index is illegal.");
}
data[index] = e;
set(0, 0, data.length - 1, index, e);
}
private void set(int treeIndex, int l, int r, int index, E e) {
if (l == r) {
data[treeIndex] = e;
return;
}
int mid = l + (r - l) / 2;
int leftChildIndex = leftChild(treeIndex);
int rightChildIndex = rightChild(treeIndex);
if (index >= mid + 1) {
set(rightChildIndex, mid+1, r, index, e);
} else {
set(leftChildIndex, l, mid, index, e);
}
tree[treeIndex] = merger.merge(tree[leftChildIndex], tree[rightChildIndex]);
}
}
9.2 区域和检索 - 数组不可变
思路:创建一个比传来的数组多一个空间的数组,循环新数组的值等于新数组上一个索引的值加传参数组上一个索引值。
public class NumArray {
private int[] sum;
public NumArray(int[] nums) {
sum = new int[nums.length + 1];
sum[0] = 0;
for (int i = 1; i < sum.length; i++) {
sum[i] = sum[i - 1] + nums[i - 1];
}
}
public int sumRange(int i, int j) {
return sum[j + 1] - sum[i];
}
}
9.3 区域和检索 - 数组可修改
思路:和9.2一样,只不过增加了更新,更新之后需要从索引开始遍历修改后面的元素值。
class NumArray {
private int[] sum;
private int[] data;
public NumArray(int[] nums) {
data = new int[nums.length];
for (int i = 0; i < data.length; i++) {
data[i] = nums[i];
}
sum = new int[nums.length + 1];
sum[0] = 0;
for (int i = 1; i < sum.length; i++){
sum[i] = sum[i - 1] + nums[i - 1];
}
}
public void update(int i, int val) {
data[i] = val;
for (int j = i + 1; j < sum.length ; j++) {
sum[j] = sum[j - 1] + data[j - 1];
}
}
public int sumRange(int i ,int j) {
return sum[j + 1] - sum[i];
}
}
No.10 Trie
在计算机科学中,Trie,又称字典树、单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
10.1 代码实现
import java.util.HashMap;
import java.util.Map;
public class Trie {
private class Node {
public boolean isWord;
public Map<Character, Node> next;
public Node() {
this(false);
}
public Node(boolean isWord) {
this.isWord = isWord;
next = new HashMap<>();
}
}
private Node root;
private int size;
public Trie() {
root = new Node();
size = 0;
}
public int getSize() {
return size;
}
public void add(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (!cur.isWord) {
cur.isWord = true;
size ++;
}
}
public boolean contains(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return cur.isWord;
}
public boolean isPrefix(String prefix) {
Node cur = root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return true;
}
}
10.2 添加与搜索单词 - 数据结构设计
思路:将传来的字符串循环遍历,添加到trie中,遍历结束,将最后一个字符设置单词结束标志。
import java.util.HashMap;
import java.util.Map;
public class WordDictionary{
private class Node {
public boolean isWord;
public Map<Character, Node> next;
public Node() {
this(false);
}
public Node(boolean isWord) {
this.isWord = isWord;
next = new HashMap<>();
}
}
private Node root;
public WordDictionary() {
root = new Node();
}
public void addWord(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (!cur.isWord) {
cur.isWord = true;
}
}
public boolean search(String word) {
return match(root, word, 0);
}
private boolean match(Node node, String word, int index) {
if (index == word.length()) {
return node.isWord;
}
char c = word.charAt(index);
if (c != '.') {
if (node.next.get(c) == null) {
return false;
} else {
return match(node.next.get(c), word, index + 1);
}
} else {
for (char nextChar : node.next.keySet()) {
if (match(node.next.get(nextChar), word, index + 1)) {
return true;
}
}
return false;
}
}
}
10.3 实现 Trie (前缀树)
public class Trie {
private class Node {
public boolean isWord;
public Map<Character, Node> next;
public Node() {
this(false);
}
public Node(boolean isWord) {
this.isWord = isWord;
next = new HashMap<>();
}
}
private Node root;
public Trie() {
root = new Node();
}
/** Inserts a word into the trie. */
public void insert(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (!cur.isWord) {
cur.isWord = true;
}
}
public boolean search(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return cur.isWord;
}
public boolean startsWith(String prefix) {
Node cur = root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return true;
}
}
No.11 并查集
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
11.1 代码实现
11.1.1 接口
public interface UF {
int getSize();
boolean isConnected(int p, int q);
void unionElements(int p, int q);
}
11.1.2 数组实现
public class UnionFind implements UF {
private int[] id;
public UnionFind(int size) {
id = new int[size];
for (int i = 0; i < size; i++) {
id[i] = i;
}
}
@Override
public int getSize() {
return id.length;
}
// 查看元素p所属的集合编号
private int find(int p) {
if (p < 0 || p >= id.length) {
throw new IllegalArgumentException("p is out of bound.");
}
return id[p];
}
// 查看元素p和元素q是否所属相同集合
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
//合并元素p和元素q所属的集合
public void unionElements(int p, int q) {
int pID = find(p);
int qID = find(q);
if (pID == qID) {
return;
} else {
for (int i = 0; i < id.length; i++) {
if (id[i] == pID) {
id[i] = qID;
}
}
}
}
}
11.1.3 基于孩子指向父亲的数据结构实现
public class UnionFind implements UF {
private int[] parent;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
parent[pRoot] = qRoot;
}
}
11.2 size 优化
集合内元素少那个集合挂载在集合元素多的那个集合的根节点上。
public class UnionFind implements UF {
private int[] parent;
private int[] sz;
public UnionFind(int size) {
parent = new int[size];
sz = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
sz[i] = 1;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
if (sz[pRoot] < sz[qRoot]) {
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
} else {
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}
11.3 rank 优化
集合深度小的挂载在集合深度大的根节点上
public class UnionFind implements UF {
private int[] parent;
private int[] rank;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
if (rank[pRoot] < rank[qRoot]) {
parent[pRoot] = qRoot;
} else if (rank[qRoot] < rank[pRoot]){
parent[qRoot] = pRoot;
} else {
parent[qRoot] = pRoot;
rank[qRoot] += 1;
}
}
}
11.4 路径压缩
在查找过程中让节点的父节点变成爷爷节点,减少深度
public class UnionFind implements UF {
private int[] parent;
private int[] rank;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
// 路径压缩,将p的父节点指向p的爷爷节点,减少深度
parent[p] = find(parent[p]);
}
return parent[p];
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
if (rank[pRoot] < rank[qRoot]) {
// 如果pRoot的深度小qRoot,就让qRoot指向pRoot
parent[pRoot] = qRoot;
} else if (rank[qRoot] < rank[pRoot]){
// 如果pRoot的深度大于qRoot,就让pRoot指向qRoot
parent[qRoot] = pRoot;
} else {
// 如果pRoot和qRoot的深度相同,就让pRoot指向qRoot,rank加1
parent[qRoot] = pRoot;
rank[qRoot] += 1;
}
}
}
No.12 平衡二叉树
平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。 最小二叉平衡树的节点总数的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci(斐波那契)数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。
12.1 代码实现
import java.util.ArrayList;
public class AVLTree<K extends Comparable<K>, V> {
private class Node {
public K key;
public V value;
public Node left, right;
public int height;
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
}
private Node root;
private int size;
public AVLTree() {
root = null;
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
// 判断是否是二叉搜索树
public boolean isBST() {
ArrayList<K> keys = new ArrayList<>();
inOrder(root, keys);
for (int i = 1; i < keys.size(); i++)
if (keys.get(i - 1).compareTo(keys.get(i)) > 0)
return false;
return true;
}
// 判断是否是平衡二叉树
public boolean isBalanced() {
return isBalanced(root);
}
// 判断node为根的二分搜索树是否是平衡二叉树,递归算法
private boolean isBalanced(Node node) {
if (node == null)
return true;
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1)
return false;
return isBalanced(node.left) && isBalanced(node.right);
}
private void inOrder(Node node, ArrayList<K> keys) {
if (node == null)
return;
inOrder(node.left, keys);
keys.add(node.key);
inOrder(node.right, keys);
}
private int getHeight(Node node) {
if (node == null)
return 0;
return node.height;
}
// 获得node的平衡因子
private int getBalanceFactor(Node node) {
if (node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
// 对节点y进行左旋转操作,返回旋转后的根节点x
// y x
// / \ / \
// T1 x 向左旋转(y) y z
// / \ ---------------> / \ / \
// T2 z T1 T2 T3 T4
// / \
// T3 T4
private Node leftRotate(Node y) {
Node x = y.right;
Node T2 = x.left;
// 向左旋转过程
x.left = y;
y.right = T2;
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
// 对节点y进行向右旋转操作,返回旋转后的根节点x
// y x
// / \ / \
// x T4 向右旋转(y) z y
// / ---------------> / \ / \
// z T1 T2 T3 T4
// /
// T1
private Node rightRotate(Node y) {
Node x = y.left;
Node T3 = x.right;
// 向右旋转过程
x.right = y;
y.left = T3;
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
// 向二分搜索树中添加新的元素(key, value)
public void add(K key, V value) {
root = add(root, key, value);
}
// 向以node为根的二分搜索树中插入元素(key, value),递归算法,返回插入新节点后二分搜索树的根
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if (key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value;
// 更新height
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
// 计算平衡因子
int balanceFactor = getBalanceFactor(node);
// 平衡维护
// LL
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rightRotate(node);
// RR
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return leftRotate(node);
// LR
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
// 返回以node为根节点的二分搜索树中,key所在的节点
private Node getNode(Node node, K key) {
if (node == null)
return null;
if (key.equals(node.key))
return node;
else if (key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
public boolean contains(K key) {
return getNode(root, key) != null;
}
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
// 返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
// 从二分搜索树中删除键为key的节点
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
// 向以node为根的二分搜索树中删除元素,递归算法
private Node remove(Node node, K key){
if( node == null )
return null;
Node retNode;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
// return node;
retNode = node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
// return node;
retNode = node;
}
else{ // key.compareTo(node.key) == 0
// 待删除节点左子树为空的情况
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
// return rightNode;
retNode = rightNode;
}
// 待删除节点右子树为空的情况
else if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
// return leftNode;
retNode = leftNode;
}
// 待删除节点左右子树均不为空的情况
else{
// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
// 用这个节点顶替待删除节点的位置
Node successor = minimum(node.right);
//successor.right = removeMin(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
// return successor;
retNode = successor;
}
}
if(retNode == null)
return null;
// 更新height
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
// 计算平衡因子
int balanceFactor = getBalanceFactor(retNode);
// 平衡维护
// LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)
return rightRotate(retNode);
// RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)
return leftRotate(retNode);
// LR
if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
return retNode;
}
}
12.2 左旋转和右旋转
左旋转
- 将根节点的右孩子作为新根节点。
- 将新根节点的左孩子作为原根节点的右孩子。
- 将原根节点作为新根节点的左孩子。
右旋转
- 将根节点的左孩子作为新根节点。
- 将新根节点的右孩子作为原根节点的左孩子。
- 将原根节点作为新根节点的右孩子。
12.2 保持平衡的原理
添加方法同二分搜索树相同,添加之后会进行平衡维护,分为四种情况:
- LL:对该节点进行右旋转
- RR:对该节点进行左旋转
- LR:对左孩子进行左旋转,对该节点进行右旋转
- RL:对右孩子进行右旋转,对该节点进行左旋转
No.13 红黑树
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
13.1 代码实现
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
public K key;
public V value;
public Node left, right;
public boolean color;
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
color = RED;
}
}
private Node root;
private int size;
public RBTree() {
root = null;
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
// 判断节点颜色
private boolean isRed(Node node) {
if (node == null) {
return BLACK;
}
return node.color;
}
// node x
// / \ 左旋转 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
private Node leftRotate(Node node){
Node x = node.right;
// 左旋转
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
// node x
// / \ / \
// x T2 右旋转 z node
// / \ ---------> / \
// y T1 T1 T2
private Node rightRotate(Node node) {
Node x = node.left;
// 右旋转
node.left = x.right;
x.right = node;
x.color = node.color;
node.color = RED;
return x;
}
// 颜色翻转
private void filpColors(Node node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
// 向二分搜索树添加新的元素
public void add(K key, V value) {
root = add(root, key, value);
root.color = BLACK;
}
private Node add(Node node, K key, V value) {
if (node == null) {
size ++;
return new Node(key, value); // 默认节点颜色为红色
}
if (key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if (key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else
node.value = value;
// 如果该节点的右孩子是红色并且左孩子不是红色,进行左旋转
if (isRed(node.right) && !isRed(node.left))
node = leftRotate(node);
// 如果该节点的左孩子是红色并且左孩子的左孩子是红色,进行右旋转
if (isRed(node.left) && isRed(node.left.left))
node = rightRotate(node);
// 如果该节点的左孩子和右孩子都是红色,颜色翻转
if (isRed(node.left) && isRed(node.right))
filpColors(node);
return node;
}
private Node getNode(Node node, K key) {
if (node==null)
return null;
if (key.equals(node.key))
return node;
else if (key.compareTo(node.key) < 0)
return getNode(node.left, key);
else
return getNode(node.right, key);
}
public boolean contains(K key) {
return getNode(root, key) == null;
}
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null)
throw new IllegalArgumentException(key + "doesn't exist.");
node.value = newValue;
}
private Node minimum(Node node) {
if (node.left == null)
return null;
return minimum(node.left);
}
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key) {
if (node == null)
return null;
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
return node;
}
else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
return node;
}
else {
// 待删除节点左子树为空,
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 待删除节点右子树为空
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待删除节点左右子树均不为空,找到比待删除节点大的最小节点,将这个节点替换待删除节点的位置
Node successor = minimum(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
}
13.2 红黑树添加元素
- 如果添加的元素比根节点还要大,我们直接对其进行颜色翻转。
- 如果添加的元素比根节点的左孩子小的时候,我们对其进行右旋转,变成第一种情况。
- 如果添加的元素介于根节点和左孩子之间的时候,我们对其进行左旋转,可以变成第二种情况。
No.14 哈希表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
哈希冲突的处理方式
链地址法:
当哈希表的索引产生冲突时,会将新的元素放在该索引元素的后面,形成一个查找表,换句话说,哈希表就是一个Tree 数组,在 Java 8之前,每一个位置对应一个链表,从 JAVA 8开始,当冲突达到一定规模时,会将链表转换成红黑树
import java.util.TreeMap;
public class HashTable<K, V> {
private final int[] capacity
= {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};
private static final int upperTol = 10;
private static final int lowerTol = 2;
private int capacityIndex = 0;
private TreeMap<K, V>[] hashtable;
private int M;
private int size;
public HashTable() {
this.M = capacity[capacityIndex];
size = 0;
hashtable = new TreeMap[M];
for (int i = 0; i < M; i++) {
hashtable[i] = new TreeMap<>();
}
}
private int hash(K key) {
return (key.hashCode() & 0x7fffffff) % M;
}
public int getSize() {
return size;
}
public void add(K key, V value) {
TreeMap<K, V> map = hashtable[hash(key)];
if (map.containsKey(key))
map.put(key, value);
else {
map.put(key, value);
size++;
if (size >= upperTol * M && capacityIndex + 1 < capacity.length ) {
capacityIndex ++;
resize(capacity[capacityIndex]);
}
}
}
public V remove(K key) {
TreeMap<K, V> map = hashtable[hash(key)];
V ret = null;
if (map.containsKey(key)) {
ret = map.get(key);
ret = map.remove(key);
size --;
if (size < lowerTol * M && M / 2 >= capacity[capacityIndex] && capacityIndex -1 >= 0) {
capacityIndex --;
resize(M / 2);
}
}
return ret;
}
public void set(K key, V value) {
TreeMap<K, V> map = hashtable[hash(key)];
if (map.containsKey(key))
map.put(key, value);
else
throw new IllegalArgumentException(key + "doesn't exist.");
}
public V get(K key) {
TreeMap<K, V> map = hashtable[hash(key)];
if (map.containsKey(key))
return map.get(key);
else
throw new IllegalArgumentException(key + "doesn't exist.");
}
private void resize(int newM) {
TreeMap<K,V>[] newHashtable = new TreeMap[newM];
for (int i = 0; i < newM; i++)
newHashtable[i] = new TreeMap<>();
int oldM = this.M;
this.M = newM;
for (int i = 0; i < oldM; i++) {
TreeMap<K, V> map = hashtable[i];
for (K key : map.keySet())
newHashtable[hash(key)].put(key, map.get(key));
}
this.hashtable = newHashtable;
}
}
转载于:https://blog.51cto.com/13559120/2352392