链表理论基础
● 链表是一种通过指针串联在一起的线性结构,每一个节点有两个部分,数据域和指针域,最后一个节点指针域指向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)
● 数组定义的时候,长度固定,不能修改;链表可以动态增删,适合数据量不固定,增删频繁,查询少
203.移除链表元素
● 力扣题目链接
● 题意:删除链表中等于给定值 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; // 返回头结点}
}
707.设计链表
● 力扣题目链接
● 题意:
● 在链表类中实现这些功能:
○ 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;}
}
206.反转链表
● 力扣题目链接
● 题意:反转一个单链表。
思路
● 使用栈,时间复杂度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设为nullreturn head;}
}
// 递归逻辑
class Solution {public ListNode reverseList(ListNode head) { // 1 -> 2 -> 3if (head == null || head.next == null) return head;ListNode newHead = reverseList(head.next); // 3 -> 2 <- 1head.next.next = head; // 3 -> 2 <=> 1head.next = null; // 3 -> 2 -> 1 -> nullreturn 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;}
}
35.搜索插入位置(补)
● 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
● 你可以假设数组中无重复元素。
思路
● 和正常的二分查找类似,分析一下应该插入的位置即可
代码
// 左闭右闭写法
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也可以}
}