九章算法 第七到九章 预习笔记


********************第七章 基于排列、图的DFS预习笔记********************


本章节的先修知识有:

全排列问题如何使用深度优先搜索来实现?和全子集问题的异同在哪儿?
全排列问题的 Follow up Permutation II。如何去重?
如何求一个排列的下一个排列?
课后补充内容有:

如何求一个排列是第几个排列?


**如何求下一个排列**

问题描述
给定一个若干整数的排列,给出按整数大小进行字典序从小到大排序后的下一个排列。若没有下一个排列,则输出字典序最小的序列。
例如1,2,3 → 1,3,2,3,2,1 → 1,2,3,1,1,5 → 1,5,1
原题链接:
190. 下一个排列 发布
52. 下一个排列 发布
(两题类似,一个要求原地修改,一个要求返回新的排列)

算法描述
如果上来想不出方法,可以试着找找规律,我们关注的重点应是原数组末尾。

从末尾往左走,如果一直递增,例如...9,7,5,那么下一个排列一定会牵扯到左边更多的数,直到一个非递增数为止,例如...6,9,7,5。对于原数组的变化就只到6这里,和左侧其他数再无关系。6这个位置会变成6右侧所有数中比6大的最小的数,而6会进入最后3个数中,且后3个数必是升序数组。

所以算法步骤如下:

从右往左遍历数组nums,直到找到一个位置i,满足nums[i] > nums[i - 1]或者i为0。
i不为0时,用j再次从右到左遍历nums,寻找第一个nums[j] > nums[i - 1]。而后交换nums[j]和nums[i - 1]。注意,满足要求的j一定存在!且交换后nums[i]及右侧数组仍为降序数组。
将nums[i]及右侧的数组翻转,使其升序。
Q:i为0怎么办?
A:i为0说明整个数组是降序的,直接翻转整个数组即可。

Q:有重复元素怎么办?
A:在遍历时只要严格满足nums[i] > nums[i - 1]和nums[j] > nums[i - 1]就不会有问题。

Q:元素过少是否要单独考虑?
A:当元素个数小于等于1个时,可以直接返回。

**全排列问题**

```

全排列问题是“排列式”深度优先搜索问题的鼻祖。很多搜索的问题都可以用类似全排列的代码来完成。包括我们前面学过的全子集问题的一种做法。

这一小节中我们需要掌握:

普通的全排列问题怎么做?
15. Permutations 发布

有重复的全排列问题怎么做?如何在搜索类问题中去重?
16. 带重复元素的排列 发布

如何实现一个非递归的全排列算法?

基本思路
非递归的全排列,采用的是迭代方式,在如何求下一个排列中,我们讲过如何求下一个排列,那么我们只需要不断调用这个nextPermutation方法即可。

一些可以做得更细致的地方:

为了确定何时结束,建议在迭代前,先对输入nums数组进行升序排序,迭代到降序时,就都找完了。有心的同学可能还记得在nextPermutation当中,当且仅当数组完全降序,那么从右往左遍历的指针i最终会指向0。所以可以为nextPermutation带上布尔返回值,当i为0时,返回false,表示找完了。要注意,排序操作在这样一个NP问题中,消耗的时间几乎可以忽略。
当数组长度为1时,nextPermutation会直接返回false;当数组长度为0时,nextPermutation中i会成为-1,所以返回false的条件可以再加上i为-1。
Java中,如果输入类型是int[],而输出类型是List>,要注意,并没有太好的方法进行类型转换,这是由于int是基本类型。建议还是自行手动复制,实际工作中还可使用guava库。

```

如何求一个排列是第几个排列?

题目描述
给出一个不含重复数字的排列,求这些数字的所有排列按字典序排序后该排列的编号,编号从1开始。
例如排列[1,2,4]是第1个排列。
197. 排列序号

