链表

链表要注意

  1. 设计到头节点的更改,可以添加虚拟节点
  2. 如果设计节点更改,尾指针要设置为null (反转链表)

递归

归并 + 双指针21. 合并两个有序链表

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  13. //合并有序的两个链表 ,类似于归并排序的思路
  14. ListNode cur1 = l1, cur2 = l2;
  15. ListNode head = new ListNode();
  16. ListNode res = head;
  17. while(cur1 != null && cur2 != null){
  18. if (cur1.val <= cur2.val) {
  19. res.next = new ListNode(cur1.val);
  20. res = res.next;
  21. cur1 = cur1.next;
  22. }
  23. else{
  24. res.next = new ListNode(cur2.val);
  25. res = res.next;
  26. cur2 = cur2.next;
  27. }
  28. }
  29. while(cur1 != null){
  30. res.next = new ListNode(cur1.val);
  31. res = res.next;
  32. cur1 = cur1.next;
  33. }
  34. while(cur2 != null){
  35. res.next = new ListNode(cur2.val);
  36. res = res.next;
  37. cur2 = cur2.next;
  38. }
  39. return head.next;
  40. }
  41. }

递归 or 双指针206. 反转链表

  1. 1.递归反转
  2. /**
  3. * Definition for singly-linked list.
  4. * public class ListNode {
  5. * int val;
  6. * ListNode next;
  7. * ListNode() {}
  8. * ListNode(int val) { this.val = val; }
  9. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  10. * }
  11. */
  12. class Solution {
  13. public ListNode reverseList(ListNode head) {
  14. //递归法:我们直接来看递归最后一次(只有两个节点)的情况:tail = head
  15. //head.next.next = head;
  16. //head.next = null;
  17. //这样,每一次递归都返回反转链表的头节点。因此最后返回的就是整个链表的头节点
  18. //需要特判只有一个节点
  19. if (head == null || head.next == null)return head;
  20. ListNode tail = reverseList(head.next);
  21. head.next.next = head;
  22. head.next = null;
  23. return tail;
  24. }
  25. }
  26. 2.双指针遍历
  27. /**
  28. * Definition for singly-linked list.
  29. * public class ListNode {
  30. * int val;
  31. * ListNode next;
  32. * ListNode() {}
  33. * ListNode(int val) { this.val = val; }
  34. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  35. * }
  36. */
  37. class Solution {
  38. public ListNode reverseList(ListNode head) {
  39. //使用两个指针来判断
  40. if (head == null || head.next == null)return head;
  41. ListNode curNode = head;
  42. ListNode nextNode = head.next;
  43. while(nextNode != null){
  44. ListNode temp = nextNode.next;
  45. nextNode.next = curNode;
  46. curNode = nextNode;
  47. nextNode = temp;
  48. }
  49. //最后尾指针要设定
  50. head.next = null;
  51. return curNode;
  52. }
  53. }

迭代归并 + 链表排序148. 排序链表

### 解题思路

由于链表不适合使用递归,使用迭代法求归并排序

时间复杂度o(2nlogn >=nlogn) 空间复杂度o(1)在循环内开辟的节点,每次跳出第二层循环 之前的dummy都会被GC,因此只用了一个节点的空间

链表 - 图1

### 代码

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. public ListNode sortList(ListNode head) {
  13. //存到数组里面,排完序再更新到链表里面 时间复杂度o(n + nlog), space o(N)
  14. //如果需要o(N)的空间,使用迭代的归并排序
  15. int n = 0;
  16. ListNode e = head;
  17. while (e != null) {e = e.next; n ++;}
  18. //每次归并的长度是i
  19. for (int i = 1; i < n; i *= 2){
  20. ListNode dummy = new ListNode(-1);
  21. ListNode cur = dummy;
  22. ListNode p = head, q, o = head;
  23. //每两个区间进行归并处理,表示当前一层的归并
  24. for (int j = 1; j <= n; j += i * 2){
  25. q = p = o;
  26. //p是第一个区间的头节点, q是第二个区间的头节点
  27. for (int k = 1;k <= i && q != null; k ++) q = q.next;
  28. int l = 0, r = 0;
  29. //归并处理
  30. while (l < i && r < i && q != null && p != null){
  31. if (p.val <= q.val) {
  32. cur.next = p;
  33. cur = cur.next;
  34. p = p.next;l++;
  35. }
  36. else {
  37. cur = cur.next = q; q = q.next;r++;
  38. }
  39. }
  40. while (l < i && p != null){cur = cur.next = p; p = p.next; l++;}
  41. while (r < i && q != null){cur = cur.next = q; q = q.next; r++;}
  42. o = q;
  43. cur.next = null;
  44. }
  45. head = dummy.next;
  46. for (ListNode c = head; c != null; c = c.next)
  47. System.out.print(c.val);
  48. System.out.println();
  49. }
  50. return head;
  51. }
  52. }

