链表经典算法题(2)

一、合并两个有序链表

(Leetcode21)

1.1 题目
1.2 解题思路

创建新的空链表,用两个指针变量l1、l2分别遍历原链表,将节点值小的节点拿到新链表中进行尾插操作。 一个指针变量走到空之后,把另一个链表剩下的元素也全都尾插到新链表中。在开始遍历之前要判空。

需要注意的是,如果给出的两个链表中有空链表,那么无法进入遍历的循环。因此在遍历之前要判空,如果一个链表为空,就返回另一个链表。

1.3 代码实现
cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    //判空
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }
    ListNode* l1 = list1;
    ListNode* l2 = list2;

    ListNode* newHead, *newTail;
    newHead = newTail = NULL;

    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            //把l1拿下来尾插
            if(newHead == NULL)
            {
                newHead = newTail = l1;
            }
            else
            {
                newTail->next = l1;
                newTail = newTail->next;
            }
            l1 = l1->next;
        }
        else
        {
            //把l2拿下来尾插
             if(newHead == NULL)
             {
                newHead = newTail = l2;
             }
             else
             {
                newTail->next = l2;
                newTail = newTail->next;
             }
             l2 = l2->next;
        }
    }
    //跳出循环,要么是l1走到空了,要么是l2走到空了
    if(l2)
    {
        newTail->next = l2;
    } 
    if(l1)
    {
        newTail->next = l1;
    }
    return newHead;
}

我们发现判断新链表的头结点是否为空的代码写了两遍,有重复。我们可以通过以下方法进行优化:

我们在创建新链表的头节点的时候可以不让它们为空,而是向系统申请一块空间。这样我们不需要判断新链表是否为空,拿过来直接尾插即可。

不过这样的话,最后返回的就不是newHead,而 是newHead的下一个节点。

记得代码写完后要手动释放申请的空间。不过,释放空间后就找不到newHead的下一个节点,因此要先创建一个变量把newHead的下一个节点记录下来。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
 typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    //判空
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;
    }
    ListNode* l1 = list1;
    ListNode* l2 = list2;

    ListNode* newHead, *newTail;
    newHead = newTail = (ListNode*)malloc(sizeof(ListNode));

    while(l1 && l2)
    {
        if(l1->val < l2->val)
        {
            //把l1拿下来尾插
            newTail->next = l1;
            newTail = newTail->next;
            l1 = l1->next;
        }
        else
        {
            //把l2拿下来尾插
            newTail->next = l2;
            newTail = newTail->next;
            l2 = l2->next;
        }
    }
    //跳出循环,要么是l1走到空了,要么是l2走到空了
    if(l2)
    {
        newTail->next = l2;
    } 
    if(l1)
    {
        newTail->next = l1;
    }
    ListNode* ret = newHead->next;
    free(newHead);
    return ret;
}

这个优化代码中新加的一个没有用的节点,是带头链表中的"哨兵位"或叫"头节点"(单链表中的第一个节点称为"头节点",其实是不标准的)。

二、 环形链表的约瑟夫问题

1.1 题目
1.2 解题思路

在一个环形链表中,定义两个指针变量顺着链表走,每走到m就销毁掉这个节点。到最后,链表会只剩下一个节点,指向它自己。

销毁节点的时候,要先让被销毁节点的前一个节点的next指针指向被销毁节点的下一个节点,否则就找不到被销毁节点的下一个节点了。

1.3 代码实现

假设节点的结构体已经定义好了。

cpp 复制代码
typedef struct ListNode ListNode;
 //创建节点
 ListNode* buyNode(int x)
 {
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    if (node == NULL)
    {
        exit(1);
    }
    node->val = x;
    node->next = NULL;
    return node;
 }
 //创建带环链表
ListNode* createCircle(int n)
{
    ListNode* phead = buyNode(1);
    ListNode* ptail = phead;
    for(int i = 2; i<=n; i++)
    {
        ptail->next = buyNode(i);
        ptail = ptail->next;
    }
    //首尾相连,链表成环
    ptail->next = phead;
    return ptail;
}

