算法问题
数组
数组是存放在连续内存空间上的相同类型数据的集合
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
- 删除或者增添元素的时候,难免要移动其他元素的地址
- 数组的元素是不能删的,只能覆盖
- java中二维数组的头节点 不连续 C++是连续的
二分查找
搜索插入位置
移除元素
有序数组的平方
长度最小的子数组
两数之和
三数之和
最接近三数之和
四数之和
链表
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
- 链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理
翻转链表
交换链表节点
删除倒数第N个节点
链表相交
环形链表
合并K个升序链表
反转链表 II
栈和队列
单调栈
- 通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
每日温度
下一个更大元素 I
下一个更大元素 II
优先队列(堆)
- PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
- peek()//返回队首元素
poll()//返回队首元素,队首元素出队列
add()//添加元素
size()//返回队列元素个数
isEmpty()//判断队列是否为空,为空返回true,不空返回false
双端队列(队首是栈顶)
Stack | ArrayDeque | LinkedList |
---|---|---|
push(e) | addFirst(e)/offerFirst(e) | addFirst(e)/offerFirst(e) |
pop() | removeFirst()/pollFirst() | removeFirst()/pollFirst() |
peek() | getFirst()/peekFirst() | getFirst()/peekFirst() |
动态规划
子序列问题
注:子序列默认不连续 子数组默认连续
最长递增子序列
最长连续递增子序列
最长重复子数组
最长公共子序列
01 背包问题
重量 | 价值 | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
物品\容量 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
A | 0 | 15 | 15 | 15 | 15 |
B | 0 | 15 | 15 | 20 | 35 |
C | 0 | 15 | 15 | 20 | 35 |
注: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
for (int i = 0; i < 8; i++){
for (int j = n; j >= weight[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - weight[i]] + val[i]);
}
}
逆序遍历背包:二维变一维 因为右边需要左边的状态 遍历时不能破坏dp[i-1][jj] 因此只能从大到小遍历
- 求容量内的最大价值:第一个物品初始化 0,15,15,15
- 求每个容量的最大价值:0 容量初始化为0 其他全部-00
组合问题:在集合nums中找出和为left的组合 dp[j] += dp[j - nums[i]]
完全背包问题
重量 | 价值 | |
---|---|---|
物品0 | 1 | 15 |
物品1 | 3 | 20 |
物品2 | 4 | 30 |
物品\容量 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
A | 0 | 15 | 30 | 45 | 60 |
B | 0 | 15 | 30 | 45 | 60 |
C | 0 | 15 | 30 | 45 | 60 |
- 01背包完全背包唯一不同就是体现在遍历顺序上,物品无限所以容量从小到大遍历
- 先遍历物品:0 1 2 此时{0,2} {2,0} 算同一种 因此是求组合的
- 先遍历容量 :每次都重新遍历物品 {0,2}{2,0}算两种答案
// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
双指针和前缀和
双指针解法
和>=k的长度最小子数组
乘积>k的子数组个数
左右子数组和相等的位置
包含相同01数的子数组个数
三数之和
前缀和解法
因为有负数,不能用双指针。用大前缀减去小前缀
和==k的子数组个数
排序算法(插入、选择、交换+归并)
直接插入排序(有序表和无序表)
- 基本思想:把n个元素看为一个有序表和一个无序表 开始时有序表只有一个元素 无序表有n-1个元素 每次从无序表取出一个元素 将其与有序表的袁旭比较 插入到它的合适位置 形成新的有序表
- 共循环 n-1 次
希尔排序(下标+增量 对数据分组 改变逆序对)
- 直接插入的问题:当插入较小的数 后移的次数很多 影响效率
- 基本思想:按下标+增量 对数据分组 使用直接排序 随着增量减少 分组逐渐减少 最终被分成一组
简单选择排序(每次选最小 交换)
- 基本思想:第一次从arr[i]中选择最小的元素与arr[0]交换 以此类推
- 共循环 n-1 次
堆排序(调整大小顶堆结构)
-
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
-
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右子结点的值,称为大顶堆, 注意 : 没有要求结点的左子的值和右子的值的大小关系。
-
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
-
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] // i 对应第几个节点,i从0开始编号
-
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] // i 对应第几个节点,i从0开始编号
一般升序采用大顶堆,降序采用小顶堆 -
基本思想:
-
将待排序序列构造成一个堆 根据升序降序选择大小顶堆
-
交换堆顶和末尾元素 将最大元素沉到数组末端
-
重新调整堆结构 继续交换元素
如此反复执行,便能得到一个有序序列了。可以看到在构建堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了.
冒泡排序(调整逆序对)
-
基本思想:从前向后,依次比较相邻元素的值,若发现逆序则交换·
-
共循环 n-1 次排序
3 ,9,-1,10,20
(1) 3 ,-1,9,10,20
(2) -1 ,3,9,10,20
(3) -1 ,3,9, 10,20
(4) -1 ,3,9, 10,20
快速排序(右边找小 左边找大 相遇得到真是下标 递归)
- 基本思想:冒泡排序的改进 通过一趟排序 将要排序的元素分为两个部分 其中以比分的数据比另一部分的数据都要小 再按照这样的方法分别进行快速排序 使整个数据变成有序序列
归并排序-先分后和
- 分:分解数据到单个单位
- 合:共合并n-1次 两个有序子序列 下标i,j 将较小的那个数放入临时数组 i++或者j++ 不断迭代 最后将临时数组复制得到结果