虚拟节点 + 递归 + 节点保存92. 反转链表 II

反转链表,可能会需要用到首节点的前一个点,因此使用虚拟节点

递归进行反转

保存开始反转链表首节点st 的前一个节点,尾节点ed的后一个节点

链表 - 图2

  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode() {}
  7. * ListNode(int val) { this.val = val; }
  8. * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
  9. * }
  10. */
  11. class Solution {
  12. //记录st的前一个点和ed的后一个点
  13. ListNode tmp, tmp1;
  14. int left, right;
  15. public ListNode reverseBetween(ListNode head, int left, int right) {
  16. //递归或者双指针
  17. if (head.next == null) return head;
  18. this.left = left; this.right = right;
  19. ListNode dummy = new ListNode(-1);
  20. dummy.next = head;
  21. ListNode st = dummy, ed = dummy;
  22. for (int i = 0; i < right; i ++){
  23. ed = ed.next;
  24. if (i < left){
  25. tmp1 = st;
  26. st = st.next;
  27. }
  28. }
  29. tmp = ed.next;
  30. ListNode new_head = reverse(st, ed);
  31. tmp1.next = new_head;
  32. st.next = tmp;
  33. return dummy.next;
  34. }
  35. //内部反转指向
  36. public ListNode reverse(ListNode cur, ListNode ed){
  37. if (cur == ed) return cur;
  38. ListNode new_head = reverse(cur.next, ed);
  39. cur.next.next = cur;
  40. cur.next = null;
  41. return new_head;
  42. }
  43. }

虚拟节点

无需额外空间 48. 复杂链表的复刻 - AcWing题库

思路

直接在每个点后面添加这个点复刻,并复制random,最后再将复刻的点分离出来

  1. class Solution {
  2. public ListNode copyRandomList(ListNode head) {
  3. ListNode cur = head;
  4. //复刻节点,random指针不赋值,由于复刻了之后变成了两倍,因此要next两次
  5. for (; cur != null; cur = cur.next.next){
  6. ListNode n = new ListNode(cur.val);
  7. n.next = cur.next;
  8. cur.next = n;
  9. }
  10. //赋值random指针
  11. for (cur = head; cur != null; cur = cur.next.next){
  12. //如果random指针不为空,ndom指针,让新节点的random指向对应节点的复刻
  13. if (cur.random != null)
  14. cur.next.random = cur.random.next;
  15. }
  16. //分离复刻节点,建立dummy虚拟节点为头节点,用于连接复刻链表
  17. ListNode dummy = new ListNode(-1);
  18. cur = dummy;
  19. for (ListNode p = head; p != null; p = p.next){
  20. cur.next = p.next;
  21. cur = cur.next;
  22. //恢复原链表
  23. p.next = p.next.next;
  24. }
  25. return dummy.next;
  26. }
  27. }

虚拟节点+双指针29. 删除链表中重复的节点 - AcWing题库

思路居然和y总代码思路差不多,感动

定义两个指针 st,ed,同时定义虚拟节点 dummy

st表示重复节点的上一个节点

ed 退出循环的时候表示重复节点的尾节点的下一个节点。在循环中表示重复节点的尾节点

因此,从dummy开始遍历

  1. ed向后移动一格
  2. 如果 edst.next 的值相等,并且 ed没有遍历的到结尾, ed = ed.next,定义 isRepeated = true表示 st ~ed 有重复值

    1. st.next = ed 删除重复值,重置标志位
  3. 如果 edst.next 的值不相等,st向后移动一格
  1. /**
  2. * Definition for singly-linked list.
  3. * public class ListNode {
  4. * int val;
  5. * ListNode next;
  6. * ListNode(int x) { val = x; }
  7. * }
  8. */
  9. class Solution {
  10. public ListNode deleteDuplication(ListNode head) {
  11. if (head == null || head.next == null) return head;
  12. ListNode dummy = new ListNode(-1);
  13. dummy.next = head;
  14. ListNode st = dummy, ed = head;
  15. boolean isRepeated = false;
  16. while (ed != null){
  17. ed = ed.next;
  18. while (ed != null && ed.val == st.next.val){
  19. ed = ed.next;
  20. isRepeated = true;
  21. }
  22. if (isRepeated){
  23. st.next = ed;
  24. isRepeated = false;
  25. }
  26. else
  27. st = st.next;
  28. }
  29. return dummy.next;
  30. }
  31. }

