数据结构List实例(三):寻找倒数第k个结点值
2017-05-09 10:43
381 查看
这个也是非常常见的问题。
分析:为了得到倒数第k个结点,很自然的想法是先走到链表的尾端,再从尾端回溯k步。可是输入的是单向链表,只有从前往后的指针而没有从后往前的指针。因此我们需要打开我们的思路。既然不能从尾结点开始遍历这个链表,我们还是把思路回到头结点上来。假设整个链表有n个结点,那么倒数第k个结点是从头结点开始的第n-k-1个结点(从0开始计数)。如果我们能够得到
链表中结点的个数n,那我们只要从头结点开始往后走n-k-1步就可以了。如何得到结点数n?这个不难,只需要从头开始遍历链表,每经过一个结点,计数器加一就行了。这种思路的时间复杂度是O(n),但需要遍历链表两次。第一次得到链表中结点个数n,第二次得到从头结点开始的第n?-k-1个结点即倒数第k个结点。
如果链表的结点数不多,这是一种很好的方法。但如果输入的链表的结点个数很多,有可能不能一次性把整个链表都从硬盘读入物理内存,那么遍历两遍意味着一个结点需要两次从硬盘读入到物理内存。我们知道把数据从硬盘读入到内存是非常耗时间的操作。我们能不能把链表遍历的次数减少到1?如果可以,将能有效地提高代码执行的时间效率。
如果我们在遍历时维持两个指针,第一个指针从链表的头指针开始遍历,在第k-1步之前,第二个指针保持不动;在第k-1步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
这种思路只需要遍历链表一次。对于很长的链表,只需要把每个结点从硬盘导入到内存一次。因此这一方法的时间效率前面的方法要高。
这是双指针思维在链表中的第二次应用。实现代码如下所示:
/**
* @Title: KthBackwards.java
* @Package List
* @Description: TODO
* @author peidong
* @date 2017-4-24 上午8:23:02
* @version V1.0
*/
package List;
import List.MergeSortList.ListNode;
/**
* @ClassName: KthBackwards
* @Description: 寻找链表的倒数第k个结点
* @date 2017-4-24 上午8:23:02
*
*/
public class KthBackwards {
/**
*
* @ClassName: ListNode
* @Description: 构建链表结点
* @date 2017-4-24 上午8:30:22
*
*/
public static class ListNode{
public int data;
public ListNode next;
public ListNode(int data) {
this.data = data;
this.next = null;
}
}
/**
*
* @Title: findKthBackwardsSolution1
* @Description:实现第一种思路
* @param @param head
* @param @param k
* @param @return
* @return ListNode
* @throws
*/
public static ListNode findKthBackwardsSolution1(ListNode head, int k){
//如果链表为空
if(head == null){
return null;
}
//计算当前链表的结点数
ListNode pCur = head;
int num = 0;
while(pCur.next != null){
pCur = pCur.next;
num ++;
}
//如果k的值比num大
if(num < k){
return null;
}
//重新开始查找
pCur = head;
for(int i = 0; i< num-k; i++){
pCur = pCur.next;
}
return pCur;
}
/**
*
* @Title: findKthBackwardsSolution2
* @Description: 实现第2种思路
* @param @param head
* @param @param k
* @param @return
* @return ListNode
* @throws
*/
public static ListNode findKthBackwardsSolution2(ListNode head, int k){
//如果链表为空
if(head == null){
return null;
}
//构建亮哥指针
ListNode p1 = head;
ListNode p2 = null;
//先让第一个指针走k-1步
for(int i = 0; i< k; i++){
if(p1.next != null){
p1 = p1.next;
}else{
return null;
}
}
//开始走第二个指针
p2 = head;
while(p1.next != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
/**
* @Title: main
* @Description:
* @param @param args
* @return void
* @throws
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
// 添加数据
ListNode t1 = new ListNode(13);
ListNode t2 = new ListNode(11);
ListNode t3 = new ListNode(15);
ListNode t4 = new ListNode(17);
ListNode t5 = new ListNode(16);
ListNode t6 = new ListNode(20);
ListNode t7 = new ListNode(9);
ListNode t8 = new ListNode(6);
// 构建链表
t1.next = t2;
t2.next = t3;
t3.next = t4;
t4.next = t5;
t5.next = t6;
t6.next = t7;
t7.next = t8;
t1 = KthBackwards.findKthBackwardsSolution1(t1, 3);
ListNode t =KthBackwards.findKthBackwardsSolution2(t1, 3);
System.out.println(t1.data);
System.out.println(t.data);
}
}
分析:为了得到倒数第k个结点,很自然的想法是先走到链表的尾端,再从尾端回溯k步。可是输入的是单向链表,只有从前往后的指针而没有从后往前的指针。因此我们需要打开我们的思路。既然不能从尾结点开始遍历这个链表,我们还是把思路回到头结点上来。假设整个链表有n个结点,那么倒数第k个结点是从头结点开始的第n-k-1个结点(从0开始计数)。如果我们能够得到
链表中结点的个数n,那我们只要从头结点开始往后走n-k-1步就可以了。如何得到结点数n?这个不难,只需要从头开始遍历链表,每经过一个结点,计数器加一就行了。这种思路的时间复杂度是O(n),但需要遍历链表两次。第一次得到链表中结点个数n,第二次得到从头结点开始的第n?-k-1个结点即倒数第k个结点。
如果链表的结点数不多,这是一种很好的方法。但如果输入的链表的结点个数很多,有可能不能一次性把整个链表都从硬盘读入物理内存,那么遍历两遍意味着一个结点需要两次从硬盘读入到物理内存。我们知道把数据从硬盘读入到内存是非常耗时间的操作。我们能不能把链表遍历的次数减少到1?如果可以,将能有效地提高代码执行的时间效率。
如果我们在遍历时维持两个指针,第一个指针从链表的头指针开始遍历,在第k-1步之前,第二个指针保持不动;在第k-1步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
这种思路只需要遍历链表一次。对于很长的链表,只需要把每个结点从硬盘导入到内存一次。因此这一方法的时间效率前面的方法要高。
这是双指针思维在链表中的第二次应用。实现代码如下所示:
/**
* @Title: KthBackwards.java
* @Package List
* @Description: TODO
* @author peidong
* @date 2017-4-24 上午8:23:02
* @version V1.0
*/
package List;
import List.MergeSortList.ListNode;
/**
* @ClassName: KthBackwards
* @Description: 寻找链表的倒数第k个结点
* @date 2017-4-24 上午8:23:02
*
*/
public class KthBackwards {
/**
*
* @ClassName: ListNode
* @Description: 构建链表结点
* @date 2017-4-24 上午8:30:22
*
*/
public static class ListNode{
public int data;
public ListNode next;
public ListNode(int data) {
this.data = data;
this.next = null;
}
}
/**
*
* @Title: findKthBackwardsSolution1
* @Description:实现第一种思路
* @param @param head
* @param @param k
* @param @return
* @return ListNode
* @throws
*/
public static ListNode findKthBackwardsSolution1(ListNode head, int k){
//如果链表为空
if(head == null){
return null;
}
//计算当前链表的结点数
ListNode pCur = head;
int num = 0;
while(pCur.next != null){
pCur = pCur.next;
num ++;
}
//如果k的值比num大
if(num < k){
return null;
}
//重新开始查找
pCur = head;
for(int i = 0; i< num-k; i++){
pCur = pCur.next;
}
return pCur;
}
/**
*
* @Title: findKthBackwardsSolution2
* @Description: 实现第2种思路
* @param @param head
* @param @param k
* @param @return
* @return ListNode
* @throws
*/
public static ListNode findKthBackwardsSolution2(ListNode head, int k){
//如果链表为空
if(head == null){
return null;
}
//构建亮哥指针
ListNode p1 = head;
ListNode p2 = null;
//先让第一个指针走k-1步
for(int i = 0; i< k; i++){
if(p1.next != null){
p1 = p1.next;
}else{
return null;
}
}
//开始走第二个指针
p2 = head;
while(p1.next != null){
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
/**
* @Title: main
* @Description:
* @param @param args
* @return void
* @throws
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
// 添加数据
ListNode t1 = new ListNode(13);
ListNode t2 = new ListNode(11);
ListNode t3 = new ListNode(15);
ListNode t4 = new ListNode(17);
ListNode t5 = new ListNode(16);
ListNode t6 = new ListNode(20);
ListNode t7 = new ListNode(9);
ListNode t8 = new ListNode(6);
// 构建链表
t1.next = t2;
t2.next = t3;
t3.next = t4;
t4.next = t5;
t5.next = t6;
t6.next = t7;
t7.next = t8;
t1 = KthBackwards.findKthBackwardsSolution1(t1, 3);
ListNode t =KthBackwards.findKthBackwardsSolution2(t1, 3);
System.out.println(t1.data);
System.out.println(t.data);
}
}
相关文章推荐
- 数据结构之线性表——倒数第k个结点
- 数据结构之线性表——倒数第k个结点
- 数据结构——算法之(004)(输入一个单向链表,输出该链表中倒数第k个结点)
- 数据结构——寻找单链表倒数第n个结点
- 【数据结构】单链表—链表中倒数第K个结点 — 快慢指针
- 数据结构与算法---C#实现LinkedList实例
- 9.链表中倒数第k个结点[FindReverseKthLinkedListNode]
- 链表面试题(五)---寻找链表的倒数第k个结点O(N)
- 《剑指offer》——寻找链表中倒数第k个结点
- 寻找链表中倒数第k个结点
- 【数据结构】单链表—寻找两个相交链表中第一个公共结点 — 蛮力法 / 栈 / 指针先走
- 【LeetCode】Remove Nth Node From End of List && 【九度】题目1517:链表中倒数第k个结点
- 《剑指Offer》面试题:寻找链表中倒数第k个结点
- 寻找倒数第K个结点
- 数据结构之链表面试题汇总(一)--查找单向链表的中间节点、倒数第K个节点
- 【数据结构基础】链表的倒数第M个结点的查找
- 寻找链表中倒数第K个结点的位置
- 寻找单链表中倒数第k个结点
- 寻找链表中倒数第K个结点的位置
- 数据结构List实例(二): 调整奇偶次序