算法描述
只需计算有多少个排列在当前排列A的前面即可。如何算呢?举个例子,[3,7,4,9,1],在它前面的必然是某位置i对应元素比原数组小,而i左侧和原数组一样。也即[3,7,4,1,X],[3,7,1,X,X],[3,1或4,X,X,X],[1,X,X,X,X]。
而第i个元素,比原数组小的情况有多少种,其实就是A[i]右侧有多少元素比A[i]小,乘上A[i]右侧元素全排列数,即A[i]右侧元素数量的阶乘。i从右往左看,比当前A[i]小的右侧元素数量分别为1,1,2,1,所以最终字典序在当前A之前的数量为1×1!+1×2!+2×3!+1×4!=39,故当前A的字典序为40。

具体步骤:

用permutation表示当前阶乘,初始化为1,result表示最终结果,初始化为0。由于最终结果可能巨大,所以用long类型。
i从右往左遍历A,循环中计算A[i]右侧有多少元素比A[i]小,计为smaller,result += smaller * permutation。之后permutation *= A.length - i,为下次循环i左移一位后的排列数。
已算出多少字典序在A之前,返回result+1。


**PPT部分笔记
大纲
• 排列式搜索
• 隐式图,矩阵图搜索 • Word 四兄弟问题

排列搜索问题 Permutation
问题模型求出所有满足条件的“排列”。
判断条件组合中的元素是顺序“相关”的。 
时间复杂度与 n! 相关。
10. String Permutation II
33. N-Queens
52. Next Permutation
197. Permutation Index

基于图的dfs 
425. Letter Combinations of a Phone Number
Follow up
如果有一个词典(Dictionary),要求组成的单词都是词典里的,如何优化?
829. Word Pattern II
121. Word Ladder II
132. Word Search II


********************第八章  数据结构 栈,队列,哈希表,堆预习笔记********************
###网页预习基础部分
####**如何实现一个栈?**
什么是栈(Stack)?
栈(stack)是一种采用后进先出(LIFO,last in first out)策略的抽象数据结构。比如物流装车,后装的货物先卸,先转的货物后卸。栈在数据结构中的地位很重要,在算法中的应用也很多,比如用于非递归的遍历二叉树,计算逆波兰表达式,等等。

栈一般用一个存储结构(常用数组,偶见链表),存储元素。并用一个指针记录栈顶位置。栈底位置则是指栈中元素数量为0时的栈顶位置,也即栈开始的位置。
栈的主要操作:

push(),将新的元素压入栈顶,同时栈顶上升。
pop(),将新的元素弹出栈顶,同时栈顶下降。
empty(),栈是否为空。
peek(),返回栈顶元素。
在各语言的标准库中:

Java,使用java.util.Stack,它是扩展自Vector类,支持push(),pop(),peek(),empty(),search()等操作。
C++,使用中的stack即可,方法类似Java,只不过C++中peek()叫做top(),而且pop()时,返回值为空。
Python,直接使用list,查看栈顶用[-1]这样的切片操作,弹出栈顶时用list.pop(),压栈时用list.append()。
如何自己实现一个栈?
参见问题:
495. Implement Stack

**如何用两个队列实现一个栈?**

算法步骤
队列的知识请看:http//www.jiuzhang.com/tutorial/algorithm/391
两个队列实现一个栈,其实并没有什么优雅的办法。就看大家怎么去写这个东西了。

构造的时候,初始化两个队列,queue1,queue2。queue1主要用来存储,queue2则主要用来帮助queue1弹出元素以及访问栈顶。
push:将元素推入queue1当中。
pop:注意要弹出的元素在queue1末端,故将queue1中元素弹出,并直接推入queue2,当queue1只剩一个元素时,把它pop出来,并作为结果。而后交换两个队列。
top:类似pop,不过不扔掉queue1中最后一个元素,而是把它也推入queue2当中。
isEmpty:判断queue1是否为空即可。

相关题目
494. 双队列实现栈 搞定了
http//www.lintcode.com/zh-cn/problem/implement-stack-by-two-queues/

**如何用一个数组实现三个栈?**
教程
问答(7)
题目描述
用一个数组实现三个栈。你可以假设这三个栈都一样大并且足够大。你不需要担心如果一个栈满了之后怎么办。
详见:224. Implement Three Stacks by Single Array

http//www.lintcode.com/zh-cn/problem/implement-three-stacks-by-single-array/

