链表题最好画图来说明整个流程。

LeetCode

2. 两数相加

给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照逆序的方式存储的,并且它们的每个节点只能存储一位数字。
例如 2 -> 4 -> 3 表示 342
需要注意进位的问题,还有头结点的使用。

  1. public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
  2. ListNode dummy = new ListNode(-1), nextNode = dummy;
  3. int carry = 0;
  4. while (l1 != null || l2 != null || carry != 0) {
  5. int v1 = (l1 != null ? l1.val : 0);
  6. int v2 = (l2 != null ? l2.val : 0);
  7. ListNode newNode = new ListNode((v1 + v2 + carry)%10);
  8. carry = (v1 + v2 + carry) / 10;
  9. nextNode.next = newNode;
  10. nextNode = newNode;
  11. l1 = (l1 != null ? l1.next : null);
  12. l2 = (l2 != null ? l2.next : null);
  13. }
  14. return dummy.next;
  15. }

19. 删除链表的倒数第N个节点

快慢指针实现一遍扫描

  1. public ListNode removeNthFromEnd(ListNode head, int n) {
  2. ListNode dummy = new ListNode(-1);
  3. dummy.next = head;
  4. ListNode first = dummy, second = dummy;
  5. while (n-- > 0) {
  6. first = first.next;
  7. if (first == null) break;
  8. }
  9. if (n > 0) return head;
  10. while (first.next != null) {
  11. first = first.next;
  12. second = second.next;
  13. }
  14. ListNode removeNode = second.next;
  15. second.next = second.next.next;
  16. removeNode.next = null;
  17. return dummy.next;
  18. }

24. 两两交换链表中的节点

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

  1. public ListNode swapPairs(ListNode head) {
  2. ListNode dummy = new ListNode(-1);
  3. dummy.next = head;
  4. ListNode prev1 = dummy, prev2 = dummy.next;
  5. while (prev1.next != null && prev2.next != null) {
  6. ListNode temp = prev2.next.next;
  7. prev1.next = prev2.next;
  8. prev1.next.next = prev2;
  9. prev2.next = temp;
  10. prev1 = prev2; prev2 = temp;
  11. }
  12. return dummy.next;
  13. }

142. 环形链表 II

返回链表入环的第一个节点,如果不存在环,则返回null

  1. public ListNode detectCycle(ListNode head) {
  2. if (head == null || head.next == null) return null;
  3. ListNode slow = head, fast = head;
  4. while (fast != null && fast.next != null) {
  5. slow = slow.next;
  6. fast = fast.next.next;
  7. if (fast == slow) break;
  8. }
  9. if (fast == null || fast.next == null) return null;
  10. //找环
  11. slow = head;
  12. while (slow != fast) {
  13. slow = slow.next;
  14. fast = fast.next;
  15. }
  16. return slow;
  17. }

1602209187630.jpg
快慢指针第一次相遇的点将环分为两部分,一部分是l,另一部分是l,因此有第一个等式的成立,其中k表示快指针走的圈数,然后等式可以化为第二个。
第二个等式的含义是,一个指针走过l的距离等同于另一个指针走了k圈,再减去l的距离。通常小环的话有k>1,大环的话k=1。(大环比较好理解,直接有l等于l)

143. 重排链表

给定一个单链表 LLL→…→L__nL将其重新排列后变为: LL__nLL__nLL__n→…

  1. //找中间节点,然后逆转中间节点以后的
  2. public void reorderList(ListNode head) {
  3. if (head == null) return ;
  4. ListNode middle = findMiddleNode(head);
  5. ListNode second = reverseList(middle.next);
  6. middle.next = null;
  7. ListNode dummy = new ListNode(-1, head), curNode = dummy;
  8. ListNode curNode1 = head, curNode2 = second;
  9. while (curNode1 != null && curNode2 != null) {
  10. curNode.next = curNode1;
  11. curNode1 = curNode1.next;
  12. curNode = curNode.next;
  13. curNode.next = curNode2;
  14. curNode = curNode.next;
  15. curNode2 = curNode2.next;
  16. }
  17. curNode.next = curNode1; //奇数个节点的情况
  18. dummy.next = null;
  19. }
  20. private ListNode findMiddleNode(ListNode head) {
  21. ListNode fast = head, slow = head;
  22. while (fast != null && fast.next != null) {
  23. fast = fast.next.next;
  24. slow = slow.next;
  25. }
  26. return slow;
  27. }
  28. private ListNode reverseList(ListNode head) {
  29. if (head == null) return null;
  30. ListNode curNode = head, reversed = null;
  31. while (curNode != null) {
  32. ListNode temp = curNode.next;
  33. curNode.next = reversed;
  34. reversed = curNode;
  35. curNode = temp;
  36. }
  37. return reversed;
  38. }

要一点技巧,找到中间节点,然后将逆转中间节点之后的链表,这样就相当于两个链表的合并了。

234. 回文链表

判断链表是否回文。要求时间复杂度为O(n),空间复杂度为O(1)。
由于链表的顺序读取性质,如果想要判断回文而且要满足时间复杂度的话,意味着要改变链表的结构,自然想到反转链表。因此思路是,先找到中间节点,然后反转链表后半部分成为一个新链表,判断链表两部分是否相同,最后不要忘记恢复链表。此外,自然要想到奇数个节点的情况,多余的那个节点是放在哪部分链表中才不会需要特判,即中间节点选择哪一个,这里就直接放在了前半部分链表中(第6行代码),由于没有改变middleNode的指向,所以恢复链表直接反转就行了。
官方递归解法还挺有趣的,可以看一下。

  1. public boolean isPalindrome(ListNode head) {
  2. if (head == null) return true;
  3. //获得链表长度
  4. int nums = calculateListNums(head);
  5. ListNode curNode1 = head, middleNode = head;
  6. for (int i = 1; i < (nums + 1)/2; i++) {
  7. middleNode = middleNode.next;
  8. }
  9. ListNode head2 = reverseList(middleNode.next), curNode2 = head2;
  10. while (curNode1 != null && curNode2 != null) {
  11. if (curNode1.val != curNode2.val) {
  12. reverseList(head2); //不要忘记恢复链表
  13. return false;
  14. }
  15. curNode1 = curNode1.next;
  16. curNode2 = curNode2.next;
  17. }
  18. reverseList(head2); //不要忘记恢复链表
  19. return true;
  20. }