【数据结构入门】单链表和数组的OJ题(1)

发布于:2025-08-06 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

1.移除链表元素

分析:

代码: 

2.链表的中间结点

分析:

 代码:

3.链表中倒数第k个结点

分析:

代码:

4. 合并两个有序链表

分析: 

代码:

5.分割链表

分析:

代码:


1.移除链表元素

在原链表中直接修改

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

分析:

        ①这里需要两个指针,prev、curr,分别初始化为NULL、head;如果要删除curr所指向的元素,直接使用prev跳过curr即可。

        ②循环条件是curr不为空,当curr找到val的时候,此时分两种情况

                (1)curr就在首元素:此时将head重新赋值为curr的下一个元素,然后释放掉curr,更新curr为head;

                (2)curr指向链表非首元素位置,prev此时就不是空,此时需要prev指向curr的下一个元素,释放curr,然后将curr更新为prev;

        ③当curr没找到val的时候,此时直接先更新prev为curr,在更新curr为curr的next。

代码: 

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    if(head == NULL)
    {
        return head;
    }
    while (curr) {
        if (curr->val == val) {
            // 如果第一个就是要删除的
            if(curr == head)
            {
                head = curr->next;
                free(curr);
                curr = head;
            }else
            {   // 正常情况
                prev->next = curr->next;
                free(curr);
                curr = prev->next;

            }
            
        }else{
             prev = curr; // 始终在curr之前
            curr = curr->next;
        }
       
    }

    return head;
}

2.链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。

示例 2:

输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。

分析:

        使用最简单的方式是:先遍历一边拿到总结点个数,根据总节点个数算出中间节点,最后遍历一遍得到中间节点。

        有没有更好的方法呢? 能不能只遍历一次就找到中间节点呢?可以使用快慢指针,快指针一次走两步,慢指针一次走一步,快指针走到尾的时候,慢指针刚好在中间。

 

细节:如果是奇数,那么快指针走到最后一个节点结束 ,如果是偶数,快指针走到空结束

停止条件:快指针走到最后一个或者快指针走到空这个循环就结束;

继续条件:快指针走到最后一个并且快指针走到空这个循环就结束。

即:

while(fast && fast->next)

 代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

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;
}

3.链表中倒数第k个结点

牛客网传送门

输入一个链表,输出该链表中倒数第k个结点。

示例1

输入

1,{1,2,3,4,5}

输出

{5}

分析:

        这里能不能使用双指针,只遍历一次就能完成任务呢?首先让快指针先走k次,然后和慢指针同步走,直到快指针走到空,此时慢指针的位置就是倒数第k个的位置。

        原理其实就是让快指针和慢指针保持k个距离,当快指针为空的时候,慢指针就是倒数第k个。

        需要注意的事牛客网的测试用例有可能k>链表长度,这就会让链表在走k个元素的时候发生空指针解引用的问题,所以每次k往前走的时候都需要判断k不为空,如果在向前走k步的过程中发现快指针为空了,那么说明此时链表的长度小于k,直接返回空即可。

代码:

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */

/**
 *
 * @param pListHead ListNode类
 * @param k int整型
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    struct ListNode* fast = pListHead;
    struct ListNode* slow = pListHead;
    // 先让fast走k步
    while (k--) {
        if (fast != NULL) {
            fast = fast->next;
        } else {
            return NULL;
        }
    }
    while (fast) {

        // 再一起走
        fast = fast->next;
        slow = slow->next;
    }
    // fast为空的时候slow指向倒数第k个
    return slow;
}



4. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

示例 2:

输入:l1 = [], l2 = []
输出:[]

示例 3:

输入:l1 = [], l2 = [0]
输出:[0]

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列
  • 分析: 

  •         首先使用一个全新的链表来作为返回链表,这里定义一个链表头和链表指针,根据l1指针和l2指针所指向的值的大小来判断,新链表中是否要增加新节点。

    ①判断l1是否为空,为空,返回l2,反之亦如此;

    ②给新链表的头结点赋初值,这里将l1或者l2指针中较小的元素赋给新链表的头节点、遍历指针,赋值的指针需要向后移动,否则后面的循环就是死循环(自己指向自己);

    ③循环的条件要保证两个链表均不为空,若有一个链表为空,那么直接将另外一个链表接到新链表后面。

  • ④循环体内判断l1还是l2所指向的节点更小,将更小的节点赋给新链表指针指向的位置,此时需要更新新链表指针和更小的节点所在的链表的指针;

  • ⑤循环结束之后,判断是哪一个链表为空,将另一个链表接到新链表后面即可

  • ⑥返回新链表的头结点。

  • 代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    struct ListNode* curr1 = list1;
    struct ListNode* curr2 = list2;
    struct ListNode* head = NULL;
    struct ListNode* newcurr = head;
    if (curr1 == NULL) {
        return curr2;
    } else if(curr2 == NULL) {
        return curr1;
    }

    // 初始化头
    if (curr1->val < curr2->val) {
        head = curr1;
        newcurr = head;
        curr1 = curr1->next;
    } else {
        head = curr2;
        newcurr = head;
        curr2 = curr2->next;
    }
    // 停止条件:两个指针有一个为空
    while (curr1 && curr2) {
        if (curr1->val < curr2->val) {
            newcurr->next = curr1;
            curr1 = curr1->next;
        } else {
            newcurr->next = curr2;
            curr2 = curr2->next;
        }
        newcurr = newcurr->next;
    }
    if (curr1 == NULL) {
        newcurr->next = curr2;
    } else {
        newcurr->next = curr1;
    }
    return head;
}

5.分割链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你不需要 保留 每个分区中各节点的初始相对位置。

示例 1:

输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]

示例 2:

输入:head = [2,1], x = 2
输出:[1,2]

提示:

  • 链表中节点的数目在范围 [0, 200] 内
  • -100 <= Node.val <= 100
  • -200 <= x <= 200

分析:

        可以使用两个带头结点的链表来分别保存大于x的节点和小于x的节点,最后合并这两个节点即可。

①首先创建带头节点的两个链表,这里的头结点不存实际的值,只是为了逻辑上更简单不需要判断链表是否为空。

②开始遍历原链表,当找到某节点的val比x更小,需要将其放入lessList中去,同时原链表和lessList链表都需要向后移动指针,greaterList同理。

③由于这是带头结点的链表,这里需要将lp(指向lessList的最后一个指针)指向greaterList的下一个节点(这里跳过头指针),然后释放greaterList(头指针),同理先保存lessList的头结点的next,然后释放头结点,最后得到的是一整串链表;

需要注意的是:由于每次gp指向的都是原链表的内容,如果最后一次指向的内容中next是新链表中的内容,那么就会导致循环链表的产生,所以当循环结束之后需要将gp的next置为空防止出现死循环

代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x) {
    // 定义两个带头结点的链表,分别保存比x大/小的节点
    ListNode* greaterList = (ListNode*)malloc(sizeof(ListNode));
    ListNode* lessList = (ListNode*)malloc(sizeof(ListNode));
    ListNode* gp = greaterList;
    ListNode* lp = lessList;
    // 遍历原链表
    while (head) {
        if (head->val < x) {
            lp->next = head;
            lp = lp->next;
            head = head->next;
        } else {
            gp->next = head;
            gp = gp->next;
            head = head->next;
        }
    }
    // 关键修正:确保greaterList的尾部正确终止(避免循环引用)
    gp->next = NULL;
    // 合并链表
    lp->next = greaterList->next;
    free(greaterList);
    head = lessList->next;
    free(lessList);
    return head;
}

 


网站公告

今日签到

点亮在社区的每一天
去签到