Day4C链表的应用

链表的实现

指针实现

类型定义及结点构造
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 个元素,然后让headnextk同时向后移动,直到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;
}

 如果已知两个链表交叉,要找出其中的交叉点,可以先算出两个链表head1head2的长度,不妨设为 l1,l2。如果 d=l1−l2≥0,则让head1先向后移动 d 次,然后让两个指针同时向后移动,直到两个指针相等;如果 d=l2-l1>0,则让head2先向后移动 d 次,然后让两个指针同时向后移动,直到两个指针相等。两个指针第一次相等时,就是两个链表的交叉点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值