算法描述
这道题的本质是把数组索引当作地址,用链表来实现栈。数组buffer中的每一个元素,并不能单单是简单的int类型,而是一个链表中的节点,它包含值value,栈中向栈底方向的之前元素索引prev,向栈顶方向的后来元素索引next。
在该三栈数据结构中,要记录三个栈顶指针stackPointer,也就是三个栈顶所在的数组索引,通过这三个栈顶节点,能够用prev找到整串栈。
此外还要用indexUsed记录整个数组中的所用的索引数。其实也就是下一次push的时候,向数组的indexUsed位置存储。

具体操作:

构造:要初始化stackPointer为3个-1,表示没有;indexUsed=0;buffer为一个长度为三倍栈大小的数组。
push:要把新结点new在buffer[indexUsed],同时修改该栈的stackPointer,indexUsed自增。注意修改当前栈顶结点prev和之前栈顶结点的next索引。
peek:只需要返回buffer中对应的stackPointer即可。
isEmpty:只需判断stackPointer是否为-1。
pop:pop的操作较为复杂,因为有三个栈,所以栈顶不一定在数组尾端,pop掉栈顶之后,数组中可能存在空洞。而这个空洞又很难push入元素。所以,解决方法是,当要pop的元素不在数组尾端(即indexUsed-1)时,交换这两个元素。不过一定要注意,交换的时候,要注意修改这两个元素之前、之后结点的prev和next指针,使得链表仍然是正确的,事实上这就是结点中next的作用——为了找到之后结点并修改它的prev。在交换时,一种很特殊的情况是栈顶节点刚好是数组尾端元素的后继节点,这时需要做特殊处理。在交换完成后,就可以删掉数组尾端元素,并修改相应的stackPointer、indexUsed和新栈顶的next。

####**队列 Queue**
教程
问答(0)
队列通常被用作 BFS 算法的主要数据结构。除此之外面试中其他可能考察队列这个数据结构的地方并不多。我们这里把非 BFS 的队列考题考点做一个汇总:

如何用链表实现队列?
如何用两个栈实现一个队列?
什么是循环数组,如何用循环数组实现队列?


**如何用链表实现队列**

教程
问答(4)
定义
队列为一种先进先出的线性表
只允许在表的一端进行入队,在另一端进行出队操作。在队列中,允许插入的一端叫队尾,允许删除的一端叫队头,即入队只能从队尾入,出队只能从队头出

思路
需要两个节点,一个头部节点,也就是dummy节点,它是在加入的第一个元素的前面,也就是dummy.next=第一个元素,这样做是为了方便我们删除元素,还有一个尾部节点,也就是tail节点,表示的是最后一个元素的节点
初始时,tail节点跟dummy节点重合
当我们要加入一个元素时,也就是从队尾中加入一个元素,只需要新建一个值为val的node节点,然后tail.next=node,再移动tail节点到tail.next
当我们需要删除队头元素时,只需要将dummy.next变为dummy.next.next,这样就删掉了第一个元素,这里需要注意的是,如果删掉的是队列中唯一的一个元素,那么需要将tail重新与dummy节点重合
当我们需要得到队头元素而不删除这个元素时,只需要获得dummy.next.val就可以了

**如何用两个栈实现队列**


我们已经知道,栈是一个先进后出的数据结构,而队列是一个先进先出的数据结构,那么如何用栈来实现队列呢?这里我们可以用两个栈来实现队列

思路
现在我们已经有了两个栈stack1和stack2,最暴力的做法,当需要往队列中加入元素时,可以往其中一个栈stack2中加入元素,当需要得到队头元素时,只需要将stack2中的元素倒入到stack1中,再取stack1的头元素就可以了,如果是需要删掉队头元素,那么直接pop stack1的栈顶元素就可以了,再将stack1中的元素再倒入到stack2中,以便下一次的加入元素
上面的实现中,我们每取一次队头元素或者删掉队头元素,我们都需要将stack2中的元素先倒入到stack1中,再从stack1中倒回去,每次需要倒两边十分麻烦,那么是否有更加简便一些的方法呢?答案当然是有的,其实当我们将stack2中的元素倒入到stack1中的时候,我们发现stack1中的元素的顺序就是按照队列的先进先出顺序,那么我们不再将stack1中的元素倒入到stack2中,在获取队头元素或者删除队头元素的时候,我们先判断stack1是否为空,如果不为空,从stack1中取即可,如果为空,那么将stack2中的元素倒入到stack1中,每次加入元素的时候都是往stack2中加入元素。
40. Implement Queue by Two Stacks

