文章目录
- 前言
- 参考
- 一. 排序
-
- 1. 快速排序
- 2 归并排序
- 3 堆排序
- 4 其他排序
- 5. 第K大/小问题(快速选择算法)
- 6. 有序数据合并问题
- 二、二分查找
-
- 1. 基本二分查找
- 2. 二分查找左边界
- 3. 二分查找右边界
- 4. 相关题目
- 三、搜索
-
- 1. DFS
- 2. 回溯
- 3. BFS
- 4. 排列组合问题
- 5. 矩阵搜索问题
- 6. 其他DFS问题
- 四、树与递归
前言
一个多月的时间,看完了LeetCode的题目,刷完了主要的题目。越看到后面,越觉得很多题目都是非常相似
的,很多题目都是通过基本的思想扩展来的,因此需要对题型
进行总结。
算法题目一般分为以下几类,常见的用红色背景标出:
- 算法思想:贪心、
动态规划
、分治、递归
。
- 排序和搜索:
排序
、二分查找
、DFS
、BFS
、回溯
- 数据结构:
字符串
、数组/矩阵
、栈
、队列、哈希表
、链表
、二叉树
、图
- 数学:
位运算
、概率计算、数字处理
、经典算法
- 特殊解法:
滑动窗口
、双指针
- 设计:LRU、LFU、满足O(1)的数据结构组合
参考
- https://github.com/labuladong/fucking-algorithm
这个项目详细介绍了LeetCode题型和解题模板
- https://github.com/liguigui/CyC2018-CS-Notes
这个项目的算法部分总结了题型和代表性题目
- 《剑指Offer:名企面试官精讲典型编程题(第2版)》
这本书不仅介绍了各种题型、数据结构,而且从题目理解、沟通交流、思路产生、代码编写、算法优化
等方面全方位地告诉读者如何参与一场算法面试。
更多参考文献、书籍、网址、博客写在文中对应部分
一. 排序
- 常见的8种排序方法可以参考这道题目,里面有各种排序方法的标准实现
LeetCode 912. 排序数组
- 参考题解 复习基础排序算法(Java),十二种排序算法包你满意(带GIF图解)
- 常考的排序有
快速排序,归并排序,堆排序
三种,是必须要掌握的,包括算法的各种细节部分。
1. 快速排序
private void quickSort(int[] nums, int left, int right){
swap(nums,left,left+rand.nextInt(right-left+1));
if(left>=right) return;
int p = left;
for(int i = left+1 ; i <= right ; i++){
if(nums[i] <= nums[left]){
swap(nums,++p,i);
}
}
swap(nums,left,p);
quickSort(nums,left,p-1);
quickSort(nums,p+1,right);
}
2 归并排序
private void mergeSort(int[] nums, int left, int right, int[] temp){
if(left>=right) return;
int mid = left + (right-left)/2;
mergeSort(nums,left,mid,temp);
mergeSort(nums,mid+1,right,temp);
if(nums[mid]<=nums[mid+1]) return;
int i = left, j = mid+1, k = 0;
while(i<=mid && j<=right) temp[k++] = nums[i]<nums[j]?nums[i++]:nums[j++];
while(i<=mid) temp[k++] = nums[i++];
while(j<=right) temp[k++] = nums[j++];
System.arraycopy(temp,0,nums,left,right-left+1);
}
3 堆排序
private void heapSort(int[] nums){
for(int i = nums.length/2-1 ; i>=0 ;i--){
adjustHeap(nums,i,nums.length-1);
}
int end = nums.length-1;
while(end>0){
swap(nums,0,end);
end--;
adjustHeap(nums,0,end);
}
}
private void adjustHeap(int[] nums, int i, int end){
int k = i;
while(k*2+1 <= end){
int j = 2*k + 1;
if(j+1<=end && nums[j+1]>nums[j]){
j++;
}
if(nums[k]<nums[j]){
swap(nums,k,j);
k = j;
}else{
break;
}
}
}
4 其他排序
名称 |
时间复杂度 |
解释 |
冒泡排序 |
O(n^2) |
遍历n-1次数组,每次遍历就把最大的放在末尾,末尾元素不会进入下一次遍历。可以添加isSorted变量,如果在一次遍历中没有交换元素,就代表有序了,不用继续向下遍历。 |
选择排序 |
O(n^2) |
每次选择最大的元素,然后跟末尾元素交换即可,末尾元素不会进入下一次遍历。 |
插入排序 |
O(n^2) |
从i=1开始,依次将元素插入到前面的有序数组之中。 |
希尔排序 |
O(n^2) |
对插入排序的优化,对数据按间隔分组,组内不断进行插入排序,然后逐渐缩小间隔到1 |
桶排序 |
O(n) |
设置若干个桶,使用一个映射函数将所有元素放入固定大小的桶中,然后在桶内部可以使用任意排序方法对所有元素进行排序,最后依次从全部桶中拿出所有元素即可。两种主要的桶排序方法是计数排序和基数排序。 |
计数排序 |
O(n) |
使用辅助数组记录每个元素出现的次数,然后复原即可,要求待排序数组的值在一个适当的范围 |
基数排序 |
O(n) |
根据元素的数位来分配桶,例如0到9十个桶代表元素的十位数字,那么(0至9)分配到桶0,(10至19)分配到桶1,… |
5. 第K大/小问题(快速选择算法)
求第K大
是快速排序算法的应用,称为快速选择算法
,不需要完全排序,只需要在某一次安置pivot时,pivot的索引刚好等于k即可,如果不等于k,只需要对其中一个区间继续搜索。
private int findKthLargest(int[] nums ,int left, int right, int k){
swap(nums,left,left+rand.nextInt(right-left+1));
if(left>=right) return nums[left];
int p = left;
for(int i = left+1;i<=right;i++){
if(nums[i] < nums[left]){
swap(nums,i,++p);
}
}
swap(nums,p,left);
if(p == nums.length-k){
return nums[p];
}else if(p > nums.length-k){
return findKthLargest(nums,left,p-1,k);
}else{
return findKthLargest(nums,p+1,right,k);
}
}
题目 |
方法 |
LeetCode 215. 数组中的第K个最大元素 |
标准的求第K大 |
LeetCode 324. 摆动排序 II |
使用快速选择算法求出中位数 ,求中位数等价于求第k大 ,k为length/2,数组的后半部分大于前半部分,然后依次取一个,构造摆动排序 |
LeetCode 347. 前 K 个高频元素 |
求前K大 问题,数组变成了矩阵或Entry数组 |
LeetCode 462. 最少移动次数使数组元素相等 II |
这道题目的核心就是求中位数 ,求中位数等价于求第k大 ,k为length/2,同样可以使用快速选择算法 |
LeetCode 973. 最接近原点的 K 个点 |
同样是求前K小 问题 ,只是数组变成了矩阵 |
6. 有序数据合并问题
题目 |
方法 |
LeetCode 21. 合并两个有序链表 |
按照归并排序的merge算法,如果有剩余数据,可以直接接在后面,不用再逐个复制 |
LeetCode 88. 合并两个有序数组 |
从后向前合并,三指针 |
LeetCode 148. 排序链表 |
归并排序链表 |
LeetCode 315. 计算右侧小于当前元素的个数 |
在merge的时候计算逆序对 |
剑指 Offer 51. 数组中的逆序对 |
跟上题一样 |
二、二分查找
1. 基本二分查找
public int search(int[] nums, int target) {
int left= 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return -1;
}
2. 二分查找左边界
public intsearch(int[] nums, int target) {
int left = 0 ;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right-left)/2 ;
if(nums[mid] >= target){
right = mid - 1;
}else{
left = mid + 1;
}
}
if(left == nums.length || nums[left]!=target){
return -1;
}
return left;
}
3. 二分查找右边界
public int search(int[] nums, int target) {
int left = 0 ;
int right = nums.length - 1;
while(left <= right){
int mid = left + (right-left)/2 ;
if(nums[mid] <= target){
left = mid + 1;
}else {
right = mid - 1;
}
}
if(right == -1 || nums[right]!=target){
return -1;
}
return right;
}
4. 相关题目
题目 |
方法 |
LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置 |
查找左右边界 |
LeetCode 35. 搜索插入位置 |
直接二分查找 |
LeetCode 69. x 的平方根 |
在1到x二分查找 |
LeetCode 74. 搜索二维矩阵 |
有序矩阵的二分查找,注意索引转换 |
LeetCode 33. 搜索旋转排序数组 |
数组部分有序也可以使用二分查找,只要能确定下个查找区间 |
LeetCode 153. 寻找旋转排序数组中的最小值 |
和上题一样 |
LeetCode 162. 寻找峰值 |
如果是峰值就返回,如果是上升序列,就在右边找,否则在左边找 |
LeetCode 278. 第一个错误的版本 |
二分找左边界 |
LeetCode 367. 有效的完全平方数 |
1到x二分查找 |
LeetCode 540. 有序数组中的单一元素 |
把与之相邻的重复元素拿掉后,选择区间长度为奇数的继续查找 |
LeetCode 658. 找到 K 个最接近的元素 |
二分查找定位最接近target的元素 |
LeetCode 744. 寻找比目标字母大的最小字母 |
找右边界 |
三、搜索
搜索主要包括广度优先搜索
和深度优先搜索
,回溯属于深度优先搜索。搜索很多时候相当于是暴力的解法,遍历所有可能性,因此解题模式比较套路化。
1. DFS
可以定义全局变量如count,list,hashmap等,表示每一步做出的修改,比如添加一个结果,或者计数加1等。。。
dfs(...);
返回值 dfs(参数){
if(达到边界条件) return;
if(搜索到一个符合条件的结果){
添加结果到集合、更新共享变量、向上一步返回一些信息等。。。
return;
}
没有搜索到,就继续向下搜索,先获取或计算可以做出的选择(能够传递给下一层的参数)
for (选择 in 选择列表){
dfs(参数、选择...)
}
}
2. 回溯
和DFS基本类似,主要多了一个恢复状态的过程
result = []
def backtrack(路径,选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径,选择列表)
撤销选择
3. BFS
BFS需要结合队列使用
void bfs(Node start, Node target) {
初始化队列q
队列中添加初始1个或多个值 q.offer()...
while (队列不为空){
获取队列大小size
for(int i = 0 ;i<size;i++){
Node cur = q.poll();
弹出每个节点,对每个节点进行处理
然后将每个节点相邻的节点继续加入队列
}
}
}
4. 排列组合问题
题目 |
方法 |
LeetCode 46. 全排列(不含重复元素) |
回溯,做选择后把元素交换到前面,然后传递给下一步开始做选择的索引 |
LeetCode 47. 全排列 II(包含重复元素) |
回溯,每一步的选择需要去重,即提前剪枝 |
LeetCode 39. 组合总和(无重复的正整数集合,可重复选) |
下一步的候选集合从i开始 |
LeetCode 40. 组合总和 II(包含重复的正整数集合,不可重复选) |
先排序,在进行选择的时候去重 |
LeetCode 216. 组合总和 III |
注意结束条件判断 |
LeetCode 78. 子集(不含重复元素) |
标准回溯算法 |
LeetCode 90. 子集 II(包含重复元素) |
和LeetCode40题一样 |
LeetCode 17. 电话号码的字母组合 |
标准回溯问题 |
5. 矩阵搜索问题
题目 |
方法 |
LeetCode 37. 解数独 |
在每一个空白格子尝试每一种可能 |
LeetCode 51. N 皇后 |
每一行有n个列可供选择,尝试每一种可能 |
LeetCode 79. 单词搜索 |
在每个位置进行DFS,需要用visit矩阵储存是否访问 |
LeetCode 130. 被围绕的区域 |
搜索所有与边界O相连的O |
LeetCode 200. 岛屿数量 |
在值为1的位置开始dfs,把与之相邻的变为其他数字 |
LeetCode 329. 矩阵中的最长递增路径 |
使用辅助数组储存从(i,j)出发的最长路径 |
6. 其他DFS问题
题目 |
备注 |
LeetCode 22. 括号生成 |
记录左右括号的数量 |
LeetCode 93. 复原IP地址 |
注意筛选每一步的选择,还有提前剪枝的判断 |
LeetCode 282. 给表达式添加运算符 |
|
LeetCode 306. 累加数 |
|
LeetCode 401. 二进制手表 |
构造选择集,注意去重 |
四、树与递归