前端算法入门之路(十二)(深搜(DFS)与广搜(BFS):初识问题状态空间)

119 阅读4分钟

深度优先遍历 (DFS)和广度优先遍历(BFS)

  1. 对于问题求解树,深度优先遍历总是从根节点出发,优先遍历子节点,向上回溯到兄弟节点再递归,比如树的前、中、后序遍历
  2. 广度优先遍历从根节点出发,优先遍历兄弟节点,然后层层遍历子节点,比如树的层序遍历

LeetCode肝题

    1. 二叉树的堂兄弟节点
// 深度优先:查找目标节点的深度,同时赋值父节点,最终比较深度是否一致且父节点不同
let dfs = (root, target, fa) => {
        if (!root) return -1
        if (root.val == target) return 0
        fa.fa = root
        let l = dfs(root.left, target, fa)
        if (l != -1) return l + 1
        fa.fa = root
        l = dfs(root.right, target, fa)
        if (l != -1) return l + 1
        return -1
}
var isCousins = function(root, x, y) {
    let d1, d2, f1, f2, fa = {fa: null}
    d1 = dfs(root, x, fa)
    f1 = fa.fa
    d2 = dfs(root, y, fa)
    f2 = fa.fa
    console.log(d1, d2)
    return d1 == d2 && f1.val != f2.val
};
// 广度优先,利用队列找到目标的深度和父节点
var isCousins = function(root, x, y) {
    let d1, d2, f1, f2, q = [{node: root, deep: 0}]
    while(q.length > 0) {
        let cur = q.shift()
        if (cur.node.val == x) {
            d1 = cur.deep
            f1 = cur.fa
        }
        if (cur.node.val == y) {
            d2 = cur.deep
            f2 = cur.fa
        }
        if (cur.node.left) q.push({node: cur.node.left, deep: cur.deep + 1, fa: cur.node})
        if (cur.node.right) q.push({node: cur.node.right, deep: cur.deep + 1, fa: cur.node})
    }
    return d1 == d2 && f1.val != f2.val
};
    1. 01 矩阵
// 求每个位置到0的距离,也就是求0到每个位置的距离
// 设置方向偏移数组dir,任务队列que,结果数组vis
// 初始化vis数组将0的位置设置为1,其余为-1,把0的位置放入que中
var updateMatrix = function(mat) {
    let m = mat.length, n = mat[0].length, vis = [], dir = [[0,1],[1,0],[0,-1],[-1,0]], que = []
    for(let i = 0; i < m; i++) {
        vis.push([])
        for(let j = 0; j < n; j++) {
            if (mat[i][j] == 0) {
                vis[i].push(0)
                que.push({i: i, j: j, k: 0})
            } else vis[i].push(-1)
        }
    }
    // 处理任务状态,对四个方向进行偏移,将超出的坐标continue,如果vis中该坐标不为-1说明已经走过该位置也continue
    // 通过条件说明坐标合法且没走过,将vis该坐标的值设为当前对象的步数k再+1,并且把当前坐标和步数放入队列中等待继续往下走
    while(que.length > 0) {
        const cur = que.shift()
        for(let k = 0; k < 4; k++) {
            let x = cur.i + dir[k][0]
            let y = cur.j + dir[k][1]
            if (x < 0 || x >= m) continue
            if (y < 0 || y >= n) continue
            if (vis[x][y] != -1) continue
            vis[x][y] = cur.k + 1
            que.push({i: x, j: y, k: vis[x][y]})
        }
    }
    return vis
};
    1. 二进制矩阵中的最短路径
// 有点类似上面一题,只是方向换成了八个
var shortestPathBinaryMatrix = function(grid) {
    if(grid[0][0] == 1) return -1
    let n = grid.length, que = [], vis = [], dir = [[0, 1],[0, -1],[-1, 0],[1, 0],[1, 1],[-1, 1],[1, -1],[-1, -1]]
    for(let i = 0; i < n; i++) {
        vis.push(new Array(n).fill(0))
    }
    que.push({i: 0, j: 0, k: 1})
    while(que.length > 0) {
        const cur = que.shift()
        if (cur.i == n - 1 && cur.j == n - 1) return cur.k
        for(let k = 0; k < 8; k++) {
            let x = cur.i + dir[k][0]
            let y = cur.j + dir[k][1]
            if (x < 0 || x >= n) continue
            if (y < 0 || y >= n) continue
            if (grid[x][y] == 1) continue
            if (vis[x][y]) continue
            vis[x][y] = 1
            que.push({i: x, j: y, k: cur.k + 1})
        }
    }
    return -1
};
    1. 打开转盘锁