**什么是循环数组,如何用循环数组实现队列?**

什么是循环数组
Circular array = a data structure that used a array as if it were connected end-to-end

如何实现队列
我们需要知道队列的入队操作是只在队尾进行的,相对的出队操作是只在队头进行的,所以需要两个变量front与rear分别来指向队头与队尾
由于是循环队列,我们在增加元素时,如果此时 rear = array.length - 1 ,rear 需要更新为 0;同理,在元素出队时,如果 front = array.length - 1, front 需要更新为 0. 对此,我们可以通过对数组容量取模来更新。

在线练习
955. Implement Queue by Circular Array

####哈希表 Hash
教程
问答(1)
哈希表(Java 中的 HashSet / HashMap,C++ 中的 unordered_map,Python 中的 dict)是面试中非常常见的数据结构。它的主要考点有两个:

是否会灵活的使用哈希表解决问题
是否熟练掌握哈希表的基本原理
这一小节中,我们将介绍一下哈希表原理中的几个重要的知识点:

哈希表的工作原理
为什么 hash 上各种操作的时间复杂度不能单纯的认为是 O(1) 的
哈希函数(Hash Function)该如何实现
哈希冲突(Collision)该如何解决
如何让哈希表可以不断扩容?


**哈希表基本工作原理视频转文字**


get(key)
set(key, value)
复杂度是O(1)
O(size of key)
key可以是字符串,整数等未知长度的东西。所以用O(L)合适

工作原理:对于set。key通过hash函数得到一个index,然后把value存在数组里。
对于get,通过key和hash函数求出index,得到value。

**哈希函数实现:**

练习题:
128 哈希函数

**冲突的解决办法**


冲突(Collision),是说两个不同的 key 经过哈希函数的计算后,得到了两个相同的值。解决冲突的方法,主要有两种:
开散列法(Open Hashing)。是指哈希表所基于的数组中,每个位置是一个 Linked List 的头结点。这样冲突的 二元组,就都放在同一个链表中。
闭散列法(Closed Hashing)。是指在发生冲突的时候,后来的元素,往下一个位置去找空位。

重哈希 Rehashing
129 重哈希

hash冲突:
同一个key,value一样,不同的key可能对应同一个value;

解决办法:
open hash:每个数组格子是一个链表,通过增加数组同一个位置的坑位数放置更多的key-value。
查找时候先找到对应数组位置,然后遍历对应位置的链表
closed hash,也是一个数组,每个格子里放key-value,想要的车位被占用了,那就不要吵架,去看下一个车位是否是空的,
如果是被占用了,再看下下一个车位。也就是有人占我坑位,我就占别人坑位。
哈希表扩容方法:重哈希,练习题:129 重哈希


####堆

**堆的原理和结构:**
delete, pop 复杂度都是logN,求min,or max复杂度O(1)

结构:是个二叉树,高度logN,
值特性:对于min:父节点小于子节点,对于max,子节点小于父节点。左右子节点大小无关
插入方法:永远从左侧插入,保证是一个平衡二叉树,如果插入数字小,那就上移,父节点下移。所以复杂度最大就是logN
删除方法类似
实现方法:练习题:lintcode 130 heapify


**堆化 Heapify**

**基于 Siftup 的版本 O(nlogn)**
算法思路:

对于每个元素A[i],比较A[i]和它的父亲结点的大小,如果小于父亲结点,则与父亲结点交换。
交换后再和新的父亲比较,重复上述操作,直至该点的值大于父亲。
时间复杂度分析
对于每个元素都要遍历一遍,这部分是 O(n)O(n)。
每处理一个元素时,最多需要向根部方向交换 lognlogn 次。
因此总的时间复杂度是 O(nlogn)O(nlogn)

**基于 Siftdown 的版本 O(n)**
算法思路:

