剑指Offer----面试题15:链表中倒数第K个结点

题目:


输入一个链表,输出该链表中倒数第K个结点。为了符合大多数人的习惯,本题从1开始计数,即聊表的尾结点是倒数第一个结点。例如一个链表6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点。

方法一:


分析:假设整个链表中有n个结点,那么倒数第K个结点就是从头结点开始的第n-k+1个结点。如果我们能得到链表中的结点个数n,那么只需要从前向后走n-k+1步就可以了!

源代码如下:

#include<iostream>
#include"List.h"

using std::endl;
using std::cout;
using std::cin;

using namespace ListSpace3;

int CalculateNoteNum(ListNode *pHead)
{
	if (pHead == NULL)
		return 0;
	ListNode *temp = pHead;

	int count = 0;
	while (temp != NULL)
	{
		++count;
		temp = temp->next;
	}
	return count;
}

void printNode(ListNode *pHead, int NodeNum)
{
	if (pHead == NULL)
	{
		cout << "链表为空,不能够打印结点值!" << endl;
		return;
	}

	cout << "请输入要打印的倒数第k个结点: ";
	int k = 0;
	cin >> k;

	if (k > NodeNum || k < 1)
	{
		cout << "您输入的结点数超过了该链表所拥有的结点数或输入的数字小于1,不能打印该结点!" << endl;
		return;
	}
	
	ListNode *temp = pHead;

	for (int i = 0; i < NodeNum - k; i++)
	{
		temp = temp->next;
	}

	cout << "倒数第" << k << "个结点:";
	printListNode(temp);
}

void test11()
{
	cout << "===========测试1:有6个结点结点且输入任意个结点数===============" << endl;
	ListNode *list1 = CreateListNode(1);
	ListNode *list2 = CreateListNode(2);
	ListNode *list3 = CreateListNode(3);
	ListNode *list4 = CreateListNode(4);
	ListNode *list5 = CreateListNode(5);
	ListNode *list6 = CreateListNode(6);

	ConnectListNodes(list1, list2);
	ConnectListNodes(list2, list3);
	ConnectListNodes(list3, list4);
	ConnectListNodes(list4, list5);
	ConnectListNodes(list5, list6);
	ConnectListNodes(list6, NULL);

	int NodeNum = CalculateNoteNum(list1);
	cout << "该链表总共有" << NodeNum << "个结点" << endl;

	for (int i = 0; i < 6; i++)
		printNode(list1, NodeNum);
	
}

void test12()
{
	cout << "===========测试1:链表为空且===============" << endl;
	int NodeNum = CalculateNoteNum(NULL);
	cout << "该链表总共有" << NodeNum << "个结点" << endl;

	printNode(NULL, NodeNum);
}

int main1()
{
	test11();
	test12();
	system("pause");
	return 0;
}

运行结果:
===========测试1:有6个结点结点且输入任意个结点数===============
该链表总共有6个结点
请输入要打印的倒数第k个结点: 2
倒数第2个结点:The value of this node is 5
请输入要打印的倒数第k个结点: 4
倒数第4个结点:The value of this node is 3
请输入要打印的倒数第k个结点: 6
倒数第6个结点:The value of this node is 1
请输入要打印的倒数第k个结点: 8
您输入的结点数超过了该链表所拥有的结点数或输入的数字小于1,不能打印该结点!
请输入要打印的倒数第k个结点: 0
您输入的结点数超过了该链表所拥有的结点数或输入的数字小于1,不能打印该结点!
请输入要打印的倒数第k个结点: -2
您输入的结点数超过了该链表所拥有的结点数或输入的数字小于1,不能打印该结点!
===========测试1:链表为空且===============
该链表总共有0个结点
链表为空,不能够打印结点值!
请按任意键继续. . .

分析:方法一虽然也能够解决问题,但是它对链表遍历了两次,怎么样才能遍历依次就能输出结果呢?

方法二:


一次遍历输出结果。
分析:定义两个指针。第一个指针从头结点处向前移动k-1步,第二个指针在头结点处保持不动;从第k步开始,第一个结点向前走,直至第一个结点走到链表的尾部。

源代码如下:
#include<iostream>
#include"List.h"

using std::cout;
using std::endl;
using std::cin;

using namespace ListSpace3;