保存节点 143. 重排链表

思路

保存节点,但是在实际写代码的过程中总是应为保存变量 tmp 和tmp1和最终循环中止条件设定不好而一直调试。

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public void reorderList(ListNode head) {
        ListNode st = head, ed = head, tmp = st.next, tmp1 = head;  
        if (head == null || head.next == null) return;   

        while (tmp1.next.next != null){
            tmp1 = tmp1.next;
        }
        ed = tmp1.next;


        while (st != ed && st.next != ed){
            tmp1.next = null;
            st.next = ed;
            ed.next = tmp;
            st = tmp;
            ed = tmp1;
            tmp = st.next;
            tmp1 = st;
            if (st == ed) break;
            while (tmp1.next != ed){
                tmp1 = tmp1.next;
            }
            System.out.println(st.val+ " " +tmp.val + " "+ tmp1.val+ " " +  ed.val );
        }
    }
}

虚拟节点 / 双指针遍历19. 删除链表的倒数第 N 个结点

思路

删除节点,需要用到节点的前一个点,可以创建一个虚拟节点作为链表的首节点,这样能删除链表实际的第一个节点

遍历统计个数 + 删除

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    //由于我们要找到删除节点的前一个节点,如果是删除节点是头节点,我们找到的就应该是头节点的前一个节点
    //建立一个dummy虚拟节点
    //找到倒数k + 1个节点
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (head.next == null) return null;
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        int cnt = 1;
        while(head.next != null) {
            head = head.next;
            cnt ++;
        }
        int num = cnt - n;
        head = dummy;
        while(num -- > 0){
            head = head.next;
        }
        head.next = head.next.next;
        return dummy.next;
    }
}

通过双指针遍历,指针长度就是n + 1,这样当ed为尾节点,st就是头节点的前一个节点

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    //通过双指针
    //插入虚拟节点
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (head.next == null) return null;
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode st, ed;
        st = dummy; ed = dummy;
        for (int i = 0; i < n; i ++)   
            ed = ed.next;
        while (ed.next != null){
            st = st.next;
            ed = ed.next;
        }
        st.next = st.next.next;
        return dummy.next;
    }
}

双指针+虚拟节点 + 节点保存24. 两两交换链表中的节点

设计到了头节点的更改,因此添加虚拟节点dummy

  1. 保存ed.next
  2. 修改内部指向
  3. 修改外部指向
  4. 更新ed 和st

链表 - 图3

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        //双指针
        if (head == null || head.next == null) return head;
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode st = dummy, ed = dummy.next.next;
        while (ed != null){
            ListNode tmp = ed.next;
            ed.next = st.next;
            st.next.next = tmp;
            st.next = ed;
            st = st.next.next;
            if (tmp == null) break;
            ed = tmp.next;
        }
        return dummy.next;
    }
}

双指针+虚拟节点+一组节点交换25. K 个一组翻转链表

顺序遍历翻转链表

思路和上一题是一样的,不过是st 和 ed指针中加了 ft 和 sd指针,用于内部的指向反转

  1. 保存ed.next
  2. 修改内部指向
  3. 修改外部指向(保存,新的街尾节点,更新ed指向反转后的结尾节点)
  4. 更新ed 和st

链表 - 图4

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head.next == null) return head;
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode st = dummy, ed = dummy;

        //交换多个节点,先将内部的指向翻转
        //将两端的外部指向更改
        for (int i = 0; i < k; i ++)
            ed = ed.next;
        while (ed != null){
            //内部的更改,还需要两个指针ft,sd
           ListNode ft = st.next, sd = ft.next;
            //保存ed的next,用于st的指向和移动
            ListNode tmp1 = ed.next;
           //更改内部的指向
           for (int i = 0; i < k - 1; i ++){
            ListNode tmp = sd.next;
            sd.next = ft;
            ft = sd; sd = tmp;
           }

           //外部更改,重新修改ed 的位置
           st.next.next = tmp1;
           ListNode tmp2 = st.next;
           st.next = ed;
           ed = tmp2;
            System.out.println(st.next.val + " " + ed.val);

            //st,ed移动k步
           for (int i = 0; i < k; i ++) {
               st = st.next;
               if (ed == null) break;
               ed = ed.next; 
           }
        }
        return dummy.next;
    }
}

