深度优先DFS + 广度优先BFS

广度:从起点开始,先扫一层,再扫一层,层层递进,地毯式搜索 (一开始什么都学,越来越精)

深度:一直到底,再回来 (先把一个东西学的透彻)

模板

BFS – 非递归写法 符合人类的思维习惯,需要维护一个 queue

var BFS = function(root) {
  const q = [root]
  const visited = [root]
  const res = []
  while (q.length) {
    const currentLevel = []
    const currentLevelLength = q.length   
    for (let i = 0; i < currentLevelLength; i++) {
      const node = q.shift()
      visited.push(node)   
      // 处理当前节点
      currentLevel.push(node) 
      // 给 Queue 添加节点
      if (node.left) q.push(node.left)
      if (node.right) q.push(node.right)
    }  
    // processing work
    res.push(currentLevel)
  }
  return res
};

DFS - 递归写法

let visited = new Set()
const res = []
var DFS = function(node, visited) {
  visited.add(node)
  // process current node 
  for (const next_node of node.children) {
    if (!visited.has(next_node)) {
      DFS(next_node, visited)
    }
  }
  return res
};
102. 二叉树的层序遍历

BFS

var levelOrder = function(root) {
  if (!root) return []
  const res = []
  const q = [root]
  while (q.length) {
    const currentLevel = []
    const currentLevelLength = q.length		
    // 循环当前队列所有元素,shift	然后 push 进 currentLevel     最后添加 queue
    for (let i = 0; i < currentLevelLength; i++) {
      const node = q.shift()
      currentLevel.push(node.val)
      if (node.left) q.push(node.left)
      if (node.right) q.push(node.right)
    }
    res.push(currentLevel)
  }
  return res
};
104. 二叉树的最大深度

DFS

var maxDepth = function(root) {
  if (!root) return 0
  return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1 
};

BFS

var maxDepth = function(root) {
  if (!root) return 0
  const q = [root]
  let res = 0
  while (q.length) {	 // 一直到最后的叶子节点,才退出循环,此时 res 为最大深度
    const currentLevel = []
    const currentLevelLength = q.length
    for (let i = 0; i < currentLevelLength; i++) {
      const node = q.shift()
      currentLevel.push(node) 
      if (node.left) q.push(node.left)
      if (node.right) q.push(node.right)

      if(!node.left && !node.right){
        // 没有左节点和右节点,说明其为叶子节点,可用来判断最小深度
      }
    }
    res++
  }	

  return res
};
111. 二叉树的最小深度
var minDepth = function(root) {
  if (!root) return 0
  if (!root.left) return 1 + minDepth(root.right)
  if (!root.right) return 1 + minDepth(root.left)
  return Math.min(minDepth(root.left), minDepth(root.right)) + 1
};
22. 括号生成

若输入为 n 维护一个 2n 的数组,数组中存在 ( 或 ) 两种可能性,总共生成 2^2n 可能性,判断是否合法

在上面的基础上进行剪枝

缩进使用两个空格 https://google.github.io/styleguide/jsguide.html

var generateParenthesis = function(n) {
  const list = []
  const _gen = (l, r, n, res) => {
    if (l == n && r == n) {	// n 全部花完,组装完毕
      list.push(res)
    }
    if (l < n) {		// 先组装 (
      _gen(l + 1, r, n, res + "(")
    }
    // 再组装 ), 添加 ) 时,必须要有 ( ,所以左边的值要大于右边的值
    if (r < l) {
      _gen(l, r + 1, n, res + ")")
    }
  }
  _gen(0, 0, n, "")
  return list
};

Java List<List<Integer>> 二维数组

/* 初始化  类似二位数组  */
List<List<Integer>> list = new ArrayList<>();

/* 添加list的层和值 */

/* 方法一 	添加层,再在层中添加数字*/
list.add(new LinkedList<>());  // 添加层数
list.get(0).add(1);

