九章算法系列(#5 Linked List)-课堂笔记
前言
又是很长时间才回来发一篇博客,前一个月确实因为杂七杂八的事情影响了很多,现在还是到了大火燃眉毛的时候了,也应该开始继续整理一下算法的思路了。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)-课堂笔记的更多相关文章
- 九章算法系列(#3 Binary Tree & Divide Conquer)-课堂笔记
前言 第一天的算法都还没有缓过来,直接就进入了第二天的算法学习.前一天一直在整理Binary Search的笔记,也没有提前预习一下,好在Binary Tree算是自己最熟的地方了吧(LeetCode ...
- 九章算法系列(#4 Dynamic Programming)-课堂笔记
前言 时隔这么久才发了这篇早在三周前就应该发出来的课堂笔记,由于懒癌犯了,加上各种原因,实在是应该反思.好多课堂上老师说的重要的东西可能细节上有一些急记不住了,但是幸好做了一些笔记,还能够让自己回想起 ...
- 九章算法系列(#2 Binary Search)-课堂笔记
前言 先说一些题外的东西吧.受到春跃大神的影响和启发,推荐了这个算法公开课给我,晚上睡觉前点开一看发现课还有两天要开始,本着要好好系统地学习一下算法,于是就爬起来拉上两个小伙伴组团报名了.今天听了第一 ...
- (lintcode全部题目解答之)九章算法之算法班题目全解(附容易犯的错误)
--------------------------------------------------------------- 本文使用方法:所有题目,只需要把标题输入lintcode就能找到.主要是 ...
- 九章算法:BAT国内班 - 课程大纲
第1章 国内笔试面试风格及准备方法 --- 分享面试经验,通过例题分析国内面试的风格及准备方法 · 1) C/C++部分: 实现 memcpy 函数 STL 中 vector 的实现原理 · 2)概率 ...
- 7九章算法强化班全解--------Hadoop跃爷Spark
------------------------------------------------------------第七周:Follow up question 1,寻找峰值 寻找峰值 描述 笔记 ...
- 【九章算法免费讲座第一期】转专业找CS工作的“打狗棒法”
讲座时间: 美西时间6月5日18:30-20:00(周五) 北京时间6月6日09:30-11:00(周六a.m) 讲座安排: 免费在线直播讲座 报名网址: http://t.cn/R2XgMSH,或猛 ...
- 第十九章——使用资源调控器管理资源(1)——使用SQLServer Management Studio 配置资源调控器
原文:第十九章--使用资源调控器管理资源(1)--使用SQLServer Management Studio 配置资源调控器 本系列包含: 1. 使用SQLServer Management Stud ...
- 【分享】改变未来的九大算法[pdf][清晰扫描版]
[下载地址]http://www.colafile.com/file/1179688 图书信息:中文名: 改变未来的九大算法作者: 约翰·麦考密克译者: 管策图书分类: 软件资源格式: PDF版本: ...
随机推荐
- 【3】python核心编程 第六章-序列:字符串、列表和元组
1.序列类型操作符 序列操作符 作用 seq[ind] 获得下标为ind 的元素 seq[ind1:ind2] 获得下标从ind1 到ind2 间的元素集合 seq * expr 序列重复expr 次 ...
- 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 - ...
- iOS GCD使用整理
自己进行一个复习整理 1.最简单的用法 全局并行 dispatch_async(dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_ ...
- information_schema.triggers 学习
mysql实例中的每一个trigger 对应到information_schema.triggers 中有一行 1.information_schema.triggers 表的常用列: 1.trigg ...
- 【图解ASP.NET MVC运行机制理解-简易版】
很多盆友咨询ASP.NET MVC的机制.网上也有好多.但是都是相当深奥.看的云里雾里的.我今天抽空,整理个简易版本.把处理流程走一遍. 当然,这个只是处理请求的一部分环节.百度的面试题“客户端从浏览 ...
- java设计模式--创建模式--工厂方法
工厂方法定义: 工厂方法 概述 定义一个用于创建对象的接口,让子类决定实例化哪一个类.FactoryMethod使一个类的实例化延迟到其子类. 适用性 .当一个类不知道它所必须创建的对象的类的时候. ...
- Linux系统编程(13)——Shell的基本语法
按照惯例,Shell变量由全大写字母加下划线组成,有两种类型的Shell变量:环境变量和本地变量. 环境变量: 环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给 ...
- 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 ...
- What is NicEdit?
NicEdit - WYSIWYG Content Editor, Inline Rich Text Application What is NicEdit? NicEdit is a Light ...
- Linux下alias命令
功能说明:设置指令的别名.语 法:alias[别名]=[指令名称]参 数 :若不加任何参数,则列出目前所有的别名设置.举 例 :ermao@lost-desktop:~$ alias ...