基础新手
链表
注意事项
注意保存上下文环境。注意gc,不要有垃圾变量。换头结点注意考虑头
对于链表不要在乎是O(n)还是O(2n)
长短链表互换
习题
K个节点的组内逆序调整 ?
leetcode:K 个一组翻转链表
找n函数
逆转函数
第一组是否找齐
之后每组处理:start先指向下一组头,遍历完再指向尾
不在于算法,而在于coding能力
链表相加
注意长短链表互换,链表1结点+链表2结点+进位,注意最后进位需要补结点
阶段1:两个相加+进位 while(shortNode != null)
阶段2:单个链表+进位 while(longNode != null)
阶段3:链表结束,仍有进位,补结点 if(carry != 0)
上面三个阶段分别是3个循环
有序链表合并
注意换头,有一个为空就出循环
合并K个升序链表
leetcode:合并K个升序链表
利用小根堆,注意返回空
m条链表,总结点n个。时间复制度 O(log(m)*n)
树
注意事项
对于全都遍历的,使用递归很方便
习题
判断两棵树是否相同
leetcode:相同的树
注意异或^的使用,只有一个成立时为真
对于全都遍历的,使用递归很方便!!!
判断树是否是镜面树
leetcode:对称二叉树
虽然是简单,但是也做了不少时间
传两个头结点(left,right)!!!我又用了层序遍历
判断left.val == right.val && fun(left.left, right.right) && fun(left.right, right.left)
返回树的最大深度
leetcode:二叉树的最大深度
用递归思想代码十分简单。差点又想用层序
先序遍历和中序遍历建树
leetcode:从前序与中序遍历序列构造二叉树
没啥说的,常见题
二叉树的层序遍历(逆序)
leetcode:二叉树的层序遍历II
注意用linkedlist
没啥,做的工具类都层序了,只不过要逆序而已
层序遍历可以用迭代器(index记录需要运行的次数)避免下标取不对问题
判断是否是平衡二叉树
leetcode:平衡二叉树
本来是easy难度,用递归。
总想着对深度计算的复杂度进行化简,把题做复杂了。先做出来,再优化
返回两个信息(是否平衡/高度),遍历左子树,遍历右子树,判断左右高
平衡搜索二叉树
leetcode:验证二叉搜索树
返回值:树上最大值、最小值、是否是二叉搜索树
能否组成路径和(返回能否)
leetcode:路径总和
递归判断,左边,右边
能否组成路径和(返回所有的路径)
leetcode:路径总和II
和上题一样,只不过把路径存下来
体系学习
排序
选择排序:每次选择最小数,换到最前面
插入排序:后面的依次往前放
冒泡排序:每次出来一个最值,不断交换
二分法:求局部最小。先判断第一个和最后一个,再判断中间相比临近值是上升趋势还是下降趋势
异或
异或:无进位相加
性质:0^N=N N^N=0 满足交换律和结合律
习题
不用额外变量交换两个数
a=a^b
b=a^b
a=a^b
注意,a,b不能指向同一位置
leetcode:交换数字
一个数组一种数出现了奇次,其它数出现偶次,找到这个数。空间O(1),时间O(n)
数组全异或即可
leetcode简单版:只出现一次的数字
整型的int,提取出最右侧的1
a&(~a+1)即a与上自己的取反加一。a的取反加1是自己的负数
一个数组有两个数出现了奇数次,其它数出现偶数次,怎么找到并打印。空间O(1),时间O(n)
1.全^,找到a^b的结果c
2.找到c最右侧的1,这个位置rightOne,a和b其中一个是1,一个是0
3.然后再遍历数组,只异或rightOne是1的,就能找到一个数
4.再拿c^这个数,就得到另一个数了
leetcode简单版:只出现一次的数字iii
一个数组中一种数出现k次,其它数都出现M次,M>1,K<M,找到出现K次的数。空间O(1),时间O(n)
利用一个32位数组,记录数组中每个数二进制位,有1的个数
依次遍历每个数,填充32位的数组(可以不断获取最右侧的1,迅速得到数组该加的位置。再异或最右侧的1,来抵消,while条件)
32位数组里的个数整除M,说明目标数在这一位是0,否则是1(可以利用1右移,与结果取或|)
leetcode简单版:只出现一次的数字ii
常用数据结构
链表翻转、定值删除
master公式
T(N)=a*T(N/b)+O(N^d)的递归函数可以用master公式来确定时间复制度
log(b,a) < d,复杂度为O(N^b)
log(b,a) > d,复杂度为O(N^log(b,a))
log(b,a) == d,复杂度为O(N^d*logN)
哈希表:hashmap/hashset。基本数据类型按值传递,引用数据类型按引用传递
有序表:TreeSet/TreeMap。红黑树、avl树、sb树、跳表。除了增删改查,能获得最小/最大/离某个key最近的。
哈希表增删改查时间复制度都是O(1),有序表功能多时间复杂度都是O(logN)
习题
栈(用数组和链表实现)
数组记最后一个位置即可
链表头插法
队列(用数组和链表实现)
数组需要用循环数组,记录开始位置,结束位置,里面的数据数
尾插法
栈和队列的相互实现
仅用栈实现队列:两个栈
仅用队列实现栈:只能用两个队列,不停取插。复杂度较高。在插入的时候处理,因为peek操作可能多次,保证peek的复杂度低一点
leetcode:用栈实现队列
leetcode:用队列实现栈
实现一个特殊的栈,基本功能+返回当前栈中最小元素,pop,push,getMin时间复杂度O(1)
用两个栈,一个普通的。另一个记录当前栈中最小元素
push的元素比栈中元素小则压入新元素,否则栈中元素再压一个
leetcode: 包含min函数的栈
归并排序
伪代码:
void mergeSort(arr,left,right) { int mid = left + ((right - low) >> 1); mergeSort(arr, left, mid); mergeSort(arr, mid+1, right); merge(arr, left, mid, right); } void merge(arr, left, mid, right) { int help[right - low + 1]; int i=left,j=mid,index=0; while(i<=mid && j<=right) { help[index++] = left[i] < right[j] ? left[i++] : right[j++]; } while(i <= mid) help[index++] = left[i++]; while(j <= right) help[index++] = right[j++]; arr = help; }
时间复杂度:T(N)=2*T(N/2) + T(N),根据master log(2,2) = 1,则N * logN
空间复制度:O(N)
习题
一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来叫数组小和。求数组小和。时间复杂度O(nlogn)
利用归并merge过程。
求小和可以转化为某个数右边比它大的有几个,就这个数乘几加到结果上
merge过程分左右数组。
右边比它大的个数 ==> 合并时对于每个左边的数看,右边还没合并的个数
前面数a和后面任意一个数b,如果a>b就是一个逆序对。求所有逆序对的个数。时间复杂度O(nlogn)
找右边比它小的数 ==> 合并时对于每个左边的数看,右边已经合并的个数。只剩左边数组也要计数,相同优先放左边
!!!注意,老师用的从后往前merge。
老师从后往前merge是对的。
如果从前往后,在最后只剩左边数组的时候,也要计数
找右边比它小的数 ==> 从后往前,对于每个左边的数,放到help时,看右边比它小的数有几个。相等优先放右边数组中的数
只剩左边数组是否要考虑,需要考虑只剩左边数组的时候,再放到help时,是否需要计数
leetcode:数组中的逆序对
对于每个数num,求有多少个后面的数乘2依然小于num,求总个数
找右边乘2比它小的数 ==> 合并时对于每个左边的数看,右边乘2小于num的数
leetcode:翻转对
本质上是能快速计算出数量。具体合并可以放在最后不影响时间复杂度,只要能得到数量
给定数组arr,两个整数lower和upper,找arr上多少个子数组(连续的)累加和在[lower,upper]范围上
利用前缀和,先求出前缀和
转化为求 前缀和数组右减左落在[lower,upper]上
即转化为:找右边落在[num+lower,num+upper]上的个数 ==> 合并时对每个左边数看,右边落到[num+lower,num+upper]上的个数,因为num有序,所以两个指针可以不回退
复杂度分析
普通left/right指针,中间加一遍O(n^3)。
用前缀和不用加了,O(n^2)。
用前缀和,之前感觉可以用双指针,单前缀和不是有序的,指针同样需要完全遍历,还是O(n^2)
快速排序
伪代码
void process() { mid = partition() // 如果不会,可以不返回区间,就返回一个mid就行。时间复杂度是一样的 process(left, mid - 1) process(mid + 1, right) } int partition(arr, left, right) { index = left; // 代表小于中间数区域的最右边 lessEqual = index - 1; while(index<right) { if (arr[index] <= arr[right]) { // 如果一个数小的,那么就和小于区域的最右边,再加1的数互换。即大于区的第一个数 swap(arr, index, ++lessEqual) } index++; } // 把中间值放到中间,即和大于区第一个数交换 swap(arr, ++lessEqual, R) return [] }
感觉老师的代码有误,确认一下
老师的代码非常简洁!!
自己写了很久的代码
private static int partitionWjx(int[] arr, int left, int right) { int midTmp = arr[right]; int i = left; int j = right; while (i < j) { // 注意至少有一个等于号,否则遇到相等的数字会死循环 while (i < j && arr[i] <= midTmp) { i++; } if (i < j) { swap(arr, i, j); } while (i < j && arr[j] > midTmp) { j--; } if (i < j) { swap(arr, i, j); } } return i; }
习题
荷兰国旗,就是数组中有多个0,多个1,多个2,排序。要求空间O(1),时间O(n)
快排的一次partition
leetcode:颜色分类
两个指针,小于区右边界less和大于区左边界more。
如果相等index++,
如果小于则与++less互换,index++
如果大于则与--more互换,index不++,因为这个数刚换过来,还没看过
最后不要忘记把中间的数放过来。
堆
完全二叉树(数组形式)
heapInsert和heapify操作
heapInsert(arr,index):对index位置的数向上调整堆,logN
heapify(arr,index,heapsize):对index位置的数向下调整堆,下标不超过heapsize,logN。将左右节点大的放上来
优先级队列就是堆。大小顶堆
堆排序
建堆O(n*logn),成为大顶堆
最大值和堆尾交换,size--,调整堆。
周而复始,直到全部调整完毕,即size=0,则排序完成
习题
几乎有序的数组,如果把数组排好序,每个节点移动的距离不超过k。选择合适的排序策略
堆排序。找父子节点的下标,很快就能到达k
最大线段重合问题。给定很多线段,每个线段都有两个数[start,end]表示线段开始结束位置,找最多重合区域中包含了几条线段
按start排序。
依次按end的大小进入最小堆中
进去之前,把堆里数字 小于准备进去线段的start 的元素出堆
最多重合区域即堆大小的最大值
加强堆
加强堆是在堆里加一个map,记录值对应的下标在哪里。优点:
1.当想对值更改时,可以迅速找到下标,修改并调整堆
2.当想删除某个堆中元素时,可以迅速找到并调整
给定一个整型数组表示客户编号,和布尔型数组表示购买物品1个和退货1个,求每次购买/退货时购买最多的k个用户。某个用户购买数为0但又退货则忽略,不够k则输出尽可能多的,选k用户按购买商品数量,和时间早优先,替换k时也是。题目在左老师第7节的ppt。代码:class07.Code02_EveryStepShowBoss
两个最小堆:cands存储 候选者(排序方法商品数量多、进入的时间),topK存储top的人(排序方法商品数量少、进入的时间)
有用户购买
是否在cands里,是否在topK里。
在cands里,加
在topk里,加
都没有
topK不满,直接进入topK topK满,进入cands,
有用户退货
是否在cands里,是否在topK里。都没有则忽略
在cands里,减,是否为0
为0,则删除,调整堆
不为0,则减,调整推
在topK里,减,是否为0
为0,则删除,调整堆
不为0,则减,调整堆
比较cands顶是否可以替换topK顶,可以则替换
前缀树
一颗遍历字符串数组的树,节点上有value,pass,end。
value是当前字符,层级代表字符串中第几个出现 pass代表出现的次数 end代表以这个当前路径结尾的次数 遍历每个字符串,沿途节点pass加1,结束时节点end加1
结构
insert(String str)添加某个字符串
search(String str)搜索字符串在有几个。O(str.length)的复杂度
delete(String str)删掉某个字符串
prefixNumber(String str)以str做前缀的有几个。O(str.length)的复杂度
不基于比较的排序
计数排序,数字当下标
基数排序,进到0-9的桶里,不断倒出。可以使用一个桶实现。数字出现的前缀和是下标
排序方法 | 时间复杂度 | 额外空间复杂度 | 稳定性 |
---|---|---|---|
选择排序 | O(N^2) | O(1) | 无 |
冒泡排序 | O(N^2) | O(1) | 有 |
插入排序 | O(N^2) | O(1) | 有 |
归并排序 | O(N*logN) | O(N) | 有 |
随机快排 | O(N*logN) | O(logN) | 无 |
堆排序 | O(N*logN) | O(1) | 无 |
计数排序 | O(N) | O(M) | 有 |
基数排序 | O(N) | O(N) | 有 |
为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并
jdk中排序源码。下面描述的是大致逻辑,但是细节还是不一定一致。总之jdk的排序算法很牛逼,做了非常多的优化,目前我的水平还不足以完全看懂。基本就是插排、快速、归并混合
对于基本数据类型,不需要考虑稳定性
数据量<47,选择插排
数据量>47且<286或不基本有序,用优化的快排。快排会过滤边界有序,且子排序小于47会用插排
数据量>286且基本有序,用归并
对于对象类型
长度小于32用mini-TimSort。
大于31用TimSort
TimSort混用归并、插排、二分搜索等。
是
否
是
否
是
否
start
len<286
len<47
是否基本有序,降序组的数量是否大于67
插入排序
快速排序
归并排序
End
链表问题
习题
找链表的中点。遍历不超过n
快慢指针,快走2,慢走1
leetcode:链表的中间结点
判断链表是否回文
找到中点,反转,比较
leetcode:[234]回文链表
链表划分为左边小、中间等、右边大形式。荷兰国旗/颜色划分链表版
三个链表结点依次串起来
带有随机指针的复制
leetcode:[138]复制带随机指针的链表
1.拷贝出的节点放到原节点的next里。
2.复制next指针
3.拆分两个链表
两个有环也可能无环单链表,头结点head1和head2。如果相交返回相交的第一个节点,否则返回null
leetcode:[160]相交链表 [142]环形链表II
首先一个求环的函数,求出两个链表的环
如果一个有环,一个无环则必不相交
如果均无环。终点一样则有相交利用相交链表求,不一样则不相交。
如果均有环。认为第二次到环入口为终点。看终点是否一致,一致则相交,不一致则不相交。一致时,利用相交链表求即可,只不过两个链表的终点一定要选择一样
注意均有环的情况,选定某一个环入口为终点。两个链表终点一定要一样
不给头结点,只给p,能不能删除p节点,或者p前添加一个节点
删除p节点,后一个节点的值移到p,再删除p的下一个节点。地址改不了了,只能改值了
向前添加,新建一个节点,拷贝p的值,然后把p的值改为需要添加的,再把新节点放到p后
leetcode:[237]删除链表中的节点
二叉树
知识
先中后遍历。
无论先中后,左边节点都在左边,右边节点都在右边
先序遍历:父亲在左边,孩子在右边 左边节点都在左边,右边节点都在右边 父亲+左边 自己 孩子加右边
中序遍历:左孩子在左边,有孩子在右边,父亲根据本身在左子树还是右子树可能不同 左边节点都在左边,右边节点都在右边 左边+左孩子+[父] 自己 右边+右孩子+[父]
后序遍历:孩子在左边,父亲在右边 左边节点都在左边,右边节点都在右边 孩子+左边 自己 父亲加右边
所以,先序遍历左边节点和后续遍历右边节点交集是父亲,先右&&后左=孩子,先左&&后左=左边。中序右-先序右=父或空
先中后递归伪代码如下
void traverse(node) { // 先序,print(node.val) traverse(node.left); // 中序,print(node.val) traverse(node.right); // 后序,print(node.val) }
层序 leetcode102 二叉树的层序遍历
void traverse(head) { Queue queue = new Queue(head); while(!queue.isEmpty) { node = queue.remove(); print(node); queue.add(node.left); queue.add(node.right); } }
先序非递归 leetcode144 二叉树的前序遍历
// 和层序遍历类似,只不过是层序用队列,先序用栈。层序先左后右。先序先右后左。 下面省略了判空操作,注意判空 void traverse(head) { Stack stack = new Stack(head); while(!stack.isEmpty) { node = stack.pop(); print(node); stack.add(node.right); stack.add(node.left); } }
后序非递归 leetcode145 二叉树的后序遍历
// 先序是 先左右 调整右左顺序就是 先右左 倒着输出就是 左右先(后序) void traverse(head) { Stack stack = new Stack(head); Stack printStack = new Stack(); while(!stack.isEmpty) { node = stack.pop(); printStack.add(node); stack.add(node.left); stack.add(node.right); } while(!printStack.isEmpty){ node = stack.pop(); print(node.val); } }
中序非递归 leetcode94 二叉树的中序遍历
// 取当前节点,压栈并向左递归压栈,到底弹出打印转向右边 void traverse(head) { Stack stack = new Stack(); curNode = head while(curNode != null || !stack.isEmpty) { while(curNode != null) { stack.push(curNode); curNode = curNode.left; } curNode = stack.pop(); print(curNode.val); curNode = stack.right; } }
习题
序列化与反序列化
leetcode:297 二叉树的序列化与反序列化
前序、层序、后序。除了中序都可以。后序需要转化为类前序!!!
自己做出来了前序,但是代码调了很多次。老师的代码十分简洁好记。一定记得学习老师的代码!!!不只是学习难题
private String pre(TreeNode root) { Queue<String> strQueue = new LinkedList<>(); pre(root, strQueue); return strQueue.toString().replaceAll(" ", ""); } private void pre(TreeNode root, Queue<String> strQueue) { if (root == null) { strQueue.add(null); } else { strQueue.add(String.valueOf(root.val)); pre(root.left, strQueue); pre(root.right, strQueue); } } private TreeNode preDe(String data) { List<String> collect = Arrays.stream(data.substring(1, data.length() - 1).split(",")).collect(Collectors.toList()); TreeNode root = preDe(collect); return root; } private TreeNode preDe(List<String> collect) { String remove = collect.remove(0); if (remove == null || remove.equals("") || remove.equals("null")) { return null; } TreeNode root = new TreeNode(Integer.parseInt(remove)); root.left = preDe(collect); root.right = preDe(collect); return root; }前序:头左右 后序:左右头 后序.reverse:头右左。不知道为什么,头需要先有
层序就不写了,工具类就是
将N叉树编码成二叉树
leetcode 431 收费题
大孩子左子树,兄弟右子树
看老师的代码,自己写不出来,虽然知道思路
如何设计一个打印整棵树的打印函数
横着打印树,不知道目的是什么
求二叉树最宽的层有多少个节点
leetcode应没有。有个更难的leetcode622,算空节点,需要根据下边减才行
二叉树的后继节点
右的一直左。没右则往父,作为左子树的。找到根没左子树则null
leetcode:剑指off53 二叉搜索树中的中序后继。没有父节点,
我用了中序遍历能过。
答案用二叉搜索树的性质,后继节点是大于p的最小值。p有右则找右的最左;没右则从root开始比较,node=root
p.val < node.val,则后继可能是node或在node的左子树。result更新为node
p.val > node.val,则后继在node的右子树
p.val == node.val,因为之前已经判断过没有右节点了,那就是情况1中result最后更新的结果
折纸问题
对折纸,返回上下折痕。利用树的结构,左子树是down,右子树是up
自己做的还是多遍历了一次,不需要先 生成树
是否是完全二叉树
leetcode:958 二叉树的完全性检验
需要子孩子信息:是否满树,是否完全,高度。有以下四种情况
1.满树,高相等
2.左完全,右满,左高-右高=1
3.满树,左高-右高=1
4.左满,右完全,高相等
高是子树最大高+1,满是左满右满且高等,完全有上面四种情况
二叉搜索树
leetcode:98 验证二叉搜索树
左边是,右边是
左边最大<自己<右边最小
这个题需要在前面判空
在里面也能处理,最大值设最小值,最小值设最大值。但是对于普通的int最大值可能过不了
平衡二叉树
leetcode:110 平衡二叉树
左边是,右边是
左高右高差绝对值<=1
是不是满二叉树
方法一:左满+右满+高等
方法二:2^(高度) - 1 = 节点数 高度和节点数也可以通过Info收集
最大的二叉搜索子树的大小、头结点
过当前节点和不过当前节点
过当前节点,左是+右是+左最大<当前<右最小。大小=左节点数+右节点数+1
不过当前节点,大小=左右最大值
class Info{ bool isSearch; int max; int min; int nodeNum; TreeNode maxHead; } Info search(TreeNode root) { if (root == null) { return new Info(true, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, null); } Info info = new Info(); Info left = search(root.left); Info right = search(root.right); if (left.isSearch && right.isSearch && left.max < root.val < right.min) { info.isSearch = true; info.max = Math.max(root.val, right.max); info.min = Math.min(root.val, left.min); info.nodeNum = left.nodeNum + right.nodeNum + 1; info.maxHead = root; } else { info.isSearch = false; info.max = Math.max(root.val, right.max); info.min = Math.min(root.val, left.min); info.nodeNum = Math.max(left.nodeNum, right.nodeNum); info.maxHead = left.nodeNum > right.nodeNum ? left.maxhead : right.maxhead; } return info; }上面代码正确的:找到二叉树中的最大搜索二叉子树__牛客网
整棵二叉树的最大距离,距离就是此节点到另一个节点所经过的节点数
最大距离不一定经过根结点,但是一定经过某个节点
所以就是某个节点,左深度+有深度 的最大值即可。
牛客:NC195 二叉树的直径 二叉树的直径_牛客题霸_牛客网
a,b最低公共祖先
leetcode:236 二叉树的最近公共祖先
a,b是否都在左,a,b是否都在右。如果都在左则向左,都在右则向右,在不同则当前就是最低公共祖先
info: haveA,haveB,resultNode
left.haveA && left.haveB -> resultNode = left.resultNode , haveA = true, haveB = true
left.haveOne && right.haveOne ... -> resultNode = root , haveA = true, haveB = true
!left.haveOne && !right.haveOne ->
root = One -> resultNode = null, haveA = true, haveB = false
root != One -> resultNode = null, haveA = false, haveB = false
上面是原始思路,比较混乱,下面是整理上面后的思路。
需要返回信息:是否有P,是否有Q
有P:左有+右有+自己是
有Q:左有+右有+自己是
公共祖先:分开则是自己,都在左则拿左的,都在右则拿右的,左/右有1个则判断自己是否是另一个,其它情况就是null
老师的思路更简单一些,公共祖先:左有结果就是做,右有结果就是右,否则有P有Q就是自己
party快乐值
class info { int comeHappy; // 来的最大快乐值 int goHappy; // 不来的最大快乐值 } main { left;right; info.comeHappy = left.goHappy + right.goHappy + root.happy; info.goHappy = max(left.comeHappy, left.goHappy) + max(right.comeHappy, right.goHappy); }上面应该问题不大,不过这个题不是二叉树,是多叉树
class info { int comeHappy; // 来的最大快乐值 int goHappy; // 不来的最大快乐值 } main { left;right; info.comeHappy = sum(children, goHappy) + root.happy; // 每个子树goHappy 的和 info.goHappy = sum(max(each.comeHappy, each.goHappy)); // 每个子树comeHappy,goHappy的最大值 的和 }
贪心算法
对于贪心算法的学习主要以增加阅历和经验为主,无需证明。遇到就会,不是原题就放弃。
习题
一个字符串组成的strs,把所有字符串拼接起来,返回所有拼接结果中,字段序最小的
并不是先排序,再拼接,反例:b,ba 排序结果是b,ba 但是直接拼接的bba大于bab
结论:排序方法是ab < ba则ab在前
牛客:NC85 拼接所有的字符串产生字典序最小的字符串 拼接所有的字符串产生字典序最小的字符串_牛客题霸_牛客网
给点strs,有X表示墙,.表示居民点。居民点可以放灯,能照亮自己及旁边居民。最少的灯照最多的居民
当前可以放(能到这里有个前提,就是前面不用管),下面能不能放,能放放下面否则放这里。当前i
放这里,直接到i+2
放下面,直接到i+3
类似的题,牛客:WY47 安置路灯 安置路灯_牛客题霸_牛客网
注意,牛客的题在障碍物上也能装灯,比上面的题简单一点
金条切两半,划分和长度数值一样的铜板,怎么且省铜板
哈夫曼编码
[10,20,30],转化为不断拿两个数出来加成1个数,再放进去,求怎么拿和最小问题
要捋清楚结果怎么算,堆里,每次出堆累加,到顶时不要加。(这个需要理清楚)
牛客:切金条__牛客网
有会议开始结束时间。在一个会议室里安排最多的会议
按结束时间排序,结束时间短的优先讲。一个会议结束时需要剔除不满足的会议
heap = new PriortyQueue(); each -> heap.add(cat, endTime); curStartTime count = 1; while (heap.size > 0) { node = heap.pop(); if (node.startTime < curStartTime) { // 过期任务 } else { // 安排 count++; // curStartTime = node.startTime; // 这里错了!!! curStartTime = node.endTime; } } return count;没有大问题,对数器跑过了,有个地方写错了。找leetcode遇到相似的网上贴,其实不一样,这个也告诉我,一定要精心审题,体会用例,不要看到感觉做过就开始,结果可能是驴唇不对马嘴!!!
有净收益数组和花费数组,初始资金和最多参加的项目,求最多收益
按启动资本排序,可以启动的入堆(堆以收益排序)。直到不能启动或全部入堆
拿顶,任务数--,资本加顶收益,再执行上一步
任务书==0 或 没有可启动的 结束
返回当前资本
leetcode 502 IPO
并查集
知识
一种数据结构
并:a,b所在的集合合并。复杂度O(1)
查:a,b是否在同一集合。复杂度O(1)
特点
每个节点有向上指针
a,b结点不断往上找的头相同,就在同一集合
a,b合并,就将代表节点(不断往上找的头结点)合并,b代表节点指向a的代表节点
实现方式
利用集合:所有节点map<id,node>,节点的父亲map<node,node>,代表节点所在集合尺寸map<node,int>。id是原本真实的结构
利用数组:父集合,某个集合里有多少元素,辅助help,总集合数
优化
小集合挂大集合下面
向上找时,沿途的链变扁平。实现方式就是路径上的节点记录下来,找到父节点再遍历一遍直接指向父节点
应用
两块区域合并问题
常用在图等领域
习题
城市相连是一个省,有几个省问题
leetcode 547 省份数量
构造并查集
i,j,par,size i*200+j -> node
new node(i,j,c,1)
totalsize++
遍历数组,union
union(i,j)
复杂度O(2*n*m)
整体没问题,就是找父亲的方式写错了,调了很久
岛问题
leetcode 200 岛屿数量
深度优先,华为od面过
岛问题2
上面的题,变成初始全是0(水),给一个左边变成1。然后输出当前岛屿个数
并查集
集合个数
Node: i,j,par
UnionFind: i,j->node findHead union
main: 来一个节点,加入UnionFind,union上下左右
关键在于加节点的时候,你要知道union什么,肯定不是全union
leetcode 305 岛屿数量II,收费题,老师也没写对数器
岛问题2 - 扩展
如果matrix很大,设计并行计算方案
map:先分段进行,reduce:再对分界点union
图
知识
邻接表、邻接矩阵
无递归
宽度优先:利用队列
深度优先:利用栈,进节点,出节点并进所有子节点,出节点并进所有子节点
进去之后,从左边出就是自己的兄弟(宽度优先),从右边出就是自己的孩子(深度优先)
拓扑排序
找到所有入度为0
全部删除,继续找
直到结束
最小生成树
就是权值最小的边连接整个图。是最小带权生成树的简称
Kruskal算法(最小生成树算法)
边按权值排序,从小往大进集合
如果没回路就要,否则丢弃
直到遍历完所有边
伪代码
// 判断回路用并查集比较好。 // 判断回路的方式就是看边中还没加进来的点(toNode),和已经加进来的点(fromNode)是否在一个集合里 // 就是只要边中两边的点没有同时已经加过了,就不是环 heap(edges::weight) while(heap.isNotEmpty) { edge = heap.poll(); if (isSameSet(node1, node2)) { result.add(edge); union(node1, node2); } } class Node{ int val; Node parent; } class unionFind{ find: Node ?= Node.parent union: node1.parent -> node2.parent isSameSet: node1.parent ?= node2.parent }
Prim算法(最小生成树算法)
任意点出发,加入一个点,解锁与点相邻的边
找到已解锁的最小的边,加入集合,并解锁与边相邻的点
不要环路的边
最短路径
求从v0到各其它顶点的最短路径
迪杰斯特拉
源点加入堆(到v0节点距离升序)
从堆中取点u,处理与u相邻的点。判断经过自己是否更近,更近就更新
min(到v0节点距离+次点到新点,v0本身到新点)
用visited存储是否当过跳转点,当过就不再访问
迪杰斯特拉加强堆优化
由于,过程中会根据实际值找点,更新点。所以用加强堆
能够迅速定位到点,修改点,并调整
习题
网络延迟时间
leetcode 743 网络延迟时间
trans(int[][]) dist[][] list -> k到其他节点的距离, 初始0,全无穷大 visited -> 是否访问过,初始全为0 拿出list中最小的u,且visited=0 遍历u那一行,判断 dist[u][each]+list[u] ? list[each] 更新list[] 最后list里的最大值就是答案class Edge{ Node from; Node to; int cost; // from到to的距离 } class node{ int val; int dist; // k到某个节点的最短距离 bool visited; // 是否被访问过 List<Edge> edgeList; } heap(node::dist); new allNode(): 1-n val=n dist=-1 如果是k则是0 map(n, node); // 用n找到map的node times.forEach -> map.get(times[i][0]).getEdgeList().add(edge(map.get(times[i][0]), map.get(times[i][1], times[i][2]))); while(heap.isNotEmpty) { node = heap.poll(); node.visited = true; for(edge: node.getEdgeList()) { if (!edge.to.visited) { if(edge.to.dist > edge.from.dist + edge.cost || edge.to.dist == null) { edge.to.dist = edge.from.dist + edge.cost; heap.addOrModify(edge.to); } } } }
暴力递归到动态规划
暴力递归习题
汉诺塔问题
leetcode:汉诺塔
第一遍想的有点问题,没有用长度
本质上是需要长度这个概念的
hanota(A,B,C,n) 将A上的n个盘子移动到C上
字符串全部子序列 无重复元素
leetcode:78 子集
对于一个数字,选或者不选。往下走
subsets(nums, start, result, resultList) { // 如果到达末尾则保存,并返回 start > s.length - 1 ? result.add(s); return; // 不用这个数字 subsets(nums, start+1, result, resultList); // 用这个数字 result.add(num[start]); subsets(nums, start + 1, result, resultList); result.add(num[start]); }自己以前想过二进制的方式。所有子集个数是2^n,从0-2^n的二进制形式就是所有子集的形式
字符串全部子序列,有重复元素(子集不要重复)
leetcode:90 子集ii
和无重复元素的结构类似
在前面进行一次排序,后面用一个HashSet<String>去重,直接result.toString()放进set里就行
字符串全部排序
leetcode:字符串的排列 剑指offer 38
permutation(s,start,result) { // 如果到达末尾则保存,并返回,result用set start > s.length - 1 ? result.add(s); return; // 让每个人都有机会当开始值。 当前选某一个数字 for i start-s.length { swap(start,j) // 开始值就按这个,后面的值的全排列 permutation(s,start+1,result) swap(start,j) } }没法说,这种题也不能一次过,还调了许久
字符串全部排列 ,有重复元素(子序列不要重复)
leetcode: 47 全排列ii
和普通全排列一样
最后用hashSet<String>去重即可
1,2,1和1,1,2是不同的子序列,所以不需要排序,只需要去重即可。
1,2,1和1,1,2是相同的子集,所以在之前排序一下
给一个栈,逆序,不申请额外的数据结构,只能使用递归函数
错误思路:pop -> 递归 -> push。 先pop的最后push,并没有逆序
一个移除并返回栈底的函数f
f -> 递归 -> push()。 这才对,把栈底拿到,最后push
函数f: 需依次移除栈顶元素 -> 如果是最后一个就返回 -> 元素再压进去
if (stack.size() == 1) { return stack.pop(); } else { Integer pop = stack.pop(); int i = f2(stack); stack.push(pop); return i; }
转动态规划习题
机器人从start到end的方法数。在一维上往左往右
对数器做了
往左走方法数 + 往右走方法数
两边拿牌
对数器做了
leetcode:486 预测赢家
两个函数,一个先手拿牌,一个后手拿牌
背包,不超过重量,能装下的最大价值