一、链表
1.单向链表
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
- 表元素域elem用来存放具体的数据。
- 链接域next用来存放下一个节点的位置(python中的标识)
- 变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点。
节点实现
class Node():
'''单链表节点'''
def __init__(self,elem):
# item存放数据元素
self.elem = elem
# next是下一个节点的标识
self.next = None
单链表的操作
- is_empty() 链表是否为空
- length() 链表长度
- travel() 遍历整个链表
- add(item) 链表头部添加元素
- append(item) 链表尾部添加元素
- insert(pos, item) 指定位置添加元素
- remove(item) 删除节点
- search(item) 查找节点是否存在
头部添加元素
def add(self,item):
'''头部添加元素'''
# 先创建一个保存item值的节点
node = Node(item)
# 将新节点的链接域next指向头节点,即_head指向的位置
if self._head == None:
self._head = node
else:
# 将链表的头__head指向新节点
node.next = self._head
self._head = node
尾部添加元素
def append(self,item):
'''尾部添加元素'''
# 先创建一个保存item值的节点
node = Node(item)
# 先判断链表是否为空,若是空链表,则将_head指向新节点
if self._head == None:
self._head = node
# 若不为空,则找到尾部,将尾节点的next指向新节点
else:
cur = self._head
while cur.next != None:
cur = cur.next
cur.next = node
在指定位置插入
def insert(self,pos,item):
'''指定位置插入元素'''
# 若指定位置pos为第一个元素之前,则执行头部插入
if pos <= 0:
self.add(item)
# 若指定位置超过链表尾部,则执行尾部插入
elif pos > (self.length()-1):
self.append(item)
# 找到指定位置
else:
node = Node(item)
count = 0
# pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
pre = self.__head
while count < (pos-1):
count += 1
pre = pre.next
# 先将新节点node的next指向插入位置的节点
node.next = pre.next
# 将插入位置的前一个节点的next指向新节点
pre.next = node
删除节点
def remove(self,item):
'''删除节点'''
# 若链表为空,则直接返回
if self.is_empty():
return
cur = self._head
pre = None
while cur != None:
# 若没有找到元素,继续按链表后移节点
if cur.elem != item:
pre = cur
cur = cur.next
else:
# 若要删除的点为头节点
if cur == self._head:
self.__head = cur.next
break
else:
# 要删除的点不是头节点
pre.next = cur.next
break
查找节点
def search(self,item):
'''查找节点'''
cur = self._head
while cur != None:
if cur.elem == item:
return True
else:
cur = cur.next
return False
单链表实现完整代码
class Node():
'''单链表节点'''
def __init__(self,elem):
# item存放数据元素
self.elem = elem
# next是下一个节点的标识
self.next = None
class singleLinkList():
"""单链表"""
def __init__(self):
#头节点定义为私有变量
self._head = None
def is_empty(self):
'''判断链表为空'''
if self._head == None:
return True
else:
return False
def length(self):
'''链表长度'''
# cur初始时指向头节点
cur = self._head
count = 0
while cur != None:
count += 1
# 将cur后移一个节点
cur = cur.next
return count
def travel(self):
#遍历整个链表
cur=self._head
while cur!=None:
print(cur.elem,end=' ')
cur=cur.next
print()
def add(self,item):
'''头部添加元素'''
# 先创建一个保存item值的节点
node = Node(item)
# 将新节点的链接域next指向头节点,即_head指向的位置
if self._head == None:
self._head = node
else:
# 将链表的头__head指向新节点
node.next = self._head
self._head = node
def append(self,item):
'''尾部添加元素'''
# 先创建一个保存item值的节点
node = Node(item)
# 先判断链表是否为空,若是空链表,则将_head指向新节点
if self._head == None:
self._head = node
# 若不为空,则找到尾部,将尾节点的next指向新节点
else:
cur = self._head
while cur.next != None:
cur = cur.next
cur.next = node
def insert(self,pos,item):
'''指定位置插入元素'''
# 若指定位置pos为第一个元素之前,则执行头部插入
if pos <= 0:
self.add(item)
# 若指定位置超过链表尾部,则执行尾部插入
elif pos > (self.length()-1):
self.append(item)
# 找到指定位置
else:
node = Node(item)
count = 0
# pre用来指向指定位置pos的前一个位置pos-1,初始从头节点开始移动到指定位置
pre = self.__head
while count < (pos-1):
count += 1
pre = pre.next
# 先将新节点node的next指向插入位置的节点
node.next = pre.next
# 将插入位置的前一个节点的next指向新节点
pre.next = node
def remove(self,item):
'''删除节点'''
# 若链表为空,则直接返回
if self.is_empty():
return
cur = self._head
pre = None
while cur != None:
# 若没有找到元素,继续按链表后移节点
if cur.elem != item:
pre = cur
cur = cur.next
else:
# 若要删除的点为头节点
if cur == self._head:
self.__head = cur.next
break
else:
# 要删除的点不是头节点
pre.next = cur.next
break
def search(self,item):
'''查找节点'''
cur = self._head
while cur != None:
if cur.elem == item:
return True
else:
cur = cur.next
return False
def __str__(self):
cur = self._head
temp=''
while cur.next != None:
temp += str(cur.next.elem)
temp+='->'
cur = cur.next
temp += 'None'
return temp
测试
if __name__ == "__main__":
singleObj= singleLinkList()
print(singleObj.is_empty())
print(singleObj.length())
for i in range(5):
singleObj.append(i)
singleObj.travel()
print(singleObj)
singleObj.add(-1)
singleObj.travel()
singleObj.insert(-1,2)
singleObj.travel()
print(singleObj.search(3))
singleObj.remove(3)
singleObj.travel()
结果
True
0
0 1 2 3 4
0->1->2->3->4->None
-1 0 1 2 3 4
2 -1 0 1 2 3 4
True
2 -1 0 1 2 4
二、常见问题
1.从尾到头打印单链表
我们可以用栈实现这种顺序。每经过一个节点的时候,把该节点放到一个栈中。当遍历完整个链表后,再从栈顶开始逐个输出节点的值,此时输出的结点的顺序已经反转过来了。这种思路的实现代码如下:
class Node():
'''单链表节点'''
def __init__(self,elem):
# item存放数据元素
self.elem = elem
# next是下一个节点的标识
self.next = None
class Solution():
'''从尾到头打印链表'''
def printListFromTailToHead(self,listNode):
if listNode is None:
return
newList = []
while listNode:
newList.insert(0,listNode.elem)
listNode = listNode.next
return newList
if __name__=='__main__':
A1 = Node(1)
A2 = Node(2)
A3 = Node(3)
A4 = Node(4)
A5 = Node(5)
A1.next=A2
A2.next=A3
A3.next=A4
A4.next=A5
solution=Solution()
ans=solution.printListFromTailToHead(A1)
print('->'.join(str(i) for i in ans))
c++代码:
#include <stack>
#include <iostream>
using namespace std;
struct ListNode
{
int value;
ListNode *pNext;
};
void printListFromTailToHead(ListNode *pHead) {
stack<ListNode*> nodes;
ListNode *pNode = pHead;
while (pNode != nullptr)
{
nodes.push(pNode);
pNode = pNode->pNext;
}
while (!nodes.empty())
{
pNode = nodes.top();
cout << pNode->value << " ";
nodes.pop();
}
cout << endl;
}
void printListFromTailToHead_Recursively(ListNode* pHead)
{
if (pHead != nullptr)
{
if (pHead->pNext != nullptr)
{
printListFromTailToHead_Recursively(pHead->pNext);
}
cout << pHead->value << " ";
}
}
int main()
{
ListNode* pHead = new ListNode();
ListNode *pNew, *pTemp;
pTemp = pHead;
int a[5] = { 1, 4, 2, 5, 6 };
for (int i = 0; i < 5; i++)
{
pNew = new ListNode;
pNew->value = a[i];
pNew->pNext = nullptr;
pTemp->pNext = pNew;
pTemp = pNew;
}
//生成的链表pHead为{0, 1, 4, 2, 5, 6},可以看出多了初始头结点0
ListNode *temp = pHead->pNext;//去掉初始头结点
cout << "利用栈方法从尾到头反过来打印链表的值如下:" << endl;
printListFromTailToHead(temp);
cout << "利用递归方法从尾到头反过来打印链表的值如下:" << endl;
printListFromTailToHead_Recursively(temp);
cout << endl;
system("pause");
return 0;
}
2.删除链表节点
题目一、在O(1)时间内删除结点
给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。
分析
对于上图实例链表(a)删除指针p有两种方式
- 思路1:(b)找到前一个指针pre,赋值pre.next=p.next,删掉p
- 思路2:(c)目的是删除p,但是不删p,直接用p.next的值赋值给p,把p.next删除掉(好处:不用遍历找到p的前一个指针pre,O(1)时间内搞定)
于是,定位到思路2,但是思路2有两个特例:
- 删除的是尾指针,需要遍历找到前一个指针;
- 整个链表就一个结点,我们要删除链表的头节点(也是尾节点),那么在删除节点后,要把链表的头结点设置为None。
class Node():
'''单链表节点'''
def __init__(self,elem):
# item存放数据元素
self.elem = elem
# next是下一个节点的标识
self.next = None
def __del__(self):
self.elem = None
self.next = None
class Solution():
'''
在O(1)时间内删除指定节点
'''
def dealNode(self,pHead,toBeDeleted):
if pHead == None or toBeDeleted == None:
return None
# 要删除的不是为尾节点
if toBeDeleted.next != None:
pNext = toBeDeleted.next
toBeDeleted.elem = pNext.elem
toBeDeleted.next = pNext.next
# 只有一个节点,要删除的是头节点(也是尾节点)
elif pHead == toBeDeleted:
toBeDeleted.__del__()
pHead.__del__()
# 要删除的是尾节点
else:
cur = pHead
while cur != toBeDeleted:
cur = cur.next
cur.next = None
toBeDeleted.__del__()
c++代码:
#include <iostream>
using namespace std;
struct ListNode
{
int value;
ListNode *pNext;
};
void DeleteNodeInList(ListNode **pHead, ListNode *pToBeDeleted) {
if (pHead == nullptr || pToBeDeleted == nullptr)
return;
//要删除的不是尾节点
if (pToBeDeleted->pNext != nullptr)
{
ListNode *nextNode = pToBeDeleted->pNext;
pToBeDeleted->value = nextNode->value;
pToBeDeleted->pNext = nextNode->pNext;
delete nextNode;
nextNode = nullptr;
}
//链表只有一个节点,删除尾节点(也是头结点)
else if (pToBeDeleted == *pHead)
{
delete pToBeDeleted;
pToBeDeleted = nullptr;
*pHead = nullptr;
}
//删除尾节点
else
{
ListNode *curNode = *pHead;
while (curNode->pNext != pToBeDeleted)
{
curNode = curNode->pNext;
}
curNode->pNext = nullptr;
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
void print(ListNode *pHead) {
while (pHead != nullptr)
{
cout << pHead->value << " ";
pHead = pHead->pNext;
}
}
int main()
{
ListNode *pNode1 = new ListNode();
pNode1->value = 1;
pNode1->pNext = nullptr;
ListNode *pNode2 = new ListNode();
pNode2->value = 2;
pNode2->pNext = nullptr;
pNode1->pNext = pNode2;
ListNode *pNode3 = new ListNode();
pNode3->value = 3;
pNode3->pNext = nullptr;
pNode2->pNext = pNode3;
ListNode *pNode4 = new ListNode();
pNode4->value = 4;
pNode4->pNext = nullptr;
pNode3->pNext = pNode4;
DeleteNodeInList(&pNode1, pNode4);
print(pNode1);
system("pause");
return 0;
}
时间复杂度分析:
对于n-1个非尾节点而言,可以在O(1)时间内把下一个节点的内存复制覆盖要删除的节点,并删除下一个节点;对于尾节点,优于仍然需要顺序查找,时间复杂度是O(n)。因此,总的平均时间复杂度为[(n-1)O(n)+O(n)]/n,结果还是O(1)。
题目二、删除重复的节点
题目描述:
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现的数字。
示例:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
思路:
头结点可能重复被删除,所以新建一个指针newHead指向头结点;
为了保证删除后链表仍然相连,建立指向当前节点的前一节点的指针preNode,指向当前节点的指针cur ,如果遇到相等的节点,cur向后移动,preNode不动,直到遇到不相等的节点,pPre就可以指向cur。
class Node():
'''单链表节点'''
def __init__(self,elem):
# item存放数据元素
self.elem = elem
# next是下一个节点的标识
self.next = None
def __del__(self):
self.elem = None
self.next = None
class Solution():
'''
删除排序链表中重复的节点
'''
def deleteDuplication(self,pHead):
if pHead == None or pHead.next == None:
return pHead
# 防止第一个节点就是重复的节点,所以创建一个新的头节点
newHead = Node(-1)
newHead.next = pHead
# preNode指向当前节点的前一个节点
preNode = newHead
cur = pHead
while cur and cur.next:
if cur.elem != cur.next.elem:
cur = cur.next
preNode = preNode.next
else:
val = cur.elem
while cur and cur.elem == val:
cur = cur.next
preNode.next = cur
return newHead.next
if __name__=='__main__':
A1 = Node(1)
A2 = Node(2)
A3 = Node(3)
A4 = Node(4)
A5 = Node(4)
A6 = Node(5)
A1.next=A2
A2.next=A3
A3.next=A4
A4.next=A5
A5.next=A6
solution=Solution()
ans = solution.deleteDuplication(A1)
while ans:
print(ans.elem,end=' ')
ans = ans.next
c++代码:
#include <iostream>
using namespace std;
struct ListNode
{
int m_nValue;
ListNode *m_pNext;
};
void DeleteDuplication(ListNode** pHead)
{
if (pHead == nullptr || *pHead == nullptr)
return;
ListNode* pPreNode = nullptr;
ListNode* pNode = *pHead;
while (pNode != nullptr)
{
ListNode *pNext = pNode->m_pNext;
bool needDelete = false;
if (pNext != nullptr && pNext->m_nValue == pNode->m_nValue)
needDelete = true;
if (!needDelete)
{
pPreNode = pNode;
pNode = pNode->m_pNext;
}
else
{
int value = pNode->m_nValue;
ListNode* pToBeDel = pNode;
while (pToBeDel != nullptr && pToBeDel->m_nValue == value)
{
pNext = pToBeDel->m_pNext;
delete pToBeDel;
pToBeDel = nullptr;
pToBeDel = pNext;
}
if (pPreNode == nullptr)
*pHead = pNext;
else
pPreNode->m_pNext = pNext;
pNode = pNext;
}
}
}
void print(ListNode *pHead) {
while (pHead != nullptr)
{
cout << pHead->m_nValue << " ";
pHead = pHead->m_pNext;
}
}
int main()
{
ListNode *pNode1 = new ListNode();
pNode1->m_nValue = 1;
pNode1->m_pNext = nullptr;
ListNode *pNode2 = new ListNode();
pNode2->m_nValue = 3;
pNode2->m_pNext = nullptr;
pNode1->m_pNext = pNode2;
ListNode *pNode3 = new ListNode();
pNode3->m_nValue = 3;
pNode3->m_pNext = nullptr;
pNode2->m_pNext = pNode3;
ListNode *pNode4 = new ListNode();
pNode4->m_nValue = 3;
pNode4->m_pNext = nullptr;
pNode3->m_pNext = pNode4;
DeleteDuplication(&pNode1);
print(pNode1);
system("pause");
return 0;
}
3.链表中倒数第K个节点
题目描述:
思路:
看到本题我们很自然的一个想法是从尾结点往前倒退k步,但是对于单链表是行不通的。那我们换个思路,假设链表有n个结点,要求倒数第k个结点,其实也就是从前往后数第n-k+1个结点。如果能够得到链表的节点个数n,只需要从头节点开始往后走n-k+1步就可以了。这个思路只需要遍历两次链表即可,第一遍遍历链表得到节点数n,第二遍得到倒数第K个节点。
有没有只遍历一次链表的方法呢?本题其实是很典型的快慢双指针问题,快指针先走K步,从第K步开始,慢指针开始遍历,快慢指针保持K-1的距离,当快指针到达尾节点时,慢指针正好到达倒数第K个节点。
class Node():
'''单链表节点'''
def __init__(self,elem):
# item存放数据元素
self.elem = elem
# next是下一个节点的标识
self.next = None
def __del__(self):
self.elem = None
self.next = None
class Solution():
'''
输入一个链表,输出该链表中倒数第k个结点。
'''
def findKthNode(self,pHead,k):
if pHead == 0 or k <= 0:
return None
pAHead = pHead
pBehind = None
for i in range(k-1):
# 判断链表的节点个数是否少于K
if pAHead.next != None:
pAHead = pAHead.next
else:
return None
pBehind = pHead
while pAHead != None:
pAHead = pAHead.next
pBehind = pBehind.next
return pBehind
if __name__=='__main__':
A1 = Node(1)
A2 = Node(2)
A3 = Node(3)
A4 = Node(4)
A5 = Node(4)
A6 = Node(5)
A1.next=A2
A2.next=A3
A3.next=A4
A4.next=A5
A5.next=A6
solution=Solution()
ans = solution.findKthNode(A1,3)
print(ans.elem)
c++
#include <iostream>
struct ListNode
{
int m_nValue;
ListNode *m_pNext;
};
ListNode* findKthTotail(ListNode *pHead, unsigned k) {
if (pHead == nullptr || k == 0)
{
return nullptr;
}
ListNode *ANode = pHead;
for (unsigned i = 0; i < k - 1; ++i)
{
if (ANode->m_pNext != nullptr)
ANode = ANode->m_pNext;
else
return nullptr;
}
ListNode *BNode = pHead;
while (ANode->m_pNext != nullptr)
{
ANode = ANode->m_pNext;
BNode = BNode->m_pNext;
}
return BNode;
}
4.链表中环的入口节点
如果一个链表中包含环,如何找到环的入口节点?例如,下图中环的入口节点为3
c++代码
#include <iostream>
struct ListNode
{
int m_nValue;
ListNode *m_pNext;
};
ListNode *MeetNode(ListNode *pHead) {
if (pHead == nullptr)
return nullptr;
ListNode *slowNode = pHead->m_pNext;
if (slowNode == nullptr)
return nullptr;
ListNode *fastNode = slowNode->m_pNext;
while (fastNode && slowNode)
{
if (fastNode == slowNode)
return fastNode;
slowNode = slowNode->m_pNext;
fastNode = fastNode->m_pNext;
if (fastNode != nullptr)
fastNode = fastNode->m_pNext;
}
return nullptr;
}
ListNode *entryNodeOfLoop(ListNode *pHead) {
ListNode *meetNode = MeetNode(pHead);
if (meetNode == nullptr)
return nullptr;
int numbersNodeOfLoop = 1;
ListNode *p1 = meetNode;
while (p1->m_pNext != meetNode)
{
p1 = p1->m_pNext;
++numbersNodeOfLoop;
}
p1 = pHead;
for (int i = 0; i < numbersNodeOfLoop; ++i)
p1 = p1->m_pNext;
ListNode * p2 = pHead;
while (p1 != p2)
{
p1 = p1->m_pNext;
p2 = p2->m_pNext;
}
return p1;
}
5.反转链表
定义一个函数,输入一个链表的头结点,翻转该链表并输出翻转后链表的头结点。
思路
定义3个指针,分别指向当前遍历到的结点、它的前一个结点及下一个结点。在遍历过程中
首先记录当前节点的下一个节点,然后将当前节点指向前一个节点,
前一个节点“移动到”当前节点,当前节点“移动到”下一个节点
如此反复,直到当前节点的下一个节点为NULL时。
class Node():
'''单链表节点'''
def __init__(self,elem):
# item存放数据元素
self.elem = elem
# next是下一个节点的标识
self.next = None
class Solution():
'''
反转链表
输入一个链表,反转链表后,输出链表的所有元素
'''
def reverseList(self,pHead):
if pHead == None or pHead.next == None:
return pHead
cur = pHead
newHead = None
temp = None
while cur != None:
temp = cur.next
cur.next = newHead
newHead = cur
cur = temp
return newHead
if __name__=='__main__':
A1 = Node(1)
A2 = Node(2)
A3 = Node(3)
A4 = Node(4)
A5 = Node(5)
A1.next=A2
A2.next=A3
A3.next=A4
A4.next=A5
solution=Solution()
ans = solution.reverseList(A1)
while ans:
print(ans.elem,end=' ')
ans = ans.next
c++
#include <iostream>
struct ListNode
{
int m_nValue;
ListNode *m_pNext;
};
ListNode* ReverseList(ListNode *pHead) {
ListNode *ReverseHead = nullptr;
ListNode *pNode = pHead;
ListNode *prev = nullptr;
ListNode *nextNode = nullptr;
while (pNode != nullptr)
{
nextNode = pNode->m_pNext;
if (nextNode == nullptr)
ReverseHead = pNode;
pNode->m_pNext = prev;
prev = pNode;
pNode = nextNode;
}
return ReverseHead;
}
6.合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则
思路:
先在两个头结点中选择出一个较小的作为新链表的头结点,被选的节点指向下一个节点。新头结点的下一个节点就是再次比较两个排序链表的头结点的大小,可以用递归。注意当输入为空链表的情况,输出的结果就是另一个链表。
struct ListNode
{
int m_nValue;
ListNode *pNext;
};
ListNode *Merge(ListNode *pHead1, ListNode *pHead2) {
if (pHead1 == nullptr)
return pHead2;
else if (pHead2 == nullptr)
return pHead1;
ListNode *mergeHead = nullptr;
if (pHead1->m_nValue < pHead2->m_nValue)
{
mergeHead = pHead1;
mergeHead->pNext = Merge(pHead1->pNext, pHead2);
}
else
{
mergeHead = pHead2;
mergeHead->pNext = Merge(pHead1, pHead2->pNext);
}
return mergeHead;
}
7.复杂链表的复制
请实现函数ComplexListNode Clone(ComplexListNode head),复制一个复杂链表。在复杂链表中,每个结点除了有一个Next指针指向下一个结点外,还有一个Sibling指向链表中的任意结点或者NULL。
下图是一个含有5个结点的复杂链表。图中实线箭头表示m_pNext指针,虚线箭头表示m_pSibling指针。为简单起见,指向NULL的指针没有画出。
思路
(1)O(n2)的普通解法
- 第一步是复制原始链表上的每一个结点,并用Next节点链接起来;
- 第二步是设置每个结点的Sibling节点指针。
(2)借助辅助空间的O(n)解法
- 第一步仍然是复制原始链表上的每个结点N创建N',然后把这些创建出来的结点用Next链接起来。同时我们把<N,N'>的配对信息放到一个哈希表中。
- 第二步还是设置复制链表上每个结点的m_pSibling。由于有了哈希表,我们可以用O(1)的时间根据S找到S'。
#include <iostream>
#include <unordered_map>
using namespace std;
struct ComplexListNode
{
int m_nValue;
ComplexListNode *m_pNext;
ComplexListNode *m_pSibling;
};
ComplexListNode* CloneNodes(ComplexListNode *pHead) {
if (pHead == nullptr)
return nullptr;
ComplexListNode *pCloneHead = new ComplexListNode();
pCloneHead->m_nValue = pHead->m_nValue;
pCloneHead->m_pNext = nullptr;
pCloneHead->m_pSibling = nullptr;
unordered_map<ComplexListNode*, ComplexListNode*> hasmap;
hasmap.insert(make_pair(pHead, pCloneHead));
ComplexListNode *pNode = pHead->m_pNext;
ComplexListNode *pCloneNode = pCloneHead;
while (pNode != nullptr)
{
ComplexListNode *pClone = new ComplexListNode();
pClone->m_nValue = pNode->m_nValue;
pClone->m_pNext = pNode->m_pNext;
pClone->m_pSibling = nullptr;
pCloneNode->m_pNext = pClone;
pCloneNode = pCloneNode->m_pNext;
hasmap.insert({ pNode, pCloneNode});
pNode = pNode->m_pNext;
}
pNode = pHead;
pCloneNode = pCloneHead;
while (pNode != nullptr)
{
ComplexListNode *pSibling = pNode->m_pSibling;
if (pSibling != nullptr)
{
ComplexListNode *pCloneSibling = hasmap.find(pSibling)->second;
pCloneNode->m_pSibling = pCloneSibling;
}
pNode = pNode->m_pNext;
pCloneNode = pCloneNode->m_pNext;
}
return pCloneHead;
}
(3)不借助辅助空间的O(n)解法
- 第一步仍然是根据原始链表的每个结点N创建对应的N'。(把N'链接在N的后面)
- 第二步设置复制出来的结点的Sibling。(把N'的Sibling指向N的Sibling)
- 第三步把这个长链表拆分成两个链表:把奇数位置的结点用Next链接起来就是原始链表,偶数数值的则是复制链表。
c++代码:
#include <iostream>
using namespace std;
struct ComplexListNode
{
int m_nValue;
ComplexListNode *m_pNext;
ComplexListNode *m_pSibling;
};
void CloneNode(ComplexListNode *pHead) {
ComplexListNode *pNode = pHead;
while (pNode != nullptr)
{
ComplexListNode *pClone = new ComplexListNode();
pClone->m_nValue = pNode->m_nValue;
pClone->m_pNext = pNode->m_pNext;
pClone->m_pSibling = nullptr;
pNode->m_pNext = pClone;
pNode = pClone->m_pNext;
}
}
void ConnectSbilingNode(ComplexListNode *pHead) {
ComplexListNode *pNode = pHead;
while (pNode != nullptr)
{
ComplexListNode *pClone = pNode->m_pNext;
if (pNode->m_pSibling != nullptr)
{
pClone->m_pSibling = pNode->m_pSibling->m_pNext;
}
pNode = pClone->m_pNext;
}
}
ComplexListNode* ReconnectNodes(ComplexListNode *pHead) {
ComplexListNode* pNode = pHead;
ComplexListNode* pClonedHead = nullptr;
ComplexListNode* pClonedNode = nullptr;
if (pNode != nullptr)
{
pClonedHead = pClonedNode = pNode->m_pNext;
pNode->m_pNext = pClonedNode->m_pNext;
pNode = pNode->m_pNext;
}
while (pNode != nullptr)
{
pClonedNode->m_pNext = pNode->m_pNext;
pClonedNode = pClonedNode->m_pNext;
pNode->m_pNext = pClonedNode->m_pNext;
pNode = pNode->m_pNext;
}
return pClonedHead;
}
ComplexListNode* Clone(ComplexListNode* pHead) {
CloneNode(pHead);
ConnectSbilingNode(pHead);
return ReconnectNodes(pHead);
}
8.两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。
1、暴力法
遍历第一个链表,每遍历到一个结点,在第二个链表中顺序遍历,如果在第二个链表上有一个结点和该结点一样,说明两个链表在这个结点上重合,也就是第一个公共结点。
时间复杂度:O(mn)
2、堆栈法
如果两个链表存在公共结点,那么从第一个公共结点到链尾,所有结点都是重合的,如果我们从两个链表的尾部开始往前比较,最后一个相同的结点就是第一个公共结点。
但单链表只有后继指针,不能往回移动,我们可以很快想到了栈,将两个链表的结点分别放入两个栈里,这样两个尾指针都在栈顶,接着下就比较栈顶指针是否相同,如果是,则把栈顶弹出,继续比较下一个栈顶,直至找到最后一个相同的结点。
时间复杂度:O(m+n),空间复杂度:O(m+n)
#include <stack>
#include <iostream>
using namespace std;
struct ListNode
{
int m_value;
ListNode *m_pNext;
};
ListNode* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2) {
stack<ListNode*> myStack1;
ListNode *pNode1 = pHead1;
while (pNode1 != nullptr)
{
myStack1.push(pNode1);
pNode1 = pNode1->m_pNext;
}
stack<ListNode*> myStack2;
ListNode *pNode2 = pHead2;
while (pNode2 != nullptr)
{
myStack2.push(pNode2);
pNode2 = pNode2->m_pNext;
}
while (!myStack1.empty() && !myStack2.empty() && myStack1.top() == myStack2.top())
{
myStack1.pop();
myStack2.pop();
}
return myStack1.top()->m_pNext;
}
3、两个指针
首先分别遍历两个链表A,B,得到各自的长度如lenA,lenB,假设lenA>lenB,然后让A指针先走lenA-lenB步,接着两个指针一块走,直至相遇即找到第一个公共结点。
时间复杂度:O(m+n)
#include <stack>
#include <iostream>
using namespace std;
struct ListNode
{
int m_value;
ListNode *m_pNext;
};
unsigned lengthOfList(ListNode *pHead) {
ListNode *pNode = pHead;
unsigned length = 0;
while (pNode != nullptr)
{
++length;
pNode = pNode->m_pNext;
}
return length;
}
ListNode* FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2) {
unsigned len1 = lengthOfList(pHead1);
unsigned len2 = lengthOfList(pHead2);
int diff = 0;
ListNode *pHeadLong = nullptr;
ListNode *pHeadShort = nullptr;
if (len1 > len2)
{
pHeadLong = pHead1;
pHeadShort = pHead2;
diff = len1 - len2;
}
else
{
pHeadLong = pHead2;
pHeadShort = pHead1;
int diff = len2 - len1;
}
for (int i = 0; i < diff; ++i)
{
pHeadLong = pHeadLong->m_pNext;
}
while ((pHeadLong != nullptr) && (pHeadShort != nullptr) && (pHeadLong != pHeadShort))
{
pHeadLong = pHeadLong->m_pNext;
pHeadShort = pHeadShort->m_pNext;
}
ListNode *CommonNode = pHeadLong;
return CommonNode;
}