初始选择最接近叶子的一个父结点,与其两个儿子中较小的一个比较,若大于儿子,则与儿子交换。
交换后再与新的儿子比较并交换,直至没有儿子。
再选择较浅深度的父亲结点,重复上述步骤。
时间复杂度分析
这个版本的算法,乍一看也是 O(nlogn)O(nlogn), 但是我们仔细分析一下,算法从第 n/2 个数开始,倒过来进行 siftdown。也就是说,相当于从 heap 的倒数第二层开始进行 siftdown 操作,倒数第二层的节点大约有 n/4 个, 这 n/4 个数,最多 siftdown 1次就到底了,所以这一层的时间复杂度耗费是 O(n/4)O(n/4),然后倒数第三层差不多 n/8 个点,最多 siftdown 2次就到底了。所以这里的耗费是 O(n/8 * 2), 倒数第4层是 O(n/16 * 3),倒数第5层是 O(n/32 * 4) ... 因此累加所有的时间复杂度耗费为:

T(n) = O(n/4) + O(n/8 * 2) + O(n/16 * 3) ...
然后我们用 2T - T 得到:

2 * T(n) = O(n/2) + O(n/4 * 2) + O(n/8 * 3) + O(n/16 * 4) ... 
T(n)     =          O(n/4)     + O(n/8 * 2) + O(n/16 * 3) ...

2 * T(n) - T(n) = O(n/2) +O (n/4) + O(n/8) + ...
                = O(n/2 + n/4 + n/8 + ... )
                = O(n)
因此得到 T(n) = 2 * T(n) - T(n) = O(n)

***PPT部分笔记

**先修知识**

什么是数据结构 结构类问题的三种考法 如何衡量数据结构类问题的时间复杂度 队列及相关面试问题 栈及相关面试问题
哈希表基本原理
堆的基本原理

**三种考法 **
考法1问某种数据结构的基本原理,并要求实现
例题说一下 Hash 的原理并实现一个 Hash 表
考法2使用某种数据结构完成事情
例题归并 K 个有序数组 
考法3实现一种数据结构,提供一些特定的功能
通常需要一个或 者多个数据 结构 配合在一起使用
 例题最高频 K 项问题

###**队列 Queue **

 支持操作O(1) Push / O(1) Pop / O(1) Top 
 队列的基本作用就是用来做 BFS
642. Moving Average from Data Stream

###**栈 Stack **
 支持操作O(1) Push / O(1) Pop / O(1) Top 
 非递归实现DFS的主要数据结构
  
**数据结构时间复杂度的衡量方法**

数据结构通常会提供“多个”对外接口 
只用一个时间复杂度是很难对其进行正确评价的 
所以通常要对每个接口的时间复杂度进行描述

###**数据结构设计**
比如你需要设计一个 Set 的数据结构,提供 lowerBound 和 add 两个方法。lowerBound 的意思是,找到比某个数小的最大值 算法1O(n) lowerBound O(1) add
使用数组存储,每次打擂台进行比较,插入就直接插入到数组最后面 算法2O(logn) lowerBound O(logn) add
使用红黑树(Red-black Tree)存储,Java 里的 TreeSet,C++ 里的 map 上
面两个算法谁好谁坏呢?根据情况选择不同的算法

不一定谁好谁坏!要看这两个方法被调用的频率如何。
如果 lowerBound 很少调用, add 非常频繁, 则算法1好。
如果 lowerBound 和 add 调用的频率都差不多,或者 lowerBound 被调用得更多,则算法2好
不过通常来说,在面试中的问题,我们会很容易找到一个像算法1这样的实现方法,其中一个操作时间 复杂度很大,另外一个操作时间复杂度很低。
通常的解决办法都是想办法增大较快操作的时间复杂度来加速较慢操作的时间复杂度。
  
###**哈希表 Hash **
支持操作O(1) Insert / O(1) Find / O(1) Delete
问这些操作都是 O(1) 的前提条件是什么?
 
哈希表(HashMap / unordered_map / dict) 任何操作的时间复杂度从严格意义上来说 都是 O(keySize) 而不是 O(1)