// 四位字符,两个方向,相当于八种方案,将每种方案得到的字符串与给的限制条件和已有路径作排除,最终得到最短路径
function changeStr(str,index,changeStr){
    return str.substr(0, index) + changeStr + str.substring(index + 1);
}
var getStr = function(str, i, j) {
    if (j == 0) str = changeStr(str, i, parseInt(str[i]) == 9 ? 0 : parseInt(str[i]) + 1)
    if (j == 1) str = changeStr(str, i, parseInt(str[i]) == 0 ? 9 : parseInt(str[i]) - 1)
    return str
}
var openLock = function(deadends, target) {
    let que = [], vis = {}
    for(let item of deadends) {
        vis[item] = 1
    }
    if (vis['0000']) return -1
    que.push({s: '0000', k: 0})
    while(que.length > 0) {
        const cur = que.shift()
        if (cur.s == target) return cur.k
        for(let i = 0; i < 4; i++) {
            for(let j = 0; j < 2; j++) {
                let s = getStr(cur.s, i, j)
                if (vis[s]) continue
                vis[s] = 1
                que.push({s, k: cur.k + 1})
            }
        }
    }
    return -1
};
  1. 剑指 Offer 13. 机器人的运动范围
// 同样的广搜,加一个坐标各个位数之和大于k的限制条件
function getSum(m) {
    var a = 0;
  while (m) {
      a += m % 10;
    m = parseInt(m / 10);
  }
  return a;
}
var movingCount = function(m, n, k) {
    if (k == 0) return 1
    let que = [], vis = [], dir = [[0, 1],[0, -1],[1, 0],[-1, 0],], cut = 0
    for(let i = 0; i < m; i++) {
        vis.push([])
        for(let j = 0; j < n; j++) {
            vis[i].push(0)
        }
    }
    que.push({i: 0, j: 0})
    while(que.length > 0) {
        const cur = que.shift()
        for(let i = 0; i < 4; i++) {
            let x = cur.i + dir[i][0]
            let y = cur.j + dir[i][1]
            if (x < 0 || x >= m) continue
            if (y < 0 || y >= n) continue
            if (vis[x][y]) continue
            if (getSum(x) + getSum(y) > k) continue
            que.push({i: x, j: y})
            vis[x][y] = 1
            cut++
        }
    }
    return cut
};
    1. 被围绕的区域
// 逆向思维,找到四个边上的O和其相邻的O标记为小o,遍历矩阵把其余的大O都改成X,再把小o改成大O
let m, n, dir = [[0, 1],[0, -1],[-1, 0],[1, 0]]
var dfs = function(i, j, board) {
    board[i][j] = 'o'
    for(let k = 0; k < 4; k++) {
        let x = i + dir[k][0]
        let y = j + dir[k][1]
        if (x < 0 || x >= m) continue
        if (y < 0 || y >= n) continue
        if (board[x][y] != 'O') continue
        dfs(x, y, board)
    }
}
var solve = function(board) {
    m = board.length, n = board[0].length
    // 查找左右两条边
    for(let i = 0; i < m; i++) {
        if (board[i][0] == 'O') dfs(i, 0, board)
        if (board[i][n - 1] == 'O') dfs(i, n - 1, board)
    }
    // 查找上下两条边
    for(let i = 0; i < n; i++) {
        if (board[0][i] == 'O') dfs(0, i, board)
        if (board[m - 1][i] == 'O') dfs(m - 1, i, board)
    }
    for(let i = 0; i < m; i++) {
        for(let j = 0; j < n; j++) {
            if (board[i][j] == 'O') board[i][j] = 'X'
            else if (board[i][j] == 'o') board[i][j] = 'O'
        }
    }
};
    1. 目标和