递归翻转链表


class Solution {
    ListNode dummy = new ListNode(-1); 
    public ListNode reverseKGroup(ListNode head, int k) {
        dummy.next = null;
        // st和ed为起始值,dummy连接第一次翻转返回的节点
        ListNode st = head, ed = head;
        if (k == 1) return head;
        for (int i = 0; i < k - 1 && ed.next != null; i ++) ed = ed.next;
        ListNode nst = st, ned = ed;
        while (ed != null) {
            // 保存下一次翻转的节点
            for (int i = 0; i < k && ned != null; i ++){
                ned = ned.next; nst = nst.next;
            }
            // 将st之后的节点和ed之间的节点翻转, 返回翻转后的末尾节点
            ListNode t = ed.next;
            reverse(st, ed); 
            // 否则连接下一次的首节点
            if (ned == null) st.next = t; 
            // 如果下一次也反转,反转后的末尾节点和下一次翻转后的首节点(未反转的末尾节点)
            else st.next = ned; 
            st = nst; ed = ned;
        }
        return dummy.next;
    }
    // 每一层翻转st和ed之间的节点, 返回翻转后的尾节点
    ListNode reverse(ListNode st, ListNode ed){
        // 如果st和ed相邻,返回ed,更新关系
        if (st.next == ed) {
            ed.next = st;
            if (dummy.next == null) dummy.next = ed; 
            return st;
        }
        ed = reverse(st.next, ed);
        ed.next = st;
        return st;
    }
}

虚拟节点 + 排序 147. 对链表进行插入排序

思路

新建一个链表:

  直接循环链表,对每个点cur处理

    在新建链表中,从头开始找,寻找一个比cur大的点的前一个点p,将cur插入到p后面

    如果没有找到,此时p是结尾的最后一个点,那么直接将cur插入到p后面

    可能会插入到头节点前面,因此可以使用虚拟节点dummy作为新建链表的首节点
/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode insertionSortList(ListNode head) {
        /*
            新建一个链表:
            直接循环链表,对每个点cur处理
                在新建链表中,从头开始找,寻找一个比cur大的点的前一个点p,将cur插入到p后面
                如果没有找到,此时p是结尾的最后一个点,那么直接将cur插入到p后面
                可能会插入到头节点前面,因此可以使用虚拟节点dummy作为新建链表的首节点
        */
        ListNode cur = head, p;
        ListNode dummy = new ListNode(-1);
        for (; cur != null;){
            ListNode tmp = cur.next;
            for (p = dummy; p.next != null && p.next.val <= cur.val;){
                p = p.next;
            }
            cur.next = p.next;
            p.next = cur;
            cur = tmp;
        }
        return dummy.next;
    }
}

树和链表转换

链表转二叉树

114. 二叉树展开为链表

第一种方法

使用前序遍历把对应的节点添加到list中,再分别取出每个节点,设置其左右子节点。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public void flatten(TreeNode root) {
        List<TreeNode> list = new ArrayList<TreeNode>();
        preOrder(root, list);
        for (int i = 1; i < list.size();  i++){
            TreeNode prev = list.get(i - 1), cur = list.get(i);
            prev.left = null;
            prev.right = cur;
            cur.right = null;
        }
    }
    public void preOrder(TreeNode root, List<TreeNode> list){
        if (root != null){
            list.add(root);
            preOrder(root.left, list);
            preOrder(root.right, list);
        }
    }    
}

方法2

时间复杂度 o(n)

空间复杂度o(n)

  1. 如果当前节点有子树,按照右左的顺序存放子树进栈,记录当前节点root 为 prev
  2. 把节点pop出栈,连接当前点和父节点prev
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public void flatten(TreeNode root) {
        if (root == null) return ;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        //如果把节点pop出栈,连接当前点和父节点prev
        //如果当前节点有子树,按照右左的顺序存放子树进栈,记录当前节点root 为 prev

        stack.push(root);
        TreeNode prev = null;
        while(!stack.isEmpty()){
            TreeNode cur = stack.pop();
            if (prev != null){
                prev.right = cur;
                prev.left = null;
            }
            if (cur.right != null){
                stack.push(cur.right);
            }
            if (cur.left != null)
                stack.push(cur.left);
            prev = cur;
        }
    }   
}

方法

空间复杂度o(1)


环形链表

LC141. 环形链表

解题思路

快慢指针:fast指针每次移动2步,slow每次移动一步。
这样每次迭代,fast都会比slow多移动一步。

