【Leetcode Summary】21-25题总结

本文探讨了链表操作及括号生成算法,包括链表合并、括号配对生成、链表分组反转等高级算法实现。通过具体实例解析,深入理解算法设计思路与代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

21. Merge Two Sorted Lists

把两个有序链表合并。 

solution:整体思想比较容易,让两个指针分别对应两个链表的开头,如果第一个指针对应的值比较小,那么头指针res的next则为第一个指针,将第一个指针向后移动。依次类推,直到其中一个链表结束。如果另一个链表还没有循环结束,那么把这个链表链在尾部。

注意:corner case 可能一个链表为空的情况。

22. Generate Parentheses

Given n pairs of parentheses, write a function to generate all combinations of well-formed parentheses.

For example, given n = 3, a solution set is:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

生成对应的括号必须成对匹配。

solution:利用递归。用left表示左括号的剩余数字,用right表示右括号的剩余数字。如果right>left 则返回

如果left = right =0;则把对应的字符串加入vector<string> 否则在每次递归中给字符串加上“(”或者“)”,然后对函数进行递归调用。

代码实现如下:

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> res;
        generate(n,n,"",res);
        return res;
    }
    void generate(int left,int right,string out,vector<string>&res)
    {
        if(left>right) return;
        if(left==0&&right==0) {res.push_back(out);return;}
        else 
           { 
            if(left>0)generate(left-1,right,out+"(",res);
        if(right>0)generate(left,right-1,out+")",res);}
    }
};

23. Merge k Sorted Lists

是21题的升级版

solution:我们可以借助21题的函数,先将前两个链表合并,然后依次和之后的链表进行合并。不过这样的时间复杂度比较大。

这里就需要用到分治法 Divide and Conquer Approach。简单来说就是不停的对半划分,比如k个链表先划分为合并两个k/2个链表的任务,再不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。举个例子来说比如合并6个链表,那么按照分治法,我们首先分别合并0和3,1和4,2和5。这样下一次只需合并3个链表,我们再合并1和3,最后和2合并就可以了。代码中的k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始终从后半段开始,比如当n=5时,那么此时k=3,则0和3合并,1和4合并,最中间的2空出来。当n是偶数的时候,加1也不会有影响,比如当n=4时,此时k=2,那么0和2合并,1和3合并,完美解决问题,参见代码如下:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.empty()) return NULL;
        int n = lists.size();
        while (n > 1) {
            int k = (n + 1) / 2;
            for (int i = 0; i < n / 2; ++i) {
                lists[i] = mergeTwoLists(lists[i], lists[i + k]);
            }
            n = k;
        }
        return lists[0];
    }
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode *dummy = new ListNode(-1), *cur = dummy;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                cur->next = l1;
                l1 = l1->next;
            } else {
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;
        }
        if (l1) cur->next = l1;
        if (l2) cur->next = l2;
        return dummy->next;
    }
};

24.Swap Nodes in Pairs

Given a linked list, swap every two adjacent nodes and return its head.

You may not modify the values in the list's nodes, only nodes itself may be changed.

通过只修改指针而不是修改指针的value来交换链表中的顺序

这个也比较容易,但是需要注意的是,在修改链表指针关系的时候,要先建立新的首尾关系,再去删除旧的首尾关系。

或者先把之后要用到的指针(但是会被修改掉) 先保存下来。

25.Reverse Nodes in k-Group

Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.

k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.

Example:

Given this linked list: 1->2->3->4->5

For k = 2, you should return: 2->1->4->3->5

For k = 3, you should return: 3->2->1->4->5

Note:

  • Only constant extra memory is allowed.
  • You may not alter the values in the list's nodes, only nodes itself may be changed.

这道题其实乍一看比较麻烦,我也在这里卡了很久。。。基本遇到hard题都要卡

这道题让我们以每k个为一组来翻转链表,实际上是把原链表分成若干小段,然后分别对其进行翻转,那么肯定总共需要两个函数,一个是用来分段的,一个是用来翻转的,我们就以题目中给的例子来看,对于给定链表1->2->3->4->5,一般在处理链表问题时,我们大多时候都会在开头再加一个dummy node,因为翻转链表时头结点可能会变化,为了记录当前最新的头结点的位置而引入的dummy node,那么我们加入dummy node后的链表变为-1->1->2->3->4->5,如果k为3的话,我们的目标是将1,2,3翻转一下,那么我们需要一些指针,pre和next分别指向要翻转的链表的前后的位置,然后翻转后pre的位置更新到如下新的位置:

-1->1->2->3->4->5
 |        |  |
pre      cur next

-1->3->2->1->4->5
    |     |  |
   cur   pre next

这里对k个节点进行翻转的方法叫做头插法,以上图为说明

我们要反转-1 到4之间的数字

翻转之后 1为最后一个  所以令last = pre->next 

令cur=last->next.         cur为第一个要向前插的数字

头插法需要四个语句:

last->next=cur->next;  建立尾部和被插数字后面的联系

cur->next=pre->next  让被插数字指向开头(在上图中就是把3放入-1 和1 之间 需要两步 3指向1   -1 指向3)

pre->next=cur 第二步

cur=last->next  更新被插数字的位置

以此类推,只要cur走过k个节点,那么next就是cur->next,就可以调用翻转函数来进行局部翻转了,注意翻转之后新的cur和pre的位置都不同了,那么翻转之后,cur应该更新为pre->next,而如果不需要翻转的话,cur更新为cur->next,代码如下所示:

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        if (!head || k == 1) return head;
        ListNode *dummy = new ListNode(-1), *pre = dummy, *cur = head;
        dummy->next = head;
        for (int i = 1; cur; ++i) {
            if (i % k == 0) {
                pre = reverseOneGroup(pre, cur->next);
                cur = pre->next;
            } else {
                cur = cur->next;
            }
        }
        return dummy->next;
    }
    ListNode* reverseOneGroup(ListNode* pre, ListNode* next) {
        ListNode *last = pre->next, *cur = last->next;
        while(cur != next) {
            last->next = cur->next;
            cur->next = pre->next;
            pre->next = cur;
            cur = last->next;
        }
        return last;
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值