链表节点定义
链表相关的操作中我们定义链表节点和公共操作如LinkListCommon.hpp所示:
#ifndef LinkListCommon_hpp
#define LinkListCommon_hpp
#include <stdio.h>
typedef struct tagSNode {
int value;
tagSNode* pNext;
tagSNode(int v) : value(v), pNext(NULL) {}
}SNode;
// 打印链表
void printLinkList(SNode* pHead) {
if (pHead == NULL) {
printf("No number....");
return;
}
SNode* pCurr = pHead->pNext;
while (NULL != pCurr && NULL != pCurr->pNext) {
printf("%d->", pCurr->value);
pCurr = pCurr->pNext;
}
printf("%d\n", pCurr->value);
}
// 回收链表
void destroyLinkList(SNode* pHead) {
if (pHead == NULL) {
return;
}
SNode* pCurr = pHead;
SNode* pNext = pHead->pNext;
while (pNext) {
delete pCurr;
pCurr = pNext;
pNext = pNext->pNext;
}
if (pCurr) {
delete pCurr;
}
}
// 创建数字链表
SNode* createNodeList(int listLength, int radix) {
SNode* pHead = new SNode(0);
int i;
for (i = 0; i < listLength; i++) {
SNode* pNode = new SNode(rand() % radix);
pNode->pNext = pHead->pNext;
pHead->pNext = pNode;
}
return pHead;
}
#endif /* LinkListCommon_hpp */
链表相加
问题
给定两个链表,分别表示两个非负整数,他们的数字逆序存储在链表当中,且每个节点存储一个数字,计算两个数的和,并且返回和的链表头指针。例如:
输入:2->4->3、5->6->4,输出7->0->8
分析
- 两个数都是逆序存储,正好可以从头向后依次相加,完成”两个数的的竖式计算”
- 两个数字的求和范围是[0,18],进位最大是1,因此第i位相加不会影响到第i+2位的计算。
- 在发现一个链表为空后,直接结束for循环,最后只需进位和较长链表的当前节点相加,较长链表的其他节点直接拷贝至和和链家即得到最终结果。
代码实现
LinkListAdd.hpp
#ifndef LinkListAdd_hpp
#define LinkListAdd_hpp
#include <stdio.h>
#include <stdlib.h>
#include "LinkListCommon.hpp"
// 链表相加
SNode* add(SNode* pHead1, SNode* pHead2) {
SNode* pSum = new SNode(0);
pSum->value = 0;
// 链表尾节点
SNode* pTail = pSum;
SNode* pCurrSum;
SNode* pCurr1 = pHead1->pNext;
SNode* pCurr2 = pHead2->pNext;
// 进位值
int carry = 0;
int value;
while (pCurr1 && pCurr2) {
value = pCurr1->value + pCurr2->value + carry;
carry = value / 10;
value = value % 10;
pCurrSum = new SNode(value);
pTail->pNext = pCurrSum;
pTail = pCurrSum;
pCurr1 = pCurr1->pNext;
pCurr2 = pCurr2->pNext;
}
// 将较长的链表连接到链表尾部
SNode* pLeft = pCurr1 ? pCurr1 : pCurr2;
while (pLeft) {
value = pLeft->value + carry;
carry = value / 10;
value = value % 10;
pCurrSum = new SNode(value);
pTail->pNext = pCurrSum;
pTail = pCurrSum;
pLeft = pLeft->pNext;
}
return pSum;
}
#endif /* LinkListAdd_hpp */
测试代码main.cpp
#include "LinkListAdd.hpp"
int main(int argc, const char * argv[]) {
// 测试链表数字相加
SNode* pHead1 = createNodeList(6);
SNode* pHead2 = createNodeList(9);
printLinkList(pHead1);
printLinkList(pHead2);
SNode* pSum = add(pHead1, pHead2);
printf("Result:\n");
printLinkList(pSum);
destroyLinkList(pHead1);
destroyLinkList(pHead2);
return 0;
}
执行结果
2->0->8->3->9->7
7->2->2->5->0->9->3->8->4
Result:
9->2->0->9->9->6->4->8->4
链表翻转
问题
给定一个链表,翻转该链表从m到n的位置,要求直接翻转而分申请新空间。
- 例如给定链表:1->2->3->4->5,m=2,n=4,返回 1->4->3->2->5。
- 假定给出的参数满足:1<=m<=n<=链表长度。
分析
- 空转m-1次,找到第m-1个节点,即开始翻转第一个节点的前驱,记为head;
- 以head为起点遍历n-m次,将第i次时,将找到的节点插入到head的next中即可(头插法)。
代码实现
链表去重LinkListReversal.hpp
#ifndef LinkListReversal_hpp
#define LinkListReversal_hpp
#include <stdio.h>
#include "LinkListCommon.hpp"
void reverseLinkList(SNode* pHead, int from, int to) {
SNode* pCurr = pHead->pNext;
int i;
// 空转form-1次,找到翻转起点的前一个节点和翻转的第一个节点
for (i = 0; i < from - 1; i++) {
pHead = pCurr;
pCurr = pCurr->pNext;
}
// 起始翻转节点不需要翻转, 把后面的to-1个节点翻转
SNode* pPre = pCurr;
pCurr = pCurr->pNext;
to--;
SNode* pNext;
for (; i < to; i++) {
pNext = pCurr->pNext;
pCurr->pNext = pHead->pNext;
pHead->pNext = pCurr;
pPre->pNext = pNext;
pCurr = pNext;
}
}
#endif /* LinkListReversal_hpp */
测试代码main.cpp
#include "LinkListAdd.hpp"
#include "LinkListReversal.hpp"
int main(int argc, const char * argv[]) {
// 测试链表翻转
SNode* pHead3 = createNodeList(15, 100);
printLinkList(pHead3);
printf("Reverse result:\n");
reverseLinkList(pHead3, 6, 11);
printLinkList(pHead3);
destroyLinkList(pHead3);
return 0;
}
执行结果
35->16->78->99->33->60->57->9->69->3->12->40->29->27->3
Reverse result:
35->16->78->99->33->12->3->69->9->57->60->40->29->27->3
排序链表去重
问题
给定排序的链表,删除重复的元素,重复元素只保留一个:
- 给定: 2->3->3->5->7->8->8->8->9->9->10
- 返回: 2->3->5->7->8->9->10
分析
有两种思路:
- 只保留重复元素的第一个节点,删除后面的重复元素节点
- 只保留重复元素的最后一个节点,删除前面的重复元素节点
两种实现方式均可,若要求删除全部重复元素,则使用第二种方法更加方便些。
代码实现
两种思路的实现代码如下所示
#ifndef LinkListDeleteDuplicateNode_hpp
#define LinkListDeleteDuplicateNode_hpp
#include <stdio.h>
#include "LinkListCommon.hpp"
// 链表去重:保留第一个重复元素
void deleteDuplicateNode1 (SNode* pHead) {
SNode* pPre = pHead->pNext;
SNode* pCur;
while(pPre) {
pCur = pPre->pNext;
if(pCur && (pCur->value == pPre->value)) {
pPre->pNext = pCur->pNext;
delete pCur;
}else {
pPre = pCur;
}
}
}
// 链表去重:保留最后一个重复元素
void deleteDuplicateNode2(SNode* pHead) {
SNode* pPre = pHead->pNext;
SNode* pCur = pPre->pNext;
SNode* pNext;
while (pCur) {
pNext = pCur->pNext;
while (pNext && (pCur->value == pNext->value)) {
pPre->pNext = pNext;
delete pCur;
pCur = pNext;
}
pPre = pCur;
pCur = pNext;
}
}
#endif /* LinkListDeleteDuplicateNode_hpp */
测试代码main.cpp
#include "LinkListDeleteDuplicateNode.hpp"
int main(int argc, const char * argv[]) {
// 测试链表去重:保留第一个重复元素
SNode* pHead4 = new SNode(0);
int data[] = {2, 3, 3, 5, 7, 8, 8, 8, 9, 9, 30};
int size1 = sizeof(data) / sizeof(int);
for (int i = size1 - 1; i >= 0; i--) {
SNode* pNode = new SNode(data[i]);
pNode->pNext = pHead4->pNext;
pHead4->pNext = pNode;
}
printLinkList(pHead4);
deleteDuplicateNode1(pHead4);
printLinkList(pHead4);
destroyLinkList(pHead4);
printf("\n===============================================================\n");
// 测试链表去重:保留最后一个重复元素
SNode* pHead5 = new SNode(0);
// int data[] = {2, 3, 3, 5, 7, 8, 8, 8, 9, 9, 30};
int size2 = sizeof(data) / sizeof(int);
for (int i = size2 - 1; i >= 0; i--) {
SNode* pNode = new SNode(data[i]);
pNode->pNext = pHead5->pNext;
pHead5->pNext = pNode;
}
printLinkList(pHead5);
deleteDuplicateNode1(pHead5);
printLinkList(pHead5);
destroyLinkList(pHead5);
printf("\n===============================================================\n");
return 0;
}
执行结果
===============================================================
2->3->3->5->7->8->8->8->9->9->30
2->3->5->7->8->9->30
===============================================================
2->3->3->5->7->8->8->8->9->9->30
2->3->5->7->8->9->30
===============================================================
去除全部重复元素
即若发现重复元素,则删除全部重复元素。用思路2解决该问题。用一个标志位记录当前元素是否为重复元素,实现代码如下所示:
void deleteAllDuplicateNode(SNode* pHead) {
SNode* pPre = pHead;
SNode* pCur = pPre->pNext;
SNode* pNext;
// 标志pCur是否为重复元素
bool bDup;
while (pCur) {
pNext = pCur->pNext;
bDup = false;
while (pNext && (pCur->value == pNext->value)) {
pPre->pNext = pNext;
delete pCur;
pCur = pNext;
pNext = pCur->pNext;
bDup = true;
}
if (bDup) { // pCur是重复的元素,删除
pPre->pNext = pNext;
delete pCur;
} else { // pCur不是重复元素,pPre往后移
pPre = pCur;
}
pCur = pNext;
}
}
链表划分
问题
给定一个链表和一个值x
,将链表划分成两部分,使得划分后小于x
的节点在前,大于等于x
的节点在后。这两部分中要保持原链表中出现的顺序,例如
给定链表1->4->3->2->5-2和x=3,返回1->2->2->4->3->5.
分析
分别申请两个指针p1
和p2
,小于x
的添加到p1
中,大于等于x
的添加到p2
中,最后将p2
链接到p1
的末端即可。
时间复杂度为O(N)
,空间复杂度为O(1)
,该问题要说明的是快速排序对于单链表存储结构仍然适用.
代码实现
代码实现如LinkListPartition.hpp所示
#ifndef LinkListPartition_hpp
#define LinkListPartition_hpp
#include <stdio.h>
#include "LinkListCommon.hpp"
void linkListPartition(SNode* pHead, int pivotKey) {
// 两个链表的头指针
SNode* pLeftHead = new SNode(0);
SNode* pRightHead = new SNode(0);
// 两个链表的尾指针
SNode* left = pLeftHead;
SNode* right = pRightHead;
SNode* p = pHead->pNext;
while (p) {
if (p->value < pivotKey) {
left->pNext = p;
left = p;
} else {
right->pNext = p;
right = p;
}
p = p->pNext;
}
// 将right链接到left尾部
left->pNext = pRightHead->pNext;
right->pNext = NULL;
pHead->pNext = pLeftHead->pNext;
delete pLeftHead;
delete pRightHead;
}
#endif /* LinkListPartition_hpp */
测试代码main.cpp如下所示:
#include "LinkListPartition.hpp"
int main(int argc, const char * argv[]) {
// 测试链表划分
SNode* pHead6 = createNodeList(20, 100);
printLinkList(pHead6);
linkListPartition(pHead6, 50);
printLinkList(pHead6);
return 0;
}
执行结果,执行结果如下所示
57->94->91->28->45->85->36->93->72->67->21->79->49->79->33->10->67->12->26->97
28->45->36->21->49->33->10->12->26->57->94->91->85->93->72->67->79->79->67->97
单链表公共节点问题
问题
给定两个单向链表,计算两个链表的第一个公共节点,若没有公共节点,则返回空。
分析
令两链表长度为m
、n
,不妨认为m>=n
,由于两个链表从第一个公共节点到链表的尾节点是完全重合的,所以前面的m-n
个节点一定没有公共节点。
先分别遍历两个链表得到他们的长度m
和n
,长链表空转|m-n|
次,同步遍历两链表,直到找到相同节点或到链表结束。
时间复杂度为O(m+n)
。
代码实现
代码实现如LinkListFindFirstCommonNode.hpp所示:
#ifndef LinkListFindFirstCommonNode_hpp
#define LinkListFindFirstCommonNode_hpp
#include <algorithm>
#include <stdio.h>
#include "LinkListCommon.hpp"
int calculateLength(SNode* pHead) {
int length = 0;
while (pHead) {
length++;
pHead = pHead->pNext;
}
return length;
}
SNode* findFirstCommonNode(SNode* pHead1, SNode* pHead2) {
pHead1 = pHead1->pNext;
pHead2 = pHead2->pNext;
int length1 = calculateLength(pHead1);
int length2 = calculateLength(pHead2);
if (length1 > length2) {
std::swap(pHead1, pHead2);
std::swap(length1, length2);
}
// 长的链表空转length2-length1次
for (int i = 0; i < length2 - length1; i++) {
pHead2 = pHead2->pNext;
}
while (pHead1) {
if (pHead1 == pHead2) {
return pHead1;
}
pHead1 = pHead1->pNext;
pHead2 = pHead2->pNext;
}
return NULL;
}
#endif /* LinkListFindFirstCommonNode_hpp */
扩展
单链表公共节点问题中,如果链表存在环,则需要使用快慢指针的方式计算公共节点(两个指针,每次分别移动一个/两个节点)。