笔记连接
柠檬博客面试笔记
柠檬笔记
柠檬-数据结构(hsp)
简书-hsp目录
力扣hot100 分类 github
力扣笔记一些
多路归并求解问题
算法博主
力扣专栏
github链接
labuladong 公众号笔记
BAT霜神Leetcode刷题笔记
1.链表
1.1 双指针问题 倒数第k个
2.二叉树
二叉树 前、中、后 遍历
2.1 二叉树非递归 前序列
根节点入栈
输出根节点,再入栈右子节点。
再入栈左子节点。
/**
* @brief 先序遍历(非递归版本)
*
* @param root 根节点
*/
void preOrderIterative(treeNode *root)
{
if (NULL == root) return; // 空树
stack<treeNode *> nodeStack; // 创建一个栈保存节点
nodeStack.push(root); // 根节点进栈
while (!nodeStack.empty()) // 栈非空时迭代处理
{
treeNode *node = nodeStack.top(); // 保存栈顶节点
printf("%d ", node->data); // 访问节点数据
nodeStack.pop(); // 栈顶节点出栈
// 子节点入栈
if (node->right) nodeStack.push(node->right);
if (node->left) nodeStack.push(node->left);
}
}
2.2 二叉树非递归 中序
① 先沿着根的左孩子依次入栈,直到左孩子为空为止。
② 当前栈顶元素出栈,输出当前元素,(若右孩子为空,则继续执行,输出当前栈顶元素)
③ 否者执行① 右孩子节点不为空
口诀 :入栈向左一直走,出栈 访问右子树。
入栈 push(cur);
向左 cur=cue.left;
一直走 null
出栈 temp=pop();
访问 print(temp)
右子树 cur=temp.right;
while条件
while( p!=null || !s.empty())
需要两个条件时因为
A 出栈 A的右子节点为C ,此时栈未空。若结束循环 C未访问。
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
List<Integer> list=new ArrayList<>();
//注意 需要当前节点为空且栈为空才结束循环
while(cur!=null || !stack.empty()){
if(cur!=null){
//入栈
stack.push(cur);
// 向左
cur=cur.left;
}else{
// 出栈
TreeNode node=stack.pop();
// 输出
list.add(node.val);
// 向右子树
cur=node.right;
}
}
return list;
}
2.3 二叉树非递归 后续
助记 :入栈向左一直走,判定(右子树是否访问),出栈访问(标记、置空);
public void postOrder(TreeNode tree){
TreeNode cur=tree;//记录当前访问到了哪个节点
TreeNode r=null;//记录上一个visited的节点。
//因为因为从右边访问根节点,必定是右子树已经遍历
Stack<TreeNode> stack= new Stack<>();
while(cur!=null || !stack.empty()){
if(cur!=null){
// 入栈
stack.push(cur);
cur=cur.left;
}else{
// 访问到栈顶元素的左子树为NULL时,走到这个分支
//所以需要获取栈顶元素进行操作
// 只读取当前栈顶元素
cur=stack.peek();
// 右子节点 存在 且 未被访问过
if(cur.right!=null && cur.right!=r){
// 继续右子节点
cur=cur.right;
// stack.push(cur); 与上面重复可以不写
// cur=cur.left;
}else{
//cur .right 为空 或者被访问过了 访问根节点
cur=stack.pop();
System.out.println(cur.val);
//记录当前访问的节点时cur(走到下一个循环时,就可以知道
//cur上一个访问的节点)
r=cur;
//把cur置空,进入下一次循环,
//直到栈内无元素且cur为空时完成遍历
cur=null;
}
}//else
}//while
}
图片
2.4 层序遍历
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(root==null) return res;
queue.offer(root);
while(!queue.isEmpty()){
int size=queue.size();
List<Integer> list=new ArrayList<>(size);
for(int i=0;i<size;i++){
TreeNode node= queue.poll();
list.add(node.val);
if(node.left!=null) queue.offer(node.left);
if(node.right!=null) queue.offer(node.right);
}
res.add(list);
}
return res;
}
}
3.树旋转问题 右旋转
当节点进行右旋转时 左旋转同理
1.问题分析
当符合右旋条件时,
2.如果需要旋转的节点。
–2.1 如果当前节点的左子树的右子树的高度大于当前节点的左子树的左子树的高度。
对当前节点的左子树进行左旋操作。
2.2.再对当前节点进行右旋操作。
反之 正常右旋转
需要添加return
当节点进行左旋转时
例子: 先对根节点的左子树左旋,得到 :
3.1 avl 树代码链接
avl树代码链接
调整原则
- 降低树的高度。
- 保持二叉树的性质。
4.单调队列 解决滑动窗口最大值
理解
原图
//一般都习惯写数组索引从0开始 模板要改一下
for (int i = 0; i < n; i++) {
//i-k+1 代表滑动窗口内第一个元素的索引
// b.front() 是队头 当对头的元素从窗口划出时,队头元素出队
if (!b.empty() && b.front() < i - k + 1)
b.pop_front();
//判断队头是否需要出队
//也就是下一个元素 >=队尾元素 那么队尾出队,再进行元素入队,
while (!b.empty() && a【i】 >=a【b.back()】)
b.pop_back();
//维护队列单调性
//下标入队,便于队头出队
b.push_back(i);
//取队头作为最大窗口元素
//i>k-2 表示滑动窗口内 元素满了 才取最大值。
if (i > k - 2) printf("%d ", a【b.front()】);
}
5. 滑动窗口思想
滑动窗口思想总结。
关键词 最长 最小 (无重复字符) 子串 子数组 序列
6.0求总方式数动态规划
求总方式数的问题
多数情况用到加法原理
例子不同路径问题
求走到a 有多少种不同的路径
[ ] [ a ]
[ b ] [ x ]
路线只能向下,或者向右。到x右多少种
需要考虑 走到a有多少种方法在走一步就到了x
考虑走到 b有多少种方法,再走一步到x
x=a(到a的种数)+b(到b的种数)
如果是
x=a+1 + b+1
变成了走到x需要多少步
那就是到a的步数+1到x
6.求最值动态规划
适用动态规划的问题
6.1 确定状态
开数组 开多大
两个意识:
- 最后一步
– 最优策略中的最后一个决策
– 最优策略中用的最后一枚硬币ak
(拼出i块钱,最后一枚硬币应该是谁呢) - 子问题
– 最少的硬币拼出的更少面值27-ak
6.2 转移方程
设f[x]=最少用多少枚硬币拼出x
- f[x]=min{f[x-2]+1,f[x-5]+1,f[x-7]+1}
6.3 初始条件和边界
拼出x所需要的最少硬币数
f[x]=min{f[x-2]+1,f[x-5]+1,f[x-7]+1};
- 两个问题:
1. x-2,x-5,x-7 小于0怎么办?
2. 什么时候停下来 - 如果不能拼出Y,就定义f[Y]=正无穷
例子:f[-1]+f[-2]=正无穷 - 所以 f[1]=min{f[-1]+1,f[-4]+1,f[-6]+1}=正无穷
- 初始条件: f[0]=0,
- 0块钱最少用0枚硬币
用状态方程算不出来的,需要手动定义
如果 f[0]=min{f[-2]+1,f[-5]+1,f[-7]+1}=正无穷
但是明明知道f[0]=0,所以需要手动定义初始条件
f[1]是拼不出来的,所以正无穷,f[2]=1 - 不要数组越界
6.4 计算顺序
f[x]=min{f[x-2]+1,f[x-5]+1,f[x-7]+1};
初始条件 :f[0]=0
然后计算 f[1],f[2]…f[27]
当我们计算到f[x]时,f[x-2],f[x-5],f[x-7]都已经得到结果了。
/**
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
**/
class Solution {
public int coinChange(int[] coins, int amount) {
// 开数组
int[] f=new int[amount+1];
// 初始化
f[0]=0;
int numCoins=coins.length;
// 计算面值f[1] ...f[27] 需要的硬币
for(int i=1;i<=amount;i++){
f[i]=Integer.MAX_VALUE;
//根据公式 f[x]=min{f[x-coins[0]]+1,f[x-coins[1]]+1,
// .....,f[x-coins[j-1]]+1,}
for(int j=0;j<numCoins;j++){
// 考虑 边界
if(i>=coins[j]&&f[i-coins[j]]!=Integer.MAX_VALUE){
f[i]=Math.min(f[i],f[i-coins[j]]+1);
}
}
}
return f[amount]==Integer.MAX_VALUE?-1:f[amount];
}
}
7.异或问题
8.排序
8.1 快排
public static void quickSort(int[] arr, int low, int high) {
if(low>=high) return;
int pivotpos=partition(arr,low,high);
quickSort(arr,low,pivotpos-1);
quickSort(arr,pivotpos+1,high);
}
private static int partition(int[] arr, int low, int high) {
//注意是arr[low] 不是arr[0];
int pivot=arr[low];
while (low<high){
while(low<high&&arr[high]>=pivot)
high--;
arr[low]=arr[high];
while(low<high&&pivot>=arr[low])
low++;
arr[high]=arr[low];
}
arr[low]=pivot;
return low;
}
8.2 堆排序
public static void heapSort(int[] arr){
//建堆 o(n)
for(int i=arr.length/2-1;i>=0;i--){
heapify(arr,arr.length,i);
}
//排序
for(int i=arr.length-1;i>0;i--){
swap(arr,i,0);
//i 维护数组的长度
heapify(arr,i,0);
}
}
/**
*adjustheap()函数的时间复杂度k=log(n)
* @param arr 数组
* @param length 待维护数组的长度
* @param i 需要维护的索引
*/
public static void heapify(int[] arr,int length,int i){
int largestIndex=i;
int lson=i*2+1,rson=i*2+2;
if(lson<length&&arr[lson]>arr[largestIndex])
largestIndex=lson;
if(rson<length&&arr[rson]>arr[largestIndex])
largestIndex=rson;
if(largestIndex!=i){
swap(arr,largestIndex,i);
heapify(arr,length,largestIndex);
}
}
public static void swap(int[] arr,int left,int right){
arr[left]=arr[left]^arr[right];
arr[right]=arr[left]^arr[right];
arr[left]=arr[left]^arr[right];
二.排序重建堆
在取出堆顶点放到对应位置并把原堆的最后一个节点填充到堆顶点之后,需要对堆进行重建,只需要对堆的顶点调用adjustheap()函数。
每次重建意味着有一个节点出堆,所以需要将堆的容量减一。adjustheap()函数的时间复杂度k=log(n),k为堆的层数。所以在每次重建时,随着堆的容量的减小,层数会下降,函数时间复杂度会变化。重建堆一共需要n-1次循环,每次循环的比较次数为log(i),则相加为:log2+log3+…+log(n-1)+log(n)≈log(n!)。可以证明log(n!)和nlog(n)是同阶函数:
∵(n/2)n/2≤n!≤nn,∵(n/2)n/2≤n!≤nn,
∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)∴n/4log(n)=n/2log(n1/2)≤n/2log(n/2)≤log(n!)≤nlog(n)
所以时间复杂度为O(nlogn)
9.前缀和-差分
9.1前缀和
原数列
1 2 3 4 5 6 7 8 9
前缀和数列
1 3 6 10 15 21 28 36 45
用a表示原数列
用s表示前缀和数列
前i个和
s[i]=a[1]+a[2]+…+a[i]
时间复杂度 O(1)
应用 求L-R数列之间的和:
原始方式
int sum=0;
for(int i=L;i<=R;i++)
sum+=arr[i];
前缀和方式
res=s[R]-s[L-1];
s[L]=a[1]+a[2]+....+a[L-1]+a[L];
s[L-1]=a[1]+a[2]+....+a[L-1];
S[R]=a[1]+....+a[L-1]+a[L]+....+a[R];
L-R之间的和
10.拓扑排序
步骤~
(1)从AOV网中选择一个没有前驱的顶点并且输出;
(2)从AOV网中删去该顶点,并且删去所有以该顶点为尾的弧;
(3)重复上述两步,直到全部顶点都被输出,或AOV网中不存在没有前驱的顶点。
9.1教学计划制定
拓扑排序
c1,c2,c3,c4,c5,c6,c7
结果
c0,c1,c2
此时构成了环 因此拓扑排序不能进行下去 循环退出。
此时不是AOV网
9.2 编程完成拓扑排序
设计数据结构
1.图的存储结构:采用邻接表存储,在顶点表中增加一个入度域in。
2.栈S:存储所有无前驱的顶点。也可以用队列
拓扑排序为代码