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;
    }
};
相关推荐
星火开发设计1 小时前
虚析构函数:解决子类对象的内存泄漏
java·开发语言·前端·c++·学习·算法·知识
2501_901147831 小时前
幂函数实现的优化与工程思考笔记
笔记·算法·面试·职场和发展·php
好大的月亮1 小时前
中值法排序及LexoRank排序算法简述
java·算法·排序算法
闻缺陷则喜何志丹1 小时前
【拆位法】P9277 [AGM 2023 资格赛] 反转|普及+
c++·算法·位运算·拆位法
HAPPY酷2 小时前
std::pair` 与 `std::map` 基础
开发语言·c++·算法
喜欢吃燃面2 小时前
基础算法:高精度
开发语言·c++·学习·算法
努力学算法的蒟蒻2 小时前
day84(2.12)——leetcode面试经典150
算法·leetcode·面试
程序员酥皮蛋2 小时前
hot 100 第二十三题 23.反转链表
数据结构·算法·leetcode·链表
TracyCoder1233 小时前
LeetCode Hot100(51/100)——155. 最小栈
数据结构·算法·leetcode