算法模板
文章目录
一、二分查找(BinarySearch)
详细请查看:【算法】二分查找(Java 版)
时间复杂度:每次折半查找,所以时间复杂度为 O(logN)
。
空间复杂度:只使用了 3
个变量,所以空间复杂度为 O(1)
。
约定
nums
为一个升序数组,我们把待查找区间的左边界下标设为 left
,右边界下标设为 right
,中间位置下标设为 mid
。
模板 1:while (left <= right)
思路:在循环体内部查找元素(解决简单问题时有用),即考虑下一轮目标元素应该在哪个区间。
/**
* 二分查找法<br>
* <li>模板 1:while (left <= right)</li><br>
* @param nums 待查找数组
* @param target 待查找目标值
* @return 目标值在数组中的下标<br>
* 未查找到就返回 -1
*/
public static int binarySearch1(int[] nums, int target) {
// 特殊用例判断
int len = nums.length;
if (len == 0) {
return -1;
}
// 在 [left, right] 区间里查找 target
int left = 0;
int right = len - 1;
while (left <= right) {
// 为了防止 left + right 整形溢出,写成如下形式
int mid = left + (right - left) / 2;
// 找到目标值,直接返回
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
// [mid, right]都大于目标值,下一轮查找区间:[left, mid - 1]
right = mid - 1;
} else {
// 此时:nums[mid] < target
// [left, mid]都小于目标值,下一轮查找区间:[mid + 1, right]
left = mid + 1;
}
}
return -1;
}
模板 2:while (left < right),推荐使用
思路:在循环体内部排除元素(解决复杂问题时非常有用),即考虑中间元素 nums[mid]
在什么情况下不是目标元素。
/**
* 二分查找法<br>
* <li>模板 2(下取整):while (left < right)</li><br>
* @param nums 待查找数组
* @param target 待查找目标值
* @return 目标值在数组中的下标<br>
* 未查找到就返回 -1
*/
public static int binarySearch2_floor(int[] nums, int target) {
// 特殊用例判断
int len = nums.length;
if (len == 0) {
return -1;
}
// 在 [left, right] 区间里查找 target
int left = 0;
int right = len - 1;
while (left < right) {
// 选择中位数时下取整
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
// [left, mid]都小于目标值,下一轮查找区间是 [mid + 1, right]
left = mid + 1;
} else {
// 此时:nums[mid] >= target
// [mid, right]都大于等于目标值,下一轮查找区间是 [left, mid]
right = mid;
}
}
// 退出循环的时候 left == right,程序只剩下一个元素没有看到。
// 视情况,是否需要单独判断 left(或者 right)这个下标的元素是否符合题意。
return nums[left] == target ? left : -1;
}
示例
【LeetCode】1095. 山脉数组中查找目标值(二分查找)
二、广度优先搜索(Breadth First Search,BFS)
思路:类似于树的按层次遍历的过程,使用队列保存未被检测的节点,节点按照由近及远的次序被检测和进出队列。
广度优先搜索就是把一些问题抽象成图,从一个点开始,向四周开始扩散(齐头并进),可以回答两类问题:
- 从节点
A
出发,有前往节点B
的路径吗(连通性)? - 从节点
A
出发,前往节点B
的哪条路径最短(最优解)?
public class TreeNode {
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int x) {
val = x;
}
}
public class BFS {
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
int target = 4;
System.out.println(bfs(root, target));
}
/**
* BFS 遍历二叉树
* @param root 二叉树根节点
* @param target 目标值
* @return 返回目标值距根节点的最短距离
*/
public static int bfs(TreeNode root, int target) {
// BFS 使用队列
Queue<TreeNode> q = new LinkedList<>();
// 访问标志,避免走回头路
Set<TreeNode> visited = new HashSet<>();
// 初始节点放入队列 q
q.offer(root);
visited.add(root);
// 记录扩散的步数
int step = 0;
while (!q.isEmpty()) {
int size = q.size();
// 将当前队列中的所有节点向四周扩散
for (int i = 0; i < size; i++) {
TreeNode cur = q.poll();
// 注意:判断是否到达终点
if (cur.val == target)
return step;
// 将与 cur 相邻且未被访问的节点加入队列
// 对于二叉树,就是左右子节点;
// 对于二维数组,就是未越界的上下左右 4 个元素。
if (cur.left != null) {
if (!visited.contains(cur.left)) {
q.offer(cur.left);
visited.add(cur.left);
}
}
if (cur.right != null) {
if (!visited.contains(cur.right)) {
q.offer(cur.right);
visited.add(cur.right);
}
}
}
// 划重点:更新步数在这里
step++;
}
return step;
}
}
示例
【LeetCode】200. 岛屿数量(BFS | DFS)
三、深度优先搜索(Depth First Search,DFS)
思路:类似于树的先根遍历,使用栈保存未被检测的节点,节点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出栈进行新的检测。
深度优先搜索是一条路径走到底(不撞南墙不回头),可以回答:
- 从节点
A
出发,有前往节点B
的路径吗(连通性)?
深度优先搜索实际上采用的就是回溯算法。
解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:
1、选择路径:已经做出的选择;
2、选择列表:你当前可以做的选择;
3、结束条件:到达决策树底层,无法再做选择的条件。
public class Permute_46 {
private List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
// 记录路径
LinkedList<Integer> track = new LinkedList<>();
backTrack(nums, track);
return res;
}
/**
* DFS 全排列
* <li>选择路径:记录在track中</li><br>
* <li>选择列表:nums中不存在于track中的那些元素</li><br>
* <li>结束条件:nums中的元素都在track中出现</li><br>
*
* @param nums 待排列数组
* @param track 选择路径
*/
public void backTrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件(找到一种排列)
if (track.size() == nums.length) {
res.add(new LinkedList<Integer>(track));
return;
}
for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择(选择的元素不能重复)
// contains 的时间复杂度是 O(N),可以通过访问数组用空间换时间
if (track.contains(nums[i])) {
continue;
}
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backTrack(nums, track);
// 撤销选择
track.removeLast();
}
}
}