一、排序 & 查找算法
1.1 冒泡排序
-
相邻的数据进行比较。每次遍历找到一个最大值。
-
public void sort(int[] nums) { if (nums == null) { return; } for (int i = 0; i < nums.length; i++) { for (int j = 0; j < nums.length - 1 - i; j++) { if (nums[j] > nums[j + 1]) { int temp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = temp; } } } }
1.2 二分查找
- 要求数据有序,或者至少部分有序。
int mid = left + (right - left) / 2;
直接使用(left + right) /2 时,left + right 可能出现整型溢出- 704 二分查找
- #33 搜索旋转排序数组
1.3 快速排序
- 快速排序有几种写法,左右双边指针交换法、单边指针交换法
1.3.1 左右双边指针交换法
- 创建左右两个指针。记录左指针数值为分界值,假设为key。
- 首先右指针从右向左找出比key小的数据坐标,同时保证左指针小于右指针。
- 然后左指针从左向右找出比key大的数据坐标,同时保证左指针小于右指针。
- 左右指针交换,进入下一次循环。
- 结束一次循环的条件,必然是左右指针相等。并且结束时指针指向的数值小于等于key。
- 结束一次循环后,将当前指针的数据与分界值互换。
- 然后把分界处左右两边的数据分别快速排序。(递归)
1.3.2 leetcode
1.4 桶排序
小结
二、双指针
-
双指针就是通过移动指针来满足某些条件,计算出最优结果。
-
双指针的题型是不固定的,题型多变,需要灵活的运用。
-
#剑指 Offer 22 链表中倒数第k个节点 双指针记录距离。
-
11. 盛最多水的容器 双指针求最大值,始终按照固定的规则来选择移动其中一个指针。
三、单调栈
- 将数字一次入栈,同时要求栈中的元素单调递增(也可递减),如果新入栈的数字 N 小于栈顶的数字,那么就依次出栈,直到数字N大于栈顶的元素。
- 利用单调性质作为临界点,解决问题。
- 503. 下一个更大元素 II
- 739. 每日温度
四、滑动窗口
-
用于求解满足特定条件的连续子数组问题
-
滑动窗口无法解决存在负值的问题,存在负值的问题,可以考虑使用前缀和方法进行求解。
-
1208. 尽可能使字符串相等 这个题目中,最长子串就是要满足的条件,同时也满足连续子数组的要求。
五、前缀和
- 用于求解满足特定条件的连续子数组问题。
- 可以求解数据中存在负值的问题。
- 前缀和利用的性质:
Sum(i...n) = Sum(n) - Sum(i)
。即 从i到n的连续子数组的和 等于 Sum(n) - Sum(i)。 - #560 和为 K 的子数组
- #1124 表现良好的最长时间段
六、DFS & BFS
- 深度遍历适合存在性问题,广度遍历适合寻找最短路径问题。
- 深度遍历有两种实现方式:递归 和 栈。广度遍历只能用队列进行实现。
- 在进行遍历时,必须要记录已经访问过的节点,避免造成循环。
- 在迷宫、网格、岛屿类问题遍历时,要考虑上下左右四个方向进行遍历。可以如下定义方向数组遍历四个方向:
private static final int[][] steps = {
{
1, 0}, {
-1, 0}, {
0, 1}, {
0, -1}};
for (int[] step : steps) {
Point next = new Point(point.x + step[0], point.y + step[1]);
//省略...
}
6.1 深度遍历
- #695 岛屿的最大面积
- 代码示例:
/**
* grid 二维数组表示网格
* current 表示当前正在遍历的节点
* visited 表示已经遍历的节点,是set类型
*/
privte void dfs(int[][] grid, Point current, Set<Point> visited) {
visited.add(current);
for (int[] step : steps) {
// 向四周遍历
Point next = new Point(current.x + step[0], current.y + step[1]);
if (visited.contains(next)) {
continue;
}
// 不超出边界
if (next.x >= 0 && next.x < grid.length && next.y >= 0 && next.y < grid[0].length) {
if (grid[next.x][next.y] == 1) {
number += dfs(grid, next, visited);
}
visited.add(next);
}
}
}
6.2 广度遍历
- #1293 网格中的最短路径 已经访问过的节点使用三维数据进行表示。
七、回溯算法
- 回溯算法是深度优先的一种衍生算法,是一种遍历算法。
- 回溯算法可以用来求解排列问题,存在剪枝的情况
7.1 经典题型
- 46. 全排列 经典问题,数字全排列。
// 数组全排列问题,回溯代码求解
public List<List<Integer>> permute(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int i : nums) {
list.add(i);
}
List<List<Integer>> res = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
backtrack(list, temp, res);
return res;
}
/**
* 回溯方法
*
* @param list 原始数组
* @param temp 一种排列结果
* @param res 全排列结果合集
*/
private void backtrack(List<Integer> list, List<Integer> temp, List<List<Integer>> res) {
if (list.isEmpty()) {
res.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < list.size(); i++) {
temp.add(list.remove(i));
backtrack(list, temp, res);
list.add(i, temp.remove(temp.size() - 1));
}
}
- #17 电话号码的字母组合 经典问题,用来回溯入门,体验、理解回溯过程。
- 78. 子集
public List<List<Integer>> subsets(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int i : nums) {
list.add(i);
}
List<List<Integer>> result = new ArrayList<>();
List<Integer> temp = new ArrayList<>();
backtrack(list, result, temp, 0);
return result;
}
private void backtrack(List<Integer> list, List<List<Integer>> result, List<Integer> tmp, int n) {
result.add(new ArrayList<>(tmp));
for (int i = n; i < list.size(); i++) {
tmp.add(list.get(i));
backTree(list, result, tmp, i +1);
tmp.remove(tmp.size() - 1);
}
}
八、动态规划
- 关键是寻找:状态转移方程 和 边界条件,然后使用递归。
- 因为递归可能会出现重复求解的情况,所以应该记录已经求解的问题,避免重复运算。
- 空间优化。因为在计算F(n)的时候,往往只需要F(n - 1) 和 F(n - 2)的值,所以在计算时可以只保留这两个数值,优化空间复杂度。
8.1 leetcode
-
剑指 Offer 10- I. 斐波那契数列 和 70. 爬楼梯。 状态转移方程:
F[n] = F[n -1] + F[n - 2]
-
53. 最大子数组和 面试常见动态规划问题。状态转移方程:
dp[i]=max{nums[i],dp[i−1]+nums[i]}
九、贪心算法
十、回文子串(马拉车算法)
- 回文子串分为奇对称和偶对称。通过插入特殊字符,使得字符串变为偶对称。
- 利用回文子串的对称性提高遍历效率。遍历时,已知最长的回文子串的右侧字符可以直接利用左侧中心对称位置字符的遍历结果。
- #5 最长回文子串
十一、 位运算
- 136. 只出现一次的数字 利用异或的性质去重
- 78. 子集 2的n次方,可以表示所有的排列组合结果。按位与遍历。