134. LRU缓存策略
657. Insert Delete GetRandom O(1)
954. Insert Delete GetRandom O(1) - Duplicates allowed 99成人面试都做不出来,可以放弃
209. 第一个只出现一次的字符

Data Stream类问题
960. First Unique Number in a Stream II
138. 子数组之和
105. 复制带随机指针的链表
171. 乱序字符串
124. 最长连续序列

###Heap
支持操作O(log N) Add / O(log N) Remove / O(1) 
Min or Max 
Max Heap vs Min Heap

4. 丑数 II
方法一:使用 PriorityQueue 
方法二类似归并排序的分治算法 
方法三自底向上的两两归并算法
时间复杂度均为 O(NlogK)
海量数据处理算法与面试题全集》 http//www.jiuzhang.com/tutorial/big-data-interview-questions

104. Merge K Sorted Lists
612. K个最近的点
545. 前K大数 II
613. 优秀成绩
486. Merge K Sorted Arrays
81. 数据流中位数
544. 前K大数
401. 排序矩阵中的从小到大第k个数

********************第九章  数组 预习笔记********************

预习部分笔记

**子数组与前缀和**

子数组:常考:多大,多小,之和,之差
技巧:用一个前缀公式:Sum(i - j) = PrefixSum(j + 1) - PrefixSum(i)
练习题:
41. 最大子数组
44. 最小子数组
138. 子数组之和,第八章做过了

两个排序数组的中位数
65. 两个排序数组的中位数,难题
这个题有三种做法:

基于 FindKth 的算法。整体思想类似于 median of unsorted array 可以用 find kth from unsorted array 的解题思路。
基于中点比较的算法。一头一尾各自丢掉一些,去掉一半的时候,整个问题的形式不变。可以推广到 median of k sorted arrays.
基于二分的方法。二分 median 的值,然后再用二分法看一下两个数组里有多少个数小于这个二分出来的值。

基于二分的算法
教程
问答(5)
问题描述
求两个排好序的数组合并在一起之后,他们最中间的那个数是谁。如果总共有偶数个数,那么返回中间的两个数的平均值。

LintCode 练习地址:http//www.lintcode.com/problem/median-of-two-sorted-arrays/

算法描述
我们需要先确定二分的上下界限,由于两个数组 A, B 均有序,所以下界为 min(A[0], B[0]),上界为 max(A[A.length - 1], B[B.length - 1]).
判断当前上下界限下的 mid(mid = (start + end) / 2) 是否为我们需要的答案;这里我们可以分别对两个数组进行二分来找到两个数组中小于等于当前 mid 的数的个数 cnt1 与 cnt2,sum = cnt1 + cnt2 即为 A 跟 B 合并后小于等于当前mid的数的个数.
如果 sum < k,即中位数肯定不是 mid,应该大于 mid,更新 start 为 mid,否则更新 end 为 mid,之后再重复第二步
当不满足 start + 1 < end 这个条件退出二分循环时,再分别判断一下 start 跟 end ,最终返回符合要求的那个数即可
算法详解
如果对该算法有点疑问,我们下面来详细讲解一下:

这一题如果用二分法来做,其实就是一个二分答案的过程
首先我们已经得到了上下界限,那么答案必定是在这个上下界限中的,需要实现的就是从这个歌上下界限中找出答案
我们每次取的 mid,其实就是我们每次在假设答案为 mid,二分的过程就是不断的推翻这个假设,然后再假设新的答案
需要满足的条件为:
上面算法描述中的 sum 需要等于 k,这里的 k = (A.length + B.length) / 2. 如果 sum < k,很明显当前的 mid 偏小,需要增大,否则就说明当前的 mid 偏大,需要缩小.
最终在 start 与 end 相邻的时候退出循环,判断 start 跟 end 哪个符合条件即可得到最终结果

**如何写 Comparator 来对区间进行排序?**
对于数组、链表、堆等结构,标准库中排序方法,往往是对于基本类型的升序排序,有的时候,不一定能满足我们的要求。例如我们有一些特殊的顺序要求,或待排序的对象类型不是基本类型。此时,就需用到自定义排序。自定义排序可以用在很多地方,比如数组排序,堆的排序规则等。Java实现自定义排序,主要有两种方法:
1.实现Comparable接口:
2.定义比较类:

在排好序的区间序列中插入新区间
30. 插入区间
793. 多个数组的交集
156. 合并区间

**外排序与K路归并算法**
外排序算法(External Sorting),是指在内存不够的情况下,如何对存储在一个或者多个大文件中的数据进行排序的算法。
外排序算法分为两个基本步骤:

将大文件切分为若干个个小文件,并分别使用内存排好序
使用K路归并算法(k-way merge)将若干个排好序的小文件合并到一个大文件中
486. Merge K Sorted Arrays
104. Merge K Sorted Lists

**简单位运算操作**
365. 二进制中有多少个1
https//www.jiuzhang.com/tutorial/?request=bit-manipulation更多练习题

**线段树 Segment Tree**
线段树是一种高级数据结构,也是一种树结构,准确的说是二叉树。它能够高效的处理区间修改查询等问题。

**PPT部分笔记**
请在随课教程中先修如下知识
• 如何用 Comparator 对区间进行排序
• 在排好序的区间序列中插入一个段新区间
• 快速选择算法 Quick Select(返回第三章复习)
• K 路归并算法(K-way Merge Algorithm)
• 子数组与前缀和(Subarray & Prefix Sum)
• 如何用位运算来数二进制中 1 的个数

6. 合并排序数组 II
64. 合并排序数组
839. 合并两个排序的间隔列表
577. 合并K个排序间隔列表

547. 两数组的交集
548. 两数组的交集 II
654. 稀疏矩阵乘法
931. Median of K Sorted Arrays

149. 买卖股票的最佳时机
405. 和为零的子矩阵
944. Maximum Submatrix
943. Range Sum Query - Immutable
665. 平面范围求和 -不可变矩阵

840. 可变范围求和

####**Binary Indexed Tree**
又名Fenwick Tree 中文名树状数组 简写BIT 基于“前缀和”信息来实现——
Log(n) 修改任意位置值
Log(n) 查询任意区间和
功能特性
对于一个有 N 个数的数组,支持如下功能
update(index, val) // logN 的时间内更新数组中一个位置上的值 getPrefixSum(k) // log(K) 的时间内获得数组中前 K 个数的和

虽然名字叫做 Tree,但是是用数组(Array)存储的
BIT 是一棵多叉树,父子关系代表包含关系
用 getPrefixSum(k) 实现 getRangeSum(x, y) 

**BIT的两个操作总结**
GetPrefixSum(x)
不断的做 x = x - lowbit(x) 直到 x = 0 update(x, val)
• 计算出 delta = val - A[x] 也就是增量
• 从 x 开始,不断的将 BIT[x] += delta,然后 x = x + lowbit(x),直到 x > N
817. 范围矩阵元素和-可变的
249. 统计前面比自己小的数的个数

**Binary Indexed Tree 总结**
Binary Indexed Tree 事实上就是一个有部分区段累加和数组
把原先我们累加的方式从
for (int i = index; i >= 0; i = i - 1) sum += arr[i];
改成了
for (int i = index+1; i >= 1; i = i - lowbit(i)) sum += bit[i];
这样我们很容易将这个算法拓展到 2D, 3D ...


***练习题***
Required(1/12)Optional(1/11)Related(0/11)
839. Merge Two Sorted Interval Lists
40%
547. Intersection of Two Arrays
26%
138. Subarray Sum
32%
64. Merge Sorted Array
35%
41. Maximum Subarray
40%
944. Maximum Submatrix
38%
931. Median of K Sorted Arrays
23%
840. Range Sum Query - Mutable
47%
654. Sparse Matrix Multiplication
60%
577. Merge K Sorted Interval Lists
43%
486. Merge K Sorted Arrays
31%
65. Median of two Sorted Arrays
27%

未完待续

认识你是我们的缘分,同学,等等,学习人工智能,记得关注我。

 

九章算法 第七到九章 预习笔记_第1张图片

微信扫一扫
关注该公众号

《湾区人工智能》

你可能感兴趣的:(AI学习笔记)