前言

又是很长时间才回来发一篇博客,前一个月确实因为杂七杂八的事情影响了很多,现在还是到了大火燃眉毛的时候了,也应该开始继续整理一下算法的思路了。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();
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;
}

这里的思想比较简单,就是用一个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();
dummy.next = head;
head = &dummy;
for (int i = ; 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;
}

这里就不多解释了。

这个小节主要就是让大家熟悉掌握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();
ListNode dummy2 = ListNode();
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;
}

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

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

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

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


3.fast-slow pointers

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

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

Merge K Sorted Lists

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

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

    ListNode *mergeTwoLists(ListNode *left, ListNode *right) {
ListNode dummy = ListNode();
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<ListNode *> &lists, int start, int end) {
if (start == end) {
return lists[start];
}
int mid = start + (end - start) / ;
ListNode *left = mergeHelper(lists, start, mid);
ListNode *right = mergeHelper(lists, mid + , end);
return mergeTwoLists(left,right);
} ListNode *mergeKLists(vector<ListNode *> &lists) {
// write your code here
if (!lists.size()) {
return NULL;
}
return mergeHelper(lists, , lists.size() - );
}

总结

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

知识点如下:

1.dummy的使用

2.fast-slow pointer的使用

九章算法系列(#5 Linked List)-课堂笔记的更多相关文章

  1. 九章算法系列(#3 Binary Tree & Divide Conquer)-课堂笔记

    前言 第一天的算法都还没有缓过来,直接就进入了第二天的算法学习.前一天一直在整理Binary Search的笔记,也没有提前预习一下,好在Binary Tree算是自己最熟的地方了吧(LeetCode ...

  2. 九章算法系列(#4 Dynamic Programming)-课堂笔记

    前言 时隔这么久才发了这篇早在三周前就应该发出来的课堂笔记,由于懒癌犯了,加上各种原因,实在是应该反思.好多课堂上老师说的重要的东西可能细节上有一些急记不住了,但是幸好做了一些笔记,还能够让自己回想起 ...

  3. 九章算法系列(#2 Binary Search)-课堂笔记

    前言 先说一些题外的东西吧.受到春跃大神的影响和启发,推荐了这个算法公开课给我,晚上睡觉前点开一看发现课还有两天要开始,本着要好好系统地学习一下算法,于是就爬起来拉上两个小伙伴组团报名了.今天听了第一 ...

  4. (lintcode全部题目解答之)九章算法之算法班题目全解(附容易犯的错误)

    --------------------------------------------------------------- 本文使用方法:所有题目,只需要把标题输入lintcode就能找到.主要是 ...

  5. 九章算法:BAT国内班 - 课程大纲

    第1章 国内笔试面试风格及准备方法 --- 分享面试经验,通过例题分析国内面试的风格及准备方法 · 1) C/C++部分: 实现 memcpy 函数 STL 中 vector 的实现原理 · 2)概率 ...

  6. 7九章算法强化班全解--------Hadoop跃爷Spark

    ------------------------------------------------------------第七周:Follow up question 1,寻找峰值 寻找峰值 描述 笔记 ...

  7. 【九章算法免费讲座第一期】转专业找CS工作的“打狗棒法”

    讲座时间: 美西时间6月5日18:30-20:00(周五) 北京时间6月6日09:30-11:00(周六a.m) 讲座安排: 免费在线直播讲座 报名网址: http://t.cn/R2XgMSH,或猛 ...

  8. 第十九章——使用资源调控器管理资源(1)——使用SQLServer Management Studio 配置资源调控器

    原文:第十九章--使用资源调控器管理资源(1)--使用SQLServer Management Studio 配置资源调控器 本系列包含: 1. 使用SQLServer Management Stud ...

  9. 【分享】改变未来的九大算法[pdf][清晰扫描版]

    [下载地址]http://www.colafile.com/file/1179688 图书信息:中文名: 改变未来的九大算法作者: 约翰·麦考密克译者: 管策图书分类: 软件资源格式: PDF版本: ...

随机推荐

  1. 【3】python核心编程 第六章-序列:字符串、列表和元组

    1.序列类型操作符 序列操作符 作用 seq[ind] 获得下标为ind 的元素 seq[ind1:ind2] 获得下标从ind1 到ind2 间的元素集合 seq * expr 序列重复expr 次 ...

  2. LFS,编译自己的Linux系统 - 编译临时系统

    编译GCC-4.8.2 PASS 1 解压并重命名 cd /mnt/lfs/sources tar -Jxf ../mpfr-3.1.2.tar.xz mv mpfr-3.1.2 mpfr tar - ...

  3. iOS GCD使用整理

    自己进行一个复习整理 1.最简单的用法 全局并行 dispatch_async(dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_ ...

  4. information_schema.triggers 学习

    mysql实例中的每一个trigger 对应到information_schema.triggers 中有一行 1.information_schema.triggers 表的常用列: 1.trigg ...

  5. 【图解ASP.NET MVC运行机制理解-简易版】

    很多盆友咨询ASP.NET MVC的机制.网上也有好多.但是都是相当深奥.看的云里雾里的.我今天抽空,整理个简易版本.把处理流程走一遍. 当然,这个只是处理请求的一部分环节.百度的面试题“客户端从浏览 ...

  6. java设计模式--创建模式--工厂方法

    工厂方法定义: 工厂方法 概述 定义一个用于创建对象的接口,让子类决定实例化哪一个类.FactoryMethod使一个类的实例化延迟到其子类. 适用性 .当一个类不知道它所必须创建的对象的类的时候. ...

  7. Linux系统编程(13)——Shell的基本语法

    按照惯例,Shell变量由全大写字母加下划线组成,有两种类型的Shell变量:环境变量和本地变量. 环境变量: 环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给 ...

  8. UESTC_Judgment Day CDOJ 11

    Today is the judgment day. The world is ending and all man will pay for their guilt and sin. Now the ...

  9. What is NicEdit?

    NicEdit - WYSIWYG Content Editor, Inline Rich Text Application   What is NicEdit? NicEdit is a Light ...

  10. Linux下alias命令

    功能说明:设置指令的别名.语 法:alias[别名]=[指令名称]参 数 :若不加任何参数,则列出目前所有的别名设置.举    例 :ermao@lost-desktop:~$ alias       ...