Leetcode链表类算法题目分析总结与C++ Python实现
链表是常见的数据结构,一般由关键字和成员指针构成,为动态集合提供了一种简单而灵活的表示方法,其中单链表,由于结构简单而问题变化多样,在程序员技术笔、面试中最为常见,可以作为试金石快速考察被面试者的coding能力。笔者整理了leetcode和面试中常见的链表算法题,分类分析思路,力求举一反三与融会贯通,同时给出C++、Python的实现,方便大家夯实算法基础与准备算法刷题。
0. 链表的定义
- 链表与数组比较,都是线性存储结构,区别是数组的线性顺序由下标决定,链表的顺序是由各个对象里的指针决定。
- 双向链表的每个元素都是一个对象(C++ Primer中对对象的解释为具有类型的内存区域),每个对象有一个关键字和两个指针,还可以包含其他的数据。
- 链表分为单链接(singly linked)的和双链接(double linked)的,已排序或未排序。
- 链表元素的实现如下,
C++代码
//C++版链表结点定义
struct Node {
Node(int data, Node* next=nullptr) {
v=data;
n=next;
}
int v;
Node* n;
}
python代码
#python
#python3类定义可以省略显式继承object
class Node():
def __init__(self, data, next=None):
self.val=data
#python无指针,变量均为引用形式,引用可以近似为指针,用引用代替指针
self.next=next
1. 单链表的基本操作:建、查、插、改、删
1.1 链表的创建有头插法和尾插法两种
/* params
pHead:链表头指针的指针,创建的链表作为参数返回,若使用指针则必须使用引用形式;也可以将头指针做函数返回值
n: 链表长度
pdata: 若非空,则将数组元素顺序作为关键字
*/
void CreateLinklist(Node **pHead, int n, int *pdata = nullptr) {
if (n < 1) {
return;
}
if (pdata) {
*pHead = new Node(pdata[0]);
} else {
*pHead = new Node(0);
}
Node *p = *pHead;
for (int i = 1; i < n; i++) {
Node *tmp;
if (pdata) {
tmp = new Node(pdata[i]);
} else {
tmp = new Node(i);
}
p->n = tmp;
p = tmp;
}
}
//头插法
Node *CreateLinklist(int data[], int n) {
if (!data) {
return nullptr;
}
Node *rear = new Node(data[n - 1]);
Node *head;
//头插法
for (int i = n - 2; i >= 0; i--) {
head = new Node(data[i]);
head->n = rear;
rear = head;
}
return head;
}
1.2 链表的查找
Node *ListSearch(Node* head, int key) {
Node *p=head;
while(p){
if(p->v==key){
return p;
}
p=p->n;
}
return p;
}
1.3 链表的插入、删除
void InsertNode(Node **pHead, int Index, int Value) {
if (Index < 0) {
return;
}
Node *tmp = new Node(Value);
if (Index == 0) {
tmp->n = *pHead;
*pHead = tmp;
return;
}
int cnt = Index;
Node *p = *pHead;
while (cnt > 1 && p) {
p = p->n;
cnt--;
}
tmp->n = p->n;
p->n = tmp;
}
//删除链表位置Index所在的节点
void DeleteNode(Node **pHead, int Index) {
if (Index == 1) {
Node *p = *pHead;
*pHead = (*pHead)->n;
delete p;
return;
}
if (Index > 1) {
int cnt = Index;
Node *p = *pHead;
while (cnt > 2 && p) {
p = p->n;
cnt--;
}
Node *cur = p->n;
p->n = cur->n;
delete cur;
}
}
//删除整个链表
void DeleteLinkedList(Node **pHead) {
if (*pHead == nullptr) {
return;
}
Node *p = *pHead;
Node *tmp = *pHead;
while (p) {
tmp = p;
p = p->n;
delete tmp;
}
}
2. 常见单链表算法题
建议选择一个简单数据,按照代码逐行分析变量,画出链表图分析,熟能生巧。
2.1 题目:反转一个单链表
可以采用递归思路实现,将当前结点以后的链表完成反转,再把当前结点的下一个结点的next指向当前结点,当前结点的next指针置空。
Node *ReverseLinkedList(Node *head) {
if (!head || !head->n) {
return head;
}
Node *newhead = ReverseLinkedList(head->n);
head->n->n = head;
head->n = nullptr;
return newhead;
}
考虑非递归方式如何实现,可以想象在平地上移动一艘船该如何做呢,取用多个圆木,依次垫底,从后往前放置,为了能够修改整个链表的顺序需要三个指针来完成这个目的。
//对参数传入的链表进行逆转
void ReverseLinkedList(Node **pHead) {
if (*pHead == nullptr || (*pHead)->n == nullptr) {
return;
}
Node *pre = nullptr;
Node *cur = *pHead;
Node *n = (*pHead)->n;
while (cur) {
n = cur->n;
cur->n = pre;
pre = cur;
cur = n;
}
*pHead = pre;
}
用三个指针当作传递的圆木流动,也是解决链表的插入排序问题一个方法。
2.2 题目:链表上实现插入排序(Leetcode )
插入排序的基本思路是,每次从未排序的右侧元素去一个,插入到已经排好序的左侧的适当位置,相比对数组做插入排序,更加直观;需要考虑待插入元素是最小的,即要把这个结点放到头,所以需要构造一个关键字为无穷小的头指针dum
,每次循环前pre
置为头指针,cur
为排好序的部分的尾结点,lat
为待插入元素,循环中pre
移动到待插入结点的左侧,并用tmp
标出插入位置,插入结点,并保持链表完整。
void InsertSort(Node *&pHead) {
if (!pHead || !pHead->n) {
return;
}
Node *dum = new Node(INT_MIN);
dum->n = pHead;
Node *pre = dum;
Node *cur = pHead;
Node *lat = pHead->n;
while (cur) {
lat = cur->n;
if (lat && cur->v > lat->v) {
while (pre->n && pre->n->v < lat->v) {
pre = pre->n;
}
Node *tmp = pre->n;
pre->n = lat;
cur->n = lat->n;
lat->n = tmp;
} else {
cur = lat;
}
pre = dum;
}
pHead = dum->n;
}
2.3 两个链表做十进制加法
Node *AddTwoNumbers(Node *l1, Node *l2) {
Node *dummy = new Node(0), *p = dummy;
int carry = 0;
while (l1 || l2 || carry) {
if (l1) {
carry += l1->v;
l1 = l1->n;
}
if (l2) {
carry += l2->v;
l2 = l2->n;
}
p->n = new Node(carry % 10);
carry /= 10;
p = p->n;
}
return dummy->n;
}