leetcode算法学习-递归+回溯+动态规划

228 阅读5分钟

1.刷题

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入: nums = [1,2,3]
输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入: nums = [0,1]
输出: [[0,1],[1,0]]

示例 3:

输入: nums = [1]
输出: [[1]]

答题

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
function findFun(list,temp,nums) {
    if (temp.length == nums.length) {
        list.push([...temp])
        return
    }
    for (var i = 0; i < nums.length; i++) {
       if(temp.includes(nums[i]))continue
        
        temp.push(nums[i])
        findFun(list,temp,nums)
        temp.pop()
    }
}

var permute = function(nums) {
    let list = []
    let temp = []
    findFun(list,temp,nums) 
    return list
}; 

问题解析

核心逻辑

通过temp数组做标识(区分哪些已经使用过),一直递归往下找到所有排列的数组,

当满足长度的时候,记录到全局list,然后通过pop,往上回溯。

image.png

image.png

79. 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例 1:

输入: board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出: true

示例 2:

输入: board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出: true

答题

/**
 * @param {character[][]} board
 * @param {string} word
 * @return {boolean}
 */
 var exist = function(board, word) {
    if(board.length == 0) {
        return false
    }
    if(word.length == 0) {
        return true
    }
    let rowLen = board.length
    let colLen = board[0].length 
    for (let i=0; i < rowLen; i++) {
        for (let j=0; j < colLen; j++) {
          let flag =  find(i,j,0)
          if(flag) {
              return true
          }
        }
    }
    return false
    function find(row,col,wordIndex) {
        if(row < 0 || col < 0) {
            return false
        }
        if(row >= rowLen || col >= colLen) {
            return false
        } 
        
        let key = board[row][col] 
        if(key != word[wordIndex]) { 
            return false 
        }
        if(word.length - 1 == wordIndex) { //已经找到最后一个索引返回找到 ,注意 一定要先判断完 文字是否匹配,再到总长度
            return true
        }
        board[row][col] = null //把当前找到的坐标打上标识
        wordIndex ++ //索引递增
        //这里只要有一个递归找到true 都算找到
       let flag = find(row + 1,col,wordIndex ) ||
              find(row - 1,col,wordIndex) ||
              find(row,col + 1,wordIndex) || 
              find(row,col -1 ,wordIndex) ; 
        board[row][col] = key  //还原标识,以便下次判断可以正常使用判断
        return flag
    }
};
//  console.log(exist([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]],"ABCB"))

51. N 皇后

  • n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
  • 给你一个整数 n ,返回所有不同的 **n 皇后问题 的解决方案。
  • 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

输入: n = 4
输出: [[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释: 如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入: n = 1
输出: [["Q"]]

答题

主要逻辑

[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9,10,11,12],
[13,14,15,16]
]

//注意 索引的 逻辑是先 行 后 列

  1. 行不能相同
  2. 列不能相同
  3. 右斜边不能相同 等价于 0,0 1,1 2,2 等价与 每个节点索引相减都相等
  4. 左斜边不能相同 等价于 0,3 1,2 2,1 等价与 每个节点索引相加都相等
/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function(n) { 
    // 通过一维数据就实现二维的摆放逻辑 ,如
    // [1,3,0,2]  每一个元素代表哪一行只摆放了那一列。如1 就是 第一行第二个元素
    // (成立的提前也是因为每一行和列都一定有内容)
    // 等价于
    //  [[".Q..","...Q","Q...","..Q."]
    let ret = [] //ret结果存储多个方案 是二维数组
    find(0)
    function find(row ,flagArr = [] ){ //按行查询  flagArr 用于临时记录有效摆放的信息,
        if( row == n) { //正常 row 最大是n-1 ,也只有row 才能证明 前面所有安放的棋子都是全部正确
            let sol = flagArr.map((col,row) => {
                let arr = new Array(n).fill(".")
                arr[col]= 'Q'
                return arr.join("")
            })
            ret.push( sol)
            return 
        } 
        for (let col = 0; col < n; col++) {//依次查询每一列
            // 主循环是每一列,然后再嵌套循环当前行的下一行
            //先检查 当前遍历的列节点是否与 记录表 行和列,斜边冲突碰撞?
            // 这里的行不用判断,是因为 当前就在同一行row操作里面的列数据,所以忽略行判断
            let isCash = flagArr.some((flagCol,flagRow) => {
                if  (col === flagCol  ||
                    (col+row === flagCol + flagRow) || 
                    (col-row === flagCol - flagRow)
                    )  {
                        return true
                    }
                return false
            })
            if(isCash) { //证明当前列无效,继续下一列
                continue
            } 
            find(row+1,[...flagArr,col]) //当 当前列某个单元没有碰撞,以当前行与下一行一直递归遍历
            //flagArr 复制一份
        }
    }  
    return ret
};

37解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  • 数字 1-9 在每一行只能出现一次。
  • 数字 1-9 在每一列只能出现一次。
  • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例:

输入: board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出: [["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释: 输入的数独如上图所示,唯一有效的解决方案如下所示:

提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

答题

/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
 //主循环是 从左到右,上到下,每遇到一个格子,如当前是A格子,试着放入数字1,然后递归循环放入当前A格子以外的所有格子,
 //会依次把剩下的所有空格都放入数字,都满足,A格子才保留数字1,同时其他格子也填充完毕,数独填充结束。
 //否则一旦有一个无效,当前A格子的1+其他格子已经填入的都回溯还原为数组空'.'内容,返回false,继续下一个数字测试。
var solveSudoku = function(board) {
    for (let i = 0; i < 9; i ++) {
        for (let j = 0; j < 9; j ++) { 
            if(board[i][j] !== '.') { //如果当前不为空 ,则跳过
                continue
            }
            for (let k = 1; k <= 9; k++) { //从1-9 取出数字 ,逐个实验
                const testNum = k.toString(); 
                if(isValid(board,i,j,testNum)){// 检查当前的行列 是否可以放置
                    board[i][j] = testNum
                    if(solveSudoku(board)) { //这里会递归去检查该元素是否穷举有效
                        return true    //这里如果找到了就直接返回,不走下面的还原标识
                    }
                    board[i][j] = '.'
                }
            }
            return false //当9个数字都不能放,返回false
        }
    }
    return true //全部可能性都尝试完成 返回true 说明有解  
};
//判断整行和整列 与 9宫格 是否可以放 , k是从9个数字中选择的数字
function isValid(board,row,col,k) {
    // 由于是9*9 的表格,所以,当前的x,y 坐标统一除3 
    //检查行+列
    for (let i = 0; i < 9; i++) {
        if( board[row][i] === k || board[i][col] === k) {
            return false
        }
    }
    //检查小方块 x和y 是循环时候的偏移量 ,默认第一个从0,0开始,
    const x = Math.floor(row/3)*3
    const y = Math.floor(col/3)*3 
    // 直接找到要判断的小方块 进行比较,是否有重复出现过
    for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
             if(board[x+i][y+j] === k){
                 return false
             }
        }
    }
    return true
}