其他分享
首页 > 其他分享> > 7.单链表的六大解题套路

7.单链表的六大解题套路

作者:互联网

单链表的六大解题套路

合并两个有序链表

将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成的

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1),p = dummy;//虚拟头结点
        ListNode p1=l1,p2=l2;
        while(p1!=null&&p2!=null){//比较p1和p2两个指针
				//将值较小的节点接到p指针
            if(p1.val>p2.val){
                p.next=p2;
                p2=p2.next;
            }else{
                p.next=p1;
                p1=p1.next;
            }
            p=p.next;//p指针不断前进
        }
        if(p1!=null){
            p.next=p1;
        }
        if(p2!=null){
            p.next=p2;
        }
        return dummy.next;
    }

Untitled

while循环每次比较p1和p2的大小,把较小的节点接到结果链表上

链表算法题,很常见的【虚拟头结点】技巧,就是dummy节点,有了这个占位符,可以避免处理空指针的情况,降低代码复杂性

合并k个有序链表?

逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上?

用到优先级队列(二叉堆)这种数据结构,把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点

public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0)return null;
        ListNode dummy = new ListNode(-1), p = dummy;//虚拟头结点
				//优先级队列,最小堆?
        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();//获取最小节点,接到结果链表中
            p.next = node;
            if(node.next!=null){
                pq.add(node.next);
            }
            p=p.next;//p指针不断前进
        }
        return dummy.next;
    }

优先队列 pq 中的元素个数最多是k,所以一次 poll或者 add方法的时间复杂度是O(logk)所有的链表节点都会被加入和弹出 pq,所以算法整体的算法复杂度是O(Nlogk),其中 k 是链表的条数,N是这些链表的节点总数

单链表的倒数第k个节点

从前往后寻找单链表第 k 个节点很简单,一个for循环遍历过去就找到了

Untitled

让快指针指向链表头结点 head,先走 k 步

Untitled

再用一个慢指针指向链表头结点head

让快慢指针同时向前走,快指针走到链表末尾的空指针,走了n-k步,那慢指针也走了n-k步,也就是链表的倒数第k个节点。

//返回链表的倒数第 k 个节点
ListNode findFromEnd(ListNode head,int k){
	ListNode p1 = head, p2 = head;
	for(int i=0; i < k;i++){
		p1 = p1.next;
	}
	
	while(p1 != null){
		p2 = p2.next;
		p1 = p1.next;.
	}
	return p2;
}

无论遍历一次链表和遍历两次链表的时间复杂度都是O(N),但是上述算法更有技巧性

删除链表的倒数第N个结点

public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast,slow;
        fast =slow = head;
        while(n-- > 0){
            fast = fast.next;
        } 
        if(fast == null){
            return head.next;
        }
        while(fast!=null && fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return head;
    }
public ListNode removeNthFromEnd(ListNode head, int n) {
       ListNode dummy = new ListNode(-1);
       dummy.next = head;
       ListNode x = findFromEnd(dummy,n+1);
       x.next = x.next.next;
       return dummy.next;
    }

    private ListNode findFromEnd(ListNode head,int k){
        ListNode p1 =head,p2 =head;
        for(int i =0;i< k;i++){
            p1= p1.next;
        }
        while(p1!=null){
            p1= p1.next;
            p2=p2.next;
        }
        return p2;
    }

单链表的中点

Untitled

解决这个问题的关键是,通过某些方式,让p1和p2能够同时到达相交节点

Untitled

让两个指针分别遍历两个链表,相当于【逻辑上】两条链表接在一起,可以让两个指针,同时进入公共部分,也就是同时到达相交节点。

如果没有相交点,相当于c1节点是null空指针,可以正确返回null

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p1 =headA,p2 = headB;
        while(p1!=p2){
            if(p1 == null) p1 = headB;
            else p1 = p1.next;
            if(p2 == null)p2 = headA;
            else p2 = p2.next;
        }
        return p1;
    }

标签:p2,head,单链,ListNode,套路,p1,next,链表,解题
来源: https://www.cnblogs.com/autumnmoonming/p/16282440.html