Day87:2.12:leetcode 动态规划8道题,用时3h

分享丨【算法题单】动态规划(入门/背包/划分/状态机/区间/状压/数位/树形/优化) - 讨论 - 力扣(LeetCode)

对于一些二维 DP (例如背包、最长公共子序列),如果把 DP 矩阵画出来,其实状态转移可以视作在网格图上的移动。所以在学习相对更抽象的二维 DP 之前,做一些形象的网格图 DP 会让后续的学习更轻松(比如 0-1 背包的空间优化写法为什么要倒序遍历)。

1.套路

1.想好开几维dp数组,且遍历时要按顺序遍历所有维度,最后的答案才让每个维度变成固定值\[十九.动态规划-二.网格图DP#5. 3393.统计异或值为给定值的路径数目(中等,学习递推数组的第三个参数)]

2.题目描述
3.学习经验
1. 64.最小路径和(中等)

64. 最小路径和 - 力扣(LeetCode)

思想

1.给定一个包含非负整数的 _m_ x _n_ 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明: 每次只能向下或者向右移动一步。

2.转态方程含义是到当前点的数字总和最小的路径的数字总和

代码
复制代码
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<int>> f(n, vector<int>(m, 0));
        // 初始化
        f[0][0] = grid[0][0];
        for (int i = 1; i < n; ++i)
            f[i][0] = f[i - 1][0] + grid[i][0];
        for (int j = 1; j < m; ++j)
            f[0][j] = f[0][j - 1] + grid[0][j];
        for (int i = 1; i < n; ++i) {
            for (int j = 1; j < m; ++j) {
	            // 状态转移方程
                f[i][j] = min(f[i - 1][j], f[i][j - 1]) + grid[i][j];
            }
        }
        // 返回答案
        return f[n - 1][m - 1];
    }
};
2. 62.不同路径(中等)

62. 不同路径 - 力扣(LeetCode)

思想

1.一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 "Start" )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 "Finish" )。

问总共有多少条不同的路径?

2.跟\[十九.动态规划-二.网格图DP#1. 64.最小路径和(中等)]一样,只是状态方程含义是不同路径数量,状态转移方程变成相加

代码
复制代码
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> f(m, vector<int>(n, 0));
        // 初始化
        f[0][0] = 1;
        for (int i = 1; i < m; ++i)
            f[i][0] = 1;
        for (int j = 1; j < n; ++j)
            f[0][j] = 1;
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                // 状态转移方程
                f[i][j] = f[i - 1][j] + f[i][j - 1];
            }
        }
        // 返回答案
        return f[m - 1][n - 1];
    }
};
3. 63.不同路径II(中等)

63. 不同路径 II - 力扣(LeetCode)

思想

1.给定一个 m x n 的整数数组 grid。一个机器人初始位于 左上角 (即 grid[0][0])。机器人尝试移动到 右下角 (即 grid[m - 1][n - 1])。机器人每次只能向下或者向右移动一步。

网格中的障碍物和空位置分别用 10 来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。

返回机器人能够到达右下角的不同路径数量。

测试用例保证答案小于等于 2 * 10^9

2.跟\[十九.动态规划-二.网格图DP#2. 62.不同路径(中等)]相比,增加了障碍物这一限制条件,即到障碍物位置路径数为0,则更新状态数组为0即可

代码
复制代码
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m = obstacleGrid.size(), n = obstacleGrid[0].size();
        vector<vector<int>> f(m, vector<int>(n, 0));
        if (obstacleGrid[0][0] == 1)
            return 0;
        // 初始化
        f[0][0] = 1;
        for (int i = 1; i < m; ++i) {
            if (obstacleGrid[i][0] == 1)
                f[i][0] = 0; // 有障碍物说明无路径
            else
                f[i][0] = f[i - 1][0];
        }
        for (int j = 1; j < n; ++j) {
            if (obstacleGrid[0][j] == 1)
                f[0][j] = 0;
            else
                f[0][j] = f[0][j - 1];
        }
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                if (obstacleGrid[i][j] == 1) {
                    f[i][j] = 0;
                } else {
                    // 状态转移方程
                    f[i][j] = f[i - 1][j] + f[i][j - 1];
                }
            }
        }
        // 返回答案
        return f[m - 1][n - 1];
    }
};
4. 120.三角形最小路径和(中等)

120. 三角形最小路径和 - 力扣(LeetCode)