list.add(new LinkedList<>());
list.get(1).add(11);
list.get(1).add(12);
list.get(1).add(0, 13);      // [13, 11, 12]


System.out.println(list);       // [[1], [13, 11, 12]]
System.out.println(list.get(1));        // [13, 11, 12]

/* 方法二 	一次添加一个一维数组 	*/
list.add(new LinkedList<>(Arrays.asList(1, 2, 3, 6)));

System.out.println(list);		// [[1], [13, 11, 12], [1, 2, 3, 6]]

/* list元素的删除 */
list.remove(1); 
list.get(1).remove(0); 
System.out.println(list);		// [[1], [2, 3, 6]]

Arrays.toString( list.get(i).toArray() )

如果想要把数组中的内容打印出来,直接使用toString方法只会打印出数组的地址,因此需要使用Arrays的toString方法

Arrays.asList(1, 2, 3, 6)

将一个数组转换为 List

46. 全排列

回溯算法入门级详解 + 练习(持续更新)

回溯算法与深度优先遍历

以下是维基百科中「回溯算法」和「深度优先遍历」的定义。

回溯法 采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

  • 找到一个可能存在的正确的答案;
  • 在尝试了所有可能的分步方法后宣告该问题没有答案

深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历搜索树的算法。这个算法会 尽可能深 的搜索树的分支。当结点 v 的所在边都己被探寻过,搜索将 回溯 到发现结点 v 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止。如果还存在未被发现的结点,则选择其中一个作为源结点并重复以上过程,整个进程反复进行直到所有结点都被访问为止。

我刚开始学习「回溯算法」的时候觉得很抽象,一直不能理解为什么递归之后需要做和递归之前相同的逆向操作,在做了很多相关的问题以后,我发现其实「回溯算法」与「 深度优先遍历 」有着千丝万缕的联系。

个人理解
「回溯算法」与「深度优先遍历」都有「不撞南墙不回头」的意思。我个人的理解是:

回溯算法」强调了「深度优先遍历」思想的用途,用一个 不断变化 的变量,在尝试各种可能的过程中,搜索需要的结果。强调了 ⭐️ 回退 操作对于搜索的合理性。(🌲 ​ 树形状态上的深度优先遍历,范围更细)

而「深度优先遍历」强调一种⭐️ 遍历的思想,与之对应的遍历思想是「广度优先遍历」。至于广度优先遍历为什么没有成为强大的搜索算法,我们在题解后面会提。

java 实现

import java.util.ArrayList;
import java.util.List;

class Solution {
  public List<List<Integer>> permute(int[] nums) {

    List<List<Integer>> res = new ArrayList<>();
    boolean[] used = new boolean[nums.length];
    List<Integer> path = new ArrayList<>();

    dfs(nums, 0, path, used, res);
    return res;
  }

  private void dfs(int[] nums, int depth,
                   List<Integer> path, boolean[] used,
                   List<List<Integer>> res) {

    if (depth == nums.length) {
      // 添加 path 的拷贝
      res.add(new ArrayList<>(path));
      return;
    }

    for (int i = 0; i < nums.length; i++) {
      if (!used[i]) {
        path.add(nums[i]);
        used[i] = true;

        dfs(nums, depth + 1, path, used, res);

        used[i] = false;
        // 撤销已选择的元素
        path.remove(path.size() - 1);   
      }
    }
  }

  public static void main(String[] args) {
    int[] nums = {1, 2, 3};
    Solution solution = new Solution();
    List<List<Integer>> lists = solution.permute(nums);
    System.out.println(lists);
  }

}

js 实现

var permute = function (nums) {
  const path = []
  const used = new Array(nums.length).fill(false)
  const res = []
  const dfs = (nums, depth, path, used, res) => {
    if (depth == nums.length) {
      res.push([...path])
    }
    for (let i = 0; i < nums.length; i++) {
      if (!used[i]) {
        used[i] = true
        path.push(nums[i])
        dfs(nums, depth + 1, path, used, res)
        path.pop()
        used[i] = false
      }
    }
  }
  dfs(nums, 0, path, used, res)
  return res
};

