链表问题及解题思路

常见问题如下:

1.求链表中倒数第K个节点

2.判断链表是否存在环

3.判断存在环的链表中环的长度

4.找到存在环的链表中环的入口

5.判断两个链表是否相交

6.如果两个链表相交,返回相交的第一个节点

7.链表指针反转

8. 求无环链表的中间结点


下面将对这些问题一一解答。

 

问题1:求链表中倒数第K个节点

解题思路:定义两个指针p1,p2,开始都指向链表头位置,然后先将P1向前移动K个位置。之后p1,p2同时向前移动,直到P1到链表尾部。此时p2的位置就是倒数第K个。

示例代码:

 

//求解链表中倒数第k个节点
#include <iostream>
#include<assert.h>
using std::cin;
using std::cout;
using std::endl;

struct ListNode
{
	char data_;
	struct ListNode* next_;
};
struct ListNode head = {0, NULL};

void InsertTestData()
{
	struct ListNode *p = &head;
	struct ListNode *q = NULL;

	// 插入测试数据
	for(int i = 0; i != 10; i++)
	{
		q = new struct ListNode;
		assert(q != NULL);
		q->data_ = i; 
		q->next_ = NULL;
		p->next_ = q;
		p = p->next_;
	}
}

//成功时返回值,不成功返回-1
int getK(int k)
{
	assert( k >0);
	struct ListNode *p = &head;
	struct ListNode *q = &head;
	//将p 与 q拉开k的距离
	for(int i = 0; i != k; i++)
	{
		if(NULL == q->next_)
		{
			return -1;
		}
		q = q->next_;
	}
	p = p->next_;

	// p与q同时向前移进
	while(q->next_ != NULL)
	{
		q = q->next_;
		p = p->next_;
	}
	return p->data_;



}
int main()
{
	InsertTestData();
	cout <<getK(11);
	system("pause");
	return 0;
}

问题2:判断链表中是否存在环

解题思路:定义两个快慢指针p_fast, p_slow, 初始时都指向链表头结点。然后p_fast以每次两步向前移动。p_slow以每次一步向前移动。如果在移动过程中两指针相遇,则表示存在环。否则链表中不存在环。(当然还有一种解题思路是也是用hash方法来判断链表中是否存在环)

示例代码:

 

// 判断两个链表是否带环
#include<iostream>
#include<cassert>

using std::cout;
using std::endl;
struct ListNode
{
	char data_;
	struct ListNode* next_;
};
struct ListNode head = {0, NULL}; // 第一个链表的头结点


// 为链表加入测试数据
void InsertTestData() 
{
	struct ListNode *p = &head;
	struct ListNode *q = NULL;

	// 插入测试数据
	for(int i = 0; i != 10; i++)
	{
		q = new struct ListNode;
		assert(q != NULL);
		q->data_ = i; 
		q->next_ = NULL;
		p->next_ = q;
		p = p->next_;
	}
	p->next_ = &head;  // 给链表加入环
}

bool isCircle()
{
	struct ListNode *p_fast = &head;
	struct ListNode *p_slow = &head;
	while(p_fast != NULL && p_fast->next_ != NULL)
	{
		p_fast = p_fast->next_->next_; // 每次快指针往前移动两步
		p_slow = p_slow->next_;        // 每次慢指针往前移动一步
		if(p_fast == p_slow) // 两个指针相交,存在环
		{
			return true;
		}
	}
	return false;
}
int main()
{
	InsertTestData();
	cout << isCircle();
	system("pause");
	return 0;
}

问题3:判断存在环的链表中环的长度

解题思路:两个快慢指针相遇时一定会在环中相遇,根据问题2,当两个指针相遇时,保持快指针不动,然后让慢指针一次以一步的速度移动,当快慢指针再次相遇时慢指针走过的路程便是环的长度(可以想象在一个圆中两个点相遇,然后一个点不动,另一个点继续移动,当两个点相遇时,移动的那个点走过的距离便是圆的周长)

示例代码:

 

// 判断两个链表中环的长度
#include<iostream>
#include<cassert>

using std::cout;
using std::endl;
struct ListNode
{
	char data_;
	struct ListNode* next_;
};
struct ListNode head = {0, NULL}; // 第一个链表的头结点