思想

1.给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 ii + 1

2.从长方形网格变成下三角矩阵,初始化条件变成了对角线,最终答案为最后一行的最小值

代码
复制代码
class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int n = triangle.size();
        vector<vector<int>> f(n, vector<int>(n, 0));
        // 初始化
        f[0][0] = triangle[0][0];
        for (int i = 1; i < n; ++i) {
            f[i][0] = f[i - 1][0] + triangle[i][0];     // 第一列只能来自上方
            f[i][i] = f[i - 1][i - 1] + triangle[i][i]; // 对角线只能来自左上角
        }
        for (int i = 1; i < n; ++i) {
            for (int j = 1; j < i; ++j) { // 下三角矩阵(不含对角线和第一列)
                // 状态转移方程
                f[i][j] = min(f[i - 1][j], f[i - 1][j - 1]) + triangle[i][j];
            }
        }
        // 答案
        int res = INT_MAX;
        for (int j = 0; j < n; ++j)
            res = min(res, f[n - 1][j]);
        return res;
    }
};
5. 3393.统计异或值为给定值的路径数目(中等,学习递推数组的第三个参数)

3393. 统计异或值为给定值的路径数目 - 力扣(LeetCode)

思想

1.给你一个大小为 m x n 的二维整数数组 grid 和一个整数 k

你的任务是统计满足以下 条件 且从左上格子 (0, 0) 出发到达右下格子 (m - 1, n - 1) 的路径数目:

  • 每一步你可以向右或者向下走,也就是如果格子存在的话,可以从格子 (i, j) 走到格子 (i, j + 1) 或者格子 (i + 1, j)
  • 路径上经过的所有数字 XOR 异或值必须 等于 k
    请你返回满足上述条件的路径总数。
    由于答案可能很大,请你将答案对 10^9 + 7 取余 后返回。
    2.把到当前结点的异或值作为dp数组的第三个参数 ,即dp[i][j][x](表示到当前点异或值为x的路径总数),假如从上方dp[i-1][j][y]转移而来,有y^grid[i][j]=x,根据异或性质可知,y=x^grid[i][j](因为递推时要遍历第三个参数,已知当前位置异或值需要反推转移处的异或值 ),而最终求答案时第三维才取k
    3.dp数组初始化时最大异或值的选取规则 :
    XOR 运算本质是按位异或(不可能进位1) ,所以多个数异或结果一定小于(数组最大数的二进制位数所表示的最大数),公式化为

设二维数组中的所有元素满足

0 ≤ a i < 2 b 0 \le a_i < 2^b 0≤ai<2b

其中
b = b i t _ w i d t h ( max ⁡ ( grid ) ) b = \mathrm{bit\_width}(\max(\text{grid})) b=bit_width(max(grid))

则对于任意有限个这样的数
x = a 1 ⊕ a 2 ⊕ ⋯ ⊕ a t x = a_1 \oplus a_2 \oplus \dots \oplus a_t x=a1⊕a2⊕⋯⊕at

恒有
0 ≤ x < 2 b 0 \le x < 2^b 0≤x<2b

证明

0 ≤ a i < 2 b 0 \le a_i < 2^b 0≤ai<2b

可知每个 a i a_i ai 的二进制表示最多只有 b b b 位,即

a i = ∑ k = 0 b − 1 c i , k 2 k a_i = \sum_{k=0}^{b-1} c_{i,k} 2^k ai=k=0∑b−1ci,k2k

其中
c i , k ∈ { 0 , 1 } c_{i,k} \in \{0,1\} ci,k∈{0,1}

并且对于所有 k ≥ b k \ge b k≥b,

c i , k = 0 c_{i,k} = 0 ci,k=0

按位异或运算定义为

(a_1 \\oplus \\dots \\oplus a_t)_k c_{1,k} \\oplus \\dots \\oplus c_{t,k}

对于所有 k ≥ b k \ge b k≥b,由于

c 1 , k = c 2 , k = ⋯ = c t , k = 0 c_{1,k} = c_{2,k} = \dots = c_{t,k} = 0 c1,k=c2,k=⋯=ct,k=0

因此

c 1 , k ⊕ ⋯ ⊕ c t , k = 0 c_{1,k} \oplus \dots \oplus c_{t,k} = 0 c1,k⊕⋯⊕ct,k=0