代码

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        //首先我们看到这是单向链表,意味着它只能由一个出口,最多只有一个环
        //如果从头节点开始遍历,就意味着一定会进入环
        //定义快慢指针(快指针是慢指针的两倍)。如果两个指针相遇,就是有环
        //如果快指针走到了尾节点,就说明没有环
        ListNode fast = head, slow = head;
        if (fast == null || fast.next == null) return false;
        while(fast != null){
            fast = fast.next; slow = slow.next;
            if (fast == null) return false;
            fast = fast.next;
            if (fast == slow) return true;
        }
        return false;
    }
}

142. 环形链表 II

解题思路

   //我们让快慢指针的初始位置更改,让他们第一次相遇的位置就是入口点
    //位置定在哪里?
    //让慢指针从head开始,它进入入口的时候,快指针也进入入口
    //快指针初始位置在那?我们先看到:假设快慢指针同时在起点,他们最终相遇的位置距离入口是y
    //当慢指针走了x步进入入口,快指针已经在环走了x步。
    //慢指针回退y步,快指针回退2y步。慢指针进入入口的时候,快指针在环-y(== x)的地方
    //如果快指针从y点(相遇点)出发(它此时距离入口就是环-y == x,和慢指针同样的距离),
    //慢指针从head出发,他们最终相遇就是在入口点

代码

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        //我们让快慢指针的初始位置更改,让他们第一次相遇的位置就是入口点
        //位置定在哪里?
        //让慢指针从head开始,它进入入口的时候,快指针也进入入口
        //快指针初始位置在那?我们先看到:假设快慢指针同时在起点,他们最终相遇的位置距离入口是y
        //当慢指针走了x步进入入口,快指针已经在环走了x步。
        //慢指针回退y步,快指针回退2y步。慢指针进入入口的时候,快指针在环-y(== x)的地方
        //如果快指针从y点(相遇点)出发(它此时距离入口就是环-y == x,和慢指针同样的距离),
        //慢指针从head出发,他们最终相遇就是在入口点
        ListNode fast = head, slow = head;
        while(fast != null){
            fast = fast.next; slow = slow.next;
            if (fast == null) return null;
            fast = fast.next; 
            if (fast == slow){
                //System.out.println(slow.val);
                slow = head;
                while(fast != slow){
                    fast = fast.next; slow = slow.next;
                }
                return slow;
            }
        }
        return null;
    }
}

技巧题

伪删除(替换值而不删除节点)剑指 Offer 18. 删除链表的节点

不用真正删除节点,把下一个节点的值更新成当前值,并删除下一个节点即可

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode _head, int val) {
        ListNode head = _head;
        while (head.val != val && head.next.next != null){
            head = head.next;
        }
        if (head.val != val) {
            head.next = head.next.next;
            return _head;
        }
        head.val = head.next.val;
        head.next = head.next.next;
        return _head;
    }
}

66. 两个链表的第一个公共结点

链表 - 图5

null节点是一定要经过的。

当两个点没有重合的时候,A的路径:a->c->null->b->c->null B的路径:b->c->null->a->c->null

最后会在null重合。

两条路径的总步数是一样的。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
class Solution {
    public ListNode findFirstCommonNode(ListNode headA, ListNode headB) {
        ListNode l1 = headA, l2 = headB;
        while (l1 != l2){
            if (l1 != null)
                l1 = l1.next;
            else 
                l1 = headB;
            if (l2 != null)
                l2 = l2.next;
            else 
                l2 = headA;
        }
        return l1;
    }
}

堆+合并23. 合并K个升序链表

注意

  ListNode  dummy = new ListNode(-1), pre = dummy;

dummy是哨兵头,pre是哨兵尾

/**
 * Definition for singly-linked list.
 * 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; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        //最小堆+合并链表 o(logn * mn)
        //如果使用多路归并,o(mnn)
        // 插入对logn ,从堆中拿最小值,堆删除这个值,对应的数组下标后移一位,再加入这个值
        PriorityQueue<ListNode> heap = new PriorityQueue<>((a, b)->a.val - b.val);
        int n = lists.length; 
        if (n == 0) return null;
        ListNode  dummy = new ListNode(-1), pre = dummy;
        //加入前n个
        for (int i = 0; i < n; i ++)
            if (lists[i] != null) heap.add(lists[i]);

        while (!heap.isEmpty()){
            //取出最小元素
            ListNode num = heap.poll();
            if (num.next != null) heap.add(num.next);
            pre.next = num;
            pre = num;
        }
        return dummy.next;
    }
}