【链表OJ】常见面试题

学习完链表后,当然还需要实操才行。

1.移除链表元素

移除链表元素

1.1 题目要求

写编程题首先需要了解题目到底问的是什么,要认真的阅读题目,找出题目的要求。不然可能会因为题目看错了,导致后面的代码全部写错就得不偿失了。
移除链表元素这题比较的简单,题目要求一目了然:将链表中值为val的节点全部移除。

1.2 迭代法

采用循环的方式解决问题,解决的方式其实是类似于删除链表pos位置的节点的。为了删除pos位置的节点我们是要知道pos节点前后的节点。这里也一样,遍历链表找到符合要求的节点就将该节点前的节点指向该节点的后一个节点。
迭代法

但是有一种特殊情况就是符合条件的节点是第一个节点,这样的话,prev就无法指向cur的前一个节点。为了解决这一情况,可以先预处理这种情况,当然也可以在循环里加入特别判断。

//预处理目标节点为链表第一个节点的情况
struct ListNode* removeElements(struct ListNode* head, int val) {
    while(head!=NULL&&head->val == val)
        head = head->next;
    struct ListNode* cur = head;
    struct ListNode* prev = cur;
    while(cur)
    {
        struct ListNode* next = cur->next;
        if(cur->val == val)
        {
            prev->next = next;
            free(cur);
            cur = NULL;
        }
        else
            prev = cur;
        cur = next;
    }
    return head;
}

//在循环中加入特别判断
struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    while(cur)
    {
        if(cur->val == val)
        {
            struct ListNode* next = cur->next;
            free(cur);
            if(prev)
                prev->next = next;
            else
                head = next;
            cur = next;
        }
        else
        {
            prev = cur;
            cur = cur->next;
        }
    }
    return head;
}

其实还可以加入一个虚拟的头节点,也可以很好的解决问题。读者可以尝试一下。

1.3 递归法

链表的性质就决定了它是具有递归的性质的,可以看作特殊的二叉树来看。因此链表的题目也常常可以用递归来解决问题。
即使是递归,我们在删除目标节点时也是需要找到目标节点的前一个节点和后一个节点的。后一个节点好办,就是cur->next。可是前一个节点怎么找呢?
答案就在上一个函数里,因为函数会一层层的递归下去,我让上一层的函数中的节点指向下一个函数不就可以吗,这样就找到目标节点前一个节点,然后就是如果当前节点是目标节点就返回目标节点的下一给节点,不是目标节点就返回当前节点。
最后确定一下递归的停止条件,当节点为NULL时肯定就不能在递归下去了,返回当前节点(NULL)就可以了。

struct ListNode* removeElements(struct ListNode* head, int val) {
    if(head == NULL)
        return head;
    head->next = removeElements(head->next,val);
    return head->val == val ? head->next : head;
}

2. 反转链表

反转链表

2.1 题目要求

将链表反转,然后返回头节点。

2.2 迭代法

为了将链表反转,使得1->2->3->4->NULL变为4->3->2->1->NULL
在遍历链表时就要将当前节点的next指向其前一个节点,所以我们肯定需要一个指针来存储前一个节点的地址。prev在初始化时指向NULL。在迭代时,我们只需要将当前节点cur的next指向prev,然后再更新cur和prev就可以了。
最后返回prev,次数prev就是头节点。

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    while (cur)
    {
        struct ListNode* next = cur->next;
        cur->next = prev;
        prev = cur;
        cur = next;
    }
    return prev;
}

2.3 递归法

递归法比较难,看不懂可自行跳过。
因为要反转链表,我们要先通过递归找到链表的最后一个节点,也就是新链表的第一个节点。
当我们找到了,就要开始返回
递归法

代码的执行逻辑就和我画的这张图一样

struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }
    struct ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = NULL;
    return newHead;
}

3. 链表的中间结点

链表的中间节点

3.1 题目要求

找到链表的中间节点,如果有两个中间结点,则返回第二个中间结点

3.2 快慢指针

定义两个链表节点的指针变量,初始化时都指向链表的第一个节点。
不同的时,一个指针一次走两个节点的距离,一个走一个节点的距离。如此一来当快指针指向最后一个节点时,慢指针不就走到了中间吗。
不过还是要注意是快指针是不能走到最后一个节点的,如果快指针走到了最后一个节点,fast->next是NULL,没有next了,所以我们的判断条件还要加上fast->next不能为NULL

struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    while(fast&&fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

4. 合并两个有序链表

合并两个有序链表

4.1 题目要求

让两条升序的链表合成一个非降序的链表

4.2 迭代法

我们可以设置一个哨兵位,这样可以使题目简单些。
让两条升序的链表合成一个非降序的链表,我们设置两个指针,分别指向不同的链表,进入循环开始比较,如果cur1的值小于cur2的值,那么我们就把cur1节点接到新的链表当中,为了每次接到新的链表的最后,我们还需要设置一个tial指针指向新链表的末尾,以后的每次操作我们都让比较中更小的接到新链表后,在让值小的那个节点往后移动。
但是最后还是会存在一条链表没有遍历完,最后判断一下那条没有完,直接让那条链表接到新链表的后面。
最后的最后返回是不能返回哨兵位的,我们返回哨兵位的下一个节点。
迭代法

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    struct ListNode* cur1 = list1;
    struct ListNode* cur2 = list2;
    struct ListNode* phead = (struct ListNode*)malloc(sizeof(struct ListNode));
    phead->val = -1;
    phead->next = NULL;
    struct ListNode* tail = phead;
    while(cur1&&cur2)
    {
        if(cur1->val>cur2->val)
        {
            tail->next = cur2;
            cur2 = cur2->next;
        }
        else
        {
            tail->next = cur1;
            cur1 = cur1->next;
        }
        tail = tail->next;
    }
    tail->next = (cur1==NULL)?cur2:cur1;
    return phead->next;
}

4.3 递归法

主要的逻辑是和迭代一样的,但是递归会更难理解一些。
如果list1或者list2一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断list1和 list2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1==NULL)
        return list2;
    else if(list2 == NULL)
        return list1;
    else if(list1->val<=list2->val)
    {
        list1->next = mergeTwoLists(list1->next,list2);
        return list1;
    }
    else
    {
        list2->next = mergeTwoLists(list1,list2->next);
        return list2;
    }
}
  • 0
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yui_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值