故异或结果在第 b b b 位及以上仍为 0 0 0,从而

x < 2 b x < 2^b x<2b

证毕。

代码

1.暴力方法

复制代码
class Solution {
public:
    const int mod = 1e9 + 7;
    typedef long long ll;
    int countPathsWithXorValue(vector<vector<int>>& grid, int k) {
        int n = grid.size(), m = grid[0].size();
        vector<vector<map<int, ll>>> f(
            n, vector<map<int, ll>>(m)); // 异或值-路径数量
        f[0][0][grid[0][0]] = 1;
        for (int i = 1; i < n; ++i) {
            for (auto it = f[i - 1][0].begin(); it != f[i - 1][0].end(); ++it) {
                int curXor = (it->first) ^ grid[i][0];
                f[i][0][curXor] = it->second;
            }
        }
        for (int j = 1; j < m; ++j) {
            for (auto it = f[0][j - 1].begin(); it != f[0][j - 1].end(); ++it) {
                int curXor = (it->first) ^ grid[0][j];
                f[0][j][curXor] = it->second;
            }
        }
        for (int i = 1; i < n; ++i) {
            for (int j = 1; j < m; ++j) {
                // 更新当前点的所有异或值-路径数
                for (auto it = f[i - 1][j].begin(); it != f[i - 1][j].end();
                     ++it) {
                    int curXor = (it->first) ^ grid[i][j];
                    f[i][j][curXor] = (f[i][j][curXor] + it->second) % mod;
                }
                for (auto it = f[i][j - 1].begin(); it != f[i][j - 1].end();
                     ++it) {
                    int curXor = (it->first) ^ grid[i][j];
                    f[i][j][curXor] = (f[i][j][curXor] + it->second) % mod;
                }
            }
        }
        // 只判断终点满足异或值为k的路径总数
        ll res = 0;
        for (auto it = f[n - 1][m - 1].begin(); it != f[n - 1][m - 1].end();
             ++it) {
            if (it->first == k) {
                res = (res + it->second) % mod;
            }
        }
        return res;
    }
};

2.三维dp数组方法:

复制代码
class Solution {
public:
    const int mod = 1e9 + 7;
    int get_bit_wid(int x) { // 获取二进制位数
        int res = 0;
        while (x) {
            ++res;
            x >>= 1;
        }
        return res;
    }
    int countPathsWithXorValue(vector<vector<int>>& grid, int k) {
        int n = grid.size(), m = grid[0].size();
        int maxn = INT_MIN;
        for (int i = 0; i < n; ++i) {
            for (auto& x : grid[i]) {
                maxn = max(maxn, x);
            }
        }
        int maxXor = (1 << get_bit_wid(maxn));
        if (k >= maxXor)
            return 0;
        vector<vector<vector<int>>> f(
            n, vector<vector<int>>(m, vector<int>(maxXor, 0)));
        f[0][0][grid[0][0]] = 1;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (i == 0 && j == 0)
                    continue;
                int val = grid[i][j];
                for (int u = 0; u < maxXor; ++u) { // 遍历第三个参数
                    if (i >= 1)
                        f[i][j][u] = (f[i][j][u] + f[i - 1][j][u ^ val]) % mod;
                    if (j >= 1)
                        f[i][j][u] = (f[i][j][u] + f[i][j - 1][u ^ val]) % mod;
                }
            }
        }
        return f[n - 1][m - 1][k];
    }
};
6. 931.下降路径最小和(中等)

931. 下降路径最小和 - 力扣(LeetCode)

思想

1.给你一个 n x n方形 整数数组 matrix ,请你找出并返回通过 matrix下降路径最小和
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)(row + 1, col) 或者 (row + 1, col + 1)

2.跟\[十九.动态规划-二.网格图DP#4. 120.三角形最小路径和(中等)]差不多

代码
复制代码
class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        int n = matrix.size();
        vector<vector<int>> f(n, vector<int>(n, 0));
        for (int j = 0; j < n; ++j)
            f[0][j] = matrix[0][j];
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                int minn = f[i - 1][j];
                if (j >= 1)
                    minn = min(minn, f[i - 1][j - 1]);
                if (j + 1 < n)
                    minn = min(minn, f[i - 1][j + 1]);
                f[i][j] = minn + matrix[i][j];
            }
        }
        int res = INT_MAX;
        for (int j = 0; j < n; ++j)
            res = min(res, f[n - 1][j]);
        return res;
    }
};
7. 2304.网格中的最小路径代价(中等)

