链表节点定义
public 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; } }
*移除链表节点
题目链接:https://leetcode.cn/problems/reverse-linked-list/
【思路】
有时候增加一个虚拟节点,可以让后面的操作都一致
public static ListNode removeElements(ListNode head, int val) { if (head == null) { return null; } // 增加一个虚拟节点 ListNode virtualNode = new ListNode(); virtualNode.next = head; ListNode cur = virtualNode; ListNode next = cur.next; while (next != null) { if (next.val == val) { cur.next = next.next; } else { cur = next; } next = cur.next; } return virtualNode.next; }
*反转链表
双指针法
- 时间复杂度: O(n)
- 空间复杂度: O(1)
public static ListNode reverseList(ListNode head) { ListNode cur = head; ListNode pre = null; ListNode temp = null; while (cur != null) { temp = cur.next; cur.next = pre; pre = cur; cur = temp; } return pre; }
递归法
- 时间复杂度: O(n)
- 空间复杂度: O(n)
这里的逻辑和双指针法的逻辑是一样的,只是实现方式是递归,那为什么空间复杂度更高呢?
答:因为递归本质上是一种函数调用,在每一次递归调用时,都会在函数栈中分配一段内存空间来保存函数的局部变量、返回地址以及参数等信息。因此,递归实现相对于循环实现的空间复杂度更高,会占用更多的内存空间。尤其是在递归深度较大的情况下,可能会导致栈溢出等问题。而使用循环实现通常不需要使用额外的栈空间,因此循环实现的空间复杂度比递归实现要低
public static ListNode reverseList1(ListNode head) { return reverse(null, head); } public static ListNode reverse(ListNode pre, ListNode cur) { if (cur != null) { ListNode temp = cur.next; cur.next = pre; pre = reverse(cur, temp); } return pre; }
*两两交换链表中的节点
https://leetcode.cn/problems/swap-nodes-in-pairs/
- 时间复杂度:O(n)
- 空间复杂度:O(1)
public static ListNode swapPairs(ListNode head) { // 如果链表中没有元素 或者 只有一个元素,直接返回就行 if (head == null || head.next == null) { return head; } // 增加一个虚拟头节点 ListNode virtualNode = new ListNode(); virtualNode.next = head; ListNode last = virtualNode; ListNode cur = head; ListNode next = cur.next; ListNode temp = null; while (next != null) { // 交换 temp = next.next; cur.next = next.next; next.next = cur; last.next = next; // 指针移动到新的位置 last = cur; cur = temp; if (cur != null) { next = cur.next; } else { break; } } return virtualNode.next; }
*删除链表的倒数第N个节点
https://leetcode.cn/problems/remove-nth-node-from-end-of-list/submissions/
快慢指针法
/** * 让快指针比慢指针先移动 n 步 * * @param head * @param n * @return */ public static ListNode removeNthFromEnd(ListNode head, int n) { ListNode virtual = new ListNode(); virtual.next = head; // 让快指针先移动到n个节点 ListNode fast = virtual; for (int i = 0; i < n; i++) { if (fast.next != null) { fast = fast.next; } } // slow和fast一起移动,等fast到达最后一个节点的时候,slow也就到达了要删除的节点前面的节点 ListNode slow = virtual; while (fast.next != null) { slow = slow.next; fast = fast.next; } // 执行删除操作 slow.next = slow.next.next; return virtual.next; }
- 时间复杂度: O(n)
- 空间复杂度: O(1)
*链表相交
https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/
注意点
并不是判断val相等,而是hashcode相等
暴力求解
直接两重循环,循环两个链表
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
进阶解法
利用题目中的信息,已经是链表的尾部才有可能相交,可以先让一条链表前进 两条链表长度差值 的位置,最后再两条链表一起前进
public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode cur1 = headA; ListNode cur2 = headB; int sizeA = 0; int sizeB = 0; // 统计链表A的长度 while (cur1 != null) { sizeA += 1; cur1 = cur1.next; } // 统计链表B的长度 while (cur2 != null) { sizeB += 1; cur2 = cur2.next; } // 回到初始位置 cur1 = headA; cur2 = headB; // 先让元素较多的链表的指针移动一段距离 if (sizeA > sizeB) { // 链表A的元素较多,先走一段距离 for (int i = 0; i < (sizeA - sizeB); i++) { cur1 = cur1.next; } } else if (sizeA < sizeB) { for (int i = 0; i < (sizeB - sizeA); i++) { cur2 = cur2.next; } } // 两个链表同时走动 while (cur1 != null && cur2 != null) { // 注意,需要的是hash值 if (cur1.hashCode() == cur2.hashCode()) { return cur1; } cur1 = cur1.next; cur2 = cur2.next; } return null; }
- 时间复杂度:O(n + m)
- 空间复杂度:O(1)
*环形链表
https://leetcode.cn/problems/linked-list-cycle-ii/
public static ListNode detectCycle1(ListNode head) { if (head == null) { return null; } ListNode slow = head; ListNode fast = head; ListNode node2 = null; // 先找到相交点 while (slow != null && fast.next != null) { // slow移动一步 slow = slow.next; // fast移动两步 fast = fast.next.next; if (slow.hashCode() == fast.hashCode()) { // slow 和 fast相交 node2 = slow; // 让node1从起点出发,node2从相交点出发,当node1和node2相交的时候,相交时的节点即环的入口 ListNode node1 = head; while (true) { if (node1.hashCode() == node2.hashCode()) { return node1; } else { node1 = node1.next; node2 = node2.next; } } } } return null; }
- 时间复杂度: O(n)。因为快慢指针相遇前,指针走的次数小于链表长度;快慢指针相遇后,两个node指针走的次数也小于链表长度,因此走的总次数小于 2n
- 空间复杂度: O(1)