剑指 Offer 22. 链表中倒数第k个节点输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
这种类型的题目常用技巧是快慢指针
public ListNode getKthFromEnd(ListNode head, int k) { ListNode fast = head;//快指针 ListNode slow = head;//慢指针 for(int i = 0; i < k; i++){//先将快指针移动k步,使得快慢指针之间相隔k个节点 fast = fast.next; } //快慢指针同时移动,当快指针移动到null时,慢指针的位置就是倒数第k个节点 while(fast != null){ slow = slow.next; fast = fast.next; } return slow; }
再比如LeetCode19这道题,删除链表倒数第N个节点
在单链表中我们想要删除一个节点,必须找到节点的前一个节点,通过下列语句:pre.next = pre.next.next;所有我们想要删除倒数N个节点,那么我们先找到倒数第N+1个节点,然后使用删除语句即可:
public ListNode removeNthFromEnd(ListNode head, int n) { ListNode resHead = new ListNode(-1,head); ListNode nodeK = lastK(resHead,n+1); nodeK.next = nodeK.next.next; return resHead.next; } //返回倒数第K个节点 public ListNode lastK(ListNode head,int k){ ListNode slow = head,fast = head; for(int i = 0; i < k; i++){ fast = fast.next; } while(fast != null){ fast = fast.next; slow = slow.next; } return slow; }
给定一个头结点为 head
的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。LeetCode876题。找中点,我们通过快慢指针来解决,快指针每次走二步,慢指针每次走一步,当快指针走到链表结尾,慢指针所在的位置就是中点。
public ListNode middleNode(ListNode head) { //快慢指针找中点 ListNode fast = head,slow = head; while(fast != null && fast.next != null){ fast = fast.next.next; slow = slow.next; } return slow; }
给定一个链表,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。LeetCode141。
这种问题也可以使用快慢指针解决,快指针每次走二步,慢指针走一步,如果链表中有环,那么快慢指针最后必然会相遇,而且存在这样的关系,假定慢指针走了K步,那么快指针肯定走了2K步。
public boolean hasCycle(ListNode head) { //定义快慢指针 ListNode fast = head; ListNode slow = head; while(fast != null && fast.next != null){ fast = fast.next.next;//每次移动二步 slow = slow.next;//移动一步 if(fast == slow){ return true; } } return false; }
这个问题还可以升级,如果存在环,怎么找链表的中环的启动节点呢?
public ListNode detectCycle(ListNode head) { if(head == null || head.next == null){//当链表为空或者只有一个元素的时候。链表不可以有环 return null; } ListNode fast = head;//快指针 ListNode slow = head;//慢指针 while(fast != null && fast.next != null){ fast = fast.next.next;//移动两步 slow = slow.next;//移动一步 if(slow == fast){ //说明有环 slow = head; while(slow != fast){ fast = fast.next; slow = slow.next; } return slow; } } return null; }
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
这种问题同样也可以使用双指针解答,两个指针分别指向链表头部,依次访问。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if(headA == null || headB == null){ return null; } ListNode l1 = headA; ListNode l2 = headB; while(l1 != l2){ if(l1 == null){//当链表访问完成,把另一条链表加在后面,继续访问 l1 = headB; }else{ l1 = l1.next; } if(l2 == null){ l2 = headA; }else{ l2 = l2.next; } } return l1;//如果相交就是返回的相交的点,不相交就是返回null }