链表算法题

108 阅读5分钟

1.求链表长度

public int getLength(ListNode head){
    int length = 0;
    ListNode tmpNode = head;
    while(tmpNode!=null){
        length++;
        tmpNode = tmpNode.next;
    }
    return length;
}

2.链表翻转(遍历法 递归法)

/**
*	遍历法
*  preNode    curNode     nextNode 
*     ↓          ↓           ↓
*    null        1  ------>  2  ------>  3  ------>  4
*/
public ListNode reverseLinkedList(ListNode head){
    ListNode preNode = null;
    ListNode curNode = head;
    ListNode nextNode = null;
    while(curNode !=null){
        nextNode = curNode.next; 
        curNode.next = preNode; //curNode指向翻转
        preNode = curNode; //preNode后移
        curNode = nextNode; //curNode后移
    }
    return preNode;
}

//递归法
public ListNode reverseLinkedList(ListNode head){
	if(head.next == null) return head;
    ListNode last = reverseList(head.next);
    head.next.next = head; //建立新的反向指向
    head.next = null; //去除旧的指向
    return last;
}

3.求倒数第K个节点

/**
*	使用两个指针 first 和 second从链表head进行遍历,并且first 比second 超前 k个节点。当 first 遍历到链表
* 的尾结点后时,second 就恰好处于倒数第k个节点
*/
public ListNode getKthFromEnd(ListNode head, int k) {
     if(head == null) return head; //特判
     ListNode first = head;
     ListNode second = head;
     ListNode cur = head;
     for (int i = 0; i < k ; i++) { //first节点先遍历k个节点
         first = first.next;
     }
     while(first != null){//first遍历至尾节点后的null
         first = first.next;
         second = second.next;
     }
     return second;
}

4.求链表中间结点

//如果链表的长度为偶数时,返回中间两个节点的第一个结点 
public ListNode middleNode(ListNode head) {
     ListNode fast = head;
     ListNode slow = head;
     while(fast.next !=null && fast.next.next !=null){
         fast = fast.next.next;
         slow = slow.next;
     }
     return slow;
}

5.链表划分

//题目描述: 给定一个单链表和数值x,划分链表使得所有小于x的节点排在大于等于x的节点之前。
//输入: 1-->3-->0-->7-->2-->null  x = 3
//输出: 1-->0-->2-->3-->7-->null
public class Solution {
    public ListNode partition(ListNode head, int x) {
        if(head == null) return null;
        ListNode leftDummy = new ListNode(0);
        ListNode rightDummy = new ListNode(0);
        ListNode left = leftDummy, right = rightDummy;

        while (head != null) {
            if (head.val &lt; x) {
                left.next = head;
                left = head;
            } else {
                right.next = head;
                right = head;
            }
            head = head.next;
        }

        right.next = null;
        left.next = rightDummy.next;
        return leftDummy.next;
    }
}

6.快排实现单链表排序

class Solution {
    public ListNode sortList(ListNode head) {
        return quickSort(head, null);
    }

    public ListNode quickSort(ListNode head, ListNode end){
        if(head == end || head.next == end ) return head;
        ListNode left = head, right = head, p = head.next;
        while(p != end){
            ListNode next = p.next;
            if(p.val < head.val){
                p.next = left;
                left = p;
            }else{
                right.next = p;
                right = p;
            }
            p = next;
        }
        right.next = end;
        ListNode node = quickSort(left,head);
        head.next = quickSort(head.next,end);
        return node;
    }
}

7.归并实现单链表排序

    public ListNode sortList(ListNode head) {
        // 1、递归结束条件
        if (head == null || head.next == null) {
            return head;
        }

        // 2、找到链表中间节点并断开链表 & 递归下探
        ListNode midNode = middleNode(head);
        ListNode rightHead = midNode.next;
        midNode.next = null;

        ListNode left = sortList(head);
        ListNode right = sortList(rightHead);

        // 3、当前层业务操作(合并有序链表)
        return mergeTwoLists(left, right);
    }
    
    //  找到链表中间节点(876. 链表的中间结点)
    private ListNode middleNode(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode slow = head;
        ListNode fast = head.next.next;

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }

    // 合并两个有序链表(21. 合并两个有序链表)
    private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode sentry = new ListNode(-1);
        ListNode curr = sentry;

        while(l1 != null && l2 != null) {
            if(l1.val < l2.val) {
                curr.next = l1;
                l1 = l1.next;
            } else {
                curr.next = l2;
                l2 = l2.next;
            }

            curr = curr.next;
        }

        curr.next = l1 != null ? l1 : l2;
        return sentry.next;
    }

8.合并两个有序单链表

