前言

关于链表的算法最常用解法:递归,双指针,set

刷题网站推荐:

 牛客网

 Leetcode

1.单链表反转

//遍历法
public ListNode ReverseList(ListNode head) {
        ListNode curr = null;
        ListNode next = null;
        while (head != null) {
            next = head.next;
            head.next = curr;
            curr = head;
            head = next;
        }
        return curr;
    }

//递归法
public ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null)
            return head;
        ListNode temp = head.next;
        ListNode newHead = ReverseList(head.next);
        temp.next = head;
        head.next = null;
        return newHead;
    }

https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca

2.判断链表是否存在环

public static boolean hasCycle(Node head) {
    Node slow = head;
    Node fast = head;
    while(fast != null && fast.next != null) {
         slow = slow.next;
         fast = fast.next.next;
         if(slow == fast) {
               return true;
         }
    }
    return false;
}

public static boolean hasCycle(Node head) {
    Set<Node> nodeSet = new HashSet<>();
    while(head != null) {
        if(nodeSet.contains(head)) {
             return true;
        }
        nodeSet.add(head);
        head = head.next;
    }
   return false;
}

https://www.nowcoder.com/practice/650474f313294468a4ded3ce0f7898b9

3.链表中环的入口结点

简单方法:使用set

解题思路:

我们现在假定已经是一个有环的链表了,那么这个链表中怎么找到环的入口呢?在慢指针进入链表环之前,快指针已经进入了环,且在里面循环,这才能在慢指针进入环之后,快指针追到了慢指针,不妨假设快指针在环中走了n圈,慢指针在环中走了m圈,它们才相遇,而进入环之前的距离为x,环入口到相遇点的距离为y,相遇点到环入口的距离为z。快指针一共走了面试最常见算法2—链表_算法步,慢指针一共走了面试最常见算法2—链表_面试_02,这个时候快指针走的倍数是慢指针的两倍,则面试最常见算法2—链表_算法_03,这时候面试最常见算法2—链表_算法_04,因为环的大小是面试最常见算法2—链表_链表_05,说明从链表头经过环入口到达相遇地方经过的距离等于整数倍环的大小:那我们从头开始遍历到相遇位置,和从相遇位置开始在环中遍历,会使用相同的步数,而双方最后都会经过入口到相遇位置这y个节点,那说明这y个节点它们就是重叠遍历的,那它们从入口位置就相遇了

具体做法:

  • step 1:判断链表是否有环,并找到相遇的节点。
  • step 2:慢指针继续在相遇节点,快指针回到链表头,两个指针同步逐个元素逐个元素开始遍历链表。
  • step 3:再次相遇的地方就是环的入口。
//使用set
public ListNode EntryNodeOfLoop(ListNode pHead) {
        Set<ListNode> nodeSet = new HashSet<>();
        while (pHead != null) {
            if (nodeSet.contains(pHead)) {
                return pHead;
            }
            nodeSet.add(pHead);
            pHead = pHead.next;
        }
        return null;
    }
    
//双指针
public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null || pHead.next == null){
            return null;
        }
 
        ListNode fast = pHead;
        ListNode slow = pHead;
 
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                ListNode slow2 = pHead;
                while(slow2 != slow){
                    slow2 = slow2.next;
                    slow = slow.next;
                }
                return slow2;
            }
        }
        return null;
 
    }

https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4

3.反转链表的第m到第n个位置

public ListNode reverseBetween(ListNode head, int m, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        for(int i = 1; i < m; i++){
            pre = pre.next;
        }
        head = pre.next;
        for(int i = m; i < n; i++){
            ListNode nex = head.next;
            head.next = nex.next;
            nex.next = pre.next;
            pre.next = nex;
        }
        return dummy.next;
    }

https://leetcode.cn/problems/reverse-linked-list-ii/comments/

4.删除链表中的重复节点

