diff --git a/algorithm/1、位运算.md b/algorithm/1、位运算.md index 99502c3..342d6d1 100644 --- a/algorithm/1、位运算.md +++ b/algorithm/1、位运算.md @@ -32,3 +32,21 @@ private static void printCompleteBinaryStr(int num){ Integer.MIN_VALUE取反加一后仍然为Integer.MIN_VALUE,这不是bug。 Integer.MAX_VALUE加1后溢出为Integer.MIN_VALUE,Integer.MIN_VALUE减一则溢出为Integer.MAX_VALUE。 + +异或运算满足交换律和结合律 +a^b=b^a +(a^b)^c=a^(b^c) +a、b交换 +a=a^b +b=a^b +a=a^b + +给定数组arr,只有一个数出现了奇数次,其它数都是偶数次,找到并打印这个数。 +偶数次异或为0,奇数次异或为本身,遍历每一个元素并异或,最后结果就是奇数次的数字。 + +拿到一个数二进制最右侧的1 +a&(~a+1) + +给定数组arr,只有一种数出现了K次,其他数都出现M次。 +假设a出现k次,b出现m次 +将所有数字的二进制位相加,则t[i] % m就是a的二进制在i位置次数,要么是0,要么是k diff --git a/algorithm/2、算法.md b/algorithm/2、算法.md index 3cd53e2..6586ed6 100644 --- a/algorithm/2、算法.md +++ b/algorithm/2、算法.md @@ -92,3 +92,41 @@ private static void insertSort(int[] arr){ } } ``` + +不基于比较的排序,也叫桶排序。对数据样本有要求,但是特定场景可以做到O(N)时间复杂度,基于比较的排序最好能做到O(N*logN) +计数排序 需要指定上下限 +基数排序 非负、能用十进制理解的样本 + +排序的稳定性 +稳定排序 冒泡、插入、归并 +不稳定排序 选择、快排、堆排序、希尔 + +|序号|名称 |时间复杂度 |额外空间复杂度|稳定性| +|:----- |:----- |:-----|:-----|:-----|:-----| :-----| ----: | :----: | +|1|选择排序 |O(N^2^) |O(1) |无| +|2|冒泡排序 |O(N^2^) |O(1) |有| +|3|插入排序 |O(N^2^) |O(1) |有| +|4|归并排序 |O(N$\times$ $\log$N) |O(N) |有| +|5|随机快排 |O(N$\times$ $\log$N) |O($\log$N) |无| +|6|堆排序 |O(N$\times$ $\log$N) |O(1) |无| +|7|计数排序 |O(N) |O(M) |有| +|8|基数排序 |O(N) |O(N) |有| +|9|希尔排序 | | || + +* 不基于比较的排序,对样本数据有严格要求,不容易改写 +* 基于比较的排序,只要规定好两个样本怎么比较大小就可以直接复用 +* 基于比较的排序,时间复杂度的极限是O(N$\times$ $\log$N) +* 时间复杂度O(N$\times$ $\log$N)、额外空间复杂度低于O(N)、且稳定的基于比较的排序是不存在的 +* 为了绝对的速度选择快排、为了省空间选堆排序、为了稳定性选归并 + +工程上的改进 + +* 考虑稳定性,Arrays.sort,基础类型使用快排,有比较器使用归并排序。 +* 充分利用O(N$\times$ $\log$N)和O(N^2^)排序各自的优势,O(N^2^)排序的常数项小,在数据样本不大(比如小于60)的时候是比O(N$\times$ $\log$N)要快的。 + +单链表回文 +L1、L2、L3、L4、L5、L6、L7、R1、R2、R3、R4、R5、R6、R7变成L1、R7、L2、R6、L3、R5、L4、R4、L5、R3、L6、R2、L7、R1 + +单链表找中间节点 +单链表荷兰国旗问题 +给定表头,将节点数据按照小于、等于、大于K连接 diff --git a/algorithm/3、数据结构.md b/algorithm/3、数据结构.md index 9598e2b..95bb6ef 100644 --- a/algorithm/3、数据结构.md +++ b/algorithm/3、数据结构.md @@ -116,3 +116,7 @@ private static boolean validateArr(int[] arr) { return true; } ``` + +小和问题、逆序对、大于两倍问题 +区间和leetcode327 可以用有序表、归并解决 +荷兰国旗问题 diff --git a/algorithm/4、时间复杂度.md b/algorithm/4、时间复杂度.md index 9f88075..f072a89 100644 --- a/algorithm/4、时间复杂度.md +++ b/algorithm/4、时间复杂度.md @@ -18,7 +18,7 @@ private static int binarySearch(int[] arr, int target) { int index = -1; int middle; while (left <= right) { - middle = (left + right) >> 1; + middle = left + ((right-left) >> 1); if (arr[middle] == target) { index = middle; return index; @@ -42,7 +42,7 @@ private static int binarySearchLeft(int[] arr, int target) { int index = -1; int middle; while (left <= right) { - middle = (left + right) >> 1; + middle = left + ((right-left) >> 1); if (arr[middle] >= target) { index = middle; right = middle - 1; @@ -64,7 +64,7 @@ private static int binarySearchRight(int[] arr, int target) { int index = -1; int middle; while (left <= right) { - middle = (left + right) >> 1; + middle = left + ((right-left) >> 1); if (arr[middle] <= target) { index = middle; left = middle + 1; @@ -99,7 +99,8 @@ public int localMin(int[] arr){ int index = -1; int middle; while (left <= right) { - middle = (left + right) >> 1; + // 避免溢出 + middle = left + ((right-left) >> 1); if (arr[middle - 1] < arr[middle]) { right = middle - 1; } else if (arr[middle + 1] < arr[middle]) { diff --git a/algorithm/5、单链表.md b/algorithm/5、单链表.md index ebd602b..d4758b6 100644 --- a/algorithm/5、单链表.md +++ b/algorithm/5、单链表.md @@ -35,7 +35,11 @@ private static Node reverDoubleLinkedList(Node head) { } ``` +把给定值都删除 + 单链表实现队列和栈 +双链表实现队列和栈 +数组实现队列和栈 ### 使用双链表实现双端队列 @@ -85,3 +89,16 @@ public class BitMap { 减法delete add(a,add(~b,1)),取反加1后为负数。 乘法 a左移b的每一位进制的位数相加。 除法 先取绝对值,a右移到刚好大于等于b,拿到一个商,最后所有商相加,补上符号。对系统最小值特殊处理。 + +只用栈结构实现队列结构 +只用队列结构实现栈结构 +图的宽度优先搜索,使用队列实现,图的深度优先搜索,实现栈实现 +栈和队列互换后可以实现 +图的宽度优先搜索,使用栈实现,图的深度优先搜索,实现队列实现 +不考虑时间复杂度 + +任何地柜都可以改成非递归方式实现。将系统压栈改为自定义实现,则可以避免递归。 + +master公式预估递归的时间复杂度,要求子问题数据规模一样。 + +小和问题 diff --git a/algorithm/6、二叉树.md b/algorithm/6、二叉树.md index 5f724bb..d6373fa 100644 --- a/algorithm/6、二叉树.md +++ b/algorithm/6、二叉树.md @@ -21,18 +21,28 @@ 先序遍历的第一个元素是头结点,先序遍历剩余部分左边是左节点的先序,右边是右节点的先序,中序遍历左边是左节点的中序,右边是右节点的中序。递归构建二叉树,递归函数入参为先序遍历的起止index,中序遍历的起止index,返回头结点。 -### 二叉树按层遍历收集节点 LeetCode107 LeetCode102 +#### 二叉树按层遍历收集节点 LeetCode107 LeetCode102 使用LinkedList(有序,快速插入),递归将每一层的节点入队、出队。 -### 平衡二叉树 LeetCode110 +#### 平衡二叉树 LeetCode110 左子树的高度和右子树的高度相差不超过1,则称为平衡二叉树。 -### 二叉搜索树 LeetCode98 +#### 二叉搜索树 LeetCode98 递归解决 -### 路径总和 LeetCode112 +#### 路径总和 LeetCode112 -### 达标路径总和 LeetCode113 +#### 达标路径总和 LeetCode113 + +X是一棵二叉树的某一个节点,A是二叉树先序遍历X的左边部分,B是二叉树后序遍历X右边部分,AB相交的结果是且仅是X的所有父节点。 +1、X的所有父节点在先序遍历的左边,X的所有父节点在后序遍历的右边,交集一定包含所有父节点 +2、X的所有子节点在先序遍历的左边,X的所有子节点在后序遍历的左边,交集一定不包含所有子节点 +3、A不包含所有祖先节点的右兄弟节点,B不包含所有祖先节点的左兄弟节点 + +X的所有祖先节点、X自己、X的子节点、X或者X的父节点作为左树的右兄节点、X或者X的父节点作为右树的左兄节点 + +判断二叉树是否是平衡二叉树 +判断二叉树是否是搜索二叉树 diff --git a/algorithm/7、堆.md b/algorithm/7、堆.md new file mode 100644 index 0000000..51ac2ed --- /dev/null +++ b/algorithm/7、堆.md @@ -0,0 +1,66 @@ +堆 大根堆、小根堆 +完全二叉树的概念只有最下面一层,最右边当节点为空 + +最大线段重合问题 +给定很多线段,每个线段都有两个数[start, end],表示线段开始位置和结束位置,左右都是闭区间 +规定: +1)线段的开始和结束位置一定都是整数值 +2)线段重合区域的长度必须>=1 +返回线段最多重合区域中,包含了几条线段? + +1、将所有线段按照start升序排序 +2、构造堆,保存当前重合所有重合线段的end, heap的size就是当前重合线段的数量 +3、遍历所有线段,如果出现不重合线段则弹出不重合的线段,如果重合则加入end + +手动改写堆(非常重要)! +系统提供的堆无法做到的事情: +1)已经入堆的元素,如果参与排序的指标方法变化,系统提供的堆无法做到时间复杂度O(logN)调整!都是O(N)的调整! +2)系统提供的堆只能弹出堆顶,做不到自由删除任何一个堆中的元素,或者说,无法在时间复杂度O(logN)内完成!一定会高于O(logN) +根本原因:无反向索引表 + +手动改写堆的代码讲解 +1)建立反向索引表 +2)建立比较器 +3)核心在于各种结构相互配合,非常容易出错 + +给定一个整型数组,int[] arr;和一个布尔类型数组,boolean[] op +两个数组一定等长,假设长度为N,arr[i]表示客户编号,op[i]表示客户操作 +arr = [ 3 , 3 , 1 , 2, 1, 2, 5… +op = [ T , T, T, T, F, T, F… +依次表示:3用户购买了一件商品,3用户购买了一件商品,1用户购买了一件商品,2用户购买了一件商品,1用户退货了一件商品,2用户购买了一件商品,5用户退货了一件商品… + +一对arr[i]和op[i]就代表一个事件: +用户号为arr[i],op[i] == T就代表这个用户购买了一件商品 +op[i] == F就代表这个用户退货了一件商品 +现在你作为电商平台负责人,你想在每一个事件到来的时候, +都给购买次数最多的前K名用户颁奖。 +所以每个事件发生后,你都需要一个得奖名单(得奖区)。 + +得奖系统的规则: +1,如果某个用户购买商品数为0,但是又发生了退货事件, + 则认为该事件无效,得奖名单和上一个事件发生后一致,例子中的5用户 +2,某用户发生购买商品事件,购买商品数+1,发生退货事件,购买商品数-1 +3,每次都是最多K个用户得奖,K也为传入的参数 + 如果根据全部规则,得奖人数确实不够K个,那就以不够的情况输出结果 +4,得奖系统分为得奖区和候选区,任何用户只要购买数>0, + 一定在这两个区域中的一个 +5,购买数最大的前K名用户进入得奖区, + 在最初时如果得奖区没有到达K个用户,那么新来的用户直接进入得奖区 +6,如果购买数不足以进入得奖区的用户,进入候选区 +7,如果候选区购买数最多的用户,已经足以进入得奖区, + 该用户就会替换得奖区中购买数最少的用户(大于才能替换), + 如果得奖区中购买数最少的用户有多个,就替换最早进入得奖区的用户 + 如果候选区中购买数最多的用户有多个,机会会给最早进入候选区的用户 +8,候选区和得奖区是两套时间, + 因用户只会在其中一个区域,所以只会有一个区域的时间,另一个没有 + 从得奖区出来进入候选区的用户,得奖区时间删除, + 进入候选区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i) + 从候选区出来进入得奖区的用户,候选区时间删除, + 进入得奖区的时间就是当前事件的时间(可以理解为arr[i]和op[i]中的i) +9,如果某用户购买数==0,不管在哪个区域都离开,区域时间删除, + 离开是指彻底离开,哪个区域也不会找到该用户 + 如果下次该用户又发生购买行为,产生>0的购买数, + 会再次根据之前规则回到某个区域中,进入区域的时间重记 +请遍历arr数组和op数组,遍历每一步输出一个得奖名单 + +public List> topK (int[] arr, boolean[] op, int k) diff --git a/algorithm/8、前缀树.md b/algorithm/8、前缀树.md new file mode 100644 index 0000000..45e3c47 --- /dev/null +++ b/algorithm/8、前缀树.md @@ -0,0 +1,2 @@ +前缀树(prefix tree/trie tree) +前缀树是多叉树,便于处理字符串前缀相关的问题。