2304. 网格中的最小路径代价 - 力扣(LeetCode)

思想

1.给你一个下标从 0 开始的整数矩阵 grid ,矩阵大小为 m x n ,由从 0m * n - 1 的不同整数组成。你可以在此矩阵中,从一个单元格移动到 下一行 的任何其他单元格。如果你位于单元格 (x, y) ,且满足 x < m - 1 ,你可以移动到 (x + 1, 0), (x + 1, 1), ..., (x + 1, n - 1) 中的任何一个单元格。注意: 在最后一行中的单元格不能触发移动。

每次可能的移动都需要付出对应的代价,代价用一个下标从 0 开始的二维数组 moveCost 表示,该数组大小为 (m * n) x n ,其中 moveCost[i][j] 是从值为 i 的单元格移动到下一行第 j 列单元格的代价。从 grid 最后一行的单元格移动的代价可以忽略。
grid 一条路径的代价是:所有路径经过的单元格的 值之和 加上 所有移动的 代价之和 。从 第一行 任意单元格出发,返回到达 最后一行 任意单元格的最小路径代价。

2.跟\[十九.动态规划-二.网格图DP#6. 931.下降路径最小和(中等)]差不多,只是转移位置从上方三处变成了上方一整行

代码
复制代码
class Solution {
public:
    int minPathCost(vector<vector<int>>& grid, vector<vector<int>>& moveCost) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> f(m, vector<int>(n, 0));
        for (int j = 0; j < n; ++j)
            f[0][j] = grid[0][j];
        for (int i = 1; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                int minn = INT_MAX;
                for (int k = 0; k < n; ++k) {
                    int val = f[i - 1][k] + moveCost[grid[i - 1][k]][j];
                    minn = min(minn, val);
                }
                f[i][j] = minn + grid[i][j];
            }
        }
        int res = INT_MAX;
        for (int j = 0; j < n; ++j)
            res = min(res, f[m - 1][j]);
        return res;
    }
};
8. 2684.矩阵中移动的最大次数(中等)

2684. 矩阵中移动的最大次数 - 力扣(LeetCode)

思想

1.给你一个下标从 0 开始、大小为 m x n 的矩阵 grid ,矩阵由若干 整数组成。

你可以从矩阵第一列中的 任一 单元格出发,按以下方式遍历 grid

  • 从单元格 (row, col) 可以移动到 (row - 1, col + 1)(row, col + 1)(row + 1, col + 1) 三个单元格中任一满足值 严格 大于当前单元格的单元格。
    返回你在矩阵中能够 移动最大 次数。
    2.此题要从第一列到某一列的最大移动次数,所以不能出现起始路径不满足条件,但中间路径满足条件的情况,所以要将不能移动到此处的信息传递给后方,用dp数组为-1来传递
代码
复制代码
class Solution {
public:
    int maxMoves(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<vector<int>> f(m, vector<int>(n, 0));
        int res = 0;
        for (int j = 1; j < n; ++j) {
            bool tag = false;
            for (int i = 0; i < m; ++i) {
                bool flag = false;
                if (grid[i][j] > grid[i][j - 1] && f[i][j - 1] != -1) {
                    f[i][j] = max(f[i][j], f[i][j - 1] + 1);
                    flag = true;
                    tag = true;
                }
                if (i >= 1 && grid[i][j] > grid[i - 1][j - 1] &&
                    f[i - 1][j - 1] != -1) {
                    f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
                    flag = true;
                    tag = true;
                }
                if (i + 1 < m && grid[i][j] > grid[i + 1][j - 1] &&
                    f[i + 1][j - 1] != -1) {
                    f[i][j] = max(f[i][j], f[i + 1][j - 1] + 1);
                    flag = true;
                    tag = true;
                }
                if (flag)
                    res = max(res, f[i][j]);
                else
                    f[i][j] = -1;
            }
            if (!tag)
                break; // 整列都没有
        }
        return res;
    }
};
相关推荐
Forever Nore1 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考2 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX3 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面4 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号4 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽4 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI4 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者6 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者6 小时前
【征程6】校准量化中HistogramObserver解析
算法
OuO-27 小时前
笔试强训 Day 34:ISBN 号码、kotori 和迷宫、矩阵最长递增路径
java·算法·矩阵