【每日算法0225】搜索与回溯算法(中等)

103 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 17 天,点击查看活动详情

题目

面试题13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例:

输入:m = 2, n = 3, k = 1
输出:3

分析一

按照 24 号矩阵题的经验解了出来,先记下自己的思路

先把题目要求逐个拆解

  • 遍历矩阵,判断每个格子坐标是否满足要求
    for (let i = 0; i < m; i ++) {
        for (let j = 0; j < n; j ++) {
            if (check(i, j)) {
                // ...
            }
        }
    }
  • 完善判断方法,判断传入坐标是否满足 坐标每个位上数字之和是否小于 k

由于求解坐标之和只能个位相加,单独写个方法来计算坐标的值

    function getTotal (i , j) {
        function handle(num) {
            let str = num.toString()
            let result = 0
            for (i of str) {
                result += i%10
            }
            return result
        }
        return handle(i) + handle(j)
    }

判断方法雏形

    function check (i, j) {
        // 判断和是否大于 k
        if (getTotal(i, j) > k) {
            return false
        } else {
            return true
        }
    }

加入边界条件

      if ( i == 0 && j == 0) {
          return true
      }
      if (i >= m || j >= n || i < 0 || j < 0) {
          return false
      }
  • 这时就剩关键点:判断该点是否可以到达了

我们可以用回溯的方法进行判断

即如果该点可以到达,那么它必须要满足它上边或者左边的点可以到达

为了减少计算量,我们使用一个新的矩阵来判断每个点是否可以到达

    let canVisit = new Array(m)
    for (let i = 0 ; i < m; i ++) {
        canVisit[i] = new Array(n).fill(false)
    }

完善判断逻辑,如果该点满足要求并且可以走到,将该点标为 true,并将最终结果返回

      // 判断是否可以到达该点
      let top = [i - 1, j]
      let left = [i, j - 1]
​
      result = check(top[0], top[1]) || check(left[0], left[1])
​
      if (result) {
          canVisit[i][j] = true
          return result
      } else {
          return result
      }

最后用一个计数变量记下所有满足条件的点,既是题目所求

实现

function movingCount(m: number, n: number, k: number): number {
    let index = 0
    let canVisit = new Array(m)
    for (let i = 0 ; i < m; i ++) {
        canVisit[i] = new Array(n).fill(false)
    }
​
    function getTotal (i , j) {
        function handle(num) {
            let str = num.toString()
            let result = 0
            for (i of str) {
                result += i%10
            }
            return result
        }
        return handle(i) + handle(j)
    }
​
    function check (i, j) {
        // 边界
        if ( i == 0 && j == 0) {
            return true
        }
        if (i >= m || j >= n || i < 0 || j < 0) {
            return false
        }
        // 访问判定
        if (canVisit[i][j]) {
            return true
        }
​
        let result = false
        // 判断和是否大于 k
        if (getTotal(i, j) > k) {
            return false
        }
        
        // 判断是否可以到达该点
        let top = [i - 1, j]
        let left = [i, j - 1]
​
        result = check(top[0], top[1]) || check(left[0], left[1])
​
        if (result) {
            canVisit[i][j] = true
            return result
        } else {
            return result
        }
    }
​
    let tmp = []
​
    for (let i = 0; i < m; i ++) {
        for (let j = 0; j < n; j ++) {
            if (check(i, j)) {
                tmp.push([i, j])
                index ++
            }
        }
    }
​
    console.log(tmp)
​
    return index
​
};

分析二

看了参考答案做一波优化

  • 使用数学方法优化一下求和函数
    function getTotal (i , j) {
        function handle(num) {
            let res = 0
            while(num) {
                res += num%10
                num = Math.floor(num/10)
            }
            return res
        }
        return handle(i) + handle(j)
    }
  • 使用深度优先遍历(从右和下方一层层遍历,顺便求和)
    function dfs (i, j) {
        // 边界和是否访问过
        if (i >= m || j >= n || visited[i][j]) {
            return 0
        }
        // 判断是否满足要求
        if (getTotal(i, j) <= k) {
            visited[i][j] = 1
            return 1 + dfs(i+1, j) + dfs(i, j+1)
        } else {
            return 0
        }
    }

实现

function movingCount(m: number, n: number, k: number): number {
    function getTotal (i , j) {
        function handle(num) {
            let res = 0
            while(num) {
                res += num%10
                num = Math.floor(num/10)
            }
            return res
        }
        return handle(i) + handle(j)
    }
​
    let visited = new Array(m)
    for (let i = 0; i < m; i ++) {
        visited[i] = new Array(n).fill(0)
    }
​
    function dfs (i, j) {
        // 边界和是否访问过
        if (i >= m || j >= n || visited[i][j]) {
            return 0
        }
        // 判断是否满足要求
        if (getTotal(i, j) <= k) {
            visited[i][j] = 1
            return 1 + dfs(i+1, j) + dfs(i, j+1)
        } else {
            return 0
        }
    }
​
    return dfs(0, 0)
};
​