链表的类型
链表也是线性表的一种
- 单链表
- 双向链表
- 循环链表
- 双向循环链表
链表/数组的性能对比
- 随机取的时间复杂度
- 数组:通过下标取是 O(1);取指定值是 O(n)
- 链表:因为链表的内存地址不连续,所以没办法通过下标取;取指定值是 O(n)
- 插入/删除
- 数组:涉及到数据迁移,平均时间复杂度是 O(n)
- 链表:找到指定值的时间复杂度为 O(n),删除的时间复杂度为 O(1),平均时间复杂度为 O(n)
如何写出正确的链表代码
理解指针或引用的含义
- 对于 Java 来说,引用中存储的就是数据的内存地址。
- 指针可以理解为 Java 中的引用
- p->next=q
-
- 这行代码是说,p 结点中的 next 指针存储了 q 结点的内存地址。
警惕指针丢失和内存泄漏
- 插入节点时,一定要注意操作顺序,防止节点丢失
利用哨兵简化实现难度
- 可以利用哨兵,避免很多“边界问题”的特殊处理
- 很多地方都有用到,例如:插入排序、归并排序、动态规划
重点留意边界条件处理
- 边界条件处理,决定了程序的健壮性。不管是算法还是日常编程,都要十分注意
- 如果链表为空时,代码是否能正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
推荐练习
- 推荐练习
- 单链表反转
- 链表中环的检测
- 两个有序的链表合并
- 删除链表倒数第 n 个结点
- 求链表的中间结点
链表翻转
题目
反转一个单链表
示例
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解答
- 最简单的方法是借助 JavaAPI 进行翻转
- Collections.reverse(list);
- 自己实现思路
- 借助节点 prev 和 cur
- cur 表示当前正在处理的节点
- prev 表示当前处理节点的前一个节点
- 第 0 个元素开始翻转
public static class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
static class Solution {
public ListNode reverseList(ListNode head) {
//1. 处理特殊情况
if (head == null || head.next == null) {
return head;
}
//2. 通过 prev 指针进行翻转
ListNode prev = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = prev;
prev = cur;
cur = tmp;
}
return head;
}
}
回文子串
题目
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
解答
思路
- 回文子串可以理解为比较 前半段链表 跟 翻转后的后半段链表。所以首先要找到 中间点
- 可以通过 快/慢 两个指针找到中间点
- 快指针:每次向后移动两个位置
- 满指针:每次向后移动一个位置
- 当快指针达到链表尾部的时候,慢指针则正好到达链表中心
static class Solution {
public static boolean isPalindrome(ListNode head) {
//1. 判断特殊情况
if (head == null) {
return true;
}
//2. 使用快慢两个指针,找到中间位置
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
//3. 翻转后半段链表
ListNode reverse = reverseList(slow.next);
while (reverse != null) {
if (reverse.val != head.val) {
return false;
}
reverse = reverse.next;
head = head.next;
}
return true;
}
public static ListNode reverseList(ListNode head) {
//1. 处理特殊情况
if (head == null || head.next == null) {
return head;
}
//2. 通过 prev 指针进行翻转
ListNode prev = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = prev;
prev = cur;
cur = tmp;
}
return prev;
}
public static void main(String[] args) {
ListNode head = new ListNode(1);
ListNode next1 = new ListNode(2);
ListNode next2 = new ListNode(2);
ListNode next3 = new ListNode(1);
head.next = next1;
next1.next = next2;
next2.next = next3;
System.out.println(isPalindrome(head));
;
}
}