数组拷贝

ES6的Array.from()

ES6的扩展运算符(…)

52. N皇后 II

x 轴 在右边、 y 轴在下边

左边: y = x + c 即 x - y = -c

右边: y = -x + c 即 x + y = c

var totalNQueens = function(n) {
  let res = 0
  const cols = new Set()
  const pie = new Set()		// x + y
  const na = new Set()		// x - y
  const dfs = (n, row) => {
    if (row == n) {
      res++
      return
    }
    for (let col = 0; col < n; col++) {
      if (cols.has(col) || pie.has(row + col) || na.has(row - col)) continue
      
      cols.add(col)
      pie.add(row + col)
      na.add(row - col)

      dfs(n, row + 1)

      cols.delete(col)
      pie.delete(row + col)
      na.delete(row - col)
    }
  }
  dfs(n, 0)
  return res
};

基于位运算的回溯

var totalNQueens = function(n) {
  let res = 0
  const dfs = (row, col, pie, na) => {
    if (row == n) {
      res++
      return
    }

    // 得到可以放皇后的位置(0 表示没有皇后放置),取反为 1 表示可以放置皇后 &去掉前面冗余的数字
    let bits = (~(col | pie | na)) & ((1 << n) - 1)
    while (bits) {
      // 得到最后的 1 的位置
      const p = bits & -bits
      // col | p 更新列,表示这个列已经占据一个皇后。向下走一行后,撇捺要往左右移动一位
      dfs(row + 1, col | p, (pie | p) << 1, (na | p) >> 1)
      // 去掉最后的 1
      bits &= bits - 1
    }
    
  }
  dfs(0, 0, 0, 0)		
  return res
};
51. N 皇后
var solveNQueens = function (n) {
  const res = []
  const cols = new Set()
  const pie = new Set()		// x + y
  const na = new Set()		// x - y
  
  const dfs = (n, row, cur_state) => {
    if (row == n) {
      res.push(cur_state)
      return
    }
    for (let col = 0; col < n; col++) {
      if (cols.has(col) || pie.has(row + col) || na.has(row - col)) continue
      
      cols.add(col)
      pie.add(row + col)
      na.add(row - col)
      
      dfs(n, row + 1, cur_state + col)

      cols.delete(col)
      pie.delete(row + col)
      na.delete(row - col)
    }
  }
  
  dfs(n, 0, [])		
  
  const _generate_result = (n) => {
    const borads = []
    for (const r of res) {
      const borad = []
      for (const i of r) {		
        borad.push(".".repeat(i) + "Q" + ".".repeat(n - i - 1))
      }
      borads.push(borad)
    }
    return borads
  }
  return _generate_result(n)
};

基于位运算的回溯

var solveNQueens = function (n) {
  const res = new Set()
  const dfs = (row, col, pie, na, cur_state) => {
    if (row == n) {
      res.add(cur_state)
      return
    }
    // 得到可以放皇后的位置(0 表示没有皇后放置),取反为 1 表示可以放置皇后 &去掉前面冗余的数字
    let bits = (~(col | pie | na)) & ((1 << n) - 1)
    while (bits) {
      // 得到最后的 1 的位置
      const p = bits & -bits
      // log2^n  = Math.log(n) / Math.log(2)
      const Qcol = Math.log((col | p) - col) / Math.log(2)
      dfs(row + 1, col | p, (pie | p) << 1, (na | p) >> 1, cur_state + Qcol)
      // 去掉最后的 1
      bits &= bits - 1
    }

  }
  dfs(0, 0, 0, 0, [])

  const _generate_result = (n) => {
    const borads = []
    for (const r of res) {
      const borad = []
      for (const i of r) {
        borad.push(".".repeat(n - i - 1) + "Q" + ".".repeat(i))
      }
      borads.push(borad)
    }
    return borads
  }
  return _generate_result(n)
};

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值