博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
九章算法系列(#5 Linked List)-课堂笔记
阅读量:5222 次
发布时间:2019-06-14

本文共 8120 字,大约阅读时间需要 27 分钟。

前言

又是很长时间才回来发一篇博客,前一个月确实因为杂七杂八的事情影响了很多,现在还是到了大火燃眉毛的时候了,也应该开始继续整理一下算法的思路了。Linked List大家应该是特别熟悉不过的了,因为这个算是数据结构了里面基本上最开始讲的结构吧。这块内容也没有太多需要琢磨的技巧,可以考量的东西也不多,所以考的就是一些小的trick来完成,面试中链表考得特别多,算是面试官对面试者的基础的考查,所以我建议大家在Linked List这一章,一定要实现Bug Free。这个也是我练的比较多的,有些想法可以和大家分享。

 

outline:

  • Dummy Node in Linked List
    • Remove Duplicates from Sorted List II
    • Reverse Linked List II
    • Partition List
  • Basic Linked List Skills
    • Sort List
    • Reorder List
  • Two Pointers in Linked List (Fast-slow pointers)
    • Merge K Sorted Lists

 

课堂笔记


1. Dummy Node in Linked List

有很多时候,我们需要对整个链表进行操作,这样会导致链表的结构发生变化,或者当需要返回的链表头是不确定的时候,我们就需要用一个Dummy Node来存那个开始的“头”,最后返回的也是这个“头”。这样就不需要单独对head进行操作了,第一个题目如下:

Remove Duplicates from Sorted List II

给定一个排序链表,删除所有重复的元素只留下原链表中没有重复的元素。

样例

给出 1->2->3->3->4->4->5->null,返回 1->2->5->null

给出 1->1->1->2->3->null,返回 2->3->null

这个题算是比较简单的题目,就是考虑链表的删除操作。但是如果不用dummy的话,可能还需要单独考虑head是否为重复元素,部分代码如下:

int val = head->val;while (head->next && head->next->val == val) {    head->next = head->next->next;}head = head->next;

这样就很麻烦,代码不够简洁,而且可能在某些地方出现问题。所以这里就引入dummy的方法(这块内容一定要朗读并背诵全文)(Bug Free):

ListNode * deleteDuplicates(ListNode *head) {        // write your code here        if (!head || !head->next) {            return head;        }        ListNode dummy = ListNode(0);        dummy.next = head;        head = &dummy;        while (head->next && head->next->next) {            if (head->next->val == head->next->next->val) {                int val = head->next->val;                while (head->next && head->next->val == val) {                    head->next = head->next->next;                }            } else {                head = head->next;            }        }        return dummy.next;    }
View Code

这里的思想比较简单,就是用一个dummy存入一开始head的地址,然后再把head指向dummy,这样就相当于整个链表从head->next开始了,然后进行相应的判断就好。最后返回dummy.next即为链表当前的head。其实就是一个小技巧,大家就可以把head当成一个p节点,直接使用就好。

接下来一个题:

Reverse Linked List II

翻转链表中第m个节点到第n个节点的部分

样例

给出链表1->2->3->4->5->null, m = 2 和n = 4,返回1->4->3->2->5->null

相信大家在面试的时候被问到链表反转的时候一定是最高兴的,因为这时候你可以5分钟把bug free的代码写出来。这道题稍微增加了一点点难度,就是反转从m到n的节点,其他的不变。其实也不难,就是保留一下关键的点,然后其他地方一样进行反转就可以。这个题也是为了讲解一下dummy,所以放出来,代码如下(Bug Free):

ListNode *reverseBetween(ListNode *head, int m, int n) {        // write your code here        if (!head || !head->next) {            return head;        }        ListNode dummy = ListNode(0);        dummy.next = head;        head = &dummy;        for (int i = 1; i < m; ++i) {            head = head->next;        }        // 保存m前一个节点        ListNode *pre = head;        // 保存第m个节点        ListNode *mNode = head->next;        // 保存第n个节点        ListNode *nNode = mNode;        // 保存n的后一个节点        ListNode *post = mNode->next;        for (int i = m; i < n; ++i) {            ListNode *tmp = post->next;            post->next = nNode;            nNode = post;            post = tmp;        }        mNode->next = post;        pre->next = nNode;        return dummy.next;    }
View Code

这里就不多解释了。

这个小节主要就是让大家熟悉掌握dummy的用法,这样能够大量介绍代码量,并且给面试加分不少。

还是记住两个点:

  • 当链表的结构发生变化时
  • 当需要返回的链表头不确定时

这两种情况下是需要使用dummy的。

这里最后来一个题:

Partition List

给定一个单链表和数值x,划分链表使得所有小于x的节点排在大于等于x的节点之前。

你应该保留两部分内链表节点原有的相对顺序。

样例

给定链表 1->4->3->2->5->2->null,并且 x=3

返回 1->2->2->4->3->5->null

其实这道题是最能体现dummy的作用的。我们就遍历整个链表,比x小的放入左边的链表,否则放入右边的链表,最后再把两个链表合在一起。这时候使用dummy就不需要再去找各自的头指针了。看似简单,但是最后我还是没有实现bug free,关键的地方就是:当遍历到最后一个节点的时候,如果这个数比x小,那么需要把它向前移动,此时就需要把右边链表的尾节点指向空,否则就会出现LTE,这个是非常关键的问题,需要大家重视。代码如下:

ListNode *partition(ListNode *head, int x) {        // write your code here        if (!head || !head->next) {            return head;        }        ListNode dummy1 = ListNode(0);        ListNode dummy2 = ListNode(0);        ListNode *pre = &dummy1;        ListNode *post = &dummy2;        while (head) {           if (head->val < x) {               pre->next = head;               pre = head;           } else {               post->next = head;               post = head;           }           head = head->next;        }        // 最后的节点指向空,否则出现死循环        post->next = NULL;        pre->next = dummy2.next;        return dummy1.next;    }
View Code

2. Basic Skills in Linked List

说到链表的基本技巧,插入、删除、翻转这三个大家应该都很熟悉了,然后还有两个比较麻烦的操作就是合并两个链表或是中分两个链表(这里用这样的翻译求不吐槽,原文:middle of a linked list),可以说这两个技巧要是充分掌握了的话,基本上链表的题目你也不需要担心了。

直接来一个题吧:

Sort List

在 O(n log n) 时间复杂度和常数级的空间复杂度下给链表排序。

样例

给出 1->3->2->null,给它排序变成 1->2->3->null.

这个思路就不需要我讲了,接下来我将用归并排序来做,这里不适用快速排序是因为大量的操作对于链表来说耗时太大了,所以就不考虑了。

归并排序(Bug Free):

ListNode *merge(ListNode *left, ListNode *right) {        ListNode dummy = ListNode(0);        ListNode *res = &dummy;        while (left && right) {            if (left->val < right->val) {                res->next = left;                left = left->next;            } else {                res->next = right;                right = right->next;            }            res = res->next;        }        if (left) {            res->next = left;        } else {            res->next = right;        }        return dummy.next;    }    ListNode *sortList(ListNode *head) {        // write your code here        if (!head || !head->next) {            return head;        }        ListNode *fast = head->next;        ListNode *slow = head;        while(fast && fast->next) {            fast = fast->next->next;            slow = slow->next;        }        ListNode * right = sortList(slow->next);        // 这里非常关键,一定要把左边结尾指向空        slow->next = NULL;        ListNode * left = sortList(head);        return merge(left,right);    }
View Code

接下来的一个题在面试中见到过,算是比较简单,但是容易出错的题目:

Reorder List

给定一个单链表L: L0→L1→…→Ln-1→Ln,

重新排列后为:L0→Ln→L1→Ln-1→L2→Ln-2→…

必须在不改变节点值的情况下进行原地操作。

样例

给出链表 1->2->3->4->null,重新排列后为1->4->2->3->null

这样的题其实看上去比较复杂,但是你可以结合一下我前面讲过的几个题,就是一种非常简单的做法就可以完成了。我们首先找到链表的中点,然后把右半段链表翻转,之后再进行合并就可以,代码如下(Bug Free):

void merge(ListNode *left, ListNode *right) {        while (left && right) {            ListNode *node1 = left->next;            ListNode *node2 = right->next;            left->next = right;            right->next = node1;            left = node1;            right = node2;        }    }    void reorderList(ListNode *head) {        // write your code here        if (!head || !head->next) {            return;        }        ListNode *fast = head->next;        ListNode *slow = head;        while (fast && fast->next) {            fast = fast->next->next;            slow = slow->next;        }        ListNode *right = slow->next;        slow->next = NULL;        ListNode *ntr = NULL;        while (right && right->next) {            ListNode *tmp = right->next;            right->next = ntr;            ntr = right;            right = tmp;        }        right->next = ntr;        merge(head,right);    }
View Code

这个题关键还是要细心,应该不会出什么问题的。


3.fast-slow pointers

写了半天,突然chrome就崩溃了,真是好心塞。

这个因为之前也讲过,所以直接来做一个比较重要的题目吧:

Merge K Sorted Lists

合并k个排序链表,并且返回合并后的排序链表。尝试分析和描述其复杂度。

这个题目可以用优先队列来做,也可以两两合并然后合在一起,这里我才用的是分治法来讲解:把lists分解为k/2的规模,然后继续分解,直至两两合并,思路比较简单,代码如下

ListNode *mergeTwoLists(ListNode *left, ListNode *right) {        ListNode dummy = ListNode(0);        ListNode *res = &dummy;        while(left && right) {            if (left->val < right->val) {                res->next = left;                left = left->next;            } else {                res->next = right;                right = right->next;            }            res = res->next;        }        if (left) {            res->next = left;        } else {            res->next = right;        }        return dummy.next;    }        ListNode *mergeHelper(vector
&lists, int start, int end) { if (start == end) { return lists[start]; } int mid = start + (end - start) / 2; ListNode *left = mergeHelper(lists, start, mid); ListNode *right = mergeHelper(lists, mid + 1, end); return mergeTwoLists(left,right); } ListNode *mergeKLists(vector
&lists) { // write your code here if (!lists.size()) { return NULL; } return mergeHelper(lists, 0, lists.size() - 1); }
View Code

总结

因为链表算是比较基础的内容了,所以也不需要太多的东西。大家就把几个重要的trick掌握好就可以了,还是需要多多练习。

知识点如下:

1.dummy的使用

2.fast-slow pointer的使用

 

转载于:https://www.cnblogs.com/Raising-Sun/p/5970662.html

你可能感兴趣的文章
数据库锁机制及乐观锁,悲观锁的并发控制
查看>>
图像处理中双线性插值
查看>>
RobHess的SIFT代码解析之RANSAC
查看>>
03 线程池
查看>>
201771010125王瑜《面向对象程序设计(Java)》第十三周学习总结
查看>>
手机验证码执行流程
查看>>
python 基础 ----- 变量
查看>>
设计模式课程 设计模式精讲 2-2 UML类图讲解
查看>>
Silverlight 的菜单控件。(不是 Toolkit的)
查看>>
:hover 鼠标同时触发两个元素变化
查看>>
go语言学习十三 - 相等性
查看>>
Idea 提交代码到码云(提交到github也大同小异)
查看>>
c#连接excel2007未安装ISAM解决
查看>>
Mono 异步加载数据更新主线程
查看>>
初识lua
查看>>
我是插件狂人,jDuang,jValidator,jModal,jGallery
查看>>
张季跃 201771010139《面向对象程序设计(java)》第四周学习总结
查看>>
如何解除循环引用
查看>>
android中fragment的使用及与activity之间的通信
查看>>
字典【Tire 模板】
查看>>