链表的实现
指针实现
类型定义及结点构造
struct node {
struct node *next;
int d;
};
struct node *newnode(int d) {
struct node *p = (struct node *)malloc(sizeof(struct node));
p->d = d;
p->next = NULL;
return p;
}
struct node *head = newnode(1);
插入元素
// p is the location before insertion
struct node *x = newnode(data);
x->next = p->next;
p->next = x;
删除元素
// p is the location before deletion
p->next = p->next->next;
删除表头
head = head->next;
数组实现版(了解):
struct node {
int d, next;
} pool[1000];
int tot = 0;
int newnode(int d) {
pool[tot].d = d;
pool[tot].next = -1;
return tot++;
}
int head = newnode(1);
插入元素
// p is the location before insertion
int x = newnode(data);
pool[x].next = pool[p].next;
pool[p].next = x;
删除元素
p is the location before deletion
pool[p].next = pool[pool[p].next].next;
删除表头
head = pool[head].next;
统计链表结点数
int get_length(struct ListNode *head) {
int count = 0;
while (head != NULL) {
count++;
head = head->next;
}
return count;
}
找出倒数第 K 个结点
对于给定的链表,找出倒数第 kk 个结点的元素。
思路 1:先统计出链表中的结点个数 n,之后从前往后找到第 n-k+1 个结点。时间复杂度为 O(n)。
示例代码如下:
struct ListNode* get_kth(struct ListNode *head, int k) {
if (k <= 0 || k > n) {
return NULL;
}
int n = get_length(head);
int count = 0;
for (int i = 0; i < n - k; ++i) {
head = head->next;
}
return head;
}
思路 2:先让一个指针nextk
指向链表第 k 个元素,然后让head
和nextk
同时向后移动,直到nextk
为空。时间复杂度为 O(n)。
示例代码如下:
struct ListNode* get_kth(struct ListNode *head, int k) {
if (k <= 0 || k > n) {
return NULL;
}
struct ListNode *nextk = head;
for (int i = 0; i < k - 1; ++i) {
nextk = nextk->next;
}
while (nextk != NULL) {
head = head->next;
nextk = nextk->next;
}
return head;
}
链表翻转
将给定的链表进行翻转。例如,一个链表中的元素分别为 1,2,3,4,51,2,3,4,5,则翻转后的结果为 5,4,3,2,15,4,3,2,1。
思路 1:新建一个链表,每次把原链表中的第一个元素放到新链表的头部,并把这个元素从原链表中删掉。空间复杂度为O(n),时间复杂度为O(n)。
示例代码如下:
struct ListNode* reverse(struct ListNode *head, int k) {
struct ListNode *new_list = NULL;//新建链表,头插法
while (head != NULL) {
struct ListNode *node = (struct ListNode*)malloc(sizeof(struct ListNode));
node->val = head->val;
new_list = insert(new_list, 0, node);
head = head->next;
}
return new_list;
}
链表环的处理
判断链表是否有环
给定一个链表,判断其中是否有环。
思路 1:顺序遍历链表中的每个元素,判断链表中后面是否会再次出现该元素。时间复杂度为 O(n2)。
示例代码如下:
int has_ring(ListNode *head) {
while (head != NULL) {
ListNode *now = head->next;
while (now != NULL && now != head) {
now = now->next;
}
if (now == head) {
return 1;
}
head = head->next;
}
return 0;
}
环和柄的长度
对于包含环的链表来说,有两个长度常常会需要计算:环的长度和柄的长度。环的长度是指环上的结点个数,而柄的长度指的是从链表的第一个结点开始,直到第一个进入环的结点为止的结点个数。
例如上面这个链表,环的长度为 3,柄的长度为 2。
双链表问题
一共有两类:一类是将在两个数组上的算法迁移到链表中的题目;一类是两个链表成环、交叉等基于链式结构的题目。
对于第一类题目,考察的往往是二分、分治等算法,结合单链表基本的插入、删除、遍历操作,对链表考察的部分难度不大,这里也就不再进行额外的讲解了,大家如果做这类题目遇到困难,应该先去完成“数组版”的题目,再将它改写成“链表版”的。
第二类题目最典型的例子就是一系列关于两个链表交叉的题目,接下来,会为你介绍其中几个较为经典的题目及其思路和解法。
判断无环链表是否交叉
两个链表交叉,意味着两个链表有公共点,这说明着什么呢?
我们看到,上图中的两个链表在 2处相遇后,就完全重合了,这是由于链表的链式结构的性质决定的。因此,我们可以从两个链表的表头分别遍历到表尾,比较一下两个表尾是否相等,如果相等则说明两个链表交叉。
给出算法的 C 示例代码如下:
int intersect(struct ListNode *head1, struct ListNode *head2) {
while (head1 != NULL) {
head1 = head1->next;
}
while (head2 != NULL) {
head2 = head2->next;
}
return head1 == head2;
}
如果已知两个链表交叉,要找出其中的交叉点,可以先算出两个链表head1
、head2
的长度,不妨设为 l1,l2。如果 d=l1−l2≥0,则让head1
先向后移动 d 次,然后让两个指针同时向后移动,直到两个指针相等;如果 d=l2-l1>0,则让head2
先向后移动 d 次,然后让两个指针同时向后移动,直到两个指针相等。两个指针第一次相等时,就是两个链表的交叉点。