这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战
1 数组(Array)
优点
- 构建非常简单
- 能在 O(1) 的时间里根据数组的下标(index)查询某个元素
缺点
- 构建时必须分配一段连续的空间
- 查询某个元素是否存在时需要遍历整个数组,耗费 O(n) 的时间(其中,n 是元素的个数)
- 删除和添加某个元素时,同样需要耗费 O(n) 的时间
基本操作
- insert:在某个索引处插入元素
- get:读取某个索引处的元素
- delete:删除某个索引处的元素
- size:获取数组的长度
案例一:翻转字符串“algorithm”
解法:用两个指针,一个指向字符串的第一个字符 a,一个指向它的最后一个字符 m,然后互相交换。交换之后,两个指针向中央一步步地靠拢并相互交换字符,直到两个指针相遇。这是一种比较快速和直观的方法。
案例二:给定两个字符串s和t,编写一个函数来判断t是否是s的字母异位词。
说明:你可以假设字符串只包含小写字母。
解题思路:字母异位词,也就是两个字符串中的相同字符的数量要对应相等。
- 可以利用两个长度都为 26 的字符数组来统计每个字符串中小写字母出现的次数,然后再对比是否相等
- 可以只利用一个长度为 26 的字符数组,将出现在字符串 s 里的字符个数加 1,而出现在字符串 t 里的字符个数减 1,最后判断每个小写字母的个数是否都为 0
2 链表(Linked List)
链表(Linked List)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
优点
- 链表能灵活地分配内存空间
- 能在 O(1) 时间内删除或者添加元素
缺点
- 不像数组能通过下标迅速读取元素,每次都要从链表头开始一个一个读取
- 查询第 k 个元素需要 O(k) 时间
基本操作
- insertAtEnd:在链表结尾插入元素
- insertAtHead:在链表开头插入元素
- delete :删除链表的指定元素
- deleteAtHead :删除链表第一个元素
- search:在链表中查询指定元素
- isEmpty:查询链表是否为空
应用场景
- 如果要解决的问题里面需要很多快速查询,链表可能并不适合
- 如果遇到的问题中,数据的元素个数不确定,而且需要经常进行数据的添加和删除,那么链表会比较合适
- 如果数据元素大小确定,删除和插入的操作并不多,那么数组可能更适合
链表实现数据结构
① 用链表实现队列
② 用链表实现栈
链表翻转算法
① 递归翻转
/**
* 链表递归翻转模板
*/
public Node reverseLinkedList(参数0) {
// Step1:终止条件
if (终止条件) {
return;
}
// Step2:逻辑处理:可能有,也可能没有,具体问题具体分析
// Step3:递归调用
Node reverse = reverseLinkedList(参数1);
// Step4:逻辑处理:可能有,也可能没有,具体问题具体分析
}
/**
* 链表递归翻转算法
*/
public Node reverseLinkedList(Node head) {
// Step1:终止条件
if (head == null || head.next == null) {
return head;
}
// Step2:保存当前节点的下一个结点
Node next = head.next;
// Step3:从当前节点的下一个结点开始递归调用
Node reverse = reverseLinkedList(next);
// Step4:head挂到next节点的后面就完成了链表的反转
next.next = head;
// 这里head相当于变成了尾结点,尾结点都是为空的,否则会构成环
head.next = null;
return reverse;
}
② 三指针翻转
public static Node reverseLinkedList(Node head) {
// 单链表为空或只有一个节点,直接返回原单链表
if (head == null || head.getNext() == null) {
return head;
}
// 前一个节点指针
Node preNode = null;
// 当前节点指针
Node curNode = head;
// 下一个节点指针
Node nextNode = null;
while (curNode != null) {
// nextNode 指向下一个节点
nextNode = curNode.getNext();
// 将当前节点next域指向前一个节点
curNode.setNext(preNode);
// preNode 指针向后移动
preNode = curNode;
// curNode指针向后移动
curNode = nextNode;
}
return preNode;
}
③ 利用栈翻转
public Node reverseLinkedList(Node node) {
Stack<Node> nodeStack = new Stack<>();
// 存入栈中,模拟递归开始的栈状态
while (node != null) {
nodeStack.push(node);
node = node.getNode();
}
// 特殊处理第一个栈顶元素:反转前的最后一个元素,因为它位于最后,不需要反转
Node head = null;
if ((!nodeStack.isEmpty())) {
head = nodeStack.pop();
}
// 排除以后就可以快乐的循环
while (!nodeStack.isEmpty()) {
Node tempNode = nodeStack.pop();
tempNode.getNode().setNode(tempNode);
tempNode.setNode(null);
}
return head;
}
2.1 单向链表
单向链表包含两个域:
- 一个数据域:用于存储数据
- 一个指针域:用于指向下一个节点(最后一个节点则指向一个空值):
单链表的遍历方向单一,只能从链头一直遍历到链尾。它的缺点是当要查询某一个节点的前一个节点时,只能再次从头进行遍历查询,因此效率比较低,而双向链表的出现恰好解决了这个问题。单向链表代码如下:
public class Node<E> {
private Eitem;
private Node<E> next;
}
2.2 双向链表
双向链表的每个节点由三部分组成:
- prev指针:指向前置节点
- item节点:数据信息
- next指针:指向后置节点
双向链表代码如下:
public class Node<E> {
private E item;
private Node<E> next;
private Node<E> prev;
}
2.3 循环链表
循环链表又分为单循环链表和双循环链表,也就是将单向链表或双向链表的首尾节点进行连接。
- 单循环链表
- 双循环链表