Leetcode 980.不同路径III

目录

题目描述

思路解析1

AC代码1

复杂度分析

思路解析2

AC代码2

复杂度分析


题目描述

Leetcode 980. 不同路径

思路解析1

按照题意我们需要走遍所有的可行格子才可以。所以,我们只需要判断从 “1” 到 “2” 的路径中是否走完了所有可行路径的格子就可以了。这也是最简单直观的代码,也是最基本、基础的代码。

AC代码1

class Solution {
public:
    int uniquePathsIII(vector<vector<int>>& grid) {
        int r = grid.size(), c = grid[0].size();
        int si = 0, sj = 0, n = 0;
        //预处理,统计可行格子数量、起始点
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (grid[i][j] == 0) {
                    n++;
                 } else if (grid[i][j] == 1) {
                    n++;
                    si = i;
                    sj = j;
                 }
            }
        }

        //编写函数类 dfs,从(i, j)出发剩余n个可行格子
        function<int(int, int, int)> dfs = [&](int i, int j, int n) -> int {
            if (grid[i][j] == 2) {
                if (n == 0) {//如果可行格子都走完,合题路径+1
                    return 1;
                }
                return 0;//否则,+0
            }

            int t = grid[i][j], res = 0;
            grid[i][j] = -1;
            vector<array<int, 2>> dir({{-1, 0}, {1, 0}, {0, -1}, {0, 1}});
            for (auto &[di, dj] : dir) {//四个方向尝试
                int ni = i + di;
                int nj = j + dj;
                if (ni >= 0 && ni < r && nj >= 0 && nj < c && \
                   (grid[ni][nj] == 0 || grid[ni][nj] == 2)) {
                    res += dfs(ni, nj, n - 1);
                }
            }
            grid[i][j] = t;//还原
            return res;
        };
        return dfs(si, sj, n);
    }
};

复杂度分析

因为每一个格子都需要向四个方向尝试,同时总格子数量为 rows * column ,所以最终的代码复杂度在 O(4^{ rows * columns})。你想得没错,这道题leetcode的数据不是很强...在 0 <= rows * columns <= 20 的情况下,这个复杂度是不能通过所有案例的。可是leetcode官方数据比较弱,就导致没有卡该算法。所以复制粘贴谨慎,保不齐日后数据更新被卡。

思路解析2

思路之所以诞生可以认为是算法1可能被卡,所以去绞尽脑汁去优化的代码。

面对DFS的优化,我们往往有剪枝、记忆化、状态压缩三种简单的方式。

剪枝显然不太行,因为我们不能断言进入某个方向后是不符合题意的。所以目光来到记忆化。

记忆化顾名思义就是通过记忆某种状态,当下次再次计算到一样的情形的时候就可以直接调用。

那么如何确定一个一致的情况,我们需要一下几个元素确定 (当前点,未访问的可行点)

当这两个值一致时,我们就可以直接访问先前计算的答案。如果你不理解,不妨画一个图来确定完全一致的情形。

正是两个二元组,可以视为一个小集合,面对集合我们可以使用状态压缩成key,同理可行点的状态可以状态压缩成st。

AC代码2

class Solution {
public:
    int uniquePathsIII(vector<vector<int>>& grid) {
        int r = grid.size(), c = grid[0].size();
        int si = 0, sj = 0, st = 0;
        unordered_map<int, int> memo;//记忆数组
        //预处理,可行点--注意目的地也是可行点、起点
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (grid[i][j] == 0 || grid[i][j] == 2) {
                    st |= (1 << (i * c + j));
                } else if (grid[i][j] == 1) {
                    si = i, sj = j;
                }
            }
        }

        //在(i, j) 情形要素可行点st的路径数
        function<int(int ,int, int)> dp = [&](int i, int j, int st) -> int {
            if (grid[i][j] == 2) {
                if (st == 0) {
                    return 1;
                }
                return 0;
            }
            //设置访问情形
            int key = ((i * c + j) << (r * c)) + st;
            if (!memo.count(key)) {
                int res = 0;
                vector<array<int, 2>> dir({{-1, 0}, {1, 0}, {0, -1}, {0, 1}});
                for (auto &[di, dj] : dir) {
                    int ni = i + di, nj = j + dj;
                    if (ni >= 0 && ni < r && nj >= 0 && nj < c && (st & (1 << (ni * c + nj))) > 0) {//该点合法、未走过
                        res += dp(ni, nj, st ^ (1 << (ni * c + nj)));//标记访问过
                    }
                }
                memo[key] = res;//记录 {(i,j), st} 情形的合题路数
            }
            return memo[key];
        };
        return dp(si, sj, st);
    }
};

复杂度分析

此时每个点只有访问与未被访问两种状态,所以状态数为 rows * columns * 2^{rows*columns}

因此,时间复杂度也为 O(rows * columns * 2^{rows*columns})。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值