剑指Offer——链表中快行指针用法(链表中倒数第k个结点等)
2015-11-06 20:30
423 查看
问题一、链表中倒数第k个结点
典型的链表快行指针法,设置两个指针,使前一个指针p1先走k步,然后两个指针p1,p2同时出发;当p1走到链表末尾的时候,p2所指的元素即是所要求的链表倒数第k个节点;
实现方法要注意区别:先行指针p2先走k步,循环的截止条件是p2 == null;
先行指针p2先走k-1步,循环的截止条件为p2.next == null;
代码:
问题二、前后两端链表插入的问题(查找链表的中间节点):
问题描述:
假定有一个链表a1->a2->.......->an->b1->.....->bn,想要将其重新排列成:a1->b1->a2->b2->.....an->bn,(假设链表长度是偶数个,但不知道链表的确切长度)
问题分析:
因为不知道链表的具体长度,因此需要遍历链表来确定链表长度;但没必要再遍历第二次去确定an和b1的分解线,即中间节点(因此这个问题的前一部分其实是查找链表的中间节点的问题)
采用双指针法,p1,p2分别从链表的头前节点出发,p1每步走1步,p2每步走2步;当p2走到尾节点bn时,p1此时走到的节点必然是an;(推广到链表长度为任意数的情况,p1最终仍然指向的中间节点或者中间节点中的一个)。
代码:
问题三、判断链表是否有环:
1、简单版:仅判断链表是否有环
问题分析:判断是否有环,依然采用快行指针法,使用p1,p2;p1走一步,p2走两步;若链表有环,则p1,p2一定会相遇;
证明:假设在行进过程中,p2跳过了p1,走到了位置i + 1上,此时p1所在位置为i(因为假设p2跳过了p1);则在前一步中p2所处的位置为(i + 1 - 2) = i - 1;而p1所处的位置也为i - 1,可见两者必然相遇,不会发生跳过的现象。
代码:
2、提高版:找到环的入口节点
问题分析:假设头节点head距离入口节点的距离为k;
因为p2的速度是p1速度的两倍,则p2行进的路程是p1的两倍;当p1走k步走到入口节点,p2则走了2k步,则p2走过了入口节点并往前又走了k步;
现以入口节点root为起点,则p1在0处,p2在k处,p1,p2此时都在环内,由追及问题可知,两者必然相遇在环内某一点上;且k的长度可能很长(即环的长度为size)则此时p2在(k mod size)处,记为K;由追及问题,p2在p1前面K步,又p2的速度快于p1,p2追赶p1,则p2落后于p1共(size
- K)步;由p2每次比p1多走一步,则共需要(size-K)步之后,两者便会相遇,记该点为reach;显然该点距离入口节点的距离为(size - (size - K)) = K步(因为p1从0位置走了(size - K)步);
由前面分析知,头节点head距离入口节点root的距离为k;又相遇节点reach距离入口节点的距离也为K(k = K + n*size),则两个指针分别头节点head与相遇节点reach出发往前遍历,最后一定会相遇,并且相遇点一定是入口节点。
代码:
题目描述:
输入一个链表,输出该链表中倒数第k个结点。
问题分析:典型的链表快行指针法,设置两个指针,使前一个指针p1先走k步,然后两个指针p1,p2同时出发;当p1走到链表末尾的时候,p2所指的元素即是所要求的链表倒数第k个节点;
实现方法要注意区别:先行指针p2先走k步,循环的截止条件是p2 == null;
先行指针p2先走k-1步,循环的截止条件为p2.next == null;
代码:
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode FindKthToTail(ListNode head,int k) { // 取出null情况 if (k == 0 || head == null) return null; ListNode p1 = head; ListNode p2 = head; // 先p2走k-1步(因为p1,p2指向head其实已经走了一步) for (int i = 0; i < k - 1; i ++) { if (p2.next == null) return null; p2 = p2.next; } while (p2.next != null) { p1 = p1.next; p2 = p2.next; } return p1; } }
问题二、前后两端链表插入的问题(查找链表的中间节点):
问题描述:
假定有一个链表a1->a2->.......->an->b1->.....->bn,想要将其重新排列成:a1->b1->a2->b2->.....an->bn,(假设链表长度是偶数个,但不知道链表的确切长度)
问题分析:
因为不知道链表的具体长度,因此需要遍历链表来确定链表长度;但没必要再遍历第二次去确定an和b1的分解线,即中间节点(因此这个问题的前一部分其实是查找链表的中间节点的问题)
采用双指针法,p1,p2分别从链表的头前节点出发,p1每步走1步,p2每步走2步;当p2走到尾节点bn时,p1此时走到的节点必然是an;(推广到链表长度为任意数的情况,p1最终仍然指向的中间节点或者中间节点中的一个)。
代码:
public class B { // 假设链表的长度是偶数的 public ListNode insertNode(ListNode head) { if (head == null) return null; // 双指针法,一个每次走一步;一个每次走两步 ListNode p1 = head; ListNode p2 = head; // 进行快行遍历 while ((p2 != null) && (p2.next != null) && (p2.next.next != null)) { p1 = p1.next; p2 = p2.next.next; } // 此时p1对应位置为an,p2对应位置为bn p2 = p1.next; p1.next = null; // 这一步很重要,注意要将链表从中间断开 p1 = head; // 进行插入 while (p2 != null) { // 注意保存p1,p2的后继值 ListNode temp1 = p1.next; ListNode temp2 = p2.next; p1.next = p2; p2.next = temp1; p1 = temp1; p2 = temp2; } return head; } // ============ 测试 ================== public static void main(String[] args) { int length = 10; ListNode[] nodes = new ListNode[length]; for (int i = length; i > 0; i--) { nodes[i - 1] = new ListNode(i - 1); if (i != length) nodes[i - 1].next = nodes[i]; } B a = new B(); a.printNodes(a.insertNode(nodes[0])); } public void printNodes(ListNode head) { while (head != null) { System.out.print(head.val + "->"); head = head.next; } } }返回中间节点是上述问题的其中一部分:
// 假设链表的长度是偶数的 public ListNode insertNode(ListNode head) { // 双指针法,一个每次走一步;一个每次走两步 ListNode p1 = head; ListNode p2 = head; // 进行快行遍历 while ((p2 != null) && (p2.next != null) && (p2.next.next != null)) { p1 = p1.next; p2 = p2.next.next; } return p1; }
问题三、判断链表是否有环:
1、简单版:仅判断链表是否有环
问题分析:判断是否有环,依然采用快行指针法,使用p1,p2;p1走一步,p2走两步;若链表有环,则p1,p2一定会相遇;
证明:假设在行进过程中,p2跳过了p1,走到了位置i + 1上,此时p1所在位置为i(因为假设p2跳过了p1);则在前一步中p2所处的位置为(i + 1 - 2) = i - 1;而p1所处的位置也为i - 1,可见两者必然相遇,不会发生跳过的现象。
代码:
public boolean isCircle(ListNode head) { ListNode p1 = head; ListNode p2 = head; while ((p2 != null) && (p2.next != null)) { p2 = p2.next.next; p1 = p1.next; if (p1 == p2) return true; } return false; }
2、提高版:找到环的入口节点
问题分析:假设头节点head距离入口节点的距离为k;
因为p2的速度是p1速度的两倍,则p2行进的路程是p1的两倍;当p1走k步走到入口节点,p2则走了2k步,则p2走过了入口节点并往前又走了k步;
现以入口节点root为起点,则p1在0处,p2在k处,p1,p2此时都在环内,由追及问题可知,两者必然相遇在环内某一点上;且k的长度可能很长(即环的长度为size)则此时p2在(k mod size)处,记为K;由追及问题,p2在p1前面K步,又p2的速度快于p1,p2追赶p1,则p2落后于p1共(size
- K)步;由p2每次比p1多走一步,则共需要(size-K)步之后,两者便会相遇,记该点为reach;显然该点距离入口节点的距离为(size - (size - K)) = K步(因为p1从0位置走了(size - K)步);
由前面分析知,头节点head距离入口节点root的距离为k;又相遇节点reach距离入口节点的距离也为K(k = K + n*size),则两个指针分别头节点head与相遇节点reach出发往前遍历,最后一定会相遇,并且相遇点一定是入口节点。
代码:
public class A { private ListNode isCircle(ListNode head) { ListNode p1 = head; ListNode p2 = head; while ((p2 != null) && (p2.next != null)) { p2 = p2.next.next; p1 = p1.next; if (p1 == p2) return p2; } return null; } public ListNode findRoot(ListNode head) { ListNode reach = null; if ((reach = isCircle(head)) == null) return null; ListNode p1 = head; ListNode p2 = reach; while (p1 != p2) { p1 = p1.next; p2 = p2.next; } return p1; } }
相关文章推荐
- JavaScript设计模式 Item 7 --策略模式Strategy
- JavaScript设计模式 Item 7 --策略模式Strategy
- js父窗口访问子窗口
- Jquery常用的功能
- 《剑指offer》——矩形覆盖
- jsp小结16 - 9个内置对象09 session
- jsp小结15 - 9个内置对象08 response
- 《剑指offer》——用两个栈实现队列
- CSS-网页字体缩放样式-webkit-text-size-adjust的用法详解
- Stack is one important segment of the process’s memory layout. It is a dynamic memory buffer portion
- CSS-CSS3 box-sizing
- CSS-CSS RESET
- 53种纯CSS3炫酷loading指示器动画特效
- CSS-伪元素
- js播放wav格式的录音文件
- html- Meta中添加X-UA-Compatible和IE=Edge,chrome=1有什么作用?
- SharedPreferences
- jQuery-1.9.1源码分析系列(三) Sizzle选择器引擎——编译原理
- 通过修改manifest解决Vista/Win7/Win8下应用程序兼容性问题
- CSS3 Animation 帧动画