基础
评估算法重要指标
- 时间复杂度
- 额外空间复杂度
算法技巧
- 经验1: 把运算的指令控制在10^8 到10^9 之间。比如一个数组长度为10^2 ,找一个O(N ^2 )的算法即可通过。
课程
class 1 简单排序
- 选择排序:每一次拿当前数和所有数比较,最小的放在当前位置
- 冒泡排序:拿当前数与下一个数比较,若当前数大则交换位置
- 插入排序:拿当前数与前一个数比较,若小则交换位置,并进行下一个数判断
- 二分查找:每次找到中间的那个数,进行比较
class 2 异或运算
- 异或:无进位相加
- 如果不用额外变量,给两个变量交换位置
int a = x, b = y;
a = a ^ b;
b = a ^ b;
a = a ^ b;
// 解释
第一步时:a = a ^b , b = b
第二步时:a = a^b, b = a^b^b = a
第三步时:a = a^b^a=b, b= a
- 有一组数,只有一个数出现奇数次,其他都是偶数次,如何找到?
int arr[] = ***;
int eor = 0;
for(int a : arr[]){
eor = eor ^ a;
}
system.out.println(eor);
-
把一个整型的数,提取最右边的1
int a = a & (-a)
解释:
如果a = 7. 二进制表示为0111;
-a = ~a + 1.
所以~a = 1000,
~a+1 = 1001,
a& (-a) = 0001 -
有一组数,有2个数出现奇数次,其他都是偶数次,如何找到?
先全部异或,找到eor = a^b;
再找到最右边的1的数 rightOne;
在根据 arr去与rightOne,根据是否等于0,可以把这组数分为rightone的那个1上的数,是否为1和不为1的两部分。
再用eor与为1的去异或,得到其中一个数,再eor与这个数异或,得到另外一个数 -
有一组数,有1个数出现k次,其他都是m次,m> 1, k< m,如何找到出现k次的数,额外空间复杂度为O(1)?
准备一个32个长度的数组。然后把数组的值按位,与到准备的数组里面去。
再遍历这个数组,拿每一位的值
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import utils.MyArrayUtils;
import java.util.Map;
import java.util.Set;
/**
* @program SpringCloud_HelloWorld
* @description: 有一组数,有1个数出现k次,其他都是m次,m> 1, k< m,如何找到出现k次的数,额外空间复杂度为O(1)
* @author: zhasiwei
* @create: 2021/07/11 13:21
*/
public class findKTimes {
private static int find(int[] arr, int k, int m){
if(arr == null || k > m || m <2){
return -1;
}
int[] tmp = new int[32];
for (int a : arr) {
for (int i = 0; i < 32; i++) {
if(((a >> i) & 1) == 1 ){
tmp[i] ++;
}
}
}
int ans = 0;
for (int i = 0; i < tmp.length; i++) {
if(tmp[i] % m != 0){
ans |= 1 << i;
}
}
return ans;
}
private static int test(int[] arr, int k, int m){
Map<Integer, Integer> map = Maps.newHashMap();
for (int a : arr) {
if(map.containsKey(a)){
map.put(a, map.get(a) + 1);
}else{
map.put(a,1);
}
}
for (Integer key : map.keySet()) {
if(map.get(key) == k){
return key;
}
}
return -1;
}
private static int[] genRandomArr(int kinds, int maxValue, int k, int m){
int[] arr = new int[k + (kinds - 1) * m];
int index = 0;
int key = genRandomNum(maxValue);
Set<Integer> exists = Sets.newHashSet();
exists.add(key);
for (; index < k; index++) {
arr[index] = key;
}
for (int i = 0; i < kinds - 1; i++) {
int other = genRandomNum(maxValue);
while (exists.contains(other)){
other = genRandomNum(maxValue);
}
for (int j = 0; j < m; j++) {
arr[index++] = other;
}
}
for (int i = 0; i < arr.length; i++) {
int random = (int)(Math.random() * arr.length);
MyArrayUtils.swapArr(arr, i, random);
}
return arr;
}
private static int genRandomNum(int maxValue){
return (int)(Math.random() * maxValue + 1) - (int)(Math.random() * maxValue);
}
public static void main(String[] args) {
int testSize = 500000;
int maxValue = 100;
int kinds = 10;
int k = 5;
int m = 20;
for (int i = 0; i < testSize; i++) {
int[] arr = genRandomArr(kinds, maxValue, k, m);
if( find(arr, k, m) != test(arr,k,m)){
System.out.println("failed");
break;
}
}
System.out.println("end....");
}
}
class 3 基础数据结构
- 链表
- 单链表 反转
- 双向链表 反转
- 把指定的值删除
- 栈、队列
- 双向链表实现栈、队列
- 数组表实现栈、队列
- 实现一个数组,并且增加一个获取最小值的方法,要求时间复杂度为O(1)
- 用栈实现队列
- 用队列实现栈
- 递归
- master函数:分析递归的时间复杂度。
T(N) = a* T(N/b) + O(N^d) 。
子函数平均分为b份,其他的时间复杂度为O(N^d),可以用该公式
例如: T(n) = 2* T(n/2) + O(1) 。
T(n) 为总的时间复杂度;T(n/2)为子递归函数的时间复杂度,T(n/2)具体看子递归的复杂度,n/2是平均分为两份。
此时可以确定复杂度为:
a. log_b^a >d , O(N^d)
b. log_b^a <d , O(Nlog_ba)
c. log_b^a = d, O(N^d *logN)
- hash表
- 有序表,treeMap,底层用红黑树,avl,sb数等实现,key要有比较器,可以获得Fist和last
class4 归并排序
思路:把一个数组分成两份,采用递归,让左右两边都有序。
此时,借助辅助数组,依次将有序的两个数组,往辅助数组内放数据。
复杂度为:
T(N) = 2* T(N/2) + O(N),根据master公式,O(N*logN)
面试题:
- 小和问题。把一个数组的每个位置的左边的所有小于该值的数,加起来的和
- 求逆序对数量。把一个数组的每个位置,和右边的所有数据对比,如果当前数大于右边的数,则逆序对+1
- 左组比右边的数的2倍还大的次数。
- 给定一个数组,给两个数lower和upper,返回有多少子数组的累加和在[lower, upper]之间
class 5 快速排序
- 荷兰国旗问题:给定一组数组,指定一个数。将大于这个数的放右边,小于这个数的放左边,等于这个数的放中间。不能使用其他数组,要求时间复杂度为O(1)
- 同上,但是指定的数是最后一个数。
- 快排(递归调用2的方式就是快排)leeCode 327题
class 6 堆和堆排序
- 大根堆:完全二叉树,且结点必定比两个叶结点大或相等
- 小根堆:与大根堆相反
堆排序:
- heapInset():用当前位置与结点位置比较,若大,则交换。循环下去
- heapify():用较大的叶结点与当前比较,若当前小,则交换。并循环下去
- 把数组的数按堆的顺序排序好,再依次poll出根节点,剩下的继续按照堆的规则排序。
class 7 加强堆
手写加强堆
class 8 前缀树、计数排序、基数排序
class 9 链表
- 练习:单链表,快慢索引。然后返回
- 链表 判断是否时回文。不用额外空间
- 链表 回文:给一个数组l1 l2 l3 l4 r1 r2 r3 r4。要求返回 l1 r4 l2 r3 l3 r2 l4 r1.。不用额外空间
- 链表 给一个乱序的列表,然后给一个数。要求小于的放左边,等于的放中间,大于的放右边
- 链表 给一个链表,每个节点有一个random指针。要求:完全复制这个链表。不用额外空间。
一定要写的面试题:
class 10 二叉树
- 递归实现前序、中序、后续
- 不用递归实现前序、中序、后续
class 11 二叉树
- 按层打印
- 找 二叉树最宽层的节点数有多少
- 一张纸,对折,一根凹痕,再对折,多一根凹痕一根凸痕迹。N次折后依次打印的凹凸顺序。
class 12 二叉树的递归套路
- 判断是否是完全二叉树(所有节点要么满,要么都是在满的路上)
- 给定一颗二叉树的头,判断这颗二叉树是不是平衡二叉树
- 判断是不是搜索二叉树(每一颗树,左侧都比头小,右侧都比头大)
- 求一颗二叉树中,两个节点之间的,最大距离
- 判断一颗树是否是满二叉树
- 判断所有满足搜索树的子树的最大节点
- 一颗树,给两个节点,找到最低的公共结点
- 有一颗多叉树,有n个请帖。但是当前节点和直接节点不能同时邀请。求邀请的最大值
class 14,5 贪心算法 ,并查集
- 给一组字符串,随机排序组成字符串,取出字典序最小的组合
class 16 并查集练习
class 16 图
- 最小生成树K算法:先建立并查集,再把所有的边放到边中。每两个节点就判断是否在同一个set中,如果不在就union。(最小生成树,用最小的边,完成连通性)
- 最小生成树Prim算法
class 17 暴力递归
- 汉诺塔:写6个方法:从左->中、左->右、中->左、中->右、右->中、右->左。递归嵌套即可!!注意这里要从结果往开始分割思考。即第一步就是把n-1个圆盘看作一个整体。以此类推
- 汉诺塔优化:把左、中、右抽象为from、to、other
- 打印一个字符串的全部子序列,即 abc 要 a,ab,ac,abc,bc,
- 打印一个字符串的全部子序列,不能有重复子序列,即 abc 要 a,ab,ac,abc,bc,
- 打印一个字符串的全排列
- 打印一个字符串的全排列,不能有重复子序列
class 18 动态规划1
个人经验:
- 先按递归解出题目
- 增加缓存,避免重复计算
- 画一个二维矩阵,根据base case条件,去找规律。(直接根据第一种的,去写即可)
class 19 动态规划2
1.背包问题。int[] w是货物重量,int[] v是货物价值,背包最大重量为bag,求,最多能装的货物价值是多少
2.
最长公共子序列
class 20 动态规划3
class 21 动态规划4
采用压缩数组方式来做,
class 22 动态规划5
class 23 动态规划6
总结
class 24 窗口内最大值与最小值的更新结构
class 25 单调栈
class 26 由斐波那契数列讲述矩阵快速幂技巧
class 27 KMP算法
- 给2个字符串,找最长公共子串
- (next数组记住第二个字符串的最长前缀、后缀和。当匹配不上时,回到next数组记录的下标继续和第一个字符串进行匹配)
class 28 Manacher算法
- 给个字符串,求最长回文字符串
- (先给字符串加工,abc->#a#b#c#,然后par数组记录每个位置的最长回文字符串的长度,R代表以当前字符为中心的最右下标,C为当前下标。)
class 29 bfprt算法、蓄水池算法
pfrpt算法:一个讲究选比较数P的快排
- 先5个数分组(为啥5个?因为作者规定,无理由)
- 在每个组进行排序,此时O(N)
- 取每个组的中位数,得到数组m
- 取m的中位数,就是比较数P了
- 后续取比较数P都这样来
class 30 Morris 遍历
优化二叉树遍历的空间复杂度,从O(树高度)->O(1)
前序:第一次来到重复节点打印。
中序:第一次来到重复节点不打印,第二次打印
后续:第二次来到重复节点,逆序打印左树的右边界。最后打印整棵树的右边界
class 31 线段树
区间更新和区间查询用该数据结构。问题在于怎么实现!很难写。支持的是一维的
数组0位置不用,从1开始
class 32 indexTree、AC自动机
indexTree:线段树的弟弟版本,只能解决单点的更新。但是可以支持二维的、三维的
数组0位置不用,从1开始
建立一个heplp数组。每个位置,管理当前位置,并且与前一个管理的进行合并,如果长度相等,就管理2个位置,再向前判断,若也等于2,则管理4.
给定一个index,转换为2进制,抛弃最右的1,并且+1,此时这个index管理的就是从该值到自身的值
求前缀和就是,把index的二进制,每次拿掉1的index相加。例如 index=1110.就等于arr[111]+arr[0110] + arr[0100]
当index的数改变了,那么arr数组中受牵连的位置有是所有index的二进制的最右边的1加本身。例如 0110 ,1000,10000,100000……(index & -index 可以得到最右边的1)
AC自动机:
一个前缀树,但是每个节点都有一个fail指针,若父节点的fail有同样的路径,则当前fail指向那个路径。否则指向root节点