void PrintNode2(ListNode *pHead)
{
	if (pHead == NULL)
	{
		cout << "链表为空不能打印" << endl;
		return;
	}

	cout << "请输入要打印的倒数第k个结点: ";
	int k = 0;
	cin >> k;

	if (k <= 0)
	{
		cout << "输入的将要打印的结点位置有误" << endl;
		return;
	}

	ListNode *pAhead = pHead;
	ListNode *pBehind = pHead;

	for (int i = 0; i < k-1; i++)
	{
		if (pAhead->next != NULL)
			pAhead = pAhead->next;
		else
		{
			cout << "输入的结点数超出了链表的结点总数" << endl;
			return;
		}
	}

	while (pAhead->next != NULL)
	{
		pAhead = pAhead->next;
		pBehind = pBehind->next;
	}

	printListNode(pBehind);
}

void test21()
{
	cout << "===========测试1:有6个结点结点且输入任意个结点数===============" << endl;
	ListNode *list1 = CreateListNode(1);
	ListNode *list2 = CreateListNode(2);
	ListNode *list3 = CreateListNode(3);
	ListNode *list4 = CreateListNode(4);
	ListNode *list5 = CreateListNode(5);
	ListNode *list6 = CreateListNode(6);

	ConnectListNodes(list1, list2);
	ConnectListNodes(list2, list3);
	ConnectListNodes(list3, list4);
	ConnectListNodes(list4, list5);
	ConnectListNodes(list5, list6);
	ConnectListNodes(list6, NULL);

	for (int i = 0; i < 5; i++)
		PrintNode2(list1);

}

void test22()
{
	cout << "===========测试1:链表为空且===============" << endl;
	PrintNode2(NULL);
}

int main()
{
	test21();
	test22();
	system("pause");
	return 0;
}

运行结果:
===========测试1:有6个结点结点且输入任意个结点数===============
请输入要打印的倒数第k个结点: 6
The value of this node is 1
请输入要打印的倒数第k个结点: 1
The value of this node is 6
请输入要打印的倒数第k个结点: -1
输入的将要打印的结点位置有误
请输入要打印的倒数第k个结点: -6
输入的将要打印的结点位置有误
请输入要打印的倒数第k个结点: 0
输入的将要打印的结点位置有误
===========测试1:链表为空且===============
链表为空不能打印
请按任意键继续. . .

注意:
  1. 输入的整数不得超过链表的总结点数码;
  2. 当链表为空时的处理情况;
  3. 当输入的整数方位不对时的处理情况。
  4. 上述代码中都没有对链表进行销毁,或造成 内存泄漏

官方源代码:

#include<cstdlib>
#include<cstdio>
#include"List.h"

using namespace ListSpace3;

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k)
{
	if (pListHead == NULL || k == 0)
		return NULL;

	ListNode *pAhead = pListHead;
	ListNode *pBehind = NULL;

	for (unsigned int i = 0; i < k - 1; ++i)
	{
		if (pAhead->next != NULL)
			pAhead = pAhead->next;
		else
		{
			return NULL;
		}
	}

	pBehind = pListHead;
	while (pAhead->next != NULL)
	{
		pAhead = pAhead->next;
		pBehind = pBehind->next;
	}

	return pBehind;
}

// ====================测试代码====================
// 测试要找的结点在链表中间
void Test1()
{
	printf("=====Test1 starts:=====\n");
	ListNode* pNode1 = CreateListNode(1);
	ListNode* pNode2 = CreateListNode(2);
	ListNode* pNode3 = CreateListNode(3);
	ListNode* pNode4 = CreateListNode(4);
	ListNode* pNode5 = CreateListNode(5);

	ConnectListNodes(pNode1, pNode2);
	ConnectListNodes(pNode2, pNode3);
	ConnectListNodes(pNode3, pNode4);
	ConnectListNodes(pNode4, pNode5);

	printf("expected result: 4.\n");
	ListNode* pNode = FindKthToTail(pNode1, 2);
	printListNode(pNode);

	DestoryList(&pNode1);
}

// 测试要找的结点是链表的尾结点
void Test2()
{
	printf("=====Test2 starts:=====\n");
	ListNode* pNode1 = CreateListNode(1);
	ListNode* pNode2 = CreateListNode(2);
	ListNode* pNode3 = CreateListNode(3);
	ListNode* pNode4 = CreateListNode(4);
	ListNode* pNode5 = CreateListNode(5);

	ConnectListNodes(pNode1, pNode2);
	ConnectListNodes(pNode2, pNode3);
	ConnectListNodes(pNode3, pNode4);
	ConnectListNodes(pNode4, pNode5);

	printf("expected result: 5.\n");
	ListNode* pNode = FindKthToTail(pNode1, 1);
	printListNode(pNode);

	DestoryList(&pNode1);
}

