剑指offer中链表相关问题,目录:
14.链表中倒数第k个结点
15.反转链表
16.合并两个排序链表
25.复杂链表的复制
36.两个链表的第一个公共结点
55.链表中环的入口结点
56.删除链表中的重复结点
14.链表中倒数第k个结点
输入一个链表,输出该链表中倒数第k个结点
/*
遍历链表一次,时间复杂度O(n)
定义两个指针。
第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动。
从第k步开始,第二个指针也开始遍历。
当第一个指针叨叨链表的尾部时,第二个指针正好是倒数第k个结点。
需要处理的3个鲁棒性问题:
1.输入的pListHead为空
2.输入的链表总结点数小于k
3.输入的k为0
*/
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(pListHead == NULL || k==0)
return NULL;
ListNode *p1=pListHead;
ListNode *p2=pListHead;
for(int i=0;i<k-1;i++){
if(p1->next != NULL){
p1 = p1->next;
}else{
return NULL;
}
}
while(p1->next!= NULL ){
p1 = p1->next;
p2 = p2->next;
}
return p2;
}
};
15.反转链表
输入一个链表,反转链表后,输出新链表的表头。
/*
使用递归的方法进行链表反转
递归出口:pNode == NULL
假设h、i、j是3个相邻的结点,经过若干操作,我们已经将h之前的结点的指针调整完毕。
将I->next指向h,此时则无法在链表中遍历到j结点,为避免链表在i处断开,我们需要在调整i的next指针之前,保存下j结点。
我们在调整i的next结点之前,需要保存i的前一个结点h。
所以我们需要定义3个结点。
*/
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode *pReverseHead = NULL;
ListNode *pNode = pHead;
ListNode *pPre = NULL;
ListNode *pNext = NULL;
while(pNode != NULL){
pNext = pNode->next;
if(pNext == NULL)
pReverseHead =pNode;
pNode->next = pPre;
pPre = pNode;
pNode = pNext;
}
return pReverseHead;
}
};
16.合并两个排序链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
/*
使用递归方法,每次合并前比较两个头结点的大小。
鲁棒性问题:对空指针进行处理。当头结点为空指针时,将它和第二个链表合并。
*/
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1 == NULL) return pHead2;
else if(pHead2 == NULL) return pHead1;
ListNode* pMergedHead = NULL;
if(pHead1->val < pHead2->val){
pMergedHead = pHead1;
pMergedHead->next = Merge(pHead1->next,pHead2);
}else{
pMergedHead = pHead2;
pMergedHead->next = Merge(pHead1,pHead2->next);
}
return pMergedHead;
}
};
25.复杂链表的复制
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
O(n^2):
第一步:复制原始链表上的每一个结点,并使用next链接起来;
第二步:设置每个结点的random指针,需要从链表头开始找O(n)步,总的时间复杂度为O(n^2);
优化:使用空间换时间,时间复杂度O(n),空间复杂度O(n)
第一步:复制原始链表上的结点,使用next链接;同时将原始链表和复制链表的<N,N’>配对信息放入哈希表。
第二步:设置复制链表上的random结点,在复制链表中,对应的N’指向S’。由于哈希表的存在,可以使用O(1)的时间找到S‘。
优化:不使用辅助空间的情况下实现O(n)时间效率
第一步:复制原始链表上的结点,使用next链接;同时,将N’链接到N的后面。
第二步:设置复制出来的random。
第三步:将这个长链表拆分成为两个链表;把奇数位置结点使用next连接起来就是原始链表。
36.两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
/*
蛮力法:若第一个链表长度为m,第二个链表长度为n,时间复杂度为O(mn)。
优化:空间换时间
从链表的尾部开始向前比较,找出最后一个相同的结点。
分别将两个链表的结点放入两个栈里,揭晓来比较两个栈顶的结点是否相同。
空间复杂度O(n+m),时间复杂度O(n+m)。
优化:
第一步:遍历两个链表得到他们的长度,设较长的链表多k个结点。
第二步:先在较长的链表上走k步,接着同时在两个链表上进行遍历,找到的第一个相同结点就是第一个公共结点。
*/
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
int length1= GetListLength(pHead1);
int length2= GetListLength(pHead2);
int k = length1-length2;
ListNode *p1=pHead1,*p2=pHead2;
//保证p1是较长的链表
if(length2 >length1){
p1=pHead2;
p2=pHead1;
k = length2-length1;
}
for(int i=0;i<k;i++){
p1=p1->next;
}
while(p1!=NULL && p2!=NULL && p1 != p2){
p1 = p1->next;
p2 = p2->next;
}
return p1;//此时p1和p2为同一结点,任意返回其一
}
int GetListLength(ListNode* pHead){
int length =0;
ListNode* pNode = pHead;
while(pNode != NULL){
length++;
pNode = pNode->next;
}
return length;
}
};
55.链表中环的入口结点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
/*
第一步:确定链表中是否有环。
定义两个指针,同时从链表头结点出发,一个指针一次走一步,另一个指针一次走两步,如果走的快的指针追上了走的慢的指针,则链表包含环。
第二步:找到环入口。
确定环结点个数n。定义两个指针指向头结点,指针1先前移n步,之后两个指针同时向前移动。当两指针相遇时,指向环入口结点。
*/
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
if(pHead == NULL || pHead->next == NULL )
return NULL;
int count=1;
ListNode* pListHead = NULL;
ListNode* pNode = pHead->next;
pListHead = pNode->next;
while(pListHead != NULL && pNode != NULL) //判断是否有环
{
if(pListHead == pNode)
break;
pNode = pNode->next;
pListHead = pListHead->next;
if(pListHead == NULL || pNode == NULL)
return NULL;
pListHead = pListHead->next;
if(pListHead == NULL)
return NULL;
}
pNode = pNode->next;
while(pListHead != pNode) //计算环节点个数
{
pNode = pNode->next;
count++;
}
pListHead = pHead;
pNode = pHead;
for(int i=0;i<count;i++) //查找环入口
{
pListHead = pListHead->next;
}
while(pListHead != pNode)
{
pListHead = pListHead->next;
pNode = pNode->next;
}
return pListHead;
}
};
56.删除链表中的重复结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
/*
头结点可能会被删除,需要新建指针指向头结点。
为了保证删除后链表仍然相连,需要记录前一结点的指针和后一结点的指针。
排序链表中,如果相邻两个结点相同则删除一个结点,不同则后移。
*/
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead==NULL) return NULL;
if(pHead->next == NULL ) return pHead;
ListNode* newHead=new ListNode(-1); //新建一个指针指向头结点
newHead->next=pHead;
ListNode* pPre=newHead; //需要两个指针,一个指向前一个节点preNode,另一个指向当前节点node,
ListNode* pNode=pHead;
ListNode* pNext=NULL;
while(pNode!=NULL && pNode->next!=NULL){
pNext=pNode->next;
//如果当前节点的值和下一个节点的值相等
if(pNode->val==pNext->val){
//向后重复查找.移动
while(pNext!=NULL && pNode->val==pNext->val) pNext=pNext->next;
pPre->next=pNext;//直到不相等为止,进行连接
pNode=pNext;
}else{//如果当前节点的值和下一个节点的值不相等,直接向后移一个
pPre=pNode;
pNode=pNext;
}
}
return newHead->next;
}
};