labuladong/fucking-algorithm
CyC2018/CS-Notes
最优解
问题层序遍历
队列
队列
存储每一轮遍历得到的节点;标记
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构 Set<Node> visited; // 避免走回头路
q.offer(start); // 将起点加入队列 visited.add(start);
int step = 0; // 记录扩散的步数
while (q not empty){
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
}
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj())
if (x not in visited){
q.offer(x);
visited.add(x);
}
/* 划重点:更新步数在这里 */
step++;
}
}
111. 二叉树的最小深度
//BFS,层序遍历
//空间O(n),时间O(n)
//BFS可以不用遍历完所有节点,DFS一定是要遍历完所有节点的
//BFS的空间消耗平均比DFS要高一点,其实还是空间换了时间
public int minDepth(TreeNode root) {
if(root == null) return 0 ;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int depth = 1;
while(queue.size()!=0){
int size = queue.size();
for(int i = 0 ; i < size ;i++){
TreeNode node = queue.poll();
if(node.left == null && node.right==null){
//已经是子节点了
return depth;
}
if(node.left!=null){
queue.offer(node.left);
}
if(node.right!=null){
queue.offer(node.right);
}
}
//遍历完一层后
depth++;
}
return depth;
}
752. 打开转盘锁
//BFS,每个数字组合是一个节点,波动一次,会产生8种不同的数字组合(如:0000拨动一次的结果集合为:1000,9000,0100,0900,0010,0090,0001,0009)
//那么这就形成了一张图,图中每个节点相邻8个节点,边为拨动的次数1
//那么题目就可以转换为:求图中0000节点到target节点的最短距离(不能经过deadends节点)
//使用BFS的遍历框架即可
public int openLock(String[] deadends, String target) {
Queue<String> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();//记录已经访问过的节点
Set<String> deadendsSet = new HashSet<>();
for(String deadend : deadends){
deadendsSet.add(deadend);
}
int minSpinCount = 0;
queue.offer("0000");
while(queue.size() != 0){
int size = queue.size();
for(int i = 0 ; i < size ; i++){
String node = queue.poll();
if(visited.contains(node)) continue;//如果已经访问过,那就跳过
if(deadendsSet.contains(node)) continue; //如果是死亡数字,也跳过
if(target.equals(node)){
//如果找到target
return minSpinCount;
}
visited.add(node);
//将node其相邻的节点放入队列,node相邻的节点有8个
for(int k = 0 ; k < 4 ;k++){
queue.offer(spinUp(node,k));
queue.offer(spinDown(node,k));
}
}
//距离加1
minSpinCount++;
}
return -1;
}
//将某一位向上拨
private String spinUp(String node, int i){
char[] chars = node.toCharArray();
if(chars[i] == '0'){
chars[i] = '9';
}else{
chars[i]--;
}
return new String(chars);
}
//将某一位向下拨
private String spinDown(String node, int i){
char[] chars = node.toCharArray();
if(chars[i] == '9'){
chars[i] = '0';
}else{
chars[i]++;
}
return new String(chars);
}
深度优先搜索
,主要用于解决可达性
问题前中后序遍历
栈
栈
来保存信息,这样当更深的节点遍历完后,能够继续遍历当前节点,可以使用隐式递归
栈标记
和回溯类似
result = []
def backtrack(路径,选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径,选择列表)
撤销选择
111. 二叉树的最小深度
private int minDepth = Integer.MAX_VALUE;
public int minDepth(TreeNode root) {
if(root==null) return 0;
dfs(1,root);
return minDepth;
}
//时间O(n),空间O(树的高度h)
private void dfs(int depth,TreeNode root){
if(root.left == null && root.right ==null){
//到达叶子节点
minDepth = Math.min(depth,minDepth);
}
if(root.left != null){
dfs(depth+1,root.left);
}
if(root.right != null){
dfs(depth+1,root.right);
}
}
DFS
,主要用于求解排列组合问题
多条路径
(或形成一颗决策树
),那么在解决这种问题时,可以使用回溯的思想决策树的遍历
过程穷举
所有可能的选择,遍历所有解,因此是属于一种暴力
解法路径
、选择列表
、结束条件
result = []
def backtrack(路径,选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径,选择列表)
撤销选择
46. 全排列
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
isVisited = new boolean[nums.length];
backtrack(new ArrayList<>(),nums);
return result;
}
//时间O(n * n!),递归栈空间O(n),辅助空间O(n)
private boolean[] isVisited;
private void backtrack(List<Integer> list,int[] nums){
if(list.size() == nums.length){
result.add(new ArrayList(list));
return;
}
for(int i = 0 ; i < nums.length ;i++){
if(isVisited[i]) continue;
list.add(nums[i]);
isVisited[i] = true;
backtrack(list,nums);
list.remove(list.size()-1);
isVisited[i] = false;
}
}
list.size() == nums.length
if(isVisited[i]) continue;
list.add(nums[i]);
list.remove(list.size()-1);
51. N皇后
class Solution {
private List<List<String>> result = new ArrayList<>();
//回溯法:每一行有n个列可供选择,遍历所有可能
public List<List<String>> solveNQueens(int n) {
//初始化棋盘
char[][] board = new char[n][n];
for(int i = 0 ; i < n ;i++){
for(int j = 0 ; j < n ;j++){
board[i][j] = '.';
}
}
backtrack(board,0,n);
return result;
}
private void backtrack(char[][] board, int row,int n){
if(row == n){
List<String> boardStrList = new ArrayList<>();
for(int i = 0 ; i < n;i++){
StringBuilder sb = new StringBuilder();
for(int j = 0 ;j < n;j++){
sb.append(board[i][j]);
}
boardStrList.add(sb.toString());
}
result.add(boardStrList);
return;
}
//row有n种可能
for(int col = 0 ; col < n;col++){
//找到合适的列
if(isValid(board,row,col,n)){
board[row][col] = 'Q';
backtrack(board,row+1,n);
board[row][col] = '.';
}
}
}
//判断该位置是不是合适放置
private boolean isValid(char[][] board , int row ,int col,int n ){
//判断列是否合适,这一行肯定是没有的
for(int i = 0 ; i < n ;i++){
if(board[i][col] == 'Q') {
return false;
}
}
//判断左上方
for(int i = row-1,j = col-1 ; i>=0 && j>=0 ;i--,j--){
if(board[i][j] == 'Q') {
return false;
}
}
//判断右上方
for(int i = row-1,j = col+1 ; i>=0 && j<n ;i--,j++){
if(board[i][j] == 'Q') {
return false;
}
}
return true;
}
}
if(row == n)
isValid
方法排除掉不可能的选择board[row][col] = 'Q';
backtrack(board,row+1,n);
board[row][col] = '.';
494. 目标和
动态规划
效率最高class Solution {
private int count = 0;
//回溯,递归深度O(n),时间O(2^n)
public int findTargetSumWays(int[] nums, int S) {
backtrack(0,0,nums,S);
return count;
}
private void backtrack(int i, int sum ,int nums[] ,int S){
if(i == nums.length){
if(sum == S){
count++;
}
return;
}
//加
backtrack(i+1,sum+nums[i],nums,S);
//减
backtrack(i+1,sum-nums[i],nums,S);
}
}
i == nums.length
sum+nums[i]
、sum-nums[i]
backtrack(i+1,sum+nums[i],nums,S);backtrack(i+1,sum-nums[i],nums,S);