// 为链表加入测试数据
void InsertTestData() 
{
	struct ListNode *p = &head;
	struct ListNode *q = NULL;

	// 插入测试数据
	for(int i = 1; i != 10; i++)
	{
		q = new struct ListNode;
		assert(q != NULL);
		q->data_ = i; 
		q->next_ = NULL;
		p->next_ = q;
		p = p->next_;
	}
	p->next_ = head.next_->next_->next_;  // 给链表加入环(可以根据情况自行加入环的位置来做测试)
}

// 如果存在环,返回环的长度,否则返回-1
int getCircleLength()
{
	struct ListNode *p_fast = &head;
	struct ListNode *p_slow = &head;
	while(p_fast != NULL && p_fast->next_ != NULL)
	{
		p_fast = p_fast->next_->next_; // 每次快指针往前移动两步
		p_slow = p_slow->next_;        // 每次慢指针往前移动一步
		if(p_fast == p_slow) // 两个指针相交,存在环(注意两指针必定在环内相遇)
		{
			int cycle_length = 0; 
			p_slow = p_slow->next_;
			cycle_length++;
			while(p_slow != p_fast)
			{
				p_slow = p_slow->next_;
				cycle_length++;
			}
			return cycle_length;  
		}
	}
	return -1; //不存在环
}
int main()
{
	InsertTestData();
	cout << getCircleLength();
	system("pause");
	return 0;
}

问题4:求存在环的链表中环的入口

解题思路:通过问题3我们知道了怎么求环的长度。假设求得环的长度为n,然后用类似于问题1的解题思路,设两个指针p1,p2分别指向链表头,然后p1向前移动n个位置。之后p1,p2同时向前移动。当p1和p2相遇时 p1(或p2)指向的位置即为环的入口位置

注意:要判断类似A-B-C-A这种情况的存在。

示例代码:略

 

问题5:判断两个链表是否相交

解题思路:首先分别判断两个链表是否存在环。

                   (1)如果两个链表都不存在环,则直接比较两个链表的表尾节点是否相等即可,如果相等,则相交,否则不想交。

                   (2)如果一个链表存在环,而另一个链表不存在环,则两个链表肯定不相交。

                   (3)如果两个链表都存在环。则如果两个链表相交的话则一个链表肯定共用环。通过判断一个链表中任意一个环内节点是否在另一个链表中即可判断两个链表是否相交(

对于(3)的补充:对于每一个链表,我们在求解链表是否存在环时,我们可以得到快慢指针相交的节点,假定第一个链表快慢指针相交节点为P1, 第二个链表快慢指针相交的节点为P2,那么,如果两个链表相交则P1一定也存在于第二个链表中(两个链表肯定公用一个环)。 我们可以进行如下操作:

if(p1 != p2)  
temp = p2->next;

while(p2 != temp)  // p2 在一个环中,所以让p2 绕着环走一圈,看看是否有节点与p1相同,如果相同则表示两链表相交,否则不相交

{
if(temp == p1)
{
两链表相交
return;
}
}
两链表不相交
return;


问题6:如果两个链表相交,返回相交的第一个节点(假设链表无环,有环的情况待考虑

解题思路:1. 计算出两个链表长度len1和len2,假设len1大于len2,将第一个链表向前移动len1-len2个节点,此时两个链表到第一个相交节点的距离就相同了,比较第一个链表当前位置位置是否相同,如果相同,返回该节点,否则两个链表同时向后移动一步继续比较,直到找到相同的节点或者走到链表尾部。平均时间复杂度为O(m+n)。

                     2.如果两个链表有公共结点,那么公共结点出现在两个链表的尾部。如果我们从两个链表的尾部开始往前比较,最后一个相同的节点就是要找的节点。此方法中我们可以使用两个辅助栈来实现。分别把两个链表的节点放入两个栈里,这样两个链表的尾结点就位于两个栈的站定,接下来比较两个栈顶的节点是否相同。如果相同,就把栈顶弹出来接着比较下一个栈顶,直到找到最后一个相同的节点。这种方法时间复杂度和空间复杂度都为O(m+n)


可参考资料:http://blog.csdn.net/hackbuteer1/article/details/7583102

问题8:无环链表的中间结点

解题思路:如果链表的节点总数为奇数,返回中间结点;如果节点总数是偶数,返回中间两个节点的任意一个。我们可以定义两个指针,同时从链表的头节点处罚,一个指针一次走一步,另一个指针一次走两步。当走的快的指针走到链表的末尾时,走的慢的指针正好在链表的中间



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值