// 测试要找的结点是链表的头结点
void Test3()
{
	printf("=====Test3 starts:=====\n");
	ListNode* pNode1 = CreateListNode(1);
	ListNode* pNode2 = CreateListNode(2);
	ListNode* pNode3 = CreateListNode(3);
	ListNode* pNode4 = CreateListNode(4);
	ListNode* pNode5 = CreateListNode(5);

	ConnectListNodes(pNode1, pNode2);
	ConnectListNodes(pNode2, pNode3);
	ConnectListNodes(pNode3, pNode4);
	ConnectListNodes(pNode4, pNode5);

	printf("expected result: 1.\n");
	ListNode* pNode = FindKthToTail(pNode1, 5);
	printListNode(pNode);

	DestoryList(&pNode1);
}

// 测试空链表
void Test4()
{
	printf("=====Test4 starts:=====\n");
	printf("expected result: NULL.\n");
	ListNode* pNode = FindKthToTail(NULL, 100);
	printListNode(pNode);
}

// 测试输入的第二个参数大于链表的结点总数
void Test5()
{
	printf("=====Test5 starts:=====\n");
	ListNode* pNode1 = CreateListNode(1);
	ListNode* pNode2 = CreateListNode(2);
	ListNode* pNode3 = CreateListNode(3);
	ListNode* pNode4 = CreateListNode(4);
	ListNode* pNode5 = CreateListNode(5);

	ConnectListNodes(pNode1, pNode2);
	ConnectListNodes(pNode2, pNode3);
	ConnectListNodes(pNode3, pNode4);
	ConnectListNodes(pNode4, pNode5);

	printf("expected result: NULL.\n");
	ListNode* pNode = FindKthToTail(pNode1, 6);
	printListNode(pNode);

	DestoryList(&pNode1);
}

// 测试输入的第二个参数为0
void Test6()
{
	printf("=====Test6 starts:=====\n");
	ListNode* pNode1 = CreateListNode(1);
	ListNode* pNode2 = CreateListNode(2);
	ListNode* pNode3 = CreateListNode(3);
	ListNode* pNode4 = CreateListNode(4);
	ListNode* pNode5 = CreateListNode(5);

	ConnectListNodes(pNode1, pNode2);
	ConnectListNodes(pNode2, pNode3);
	ConnectListNodes(pNode3, pNode4);
	ConnectListNodes(pNode4, pNode5);

	printf("expected result: NULL.\n");
	ListNode* pNode = FindKthToTail(pNode1, 0);
	printListNode(pNode);

	DestoryList(&pNode1);
}

int main()
{
	Test1();
	Test2();
	Test3();
	Test4();
	Test5();
	Test6();

	system("pause");
	return 0;
}

运行结果:
=====Test1 starts:=====
expected result: 4.
The value of this node is 4
=====Test2 starts:=====
expected result: 5.
The value of this node is 5
=====Test3 starts:=====
expected result: 1.
The value of this node is 1
=====Test4 starts:=====
expected result: NULL.
The value of this node is empty
=====Test5 starts:=====
expected result: NULL.
The value of this node is empty
=====Test6 starts:=====
expected result: NULL.
The value of this node is empty
请按任意键继续. . .

相关题目:


1 求链表的中间节点


题目:如果链表中结点总数为奇数,返回中间节点;如果结点总数是偶数,返回中间两个结点的任意一个结点。

分析:为了解决这个问题,我们可以定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。当走得快的指针走到链表的尾部时,走得慢的指针正好在链表的中间


2 判断一个单链表是否形成了环形结构


分析:和前面一个问题一样,定义两个指针,同时从链表的头结点出发,一个指针一次只走一步,另外一个指针一次走两步。如果走得快的指针能够追的上走得慢的指针,那么链表就是环形链表;如果走的快的指针走到链表的末尾都没有追上走得慢的指针,那么链表就不是环形链表。

总结:


当我们用一个指针遍历链表不能解决问题的时候,我们可以尝试用两个指针来遍历链表。可以让其中一个走的慢一些,另外一个指针走的快一些。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值