public ListNode deleteDuplication(ListNode pHead) {
        //空链表
        if (pHead == null)
            return null;
        ListNode res = new ListNode(0);
        //在链表前加一个表头
        res.next = pHead;
        ListNode cur = res;
        while (cur.next != null && cur.next.next != null) {
            //遇到相邻两个节点值相同
            if (cur.next.val == cur.next.next.val) {
                int temp = cur.next.val;
                //将所有相同的都跳过
                while (cur.next != null && cur.next.val == temp)
                    cur.next = cur.next.next;
            } else
                cur = cur.next;
        }
        //返回时去掉表头
        return res.next;
    }

public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null)
            return pHead;
        ListNode next = pHead.next;
        if (pHead.val == next.val) {
            while (next != null && pHead.val == next.val)
                next = next.next;
            return deleteDuplication(next);
        } else {
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        }
    }

https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef

5.链表中倒数最后k个结点

解题思路:

设链表的长度为 N。设置两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到第 N - K 个节点处,该位置就是倒数第 K 个节点。

public ListNode FindKthToTail (ListNode pHead, int k) {
        if (pHead == null)
            return null;
        ListNode P1 = pHead;
        while (P1 != null && k-- > 0)
            P1 = P1.next;
        if (k > 0)
            return null;
        ListNode P2 = pHead;
        while (P1 != null) {
            P1 = P1.next;
            P2 = P2.next;
        }
        return P2;
    }

https://www.nowcoder.com/practice/886370fe658f41b498d40fb34ae76ff9

6.合并两个有序的链表

public ListNode Merge(ListNode list1, ListNode list2) {
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                cur.next = list1;
                list1 = list1.next;
            } else {
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
        }
        if (list1 != null)
            cur.next = list1;
        if (list2 != null)
            cur.next = list2;
        return head.next;
    }
    
    public ListNode Merge(ListNode list1, ListNode list2) {
        if (list1 == null)
            return list2;
        if (list2 == null)
            return list1;
        if (list1.val <= list2.val) {
            list1.next = Merge(list1.next, list2);
            return list1;
        } else {
            list2.next = Merge(list1, list2.next);
            return list2;
        }
    }

https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337

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

解题思路:

当访问链表 A 的指针访问到链表尾部时,令它从链表 B 的头部重新开始访问链表 B;同样地,当访问链表 B 的指针访问到链表尾部时,令它从链表 A 的头部重新开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。

简单解法:使用set

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode l1 = pHead1, l2 = pHead2;
        while (l1 != l2) {
            l1 = (l1 == null) ? pHead2 : l1.next;
            l2 = (l2 == null) ? pHead1 : l2.next;
        }
        return l1;
    }
    

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        Set<ListNode> nodeSet = new HashSet<>();
        while (pHead1 != null) {
            nodeSet.add(pHead1);
            pHead1 = pHead1.next;
        }
        while (pHead2 != null) {
            if (nodeSet.contains(pHead2)) {
                return pHead2;
            }
            pHead2 = pHead2.next;
        }
        return null;
    }

https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46

8. 在 O(1) 时间内删除链表节点

解题思路:

① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,然后令该节点指向下下个节点,再删除下一个节点,时间复杂度为 O(1)。

② 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向 null,时间复杂度为 O(N)。

综上,如果进行 N 次操作,那么大约需要操作节点的次数为 N-1+N=2N-1,其中 N-1 表示 N-1 个不是尾节点的每个节点以 O(1) 的时间复杂度操作节点的总次数,N 表示 1 个尾节点以 O(N) 的时间复杂度操作节点的总次数。(2N-1)/N ~ 2,因此该算法的平均时间复杂度为 O(1)。

public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
    if (head == null || tobeDelete == null)
        return null;
    if (tobeDelete.next != null) {
        // 要删除的节点不是尾节点
        ListNode next = tobeDelete.next;
        tobeDelete.val = next.val;
        tobeDelete.next = next.next;
    } else {
        if (head == tobeDelete)
             // 只有一个节点
            head = null;
        else {
            ListNode cur = head;
            while (cur.next != tobeDelete)
                cur = cur.next;
            cur.next = null;
        }
    }
    return head;
}

https://www.nowcoder.com/questionTerminal/3e1e18a5cc4c4645a45a2f5faddc0e89