1、从单链表中删除指定元素
2、翻转单链表
3、求单链表中节点个数
4、查找链表中倒数第K个节点
5、查找链表中间节点
6、倒序打印单链表
7、已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
8、判断一个单链表是否有环
9、判断两个单链表是否相交
10、求两个单链表相交的第一个节点
11、已知一个单链表中存在环,求进入环中的第一个节点
12、给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted
13、复杂链表的复制
14、两个单链表生成相加链表
15、如何判断两个有环链表是否相交,相交则返回第一个相交节点
下面是链表的数据结构:
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<stack>
using namespace std;
typedef struct Node {
int value;
struct Node *next;
}ListNode;
1.从单链表中删除指定元素
ListNode *removeElements(ListNode *head, int val)
{
ListNode **list = &head;
while (*list != nullptr)
{
if ((*list)->val == val) {
*list = (*list)->next;
}
else{
list = &(*list)->next;
}
}
return head;
}
void removeHelper(ListNode *&head, int val)
{
if (head == nullptr){
return;
}
else if (head->val == val) {
head = head->next;
}
else{
removeHelper(head->next, val);
}
}
ListNode* removeElements(ListNode* head, int val) {
if (head == NULL)
return NULL;
if (val == head->val)
return removeElements(head->next,val);
head->next = removeElements(head->next,val);
return head;
}
2.将单链表反转
从头到尾遍历原链表,每遍历一个结点,将其摘下放在新链表的最前端。注意链表为空和只有一个结点的情况。时间复杂度为O(n)。参考代码如下:
Node* reverse_list(Node* pHead) //翻转单链表
{
if (pHead == NULL || pHead->next == NULL)
return NULL;
Node *current=pHead;
Node *temp;
Node *reverse_pHead = NULL;
while (current->next != NULL)
{
temp = current;
current = current->next;
temp->next = reverse_pHead;
reverse_pHead = temp;
}
return reverse_pHead;
}
ListNode *ReverseList(ListNode *pHead)
{
ListNode *pReverseHead = NULL;
ListNode *pNode = pHead;
ListNode *pPrev = NULL;
while (pNode != NULL) {
ListNode *pNext = pNode->next;
if (pNext == NULL)
pReverseHead = pNode;
pNode->next = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReverseHead;
}
3.求单链表中节点的个数
这是最最基本的了,应该能够迅速写出正确的代码,注意检查链表是否为空。时间复杂度为O(n)。参考代码如下:
int find_nodes(Node* pHead) //求单链表中节点个数
{
if (pHead == NULL)
return 0;
int num = 0;
Node *current = pHead;
while (current->next != NULL){
num += 1;
current = current->next;
}
return num;
}
4.查找单链表中的倒数第K个节点
最普遍的方法是,先统计单链表中结点的个数,然后再找到第(n-k)个结点。注意链表为空,k为0,k为1,k大于链表中节点个数时的情况。时间复杂度为O(n)。代码略。
这里主要讲一下另一个思路,这种思路在其他题目中也会有应用。
主要思路就是使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。
参考代码如下:
Node* findR_Kth_node(Node *pHead,unsigned int k) //查找链表中倒数第K个节点
{
if (k == 0 || pHead == NULL)
return NULL;
Node *p1 = pHead;
Node *p2 = pHead;
while (k > 1&&pHead!=NULL) {
pHead = pHead->next;
k--;
}
while (p1->next != NULL)
{
p1 = p1->next;
p2 = p2->next;
}
return p2;
}
5.查找链表的中间节点
此题可应用于上一题类似的思想。也是设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点,即第(n/2+1)个结点。注意链表为空,链表结点个数为1和2的情况。时间复杂度O(n)。参考代码如下:
Node* findMid_node(Node *pHead) //查找链表中间节点
{
if (pHead == NULL && pHead->next == NULL)
return pHead;
Node *p1;
Node *p2;
p1 = pHead;
p2 = pHead;
while (p2->next!=NULL) {
p1 = p1->next;
p2 = p2->next;
if (p2->next != NULL)
p2 = p2->next;
}
return p1;
}
6.倒序打印单链表
(1)使用栈
对于这种颠倒顺序的问题,我们应该就会想到栈,后进先出。所以,这一题要么自己使用栈,要么让系统使用栈,也就是递归。注意链表为空的情况。时间复杂度为O(n)。参考代码如下:
自己使用栈:
void PrintR_list(Node *pHead) //使用栈倒序打印单链表
{
if (pHead == NULL)
printf("the list is empty!");
stack<Node*>s;
Node *current=pHead;
while (current!=NULL) {
s.push(current);
current = current->next;
}
while (!s.empty()){
current = s.top();
printf("%d\n",current->value);
s.pop();
}
}
(2)使用递归
void printR_list(Node *pHead) //使用递归倒序打印
{
if (pHead == NULL)
return;
else {
printR_list(pHead->next);
printf("%d\n",pHead->value);
}
}
7.已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
这个类似归并排序。尤其注意两个链表都为空,和其中一个为空时的情况。只需要O(1)的空间。时间复杂度为O(max(len1, len2))。参考代码如下:
(1)非递归方法
Node* MergeList(Node *pHead1, Node *pHead2) //按顺序合并两个单链表
{
if (pHead1 == NULL)
return pHead2;
if (pHead2 == NULL)
return pHead1;
Node *Merge_pHead = NULL;
if (pHead1->value < pHead2->value){
Merge_pHead = pHead1;
Merge_pHead->next = NULL;
pHead1 = pHead1->next;
}
else {
Merge_pHead = pHead2;
Merge_pHead->next = NULL;
pHead2 = pHead2->next;
}
Node *tempNode=Merge_pHead;
while (pHead1 != NULL && pHead2 != NULL)
{
if (pHead1->value < pHead2->value) {
tempNode->next = pHead1;
pHead1 = pHead1->next;
tempNode = tempNode->next;
tempNode->next = NULL;
}
else {
tempNode->next = pHead2;
pHead2 = pHead2->next;
tempNode = tempNode->next;
tempNode->next = NULL;
}
}
if (pHead1 != NULL)
tempNode->next = pHead1;
else if(pHead2!=NULL)
tempNode->next = pHead2;
return Merge_pHead;
}
(2)递归方法
Node* Merge_list(Node* pHead1, Node *pHead2) //递归方法合并两个有序链表
{
if (pHead1 == NULL)
return pHead2;
if (pHead2 == NULL)
return pHead1;
Node *Merge_pHead = NULL;
if (pHead1->value < pHead2->value){
Merge_pHead = pHead1;
Merge_pHead->next = Merge_list(pHead1->next,pHead2);
}
else {
Merge_pHead = pHead2;
Merge_pHead->next = Merge_list(pHead1,pHead2->next);
}
return Merge_pHead;
}
8.判断一个单链表是否有环
这里也是用到两个指针。如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。时间复杂度为O(n)。参考代码如下:
bool is_hasCricle(Node *pHead) //判断一个单链表是否有环
{
if (pHead == NULL || pHead->next ==NULL)
return false;
Node *p1 = pHead;
Node *p2 = pHead;
while (p1 !=NULL && p1->next!=NULL){
p1 = p1->next->next;
p2 = p2->next;
if (p1 == p2)
return true;
}
return false;
}
9.判断两个单链表是否相交
如果两个链表相交于某一节点,那么在这个相交节点之后的所有节点都是两个链表所共有的。也就是说,如果两个链表相交,那么最后一个节点肯定是共有的。先遍历第一个链表,记住最后一个节点,然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则不相交。时间复杂度为O(len1+len2),因为只需要一个额外指针保存最后一个节点地址,空间复杂度为O(1)。参考代码如下:
bool IsIntersected(ListNode * pHead1, ListNode * pHead2)
{
if(pHead1 == NULL || pHead2 == NULL)
return false;
ListNode * pTail1 = pHead1;
while(pTail1->m_pNext != NULL)
pTail1 = pTail1->m_pNext;
ListNode * pTail2 = pHead2;
while(pTail2->m_pNext != NULL)
pTail2 = pTail2->m_pNext;
return pTail1 == pTail2;
}
10. 求两个单链表相交的第一个节点
1) 对第一个链表遍历,计算长度len1,同时保存最后一个节点的地址。
2) 对第二个链表遍历,计算长度len2,同时检查最后一个节点是否和第一个链表的最后一个节点相同,若不相同,不相交,结束。
3) 两个链表均从头节点开始,假设len1大于len2,那么将第一个链表先遍历len1-len2个节点,此时两个链表当前节点到第一个相交节点的距离就相等了,然后一起向后遍历,知道两个节点的地址相同。
时间复杂度,O(len1+len2)。参考代码如下:
ListNode* GetFirstCommonNode(ListNode * pHead1, ListNode * pHead2)
{
if(pHead1 == NULL || pHead2 == NULL)
return NULL;
int len1 = 1;
ListNode * pTail1 = pHead1;
while(pTail1->m_pNext != NULL) {
pTail1 = pTail1->m_pNext;
len1++;
}
int len2 = 1;
ListNode * pTail2 = pHead2;
while(pTail2->m_pNext != NULL) {
pTail2 = pTail2->m_pNext;
len2++;
}
if(pTail1 != pTail2) // 不相交直接返回NULL
return NULL;
ListNode * pNode1 = pHead1;
ListNode * pNode2 = pHead2;
// 先对齐两个链表的当前结点,使之到尾节点的距离相等
if(len1 > len2) {
int k = len1 - len2;
while(k--)
pNode1 = pNode1->m_pNext;
}
else {
int k = len2 - len1;
while(k--)
pNode2 = pNode2->m_pNext;
}
while(pNode1 != pNode2) {
pNode1 = pNode1->m_pNext;
pNode2 = pNode2->m_pNext;
}
return pNode1;
}
11. 已知一个单链表中存在环,求进入环中的第一个节点
首先判断是否存在环,若不存在结束。在环中的一个节点处断开(当然函数结束时不能破坏原链表),这样就形成了两个相交的单链表,求进入环中的第一个节点也就转换成了求两个单链表相交的第一个节点。参考代码如下:
ListNode* GetFirstNodeInCircle(ListNode * pHead)
{
if(pHead == NULL || pHead->m_pNext == NULL)
return NULL;
ListNode * pFast = pHead;
ListNode * pSlow = pHead;
while(pFast != NULL && pFast->m_pNext != NULL) {
pSlow = pSlow->m_pNext;
pFast = pFast->m_pNext->m_pNext;
if(pSlow == pFast)
break;
}
if(pFast == NULL || pFast->m_pNext == NULL)
return NULL;
// 将环中的此节点作为假设的尾节点,将它变成两个单链表相交问题
ListNode * pAssumedTail = pSlow;
ListNode * pHead1 = pHead;
ListNode * pHead2 = pAssumedTail->m_pNext;
ListNode * pNode1, * pNode2;
int len1 = 1;
ListNode * pNode1 = pHead1;
while(pNode1 != pAssumedTail) {
pNode1 = pNode1->m_pNext;
len1++;
}
int len2 = 1;
ListNode * pNode2 = pHead2;
while(pNode2 != pAssumedTail) {
pNode2 = pNode2->m_pNext;
len2++;
}
pNode1 = pHead1;
pNode2 = pHead2;
// 先对齐两个链表的当前结点,使之到尾节点的距离相等
if(len1 > len2) {
int k = len1 - len2;
while(k--)
pNode1 = pNode1->m_pNext;
}
else {
int k = len2 - len1;
while(k--)
pNode2 = pNode2->m_pNext;
}
while(pNode1 != pNode2) {
pNode1 = pNode1->m_pNext;
pNode2 = pNode2->m_pNext;
}
return pNode1;
}
12. 给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted
对于删除节点,我们普通的思路就是让该节点的前一个节点指向该节点的下一个节点,这种情况需要遍历找到该节点的前一个节点,时间复杂度为O(n)。对于链表,链表中的每个节点结构都是一样的,所以我们可以把该节点的下一个节点的数据复制到该节点,然后删除下一个节点即可。要注意最后一个节点的情况,这个时候只能用常见的方法来操作,先找到前一个节点,但总体的平均时间复杂度还是O(1)。参考代码如下:
void Delete(ListNode * pHead, ListNode * pToBeDeleted)
{
if(pToBeDeleted == NULL)
return;
if(pToBeDeleted->m_pNext != NULL) {
pToBeDeleted->m_nKey = pToBeDeleted->m_pNext->m_nKey; // 将下一个节点的数据复制到本节点,然后删除下一个节点
ListNode * temp = pToBeDeleted->m_pNext;
pToBeDeleted->m_pNext = pToBeDeleted->m_pNext->m_pNext;
delete temp;
}
else // 要删除的是最后一个节点
{
if(pHead == pToBeDeleted) // 链表中只有一个节点的情况
{
pHead = NULL;
delete pToBeDeleted;
}
else {
ListNode * pNode = pHead;
while(pNode->m_pNext != pToBeDeleted) // 找到倒数第二个节点
pNode = pNode->m_pNext;
pNode->m_pNext = NULL;
delete pToBeDeleted;
}
}
}
13、请实现函数ComplexListNode *Clone(ComplexListNode *pHead),复制一个复杂链表。在复杂链表中,每个结点除了有一个next指针指向下一个节点外,还有一个sibling指向链表中的任意节点或者NULL。节点的定义如下:
struct ComplexListNode {
int value;
ComplexListNode *next;
ComplexListNode *sibling;
};
解法
ComplexListNode *Clone(ComplexListNode *pHead)
{
CloneNodes(pHead);
ConnectSiblingNodes(pHead);
return ReconnectNodes(pHead);
}
void CloneNodes(ComplexListNode *pHead)
{
ComplexListNode *node = pHead;
while (node != NULL) {
ComplexListNode *cloned = new ComplexListNode();
cloned->value = node->value;
cloned->next = node->next;
cloned->sibling = NULL;
node->next = cloned;
node = cloned->next;
}
}
void ConnectSiblingNodes(ComplexListNode *pHead)
{
ComplexListNode *node = pHead;
while (node != NULL) {
ComplexListNode *cloned = node->next;
if (node->sibling != NULL) {
cloned->sibling = node->sibling->next;
}
node = cloned->next;
}
}
ComplexListNode *ReconnectNodes(ComplexListNode *pHead)
{
ComplexListNode *node = pHead;
ComplexListNode *pClonedHead = NULL;
ComplexListNode *pCloneNode = NULL;
if (node != NULL) {
pClonedHead = pCloneNode = node->next;
node->next = pCloneNode->next;
node = node->next;
}
while (node != NULL) {
pCloneNode->next = node->next;
pCloneNode = pCloneNode->next;
node->next = pCloneNode->next;
node = node->next;
}
return pClonedHead;
}
14、假设链表中每一个节点的值都在0~9之间,那么链表整体就可以代表一个整数。
例如:9->3->7,可以代表整数937。
给定两个这种链表的头结点head1和head2,请生成代表两个整数相加值的结果链表。
例如:链表1为9->3->7,链表2为6->3,最后生成新的结果链表为1->0->0->0。
解法1:使用两个栈
ListNode *addList(ListNode *head1, ListNode *head2)
{
stack<int>s1, s2;
while (head1 != NULL) {
s1.push(head1->value);
head1 = head1->next;
}
while (head2 != NULL) {
s2.push(head2->value);
head2 = head2->next;
}
int carry = 0;
int n1 = 0,n2=0;
int sum = 0;
ListNode *node = NULL;
ListNode *pre = NULL;
while (!s1.empty() || !s2.empty()) {
if (s1.empty()) {
n1 = 0;
}
else {
n1 = s1.top();
s1.pop();
}
if (s2.empty()) {
n2 = 0;
}
else {
n2 = s2.top();
s2.pop();
}
sum = n1 + n2 + carry;
pre = node;
node = new ListNode();
node->value = sum % 10;
node->next = pre;
carry = sum / 10;
}
if (carry == 1) {
pre = node;
node = new ListNode();
node->value = 1;
node->next = pre;
}
return node;
}
解法2:利用链表的逆序求解,可以省掉用栈的空间
ListNode *ReverseList(ListNode *head) {
if (head == NULL && head->next == NULL)return NULL;
ListNode *Rhead = NULL;
ListNode *cur = head;
while (cur != NULL) {
ListNode *next = cur->next;
cur->next = Rhead;
Rhead = cur;
cur = next;
}
return Rhead;
}
ListNode *addList2(ListNode *head1, ListNode *head2) {
head1 = ReverseList(head1);
head2 = ReverseList(head2);
int carry = 0;
int n1 = 0, n2 = 0;
int sum = 0;
ListNode *node1 = head1;
ListNode *node2 = head2;
ListNode *node = NULL;
ListNode *pre = NULL;
while (node1 != NULL || node2 != NULL) {
n1 = node1 != NULL ? node1->value : 0;
n2 = node2 != NULL ? node2->value : 0;
sum = n1 + n2 + carry;
pre = node;
node = new ListNode();
node->value = sum % 10;
node->next = pre;
carry = sum / 10;
node1 = node1 != NULL ? node1->next : NULL;
node2 = node2 != NULL ? node2->next : NULL;
}
if (carry == 1) {
pre = node;
node = new ListNode();
node->value = 1;
node->next = pre;
}
ReverseList(head1);
ReverseList(head2);
return node;
}
15、如何判断两个有环链表是否相交,相交则返回第一个相交节点,不想交则返回NULL。
解法
假设我们已经得到了两个链表各自的第一个入环节点,假设链表1的第一个入环节点记为loop1,链表2的第一个入环节点记为loop2。以下是解决问题过程:
1)如果loop1==loop2,我们只要考虑链表1从头开始到loop1这一段与链表2从头开始到loop2,在哪里第一次相交即可,而不同考虑进环如何处理。
2)如果loop1!=loop2,让链表1从loop1出发,因为loop1和之后的所有节点都在环上,所以将来一定能回到loop1.如果回到loop1之前没有遇到loop2,说明两个链表不相交。如果回到loop1之前遇到了loop2,说明两个链表相交。
ListNode *getIntersectNode(ListNode *head1, ListNode *head2)
{
if (head1 == NULL || head2 == NULL)return NULL;
ListNode *loop1 = getLoopNode(head1);
ListNode *loop2 = getLoopNode(head2);
if (loop1 == NULL&&loop2 == NULL)return noLoop(head1,head2);
if (loop1 != NULL && loop2 != NULL)
return bothLoop(head1,loop1,head2,loop2);
return NULL;
}
ListNode *bothLoop(ListNode *head1, ListNode *head2, ListNode *loop1, ListNode *loop2)
{
ListNode *cur1 = NULL;
ListNode *cur2 = NULL;
if (loop1 == loop2) {
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1->next;
}
while (cur2 != loop2) {
n--;
cur2 = cur2->next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = abs(n);
while (n!=0) {
n--;
cur1 = cur1->next;
}
while (cur1 != cur2) {
cur1 = cur1->next;
cur2 = cur2->next;
}
return cur1;
}
else {
cur1 = loop1->next;
while (cur1 != loop1) {
if (cur1 == loop2)
return cur1;
cur1 = cur1->next;
}
return NULL;
}
}