● 链表是一种通过指针串联在一起的线性结构,每一个节点有两个部分,数据域和指针域,最后一个节点指针域指向null
● 单链表
● 双链表
○ 每个节点有两个指针域,一个指向下一个节点,一个是上一个节点
○ 可向前查询或向后查询
● 循环链表
○ 链表首尾相连
○ 可以解决约瑟夫环问题
● 在内存中不连续分布,通过指针域连接在内存中的各个节点,散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
● 删除
○ 前节点的next指向后节点,Java语言有自己的内存回收机制,不需要手动释放
● 添加
○ 前指我,我指后
● 都是O(1) 不影响其他节点,但注意,遍历查找的时间复杂度是O(n)
性能分析
● 数组插入删除O(n) 查询O(1)
● 链表插入删除O(1) 查询O(n)
● 数组定义的时候,长度固定,不能修改;链表可以动态增删,适合数据量不固定,增删频繁,查询少
● 力扣题目链接
● 题意:删除链表中等于给定值 val 的所有节点。
● 使用虚拟头节点或不使用,时间复杂度O(n) 空间复杂度O(1)
● 递归 时间复杂度O(n) 空间复杂度O(n)
// 设置虚拟头节点
class Solution {
public ListNode removeElements(ListNode head, int val) {
ListNode dummy = new ListNode(-1, head); // 设置后减少讨论
ListNode pre = dummy;
while (pre.next != null) {
if (pre.next.val == val) { //找到要删除的
pre.next = pre.next.next; //删除
} else {
pre = pre.next; //向后移动
}
}
return dummy.next; //头结点可能被删掉,返回dummy的next即可
}
}
// 不设置虚拟头节点
class Solution {
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val) head = head.next; // 只要头结点要删除,就不断删除
if (head == null || head.next == null) return head; // 删除完看是不是空,或只有一个节点,直接返回
ListNode temp = head; // 这里temp.val不会是val,且后面有节点
while (temp.next != null) {
if (temp.next.val == val) temp.next = temp.next.next; // 删除
else temp = temp.next;
}
return head; // 最后返回head即可
}
}
// 递归
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) return null; // 终止条件
head.next = removeElements(head.next, val); // 这个函数的作用是删除节点后返回头结点
if (head.val == val) head = head.next; // 最后处理当前逻辑,如果是就删掉,向后移动
return head; // 返回头结点
}
}
● 力扣题目链接
● 题意:
● 在链表类中实现这些功能:
○ get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
○ addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入 后,新节点将成为链表的第一个节点。
○ addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
○ addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
○ deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
● 难度不大,一点一点分析,注意方法可以复用
class MyLinkedList {
private ListNode head;
private int size;
public MyLinkedList() {
head = new ListNode(-1);
size = 0;
}
public int get(int index) {
if (index < 0 || index >= size) return -1;
ListNode cur = head;
while (index-- >= 0) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0, val);
}
public void addAtTail(int val) {
addAtIndex(size, val);
}
public void addAtIndex(int index, int val) {
if (index > size) return;
ListNode cur = head;
while (index-- > 0) {
cur = cur.next;
}
ListNode node = new ListNode(val);
node.next = cur.next;
cur.next = node;
size++;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) return;
ListNode cur = head;
while (index-- > 0) {
cur = cur.next;
}
cur.next = cur.next.next;
size--;
}
}
class ListNode {
int val;
ListNode next;
public ListNode(){};
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
● 力扣题目链接
● 题意:反转一个单链表。
● 使用栈,时间复杂度O(n) 空间复杂度O(n)
● 递归逻辑,注意递归函数的作用,时间复杂度O(n) 空间复杂度O(n)
● 迭代逻辑,时间复杂度O(n) 空间复杂度O(1)
// 使用栈
class Solution {
public ListNode reverseList(ListNode head) {
Deque<ListNode> stack = new ArrayDeque();
while (head != null) {
stack.addFirst(head); // 依次把节点入栈
head = head.next;
}
if (stack.isEmpty()) return null; // 栈空,直接返回
head = stack.removeFirst(); //先拿到头结点,因为要返回
ListNode cur = head; // 使用cur标记
while (!stack.isEmpty()) {
cur.next = stack.removeFirst(); // 不断弹出节点
cur = cur.next;
}
cur.next = null; // 这个很重要,不然会成环,必须把尾节点的next设为null
return head;
}
}
// 递归逻辑
class Solution {
public ListNode reverseList(ListNode head) { // 1 -> 2 -> 3
if (head == null || head.next == null) return head;
ListNode newHead = reverseList(head.next); // 3 -> 2 <- 1
head.next.next = head; // 3 -> 2 <=> 1
head.next = null; // 3 -> 2 -> 1 -> null
return newHead;
}
}
// 迭代
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode pre = head;
ListNode cur = pre.next;
ListNode temp;
while (cur != null) {
temp = cur.next;
cur.next = pre;
if (pre == head) pre.next = null;
pre = cur;
cur = temp;
}
return pre;
}
}
● 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
● 你可以假设数组中无重复元素。
● 和正常的二分查找类似,分析一下应该插入的位置即可
// 左闭右闭写法
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0, r = nums.length - 1, m;
while (l <= r) {
m = (l + r) >>> 1;
if (nums[m] < target) l = m + 1;
else if (nums[m] > target) r = m - 1;
else return m;
}
return l; // 这个r + 1也可以
}
}
// 左闭右开写法
class Solution {
public int searchInsert(int[] nums, int target) {
int l = 0, r = nums.length, m;
while (l < r) {
m = (l + r) >>> 1;
if (nums[m] < target) l = m + 1;
else if (nums[m] > target) r = m;
else return m;
}
return l; // 这个r也可以
}
}