240428 leetcode exercises

240428 leetcode exercises

@jarringslee

25. K 个一组翻转链表

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

img

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

示例 2:

img

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

提示:

  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

夫天地之万象,变化云起,靡有定形。然万变之途,终莫能遁其宗也。凡欲破幽冥之障,窥造化之秘,必先溯其源流,究其本原。昔庖丁解牛,目无全牛之形;扁鹊疗疾,先察腠理之病。盖万物虽纷纭,其理则一也。故曰:执本可御末,循源而得流。是以智者观象,必穷其极;达士虑事,必探其根。惟明其宗,方能应变不穷;惟究其源,始能驭机无形。

​ 我们会合并两个升序链表,于是便会了合并k个升序链表。我们又会反转链表,那为什么不能会k一个组反转链表?

🔁 探宗求源 其义自见

​ 无需多言。我们无论怎么组合,我们都只需要攥紧翻转链表这张王牌,方可抵御千招万式。

  • 翻转链表

​ 这是整个代码的核心。k组一个就是在考你如何去调用。下面是两种最常见的方法。

递归?

struct ListNode* reverseList(struct ListNode* head) {
    if ( head == NULL || head -> next == NULL) return head;
    struct ListNode* new = reverseList(head -> next); //让新头指向下一个节点
    head -> next -> next = head; //旧头指向下下一个节点
    head -> next = NULL; 
    return new;
}

迭代?

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    while (slow) {//双指针迭代
        fast = slow -> next;
        slow -> next = prev;
        prev = slow;
        slow = fast;
    }
    return prev;
}
  • 翻转前k个链表

我们在此基础上插入一个整数k,使得只翻转传入链表的前k个结点。这里只给出迭代法的修改办法:

  1. 设置计数器,放置到while循环的条件中,让迭代只进行k次;
  2. 返回头结点之前别忘的让翻转后的尾结点指向剩余节点。
struct ListNode* reverseK(struct ListNode* head, int k) {
    if (!head || k <= 0) return head; //排除边界情况
    struct ListNode* prev = NULL;
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    int cnt = 0;
    while (slow && cnt < k){  //只循环k次
        fast = slow -> next;
        slow -> next = prev;
        prev = slow;
        slow = fast;
        cnt++;
    }
    head -> next = slow; //尾结点指向链表剩余部分,连接好链表
    return prev;
}
  • 遍历式调用

    主函数中,我们先利用for循环快速求得节点数并于k进行比较。如果节点数大于k,我们进入翻转环节:

    • num / k求得我们一共要翻转几组。

    • 准备好前置工作:

      1. 设置虚拟头结点保留链表表头位置,方便最后返回;
      2. 取虚拟头结点和头结点的指针分别保存已处理链表的尾部节点待翻转链表组的起始节点
    • 循环遍历反转k个链表:

      for循环每次迭代都针对链表的下一段长度为 k 的节点进行翻转:首先用 tail = cur 记录本组翻转之前的起始节点(翻转后就成了这一组的尾节点),然后调用 reverseK(cur, k) 将从 cur开始的 k 个节点反转,并把返回的新头指针赋给newhead;接着通过 prev->next = newhead 把前一段(或哑节点)接到这一组的头部,再把prev移动到当前组的尾部(即刚才的 tail),最后令 cur = tail->next 跳到下一组待翻转的起点,如此重n次就能依次完成所有成组的反转并正确拼接。并且不会动剩下的节点。

    最后我们使用实现保存好的虚拟头结点来返回链表的头结点。

    struct ListNode* reverseK(struct ListNode* head, int k) {
        if (!head || k <= 0) return head;
        struct ListNode* prev = NULL;
        struct ListNode* fast = head;
        struct ListNode* slow = head;
        int cnt = 0;
        while (slow && cnt < k){
            fast = slow -> next;
            slow -> next = prev;
            prev = slow;
            slow = fast;
            cnt++;
        }
        head -> next = slow;
        return prev;
    }
    struct ListNode* reverseKGroup(struct ListNode* head, int k) {
        int num = 0;
        for (struct ListNode* p = head; p; p = p -> next) num++;
        if (num < k) return head;
        int n = num / k;
    
        struct ListNode dummy;
        dummy.next = head;
        struct ListNode* prev = &dummy;
        struct ListNode* cur = head;
        for (int i = 0; i < n; i++) {
            struct ListNode* tail = cur;
            struct ListNode* newhead = reverseK(cur, k);
            prev -> next = newhead;
            prev = tail;
            cur = tail -> next;
        }
        return dummy.next;
    }
    

75. 颜色分类

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库内置的 sort 函数的情况下解决这个问题。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

🔁 原地硬改

数组中只有三种元素,需要我们原地修改数组后按顺序输出。首先想到的肯定是最暴力的解法:先保存,再输出。由于我们无法开一个新数组,我们可以先for循环遍历一遍,利用三个整型变量存储下每个元素出现的次数,再利用三个for循环遍历修改数组。

void sortColors(int* nums, int numsSize) {
    int a = 0, b = 0, c = 0;
    for (int i = 0; i < numsSize; i++){
        if (nums[i] == 0) a++;
        if (nums[i] == 1) b++;
        if (nums[i] == 2) c++;
    }
    int cnt = 0;
    for (int i = 0; i < a; i++) nums[cnt++] = 0;
    for (int i = 0; i < b; i++) nums[cnt++] = 1;
    for (int i = 0; i < c; i++) nums[cnt++] = 2;
}

🔁双指针

这道题本来是很简单的一道题。但是我们可以常使用更高级的方法去解题。

我们把数组分成三段(0的区间、1 的区间、未处理区间)并在一次遍历中完成原地排序。p0 指向下一个应放0的位置,p1 指向下一个应放 1 的位置,初始时两者都指向数组开头。

当遇到1时,把它交换到 p1 指向的位置,并将 p1 向后移动一位。这样,所有1会聚集在0之后,保证了到目前为止,[0, p1) 区间只包含0或1。

如果遇0,就先把它交换到 p0 位置,让0前移到最左侧。如果此时 p0 还小于 p1,说明在把这个 0 放到最左侧的同时,我们在 i 处换下来的是一个1,需要再把这个1换到 p1 指向的位置,才能保持 [p0, p1) 这一段全是1。完成这两次交换后,再将 p0p1 同时向后移动一格,继续处理。

在每一步,指针 i 都在往右扫,已处理的元素都被移动到它们正确的区间:0 一定被放到最左边,1紧跟其后,2则自然留在遍历者 i 之后未触碰的区间。这样,无需第三个指针去专门处理2,剩下的元素最终都是2,从而实现了三色排序的线性、原地、一次遍历完成。

void swap(int *a, int *b) {
    int t = *a;
    *a = *b, *b = t;
}

void sortColors(int *nums, int numsSize) {
    int p0 = 0, p1 = 0;
    for (int i = 0; i < numsSize; ++i) {
        if (nums[i] == 1) {
            swap(&nums[i], &nums[p1]);
            ++p1;
        } else if (nums[i] == 0) {
            swap(&nums[i], &nums[p0]);
            if (p0 < p1) {
                swap(&nums[i], &nums[p1]);
            }
            ++p0;
            ++p1;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值