数据结构—链表

一、链表

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有两个特例:

  1. 删除的是尾指针,需要遍历找到前一个指针;
  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;
}

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值