算法札记——5.14

今天记录一道有难度的链表题------148. 排序链表 - 力扣(LeetCode)

题目要求是让我们对一个链表进行排序,首先可以想到的最简单的思路就是,将所有的节点存储到一个数组,然后数组以node->val排序,最后遍历数组连接各节点即可。以下是相关实现:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr)
            return nullptr;
        ListNode* cur = head;
        vector<ListNode*> tmp;
        while (cur)
        {
            tmp.push_back(cur);
            cur = cur->next;
        }
        sort(tmp.begin(), tmp.end(), [](const ListNode* n1, const ListNode* n2){
            return n1->val < n2->val;
        });

        for (int i = 1; i < tmp.size(); ++i)
        {
            ListNode* prev = tmp[i - 1];
            prev->next = tmp[i];
        }
        tmp.back()->next = nullptr;
        return tmp[0];
    }
};

但题目中存在一个进阶要求,需要空间复杂度为O(1),这便是该题的难点。可以说若使用之前的解法仅能算简单题,但要求却可让该题进化到困难题。

但还是有思路的:可以借鉴归并排序的思想 和另一道简单的算法题的思路------21. 合并两个有序链表 - 力扣(LeetCode)

该简单题的解法:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode head(-1);
        ListNode* phead = &head;
        ListNode* prev = phead, *l1 = list1, *l2 = list2;
        while (l1 && l2)
        {
            if (l1->val <= l2->val)
            {
                prev->next = l1;
                l1 = l1->next;
            }
            else 
            {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;
        }
        prev->next = l1 == nullptr ? l2 : l1;
        return phead->next;
    }
};

可是鉴于空间复杂度仅能为O(1),显然不能仿照归并排序创建一个tmp的拷贝数组的。但参照上述的简单题,我们可以使用指针指向来划分区间进行归并。而且本题无法使用递归版本的归并,因为递归涉及到函数栈帧的开辟,也会产生O(logN)的空间复杂度,此处应使用迭代版本归并排序。对于归并排序的递归与非递归的思路与实现可跳转博主这篇博文:( 八大排序算法(C++实现)-CSDN博客 )的归并排序篇。

以下是具体实现(本人水平较菜,所以使用的变量有点多):

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
    ListNode* MergeSortListNonR(ListNode* head, int n)
    {
        int gap = 1;
        ListNode t(-1, head);
        ListNode* phead = &t;
        while (gap < n)
        {
            ListNode* prev = phead;
            for (int i = 0; i < n; i += 2 * gap)
            {
                int begin = i, end = min(i + 2 * gap, n) - 1;

                int left1 = begin, right1 = begin + gap - 1;
                int left2 = begin + gap, right2 = end;
                if (right2 < left2)
                    break;

                ListNode* l1 = prev->next, *l2 = l1, *tail1 = nullptr;
                for (int i = left1; i <= right1; ++i)
                {
                    tail1 = l2;
                    if (l2 == nullptr)
                        cout << gap << endl;
                    l2 = l2->next;
                }
                ListNode* tail2 = l2;
                for (int j = left2; j < right2; ++j)
                    tail2 = tail2->next;
                ListNode* tail = tail2->next;

                while (left1 <= right1 && left2 <= right2)
                {
                    if (l1->val <= l2->val)
                    {
                        prev->next = l1;
                        ++left1;
                        l1 = l1->next;
                    }
                    else
                    {
                        prev->next = l2;
                        ++left2;
                        l2 = l2->next;
                    }
                    prev = prev->next;
                }

                if (left1 <= right1)
                {
                    prev->next = l1;
                    //让prev指向区间尾部
                    prev = tail1;
                }
                if (left2 <= right2)
                {
                    prev->next = l2;
                    //让prev指向区间尾部
                    prev = tail2;
                }
                prev->next = tail;
            }
            gap *= 2;
        }
        return phead->next;
    }
public:
    ListNode* sortList(ListNode* head) {
        int n = 0;
        ListNode* cur = head;
        while (cur)
        {
            ++n;
            cur = cur->next;
        }
        ListNode* newHead = MergeSortListNonR(head, n);
        return newHead;
    }
};

以上实现需要注意的点是prev指针 的相关细节,每趟归并最后应将prev指向余下(left1/left2)的区间末尾 ,并且让prev->next指向此趟排序的整个区间右侧外的第一个节点(tail)。

相关推荐
无限码力11 小时前
美团研发岗 4月18号笔试真题 - 包包的最长公共子序列3
算法·美团笔试题·美团研发岗笔试题·美团机试题
阿里matlab建模师11 小时前
基于matlab时域频域处理的语音信号变声处理系统设计与算法原理(论文+程序源码+GUI图形用户界面)——变声算法
算法·matlab·语音识别
IMPYLH11 小时前
HTML 的 <abbr> 元素
前端·算法·html
leo__52011 小时前
小波特征与模糊支持向量机(FSVM)的脑电信号分类方法
算法·支持向量机·分类
wabs66612 小时前
关于动态规划【纯粹的0-1背包需要思考的问题】
算法·动态规划
小小编程路12 小时前
字符串转数字时,可能会遇到哪些问题?
java·开发语言·算法
rit843249912 小时前
MATLAB近红外光谱预处理:平滑与求导(MSV方法)
数据结构·算法·matlab
蚂蚁数据AntData12 小时前
从ChatBI到业务记忆:重新定义数据智能的生产力边界
大数据·网络·数据库·人工智能·算法
_日拱一卒12 小时前
LeetCode:22括号生成
算法·leetcode·职场和发展
cfm_291412 小时前
JVM垃圾收集算法与收集器深度解析
jvm·测试工具·算法·性能优化