双指针解决链表问题

118 阅读2分钟

1、合并两个有序链表

  • 问题:# 合并两个有序链表

  • 思路: 遍历链表,定义两个指针结点分别指向两个链表,并比较大小,小的加入新的链表中,最后,将不为空的链表部分放在后边。

  • 代码:

class Solution {
    public ListNode mergeTwoLists(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;
        }
       cur.next=list1==null?list2:list1;
       return head.next;
    }
}

2、分隔链表

  • 问题:#分隔链表
  • 思路: 维护两个链表 small和 large即可,small链表按顺序存储所有小于 x 的节点,large链表按顺序存储所有大于等于 x 的节点。遍历完原链表后,我们只要将 small 链表尾节点指向 large 链表的头节点即能完成对链表的分隔。
  • 代码
class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode small = new ListNode(0);
        ListNode smallHead = small;
        ListNode large = new ListNode(0);
        ListNode largeHead = large;
        while (head != null) {
            if (head.val < x) {
                small.next = head;
                small = small.next;
            } else {
                large.next = head;
                large = large.next;
            }
            head = head.next;
        }
        large.next = null;
        small.next = largeHead.next;
        return smallHead.next;
    }
}

3、合并K个有序链表

  • 问题:合并K个有序链表
  • 思路:使用优先队列(二叉堆)这种数据结构,把链表结点放入一个最小堆,这样每次获得K个结点中的最小结点。
  • 代码

初始化最小堆/最大堆

PriorityQueue<Integer> pq1=new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2; // 小顶堆, return 前减后(o1-o2);大顶堆, return 后减前(o2-o1);
            };
        });
public class MergeKLinkedList {
    public ListNode mergeKLinkedList(ListNode[] lists){
        ListNode dummy=new ListNode(-1);
        ListNode cur=dummy;
        if(lists.length==0) return null;
        PriorityQueue<ListNode> pq=new PriorityQueue<>(lists.length,(a,b)->(a.val-b.val));
        // 将K个有序链表的头结点加入最小堆
        for(ListNode head:lists){
            if(head!=null){
                pq.add(head);
            }
        }
        while(!pq.isEmpty()){
            //每次取出最小结点加入新链表结构
            ListNode node=pq.poll();
            cur.next=node;
            if(node.next!=null){
                pq.add(node.next);
            }
            cur=cur.next;
        }
        return dummy.next;
    }
}

4、单链表的倒数第K个结点

  • 问题:单链表的倒数第K个结点
  • 思路:怎么一次遍历获取到倒数第K个结点?双指针。 指针p1先走k步,指针p2再与p1同步走,当p1为null时,p2正好指向倒数第K个结点。
  • 代码
// 主函数
public ListNode removeNthFromEnd(ListNode head, int n) {
    // 虚拟头结点
    ListNode dummy = new ListNode(-1);
    dummy.next = head;
    // 删除倒数第 n 个,要先找倒数第 n + 1 个节点
    ListNode x = findFromEnd(dummy, n + 1);
    // 删掉倒数第 n 个节点
    x.next = x.next.next;
    return dummy.next;
}
    
// 返回链表的倒数第 k 个节点
ListNode findFromEnd(ListNode head, int k) {
    ListNode p1 = head;
    // p1 先走 k 步
    for (int i = 0; i < k; i++) {
        p1 = p1.next;
    }
    ListNode p2 = head;
    // p1 和 p2 同时走 n - k 步
    while (p1 != null) {
        p2 = p2.next;
        p1 = p1.next;
    }
    // p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点
    return p2;
}


5、判断链表是否包含环

  • 问题:寻找链表中点或判断链表是否包含环
  • 思路:快慢指针,每当慢指针 slow 前进一步,快指针 fast 就前进两步。

如果 fast 最终遇到空指针,说明链表中没有环;如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈,说明链表中含有环。

  • 代码
boolean hasCycle(ListNode head) {
    // 快慢指针初始化指向 head
    ListNode slow = head, fast = head;
    // 快指针走到末尾时停止
    while (fast != null && fast.next != null) {
        // 慢指针走一步,快指针走两步
        slow = slow.next;
        fast = fast.next.next;
        // 快慢指针相遇,说明含有环
        if (slow == fast) {
            return true;
        }
    }
    // 不包含环
    return false;
}