int ysf(int n, int m )
{
    ListNode* prev = createCircle(n);
    ListNode* pcur = prev->next;
    int count = 1;
    while(pcur->next != pcur)
    {
        if(count == m)
        {
            //销毁pcur节点
            prev->next = pcur->next;
            free(pcur);
            pcur = prev->next;
            count = 1;
        }
        else
        {
            prev = pcur;
            pcur = pcur->next;
            count++;
        }
    }
    //此时剩下的一个节点就是要返回的值所在的节点
    int ret = pcur->val;
    free(pcur);
    pcur = NULL;
    return ret;
}

三、分割链表

(Leetcode面试题02.04)

3.1 题目
3.2 解题思路

思路一:在原链表上修改。

用一个指针变量pcur遍历原链表,遇到大于等于x的节点就尾插到原链表后面并删除旧节点。

思路二:创建一个新链表。

用指针变量pcur遍历原链表,如果pcur节点的值小于x,头插在新链表中;如果pcur的值大于或等于x,尾插在新链表中。

思路三:创建两个新链表。

创建一个大链表和一个小链表。遍历原链表,小于x的节点尾插到小链表里,大于等于x的节点尾插到大链表里。最后把小链表的尾结点和大链表的第一个节点首尾相连。这样就不用写头插的代码。

3.3 代码实现

由于思路三最简单,所以按照思路三实现。

创建新链表的过程中,可以用带头链表(有哨兵位的链表),来避免写判断新链表是否为空的代码。

要注意,给出的原链表为空的情况需要单独处理。

此外,我们还要考虑大链表中没有元素的问题。此时greaterTail的next指针指向的是一个随机地址,不能把这个值赋给其他变量。因此我们需要对这个变量初始化一下,将其置为空。如果不加这个语句,还会出现另一个问题,因为最后没有规定大链表尾结点的next指针指向哪里,它仍会指向原链表中排在它后面的那个节点,造成死循环。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x)
{
    if (head == NULL)
    {
        return head;
    }
    //创建两个带头链表
    ListNode* lessHead, *lessTail;
    ListNode* greaterHead, *greaterTail;
    lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
    greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
    if(lessHead ==NULL || lessTail == NULL || greaterHead == NULL || greaterTail == NULL)
    {
        perror("malloc");
    }

    //遍历原链表,将原链表中的节点尾插到大小链表中
    ListNode* pcur = head;
    while(pcur)
    {
        if(pcur->val < x)
        {
            //尾插到小链表中
            lessTail->next = pcur;
            lessTail = lessTail->next;
        }
        else
        {
            //尾插到大链表中
            greaterTail->next = pcur;
            greaterTail = greaterTail->next;
        }
        pcur = pcur->next;
    }
    //修改大链表的尾结点的next指针指向+greatertail的next指针初始化
    greaterTail->next = NULL;

    //小链表的尾结点和大链表的第一个有效节点首尾相连
    lessTail->next = greaterHead->next;

    ListNode* ret = lessHead->next;
    free(lessHead);
    free(greaterHead);
    lessHead = greaterHead = NULL;
    return ret;
}
相关推荐
y = xⁿ2 小时前
MySQL为什么抛弃了B树,选择B+树?(含面试回答)
数据结构·b树·面试
自我意识的多元宇宙2 小时前
二叉树遍历方式代码解读(1递归)
java·数据结构·算法
_日拱一卒2 小时前
LeetCode:142环形链表Ⅱ
算法·leetcode·链表
黎雁·泠崖2 小时前
二叉树遍历:LeetCode 144 / 94 / 145 之递归 + 分治 + 非递归
java·数据结构·算法·leetcode
凌波粒2 小时前
LeetCode--347.前 K 个高频元素(栈和队列)
java·数据结构·算法·leetcode
会编程的土豆2 小时前
字符串知识(LCS,LIS)区分总结归纳
开发语言·数据结构·c++·算法
北顾笙9803 小时前
day25-数据结构力扣
数据结构·算法·leetcode
lxh01133 小时前
最接近的三数之和
java·数据结构·算法
黎雁·泠崖3 小时前
二叉树基础精讲(上):树形结构 · 二叉树概念 · 性质 · 遍历 · 基础操作全解析
java·数据结构·算法