public ListNode mergeLinkedList(ListNode l1,ListNode l2){
    ListNode node = new ListNode();
    ListNode tempNode = node;
    while(l1 !=null && l2 !=null){
        if(l1.val <= l2.val){
            tempNode.next=l1;
            l1 = l1.next;
        }else{
            tempNode.next =l2;
            l2 = l2.next;
        }
        tempNode = tempNode.next;
    }
    // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
    tempNode.next = l1 == null ? l2 : l1;
    return node.next;
}

9.复杂链表复制

//剑指 Offer 35. 复杂链表的复制
public Node copyRandomList(Node head) {
    if(head == null) return null;
    Node cur = head;
    Map<Node, Node> map = new HashMap<>();
    // 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
    while(cur != null) {
        map.put(cur, new Node(cur.val));
        cur = cur.next;
    }
    cur = head;
    // 4. 构建新链表的 next 和 random 指向
    while(cur != null) {
        map.get(cur).next = map.get(cur.next);
        map.get(cur).random = map.get(cur.random);
        cur = cur.next;
    }
    // 5. 返回新链表的头节点
    return map.get(head);
}

10.判断链表是否有环

public boolean hasCycle(ListNode head) {
    if(head == null || head.next == null) return false;
    ListNode fast= head ,slow = head;
    while(true){
        if(fast == null  || fast.next == null) return false;
        fast = fast.next.next;
        slow = slow.next;
        if(fast == slow) return true;
    }           
}

11.求环形链表入环的第一个节点

public ListNode detectCycle(ListNode head) {
    ListNode fast = head, slow = head;
    while (true) {
        if (fast == null || fast.next == null) return null;
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) break;
    }
    fast = head;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return fast;
}

12.判断两个无环单链表是否相交

  • 方法一 最直接的方法是判断 A 链表的每个节点是否在 B 链表中,但是这种方法的时间复杂度为 O(Length(A) * Length(B))。
  • 方法二 转化为环的问题。把 B 链表接在 A 链表后面,如果得到的链表有环,则说明两个链表相交。可以之前讨论过的快慢指针来判断是否有环,但是这里还有更简单的方法。如果 B 链表和 A 链表相交,把 B 链表接在 A 链表后面时,B 链表的所有节点都在环内,所以此时只需要遍历 B 链表,看是否会回到起点就可以判断是否相交。这个方法需要先遍历一次 A 链表,找到尾节点,然后还要遍历一次 B 链表,判断是否形成环,时间复杂度为 O(Length(A) + Length(B))。
  • 方法三 除了转化为环的问题,还可以利用“如果两个链表相交于某一节点,那么之后的节点都是共有的”这个特点,如果两个链表相交,那么最后一个节点一定是共有的。所以可以得出另外一种解法,先遍历 A 链表,记住尾节点,然后遍历 B 链表,比较两个链表的尾节点,如果相同则相交,不同则不相交。时间复杂度为 O(Length(A) + Length(B)),空间复杂度为 O(1),思路比解法 2 更简单。
//方法三代码
public boolean isIntersect(ListNode headA, ListNode headB) {
    if (null == headA || null == headB) {
        return false;
    }
    if (headA == headB) {
        return true;
    }
    while (null != headA.next) {
        headA = headA.next;
    }
    while (null != headB.next) {
        headB = headB.next;
    }
    return headA == headB;
}

13.求两个无环单链表的第一个相交点

  • 方法一 如果两个链表存在公共结点,那么它们从公共结点开始一直到链表的结尾都是一样的,因此我们只需要从链表的结尾开始,往前搜索,找到最后一个相同的结点即可。但是题目给出的单向链表,我们只能从前向后搜索,这时,我们就可以借助栈来完成。先把两个链表依次装到两个栈中,然后比较两个栈的栈顶结点是否相同,如果相同则出栈,如果不同,那最后相同的结点就是我们要的返回值。
  • 方法二 先找出2个链表的长度,然后让长的先走两个链表的长度差,然后再一起走,直到找到第一个公共结点。
  • 方法三 由于2个链表都没有环,我们可以把第二个链表接在第一个链表后面,这样就把问题转化为求环的入口节点问题。
  • 方法四 两个指针p1和p2分别指向链表A和链表B,它们同时向前走,当走到尾节点时,转向另一个链表,比如p1走到链表 A 的尾节点时,下一步就走到链表B,p2走到链表 B 的尾节点时,下一步就走到链表 A,当p1==p2 时,就是链表的相交点
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (null == headA || null == headB) {
        return null;
    }
    if (headA == headB) {
        return headA;
    }

    ListNode p1 = headA;
    ListNode p2 = headB;
    while (p1 != p2) {
        // 遍历完所在链表后从另外一个链表再开始
        // 当 p1 和 p2 都换到另一个链表时,它们对齐了:
        // (1)如果链表相交,p1 == p2 时为第一个相交点
        // (2)如果链表不相交,p1 和 p2 同时移动到末尾,p1 = p2 = null,然后退出循环
        p1 = (null == p1) ? headB : p1.next;
        p2 = (null == p2) ? headA : p2.next;
    }
    return p1;
}