// 枚举所有的可能,如果坐标i到了nums的最后一位并且target等于0,说明存在一种可能return 1,否则不存在return 0
let dfs = function(i, target, nums) {
    if (i == nums.length) {
        if (target == 0) return 1
        else return 0
    }
    let ans = 0
    ans += dfs(i + 1, target - nums[i], nums)
    ans += dfs(i + 1, target + nums[i], nums)
    return ans
}
var findTargetSumWays = function(nums, target) {
    return dfs(0, target, nums)
};
    1. 火柴拼正方形
// 正方形就是有四条边都相等,先处理给出的数组各项之和,如果不能整除4直接return false
// 然后将数组从大到小排序,从数组最后一位往前遍历匹配正方形每一条边
// 如果下标走到-1说明找到了一组答案
// 搜索四条边,如果火柴长度大于边长则继续下一个循环
// 如果火柴长度等于边长或者当前边长-火柴长度>=最小的火柴,将当前边长减去火柴长度并继续向前搜索,如果能搜索到一组结果则return true,如果搜不到说明火柴匹配错误,把边长加回来继续下一个循环
// 循环结束说明没有找到答案return false
let dfs = function(index, arr, ms) {
    if (index == -1) return true
    for(let i = 0; i < 4; i++) {
        if (arr[i] < ms[index]) continue
        if (arr[i] == ms[index] || arr[i] - ms[index] >= ms[0]) {
            arr[i] -= ms[index]
            if (dfs(index - 1, arr, ms)) return true
            arr[i] += ms[index]
        }
    }
    return false
}
var makesquare = function(matchsticks) {
    let sum = 0
    for(let item of matchsticks) sum += item
    if (sum % 4) return false
    matchsticks.sort((a, b) => a - b)
    let arr = new Array(4).fill(sum / 4)
    return dfs(matchsticks.length - 1, arr, matchsticks)
};
    1. 组合总和
// 搜索过程中,如果目标值小于0则停止搜索
// 如果等于0说明正好搜索到一条结果,把存储的所有数字放入结果数组ans中
// 如果下标越界也停止
// 接下来可以搜索下一位或者当前位置,搜索下一位则把ind+1进行搜索
// 再搜索当前位置需要把当前位置的值先存储在buff数组中,搜索结果=target-nums[ind]的值
// 最后回溯的时候把buff清空,用来存储下一条路径
var combinationSum = function(candidates, target) {
    let ans = [], buff = []
    var dfs = function(ind, target, nums) {
        if (target < 0) return
        if (target == 0) {
            ans.push(JSON.parse(JSON.stringify(buff)))
            return
        }
        if (ind == nums.length) return
        dfs(ind + 1, target, nums)
        buff.push(nums[ind])
        dfs(ind, target - nums[ind], nums)
        buff.pop()
    }
    dfs(0, target, candidates)
    return ans
};
    1. N 皇后
// 首先初始化最终结果数组ans,路径结果buff,三个判重数组分别为列判重col,左斜下方向判重left,右斜下方向判重right
// 初始化路径结果全部填充为‘.’,从第0行开始查询
// 设置边界条件,如果查完第n-1行,说明已经查询完并得到一个路径结果,把buff放入ans数组中
// 左斜下方向的横纵坐标差总是相等,右斜下方向的横纵坐标和总是相等,利用这一点对坐标判重
// 遍历当前行的每一列,如果在三个判重数组中都不存在,说明可以选择,把buff里对应的坐标改为Q,判重数组标记为1
// 再搜索下一行
// 最后回溯的时候把buff和判重数组还原,继续存储下一条路径
var solveNQueens = function(n) {
    let ans = [], buff = [], col = new Array(10).fill(0), left = new Array(20).fill(0), right = new Array(20).fill(0)
    for(let i = 0; i < n; i++) {
        buff.push([])
        for(let j = 0; j < n; j++) {
            buff[i].push('.')
        }
    }
    let dfs = (i, n) => {
        if (i == n) {
            ans.push(buff.map(item => item.join('')))
        }
        for(let j = 0; j < n; j++) {
            let l = i - j + 10, r = i + j
            if (col[j] || left[l] || right[r]) continue
            buff[i][j] = 'Q'
            col[j] = 1
            left[l] = 1
            right[r] = 1
            dfs(i + 1, n)
            buff[i][j] = '.'
            col[j] = 0
            left[l] = 0
            right[r] = 0
        }
    }
    dfs(0, n)
    return ans
};