高维DP

目录

一,2维DP

力扣 63. 不同路径 II (空间压缩)

力扣 64. 最小路径和(空间压缩)

CSU 1899: Yuelu Scenes(空间压缩)

UVA 12034 Race

力扣 72. 编辑距离

力扣 813. 最大平均值和的分组

力扣 931. 下降路径最小和

力扣 1314. 矩阵区域和

力扣 1883. 准时抵达会议现场的最小跳过休息次数(空间压缩)

力扣 2809. 使数组和小于等于 x 的最少时间

力扣 1155. 掷骰子等于目标和的方法数

力扣 2312. 卖木头块

力扣 2684. 矩阵中移动的最大次数

二,3维DP

力扣 1223. 掷骰子模拟(空间压缩,解空间平移)

CSU 1750 切蛋糕

力扣 87. 扰乱字符串

力扣 1420. 生成数组

力扣 1473. 给房子涂色 III

力扣 1504. 统计全 1 子矩形(空间压缩)

​力扣 1682. 最长回文子序列 II

力扣 741. 摘樱桃

力扣 1463. 摘樱桃 II(空间压缩)


一,2维DP

力扣 63. 不同路径 II (空间压缩)

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

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

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

说明:m 和 n 的值均不超过 100。

示例 1:

输入:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
输出: 2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

递归写法:

class Solution {
public:
    vector<vector<int>>ans;
    int dp(vector<vector<int>>& obs,int x,int y)
    {
        if(x<0||x>=obs.size()||y<0||y>=obs[0].size()||obs[x][y])return 0;
        if(ans[x][y])return ans[x][y];
        return ans[x][y]=dp(obs,x,y-1)+dp(obs,x-1,y);
    }
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        ans=obstacleGrid;
        ans[0][0]=1;
        return dp(obstacleGrid,obstacleGrid.size()-1,obstacleGrid[0].size()-1);
    }
};

非递归写法:

class Solution {
public:
    vector<vector<int>>ans;
    int uniquePathsWithObstacles(vector<vector<int>>& obs) {
        vector<vector<int>> ans=obs;
        ans[0][0]=(obs[0][0]?0:1);        
        for(int j=1;j<obs[0].size();j++)ans[0][j]=(obs[0][j]?0:ans[0][j-1]);
        for(int i=1;i<obs.size();i++){
            ans[i][0]=(obs[i][0]?0:ans[i-1][0]);
            for(int j=1;j<obs[0].size();j++)ans[i][j]=(obs[i][j]?0:ans[i][j-1]+ans[i-1][j]);
        }
        return ans[ans.size()-1][ans[0].size()-1];
    }
};

空间压缩写法:

class Solution {
public:
    vector<vector<int>>ans;
    int uniquePathsWithObstacles(vector<vector<int>>& obs) {
        vector<int> ans=obs[0];
        ans[0]=(obs[0][0]?0:1);        
        for(int j=1;j<obs[0].size();j++)ans[j]=(obs[0][j]?0:ans[j-1]);
        for(int i=1;i<obs.size();i++){
            ans[0]=(obs[i][0]?0:ans[0]);
            for(int j=1;j<obs[0].size();j++)ans[j]=(obs[i][j]?0:ans[j-1]+ans[j]);
        }
        return ans[ans.size()-1];
    }
};

力扣 64. 最小路径和(空间压缩)

题目:

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

代码:

class Solution {
public:
	int minPathSum(vector<vector<int>>& grid) {
		if (grid.empty())return -1;
		vector<int>ans1 = grid[0];
		for (int i = 1; i < ans1.size(); i++)
		{
			ans1[i] += ans1[i - 1];
		}
		vector<int>ans2;
		for (int i = 1; i < grid.size(); i++)
		{
			ans2 = grid[i];
			ans1[0] += ans2[0];
			for (int i = 1; i < ans1.size(); i++)
			{
				ans1[i] = min(ans1[i], ans1[i - 1]) + ans2[i];
			}
		}
		return ans1[ans1.size() - 1];
	}
};

CSU 1899: Yuelu Scenes(空间压缩)

题目:

Description
An artist begins with a roll of ribbon, one inch wide. She clips it into pieces of various integral lengths, then aligns them with the bottom of a frame, rising vertically in columns, to form a Yuelu scene. A Yuelu scene must be uneven; if all columns are the same height, it’s a plain scene, not a Yuelu scene! It is possible that she may not use all of the ribbon.

If our artist has 4 inches of ribbon and a 2 × 2 inch frame, she could form these scenes:

She would not form these scenes, because they're plains, not mountains!

Given the length of the ribbon and the width and height of the frame, all in inches, how many different Yuelu scenes can she create? Two scenes are different if the regions covered by ribbon are different. There’s no point in putting more than one piece of ribbon in any column.

Input
Each input will consist of several case. Each input will consist of a single line with three space-separated integers n, w and h, where n (0 n ≤ 10,000) is the length of the ribbon in inches, w (1 ≤ w ≤ 100) is the width and h (1 ≤ h ≤ 100) is the height of the frame, both in inches.

Output
Output a single integer, indicating the total number of Yuelu scenes our artist could possibly make, modulo 1e9 + 7.

Sample Input
25 5 5
15 5 5
10 10 1
4 2 2
Sample Output
7770
6050
1022
6

题目意思是说,在宽w高h的矩形里面放格子,格子数量不超过n,问有多少种放法使得不是每一列都完全一样高。

其实只要求出所有的放法,再减去一样高的放法就可以了。

考虑在宽i(i从1到w)高h的矩形格子里面放j个格子有多少种方法,用ans[i][j]表示

那么ans[i][j]=ans[i][j-1]+ans[i-1][j]-ans[i-1][j-h]

数组越界的按0算,当然,编程要保证不会越界。

这样,ans[w][0]+ans[w][1]+......+ans[w][n]就是所有的放法,再减去一样高的放法就可以了。

用了动态规划的空间压缩(刷表),所以代码不太好看懂。

代码:

#include<iostream>
using namespace std;
 
int ans[10001],p=1000000007;
 
int main()
{
	int n,w,h;
	while(cin>>n>>w>>h)
	{	
		if(n>w*h)n=w*h;
		for(int i=0;i<=n;i++)ans[i]=(i<=h);
		for(int k=2;k<=w;k++)
		{
			for(int i=k*h;i>=h+1;i--)ans[i]=(ans[i]-ans[i-h-1])%p;
			for(int i=1;i<=k*h;i++)ans[i]=(ans[i]+ans[i-1])%p;	
		}
		int s=-1-n/w;
		for(int i=0;i<=n;i++)s=(s+ans[i])%p;
		cout<<(s%p+p)%p<<endl;
	}
	return 0;
}

UVA 12034 Race

题目:

Disky and Sooma, two of the biggest mega minds of Bangladesh went to a far country. They ate, coded and wandered around, even in their holidays. They passed several months in this way. But everything has an end. A holy person, Munsiji came into their life. Munsiji took them to derby (horse racing). Munsiji enjoyed the race, but as usual Disky and Sooma did their as usual task instead of passing some romantic moments. They were thinking- in how many ways a race can finish! Who knows, maybe this is their romance! In a race there are n horses. 

You have to output the number of ways the race can finish. Note that, more than one horse may get the same position. For example, 2 horses can finish in 3 ways.

1. Both first
2. horse1 first and horse2 second
3. horse2 first and horse1 second


Input 

Input starts with an integer T (≤ 1000), denoting the number of test cases. Each case starts with a line containing an integer n (1 ≤ n ≤ 1000).


Output
For each case, print the case number and the number of ways the race can finish. The result can be very large, print the result modulo 10056.


Sample Input
3 1 2 3


Sample Output
Case 1: 1 

Case 2: 3 

Case 3: 13
这个题目是动态规划。

假设有 i 个不同的球,要放在 j 个不同的盒子里面,每个盒子里面都要有球,一共有 list[i][j] 种放法

那么有递归式list[i][j] = (list[i - 1][j - 1] + list[i - 1][j])*j

而要求的是∑(j) list[i][j]

代码:
 

#include<iostream>
#include<string.h>
using namespace std;
 
int list[1001][1001];
int sum[1001];
 
int main()
{
	
	memset(list, 0, sizeof(list));
	memset(sum, 0, sizeof(sum));
	list[1][1] = 1;
	sum[1] = 1;
	for (int i = 2; i <= 1000; i++)for (int j = 1; j <= i; j++)
	{
		list[i][j] = (list[i - 1][j - 1] + list[i - 1][j])*j % 10056;
		sum[i] += list[i][j];
	}
	int t, n;
	cin >> t;
	for (int i = 1; i <= t; i++)
	{
		cin >> n;
		cout << "Case " << i << ": " << sum[n] % 10056 << endl;
	}
	return 0;
}

力扣 72. 编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1 和 word2 由小写英文字母组成
class Solution {
public:
	int minDistance(string word1, string word2) {
		s1 = word1, s2 = word2;
		m.clear();
		return dp(0, 0);
	}
	int dp(int i, int j)
	{
		if (i >= s1.length())return s2.length() - j;
		if (j >= s2.length())return s1.length() - i;
		if (m[i].find(j) != m[i].end())return m[i][j];
		int ans = dp(i + 1, j + 1);
		if (s1[i] == s2[j])return m[i][j] = ans;
		ans = min(ans, dp(i, j + 1));
		ans = min(ans, dp(i + 1, j));
		return m[i][j] = ans + 1;
	}
	string s1, s2;
	map<int, map<int, int>>m;
};

力扣 813. 最大平均值和的分组

给定数组 nums 和一个整数 k 。我们将给定的数组 nums 分成 最多 k 个相邻的非空子数组 。 分数 由每个子数组内的平均值的总和构成。

注意我们必须使用 nums 数组中的每一个数进行分组,并且分数不一定需要是整数。

返回我们所能得到的最大 分数 是多少。答案误差在 10-6 内被视为是正确的。

示例 1:

输入: nums = [9,1,2,3,9], k = 3
输出: 20.00000
解释: 
nums 的最优分组是[9], [1, 2, 3], [9]. 得到的分数是 9 + (1 + 2 + 3) / 3 + 9 = 20. 
我们也可以把 nums 分成[9, 1], [2], [3, 9]. 
这样的分组得到的分数为 5 + 2 + 6 = 13, 但不是最大值.

示例 2:

输入: nums = [1,2,3,4,5,6,7], k = 4
输出: 20.50000

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 104
  • 1 <= k <= nums.length
class Solution {
public:
	double dp(vector<int>& nums, int n, int k)
	{
		if (m[n].find(k) != m[n].end())return m[n][k];
		if (k == 1)return s[n - 1] / n;
		for (int i = k - 1; i <= n - 1; i++)m[n][k] = max(m[n][k], dp(nums, i, k - 1) + (s[n - 1] - s[i - 1]) / (n - i));
		return m[n][k];
	}
	double largestSumOfAverages(vector<int>& nums, int k) {
		s.resize(nums.size());
		s[0] = nums[0];
		for (int i = 1; i < s.size(); i++)s[i] = s[i - 1] + nums[i];
		return dp(nums, nums.size(), k);
	}
	vector<double>s;
	map<int, map<int, double>>m;
};

力扣 931. 下降路径最小和

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径  最小和 。

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)(row + 1, col) 或者 (row + 1, col + 1) 。

示例 1:

输入:matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出:13
解释:如图所示,为和最小的两条下降路径

示例 2:

输入:matrix = [[-19,57],[-40,-5]]
输出:-59
解释:如图所示,为和最小的下降路径

提示:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 100
  • -100 <= matrix[i][j] <= 100
class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        for(int i=1;i<matrix.size();i++){
            for(int j=0;j<matrix[i].size();j++){
                int t=matrix[i-1][j];
                if(j)t=min(t,matrix[i-1][j-1]);
                if(j<matrix[i].size()-1)t=min(t,matrix[i-1][j+1]);
                matrix[i][j]+=t;
            }
        }
        int ans=INT_MAX;
        for(int j=0;j<matrix[0].size();j++)ans=min(ans,matrix[matrix.size()-1][j]);
        return ans;
    }
};

力扣 1314. 矩阵区域和

给你一个 m * n 的矩阵 mat 和一个整数 K ,请你返回一个矩阵 answer ,其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和: 

i - K <= r <= i + K, j - K <= c <= j + K 
(r, c) 在矩阵内。
 

示例 1:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], K = 1
输出:[[12,21,16],[27,45,33],[24,39,28]]
示例 2:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], K = 2
输出:[[45,45,45],[45,45,45],[45,45,45]]
 

提示:

m == mat.length
n == mat[i].length
1 <= m, n, K <= 100
1 <= mat[i][j] <= 100

class Solution {
public:
    int f(vector<vector<int>> &mat, int i, int j)
    {
        if (i < 0 || j < 0) return 0;
        if (i >= mat.size()) i = mat.size() - 1;
        if (j >= mat[0].size()) j = mat[0].size() - 1;
        return mat[i][j];
    }
    vector<vector<int>> matrixBlockSum(vector<vector<int>> &mat, int K)
    {
        vector<vector<int>> s = mat, ans =mat;
        for (int i = 0; i < s.size(); i++) for (int j = 0; j < s[0].size(); j++) 
            s[i][j] = f(s, i - 1, j) + f(s, i, j - 1) - f(s, i - 1, j - 1) + mat[i][j];
        for (int i = 0; i < s.size(); i++) for (int j = 0; j < s[0].size(); j++)
            ans[i][j] = f(s, i + K, j + K) - f(s, i - K - 1, j + K) - f(s, i + K, j - K - 1) + f(s, i - K - 1, j - K - 1);
        return ans;
    }
};

力扣 1883. 准时抵达会议现场的最小跳过休息次数(空间压缩)

给你一个整数 hoursBefore ,表示你要前往会议所剩下的可用小时数。要想成功抵达会议现场,你必须途经 n 条道路。道路的长度用一个长度为 n 的整数数组 dist 表示,其中 dist[i] 表示第 i 条道路的长度(单位:千米)。另给你一个整数 speed ,表示你在道路上前进的速度(单位:千米每小时)。

当你通过第 i 条路之后,就必须休息并等待,直到 下一个整数小时 才能开始继续通过下一条道路。注意:你不需要在通过最后一条道路后休息,因为那时你已经抵达会议现场。

  • 例如,如果你通过一条道路用去 1.4 小时,那你必须停下来等待,到 2 小时才可以继续通过下一条道路。如果通过一条道路恰好用去 2 小时,就无需等待,可以直接继续。

然而,为了能准时到达,你可以选择 跳过 一些路的休息时间,这意味着你不必等待下一个整数小时。注意,这意味着与不跳过任何休息时间相比,你可能在不同时刻到达接下来的道路。

  • 例如,假设通过第 1 条道路用去 1.4 小时,且通过第 2 条道路用去 0.6 小时。跳过第 1 条道路的休息时间意味着你将会在恰好 2 小时完成通过第 2 条道路,且你能够立即开始通过第 3 条道路。

返回准时抵达会议现场所需要的 最小跳过次数 ,如果 无法准时参会 ,返回 -1 。

示例 1:

输入:dist = [1,3,2], speed = 4, hoursBefore = 2
输出:1
解释:
不跳过任何休息时间,你将用 (1/4 + 3/4) + (3/4 + 1/4) + (2/4) = 2.5 小时才能抵达会议现场。
可以跳过第 1 次休息时间,共用 ((1/4 + 0) + (3/4 + 0)) + (2/4) = 1.5 小时抵达会议现场。
注意,第 2 次休息时间缩短为 0 ,由于跳过第 1 次休息时间,你是在整数小时处完成通过第 2 条道路。

示例 2:

输入:dist = [7,3,5,5], speed = 2, hoursBefore = 10
输出:2
解释:
不跳过任何休息时间,你将用 (7/2 + 1/2) + (3/2 + 1/2) + (5/2 + 1/2) + (5/2) = 11.5 小时才能抵达会议现场。
可以跳过第 1 次和第 3 次休息时间,共用 ((7/2 + 0) + (3/2 + 0)) + ((5/2 + 0) + (5/2)) = 10 小时抵达会议现场。

示例 3:

输入:dist = [7,3,5,5], speed = 1, hoursBefore = 10
输出:-1
解释:即使跳过所有的休息时间,也无法准时参加会议。

提示:

  • n == dist.length
  • 1 <= n <= 1000
  • 1 <= dist[i] <= 105
  • 1 <= speed <= 106
  • 1 <= hoursBefore <= 107

这题挺难的,我也是看官方题解之后照着写的,ans[i][j]的含义要想完全准确的定义出来还挺难的。

class Solution {
public:
	int minSkips(vector<int>& dist, int speed, long long hoursBefore) {
		vector<int>ans(dist.size() + 1, INT_MAX);
        ans[0]=0;
		for (int i = 1; i <= dist.size(); i++) {
			for (int j = i; j >= 0; j--) {
				if (j != i)ans[j] = (ans[j] + dist[i - 1] + speed - 1) / speed * speed;
				if(j) ans[j] = min(ans[j], ans[j - 1] + dist[i - 1]);
			}
		}
		hoursBefore *= speed;
		for (int j = 0; j < ans.size(); j++) {
			if (ans[j] <= hoursBefore)return j;
		}
		return -1;
	}
};

力扣 2809. 使数组和小于等于 x 的最少时间

给你两个长度相等下标从 0 开始的整数数组 nums1 和 nums2 。每一秒,对于所有下标 0 <= i < nums1.length ,nums1[i] 的值都增加 nums2[i] 。操作 完成后 ,你可以进行如下操作:

  • 选择任一满足 0 <= i < nums1.length 的下标 i ,并使 nums1[i] = 0 。

同时给你一个整数 x 。

请你返回使 nums1 中所有元素之和 小于等于 x 所需要的 最少 时间,如果无法实现,那么返回 -1 。

示例 1:

输入:nums1 = [1,2,3], nums2 = [1,2,3], x = 4
输出:3
解释:
第 1 秒,我们对 i = 0 进行操作,得到 nums1 = [0,2+2,3+3] = [0,4,6] 。
第 2 秒,我们对 i = 1 进行操作,得到 nums1 = [0+1,0,6+3] = [1,0,9] 。
第 3 秒,我们对 i = 2 进行操作,得到 nums1 = [1+1,0+2,0] = [2,2,0] 。
现在 nums1 的和为 4 。不存在更少次数的操作,所以我们返回 3 。

示例 2:

输入:nums1 = [1,2,3], nums2 = [3,3,3], x = 4
输出:-1
解释:不管如何操作,nums1 的和总是会超过 x 。

提示:

  • 1 <= nums1.length <= 103
  • 1 <= nums1[i] <= 103
  • 0 <= nums2[i] <= 103
  • nums1.length == nums2.length
  • 0 <= x <= 106
class Solution {
public:
	int minimumTime(vector<int>& nums1, vector<int>& nums2, int x) {
		VectorOpt::sortExtend(nums2, nums1);
		int s1 = 0, s2 = 0;
		for (auto x : nums1)s1 += x;
		for (auto x : nums2)s2 += x;
		if (s1 <= x)return 0;
		vector<vector<int>>m(nums1.size() + 1, vector<int>(nums1.size() + 1, 0));
		for (int k = 1; k <= nums1.size(); k++) {
			for (int i = k; i <= nums1.size(); i++) {
				m[i][k] = max(m[i - 1][k], nums1[i - 1] + k * nums2[i - 1] + m[i - 1][k - 1]);
			}
			if (s1 + s2 * k - m[nums1.size()][k] <= x)return k;
		}
		return -1;
	}
};

力扣 1155. 掷骰子等于目标和的方法数

这里有 n 个一样的骰子,每个骰子上都有 k 个面,分别标号为 1 到 k 。

给定三个整数 n ,  k 和 target ,返回可能的方式(从总共 kn 种方式中)滚动骰子的数量,使正面朝上的数字之和等于 target 。

答案可能很大,你需要对 109 + 7 取模 。

示例 1:

输入:n = 1, k = 6, target = 3
输出:1
解释:你扔一个有 6 个面的骰子。
得到 3 的和只有一种方法。

示例 2:

输入:n = 2, k = 6, target = 7
输出:6
解释:你扔两个骰子,每个骰子有 6 个面。
得到 7 的和有 6 种方法:1+6 2+5 3+4 4+3 5+2 6+1。

示例 3:

输入:n = 30, k = 30, target = 500
输出:222616187
解释:返回的结果必须是对 109 + 7 取模。

提示:

  • 1 <= n, k <= 30
  • 1 <= target <= 1000
class Solution {
public:
	int dp(int n, int k, int s) {
		if (s <n || s > n*k)return 0;
		if (n == 1)return s <= k;
		if (m[n][s])return m[n][s];
		return m[n][s] = ((dp(n, k, s - 1) + dp(n - 1, k, s - 1)) % p + p - dp(n - 1, k, s - k - 1)) % p;
	}
	int numRollsToTarget(int n, int k, int target) {
		m.clear();
		return dp(n, k, target);
	}
	map<int, map<int, int>>m;
	int p = 1000000007;
};

力扣 2312. 卖木头块

给你两个整数 m 和 n ,分别表示一块矩形木块的高和宽。同时给你一个二维整数数组 prices ,其中 prices[i] = [hi, wi, pricei] 表示你可以以 pricei 元的价格卖一块高为 hi 宽为 wi 的矩形木块。

每一次操作中,你必须按下述方式之一执行切割操作,以得到两块更小的矩形木块:

  • 沿垂直方向按高度 完全 切割木块,或
  • 沿水平方向按宽度 完全 切割木块

在将一块木块切成若干小木块后,你可以根据 prices 卖木块。你可以卖多块同样尺寸的木块。你不需要将所有小木块都卖出去。你 不能 旋转切好后木块的高和宽。

请你返回切割一块大小为 m x n 的木块后,能得到的 最多 钱数。

注意你可以切割木块任意次。

示例 1:

输入:m = 3, n = 5, prices = [[1,4,2],[2,2,7],[2,1,3]]
输出:19
解释:上图展示了一个可行的方案。包括:
- 2 块 2 x 2 的小木块,售出 2 * 7 = 14 元。
- 1 块 2 x 1 的小木块,售出 1 * 3 = 3 元。
- 1 块 1 x 4 的小木块,售出 1 * 2 = 2 元。
总共售出 14 + 3 + 2 = 19 元。
19 元是最多能得到的钱数。

示例 2:

输入:m = 4, n = 6, prices = [[3,2,10],[1,4,2],[4,1,3]]
输出:32
解释:上图展示了一个可行的方案。包括:
- 3 块 3 x 2 的小木块,售出 3 * 10 = 30 元。
- 1 块 1 x 4 的小木块,售出 1 * 2 = 2 元。
总共售出 30 + 2 = 32 元。
32 元是最多能得到的钱数。
注意我们不能旋转 1 x 4 的木块来得到 4 x 1 的木块。

提示:

  • 1 <= m, n <= 200
  • 1 <= prices.length <= 2 * 104
  • prices[i].length == 3
  • 1 <= hi <= m
  • 1 <= wi <= n
  • 1 <= pricei <= 106
  • 所有 (hi, wi) 互不相同 。
class Solution {
public:
	long long sellingWood(int m, int n, vector<vector<int>>& prices) {
		vm.clear();
		for (auto &vi : prices)vm[vi[0]][vi[1]] = vi[2];
		ans.clear();
		return dp(m, n);
	}
	long long dp(int m, int n) {
		if (ans[m].find(n) != ans[m].end())return ans[m][n];
		if (m == 1 && n == 1)return vm[1][1];
		long long s = vm[m][n];
		for (int i = 1; i*2 <= m; i++)s = max(s, dp(i, n) + dp(m - i, n));
		for (int i = 1; i*2 <= n; i++)s = max(s, dp(m, i) + dp(m, n - i));
		return ans[m][n] = s;
	}
	unordered_map<int, unordered_map<int, long long>>ans;
	unordered_map<int, unordered_map<int, int>>vm;
};

力扣 2684. 矩阵中移动的最大次数

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

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

  • 从单元格 (row, col) 可以移动到 (row - 1, col + 1)(row, col + 1) 和 (row + 1, col + 1) 三个单元格中任一满足值 严格 大于当前单元格的单元格。

返回你在矩阵中能够 移动 的 最大 次数。

示例 1:

输入:grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]]
输出:3
解释:可以从单元格 (0, 0) 开始并且按下面的路径移动:
- (0, 0) -> (0, 1).
- (0, 1) -> (1, 2).
- (1, 2) -> (2, 3).
可以证明这是能够移动的最大次数。

示例 2:

 

输入:grid = [[3,2,4],[2,1,9],[1,1,7]]
输出:0
解释:从第一列的任一单元格开始都无法移动。

提示:

  • m == grid.length
  • n == grid[i].length
  • 2 <= m, n <= 1000
  • 4 <= m * n <= 105
  • 1 <= grid[i][j] <= 106
class Solution {
public:
    int maxMoves(vector<vector<int>>& grid) {
        vector<vector<int>>ans(grid.size(),vector<int>(grid[0].size()));
        int r=0;
        for(int i=1;i<grid[0].size();i++){
            for(int j=0;j<grid.size();j++){
                for(int j2=j-1;j2<=j+1;j2++){
                    if(j2>=0&&j2<grid.size()&&grid[j2][i-1]<grid[j][i]&&
                        (ans[j2][i-1]>0 || i==1)){
                        ans[j][i]=max(ans[j][i],ans[j2][i-1]+1);
                        r=max(r,ans[j][i]);
                    }
                        
                }
            }
        }
        return r;
    }
};

二,3维DP

力扣 1223. 掷骰子模拟(空间压缩,解空间平移)

题目:

有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数。

不过我们在使用它时有个约束,就是使得投掷骰子时,连续 掷出数字 i 的次数不能超过 rollMax[i]i 从 1 开始编号)。

现在,给你一个整数数组 rollMax 和一个整数 n,请你来计算掷 n 次骰子可得到的不同点数序列的数量。

假如两个序列中至少存在一个元素不同,就认为这两个序列是不同的。由于答案可能很大,所以请返回 模 10^9 + 7 之后的结果。

示例 1:

输入:n = 2, rollMax = [1,1,2,2,2,3]
输出:34
解释:我们掷 2 次骰子,如果没有约束的话,共有 6 * 6 = 36 种可能的组合。但是根据 rollMax 数组,数字 1 和 2 最多连续出现一次,所以不会出现序列 (1,1) 和 (2,2)。因此,最终答案是 36-2 = 34。

示例 2:

输入:n = 2, rollMax = [1,1,1,1,1,1]
输出:30

示例 3:

输入:n = 3, rollMax = [1,1,1,2,2,3]
输出:181

提示:

  • 1 <= n <= 5000
  • rollMax.length == 6
  • 1 <= rollMax[i] <= 15

思路一:三维DP

用ans[i][j][k]表示,第i次掷骰子得到的数为j,且这是连续出现的第k个j的情况数。

用s[i][j]表示所有ans[i][j][...]的和,用ss[i]表示所有所有ans[i][...][...]的和

则递推式是:

s[i][j]=\sum _kans[i][j][k] \\ ss[i]=\sum_js[i][j]\\ ans[i][j][k] = \left\{\begin{matrix}ans[i-1][j][k-1] ,k>1 \\ ss[i-1]-s[i-1][j],k=1 \end{matrix}\right.

代码比较简单,我就不写了。

时间复杂度:n * rollMax.length * rollMax.getMax()  空间复杂度:n * rollMax.length * rollMax.getMax()

这里时间空间都是5000*6*15, 可以接受。

思路二:空间压缩

以计算顺序,即时间,代替空间中的一个维度。

ans[j][k]表示当次掷骰子得到的数为j,且这是连续出现的第k个j的情况数。

s[j]=\sum _kans[j][k] \\ ss=\sum_js[j]\\ ans[j][k] = \left\{\begin{matrix}ans[j][k-1] ,k>1 \\ ss-s[j],k=1 \end{matrix}\right.

下面代码中的s[0]就是ss

代码:

const int p = 1000000007;
class Solution {
public:
	int dieSimulator(int n, vector<int>& rollMax) {
		rollMax.insert(rollMax.begin(), 0);
		long long ans[7][16], s[7] = { 0 };
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= 6; i++)s[i] = ans[i][1] = 1, s[0] += s[i];
		while (--n)
		{
			for (int i = 1; i <= 6; i++)
			{
				ans[i][0] = ans[i][rollMax[i]];
				for (int j = rollMax[i]; j > 1; j--)ans[i][j] = ans[i][j - 1];
				ans[i][1] = s[0] - s[i];
			}
			s[0] = 0;
			for (int i = 1; i <= 6; i++)
			{
				s[i] = (s[i] + ans[i][1] - ans[i][0]) % p, s[0] += s[i];
			}
		}
		return (s[0]%p + p) % p;
	}
};

时间复杂度:n * rollMax.length * rollMax.getMax()  空间复杂度:rollMax.length * rollMax.getMax()

思路三:解空间平移

把解空间的一个维度(这里的ans[j][k]的k这个维度)卷起来,收尾相接,从平面变成圆柱面。

一般的思路都是用解空间的绝对坐标进行对应,这里我用相对坐标,n每变化一次,解空间就平移一个单位,从而避免了ans数组的平移操作。

可以用n直接进行平移计算,不过为了程序好写好理解,我加了一个key数组。

key数组的含义是:把圆柱面沿着k=key这条直线剪开,得到的平面就是解空间的平面,用key的平移代替了数组的平移。

代码:

const int p = 1000000007;
class Solution {
public:
	int dieSimulator(int n, vector<int>& rollMax) {
		rollMax.insert(rollMax.begin(), 0);
		long long ans[7][16], s[7] = { 0 }, key[7];
		memset(ans, 0, sizeof(ans));
		for (int i = 1; i <= 6; i++)s[i] = ans[i][1] = 1, s[0] += s[i], key[i] = rollMax[i];
		while (--n)
		{			
			for (int i = 1; i <= 6; i++)
			{
				ans[i][0] = ans[i][key[i]], ans[i][key[i]] = s[0] - s[i];
				key[i] = (key[i] == 1) ? rollMax[i] : key[i]-1;
			}
			s[0] = 0;
			for (int i = 1; i <= 6; i++)
			{
				s[i] = (s[i] + ans[i][key[i] % rollMax[i] + 1] - ans[i][0]) % p, s[0] += s[i];
			}
		}
		return (s[0]%p + p) % p;
	}
};

时间复杂度:n * rollMax.length  空间复杂度:rollMax.length * rollMax.getMax()

CSU 1750 切蛋糕

题目:


Description

小明考试得了高分,妈妈很高兴,就买了蛋糕奖励小明,蛋糕呈矩形状,且上方有细小的纹路将蛋糕分成了  n*m小块,但是分割蛋糕必须要沿着纹路进行切割,每次切割都会消耗一定的体力值,消耗的体力值为切割处蛋糕的单位长度  (例如在  3*4的蛋糕处切割蛋糕分成  3*1 和  3*3两块那么消耗的体力值就是 3),妈妈担心小明吃太多甜食对身体不好,所以事先规定了小明只能吃  k单位小块的蛋糕  ( k不大于  n*m ) 。小明吃蛋糕必须整个一大块吃下去,如果只吃一部分必须要切割出来才行,他不想消耗过多体力,所以他希望你帮他算算他吃到  k单位小块蛋糕最少消耗多少体力
Input

第一行输入一个  T,表示数据组数
(T<=1000)
接下来每组数据输入  3个正整数  n , m , k (n,m<=30 , k<=50)
Output

每组数据输出一个消耗的最小体力值
Sample Input

4
2 2 1
2 2 3
2 2 2
2 2 4
Sample Output

3
3
2
0

这个题目必须用动态规划来做。

像这种三维甚至超过三维的,我更倾向于备忘录的方法。

因为这样就不需要根据三维解空间的结构进行遍历,思路会简单一些。

可以看出来,main函数是很简单的。

搜索函数f主要是2个问题,

第一,如何设置边界,这个需要仔细考虑,少了一种情况就会出错,或者死循环。

第二,递归表达式怎么写。这个倒是不难,因为当你想到用动态规划做一个题目的时候,你已经基本上清楚了递归式是怎样的了。

代码:
 

#include<iostream>
#include<string.h>
using namespace std;

int num[31][31][51];

int f(int n, int m, int k)
{
	if (num[n][m][k]>-3000)return num[n][m][k];
	if (n*m == k)return 0;
	if (n*m < k)return -2000;
	if (k == 0)return 0;
	if (k < 0)return -2000;
	int s = 1000, t;
	for (int i = 1; i < n; i++)
	{
		for (int j = 0; j <= i*m && j <= k; j++)
		{
			t = f(i, m, j) + f(n - i, m, k - j) + m;
			if (t>=0 && s>t)s = t;
		}
	}
	for (int i = 1; i < m; i++)
	{
		for (int j = 0; j <= i*n && j <= k; j++)
		{
			t = f(n, i, j) + f(n, m - i, k - j) + n;
			if (t>=0 && s>t)s = t;
		}
	}
	num[n][m][k] = s;
	return s;
}

int main()
{
	for (int i = 0; i < 31; i++)for (int j = 0; j < 31; j++)
		for (int k = 0; k < 51; k++)num[i][j][k] = -3000;
	int t, n, m, k;
	cin >> t;
	while (t--)
	{
		cin >> n >> m >> k;
		cout << f(n, m, k) << endl;
	}
	return 0;
}

力扣 87. 扰乱字符串

使用下面描述的算法可以扰乱字符串 s 得到字符串 t :

  1. 如果字符串的长度为 1 ,算法停止
  2. 如果字符串的长度 > 1 ,执行下述步骤:
    • 在一个随机下标处将字符串分割成两个非空的子字符串。即,如果已知字符串 s ,则可以将其分成两个子字符串 x 和 y ,且满足 s = x + y 。
    • 随机 决定是要「交换两个子字符串」还是要「保持这两个子字符串的顺序不变」。即,在执行这一步骤之后,s 可能是 s = x + y 或者 s = y + x 。
    • 在 x 和 y 这两个子字符串上继续从步骤 1 开始递归执行此算法。

给你两个 长度相等 的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:s1 = "great", s2 = "rgeat"
输出:true
解释:s1 上可能发生的一种情形是:
"great" --> "gr/eat" // 在一个随机下标处分割得到两个子字符串
"gr/eat" --> "gr/eat" // 随机决定:「保持这两个子字符串的顺序不变」
"gr/eat" --> "g/r / e/at" // 在子字符串上递归执行此算法。两个子字符串分别在随机下标处进行一轮分割
"g/r / e/at" --> "r/g / e/at" // 随机决定:第一组「交换两个子字符串」,第二组「保持这两个子字符串的顺序不变」
"r/g / e/at" --> "r/g / e/ a/t" // 继续递归执行此算法,将 "at" 分割得到 "a/t"
"r/g / e/ a/t" --> "r/g / e/ a/t" // 随机决定:「保持这两个子字符串的顺序不变」
算法终止,结果字符串和 s2 相同,都是 "rgeat"
这是一种能够扰乱 s1 得到 s2 的情形,可以认为 s2 是 s1 的扰乱字符串,返回 true

示例 2:

输入:s1 = "abcde", s2 = "caebd"
输出:false

示例 3:

输入:s1 = "a", s2 = "a"
输出:true

提示:

  • s1.length == s2.length
  • 1 <= s1.length <= 30
  • s1 和 s2 由小写英文字母组成
class Solution {
public:
	bool isScramble(string s1, string s2) {
		m.clear();
		return isScramble(s1, s2, 0, 0);
	}
	bool isScramble(string s1, string s2, int id1, int id2) {
		if (m[id1][id2].find(s1.length()) != m[id1][id2].end())return m[id1][id2][s1.length()];
		if (s1 == s2)return true;
		map<char, int>mc;
		int s = 0;
		for (int i = 0; i < s1.length() - 1; i++) {
			if (mc[s1[i]] == 0)s++;
			if (mc[s1[i]]++ == -1)s--;
			if (mc[s2[i]] == 0)s++;
			if (mc[s2[i]]-- == 1)s--;
			if (s == 0 && isScramble(s1.substr(0, i + 1), s2.substr(0, i + 1), id1,id2) 
				&& isScramble(s1.substr(i + 1, s1.length() - i - 1), s2.substr(i + 1, s1.length() - i - 1), id1+ i + 1, id2+ i + 1)) {
				return m[id1][id2][s1.length()] = true;
			}
		}
		mc.clear();
		s = 0;
		for (int i = 0; i < s1.length() - 1; i++) {
			if (mc[s1[i]] == 0)s++;
			if (mc[s1[i]]++ == -1)s--;
			if (mc[s2[s1.length() - 1 - i]] == 0)s++;
			if (mc[s2[s1.length() - 1 - i]]-- == 1)s--;
			if (s == 0 && isScramble(s1.substr(0, i + 1), s2.substr(s1.length() - i - 1, i + 1), id1, id2 + s1.length() - i - 1)
				&& isScramble(s1.substr(i + 1, s1.length() - i - 1), s2.substr(0, s1.length() - i - 1), id1 + i + 1, id2)) {
				return m[id1][id2][s1.length()] = true;
			}{}
		}
		return m[id1][id2][s1.length()] = false;
	}
	map<int, map<int, map<int, bool>>>m;
};

力扣 1420. 生成数组

给你三个整数 n、m 和 k 。下图描述的算法用于找出正整数数组中最大的元素。

请你生成一个具有下述属性的数组 arr :

arr 中有 n 个整数。
1 <= arr[i] <= m 其中 (0 <= i < n) 。
将上面提到的算法应用于 arr ,search_cost 的值等于 k 。
返回上述条件下生成数组 arr 的 方法数 ,由于答案可能会很大,所以 必须 对 10^9 + 7 取余。

示例 1:

输入:n = 2, m = 3, k = 1
输出:6
解释:可能的数组分别为 [1, 1], [2, 1], [2, 2], [3, 1], [3, 2] [3, 3]
示例 2:

输入:n = 5, m = 2, k = 3
输出:0
解释:没有数组可以满足上述条件
示例 3:

输入:n = 9, m = 1, k = 1
输出:1
解释:可能的数组只有 [1, 1, 1, 1, 1, 1, 1, 1, 1]
示例 4:

输入:n = 50, m = 100, k = 25
输出:34549172
解释:不要忘了对 1000000007 取余
示例 5:

输入:n = 37, m = 17, k = 7
输出:418930126
 

提示:

1 <= n <= 50
1 <= m <= 100
0 <= k <= n

思路:三维DP

int p=1000000007;
long long res[51][101][51];
class Solution {
public:
    long long numOfArrays(int n, int m, int k) {
         if(n<=0||m<=0||k<=0||k>n||k>m)return 0;
         if(n==1)return m;
         if(m==1)return 1;
         if(res[n][m][k])return res[n][m][k];
         long long ans=numOfArrays(n,m-1,k)+(numOfArrays(n-1,m,k)-numOfArrays(n-1,m-1,k))*m%p+numOfArrays(n-1,m-1,k-1);
         return res[n][m][k] = (ans%p+p)%p;
    }
};

递推式解释:

如果最后一个数不是m,那就是numOfArrays(n,m-1,k)

如果最后一个数是m,前面出现过m,那就是(numOfArrays(n-1,m,k)-numOfArrays(n-1,m-1,k))*m

如果最后一个数是m,前面没有出现过m,那就是numOfArrays(n-1,m-1,k-1)

力扣 1473. 给房子涂色 III

在一个小城市里,有 m 个房子排成一排,你需要给每个房子涂上 n 种颜色之一(颜色编号为 1 到 n )。有的房子去年夏天已经涂过颜色了,所以这些房子不需要被重新涂色。

我们将连续相同颜色尽可能多的房子称为一个街区。(比方说 houses = [1,2,2,3,3,2,1,1] ,它包含 5 个街区  [{1}, {2,2}, {3,3}, {2}, {1,1}] 。)

给你一个数组 houses ,一个 m * n 的矩阵 cost 和一个整数 target ,其中:

houses[i]:是第 i 个房子的颜色,0 表示这个房子还没有被涂色。
cost[i][j]:是将第 i 个房子涂成颜色 j+1 的花费。
请你返回房子涂色方案的最小总花费,使得每个房子都被涂色后,恰好组成 target 个街区。如果没有可用的涂色方案,请返回 -1 。

示例 1:

输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:9
解释:房子涂色方案为 [1,2,2,1,1]
此方案包含 target = 3 个街区,分别是 [{1}, {2,2}, {1,1}]。
涂色的总花费为 (1 + 1 + 1 + 1 + 5) = 9。
示例 2:

输入:houses = [0,2,1,2,0], cost = [[1,10],[10,1],[10,1],[1,10],[5,1]], m = 5, n = 2, target = 3
输出:11
解释:有的房子已经被涂色了,在此基础上涂色方案为 [2,2,1,2,2]
此方案包含 target = 3 个街区,分别是 [{2,2}, {1}, {2,2}]。
给第一个和最后一个房子涂色的花费为 (10 + 1) = 11。
示例 3:

输入:houses = [0,0,0,0,0], cost = [[1,10],[10,1],[1,10],[10,1],[1,10]], m = 5, n = 2, target = 5
输出:5
示例 4:

输入:houses = [3,1,2,3], cost = [[1,1,1],[1,1,1],[1,1,1],[1,1,1]], m = 4, n = 3, target = 3
输出:-1
解释:房子已经被涂色并组成了 4 个街区,分别是 [{3},{1},{2},{3}] ,无法形成 target = 3 个街区。
 

提示:

m == houses.length == cost.length
n == cost[i].length
1 <= m <= 100
1 <= n <= 20
1 <= target <= m
0 <= houses[i] <= n
1 <= cost[i][j] <= 10^4

我比赛提交的的代码:

int maxint=1234567;
class Solution {
public:
    int ans[105][22][105];
    int dp(vector<int>& houses, vector<vector<int>>& cost, int m, int n,int nn, int target) {
        if(m==0)
        {
            if(target!=1)return maxint;
            if(houses[m]>0)return 0;
            return cost[0][nn-1];
        }
        if(target<=0)return maxint;
        if(ans[m][nn][target]<maxint)return ans[m][nn][target];
        if(houses[m]>0 && houses[m]!=nn)return maxint;
        for(int ni=1;ni<=n;ni++)ans[m][nn][target]=min(ans[m][nn][target],((houses[m]>0)?0:cost[m][nn-1])+dp(houses,cost,m-1,n,ni,target-(ni!=nn)));
        return ans[m][nn][target];
    }
    int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
        for(int i=0;i<=m;i++)for(int j=0;j<=n;j++)for(int k=0;k<=target;k++)ans[i][j][k]=maxint;
        int r=maxint;
        for(int ni=1;ni<=n;ni++)r=min(r,dp(houses,cost,m-1,n,ni,target));
        if(r==maxint)return -1;
        return r;
    }
};

结果是解答错误,加上第九行

if(houses[m]>0 && houses[m]!=nn)return maxint;

解决了计算错误的问题,但是超时了。

仔细一分析,在于我的maxint这个值既用作初始值,也用作表示无解的-1,造成了混乱。

要区分这个,我加了一行,改了一行,代码变成:

int maxint=1234567;
class Solution {
public:
    int ans[105][22][105];
    int dp(vector<int>& houses, vector<vector<int>>& cost, int m, int n,int nn, int target) {
        if(m==0)
        {
            if(target!=1)return maxint;
            if(houses[m]>0 && houses[m]!=nn)return maxint;  add
            if(houses[m]>0)return 0;
            return cost[0][nn-1];
        }
        if(target<=0)return maxint;
        if(ans[m][nn][target]<maxint)return ans[m][nn][target];
        if(houses[m]>0 && houses[m]!=nn)return maxint;
        ans[m][nn][target]--;
        for(int ni=1;ni<=n;ni++)ans[m][nn][target]=min(ans[m][nn][target],((houses[m]>0)?0:cost[m][nn-1])+dp(houses,cost,m-1,n,ni,target-(ni!=nn)));
        return ans[m][nn][target];
    }
    int minCost(vector<int>& houses, vector<vector<int>>& cost, int m, int n, int target) {
        for(int i=0;i<=m;i++)for(int j=0;j<=n;j++)for(int k=0;k<=target;k++)ans[i][j][k]=maxint;
        int r=maxint;
        for(int ni=1;ni<=n;ni++)r=min(r,dp(houses,cost,m-1,n,ni,target));
        if(r>maxint-2)return -1;
        return r;
    }
};

这个是AC的

力扣 1504. 统计全 1 子矩形(空间压缩)

给你一个只包含 0 和 1 的 rows * columns 矩阵 mat ,请你返回有多少个 子矩形 的元素全部都是 1 。

示例 1:

输入:mat = [[1,0,1],
            [1,1,0],
            [1,1,0]]
输出:13
解释:
有 6 个 1x1 的矩形。
有 2 个 1x2 的矩形。
有 3 个 2x1 的矩形。
有 1 个 2x2 的矩形。
有 1 个 3x1 的矩形。
矩形数目总共 = 6 + 2 + 3 + 1 + 1 = 13 。
示例 2:

输入:mat = [[0,1,1,0],
            [0,1,1,1],
            [1,1,1,0]]
输出:24
解释:
有 8 个 1x1 的子矩形。
有 5 个 1x2 的子矩形。
有 2 个 1x3 的子矩形。
有 4 个 2x1 的子矩形。
有 2 个 2x2 的子矩形。
有 2 个 3x1 的子矩形。
有 1 个 3x2 的子矩形。
矩形数目总共 = 8 + 5 + 2 + 4 + 2 + 2 + 1 = 24 。
示例 3:

输入:mat = [[1,1,1,1,1,1]]
输出:21
示例 4:

输入:mat = [[1,0,1],[0,1,0],[1,0,1]]
输出:5
 

提示:

1 <= rows <= 150
1 <= columns <= 150
0 <= mat[i][j] <= 1

int ans[150][150];
int res = 0;
void dp(vector<int>&v)
{
	for (int i = 1; i < v.size(); i++)v[i] += v[i - 1];
	for (int left = 0; left < v.size(); left++)for (int right = left; right < v.size(); right++)
		if (v[right] - (left ? v[left - 1] : 0) == right - left + 1)res+=++ans[left][right];
		else ans[left][right] = 0;
}
class Solution {
public:
    int numSubmat(vector<vector<int>>& mat) {
        res = 0;
        for (int left = 0; left < mat[0].size(); left++)for (int right = left; right < mat[0].size(); right++)ans[left][right] = 0;
        for (int i = 0; i < mat.size(); i++)dp(mat[i]);
        return res;
    }
};

​力扣 1682. 最长回文子序列 II

字符串 s 的某个子序列符合下列条件时,称为“好的回文子序列”:

它是 s 的子序列。
它是回文序列(反转后与原序列相等)。
长度为偶数。
除中间的两个字符外,其余任意两个连续字符不相等。
例如,若 s = "abcabcabb",则 "abba" 可称为“好的回文子序列”,而 "bcb" (长度不是偶数)和 "bbbb" (含有相等的连续字符)不能称为“好的回文子序列”。

给定一个字符串 s, 返回 s 的最长“好的回文子序列”的长度。

示例 1:

输入: s = "bbabab"
输出: 4
解释: s 的最长“好的回文子序列”是 "baab"。
示例 2:

输入: s = "dcbccacdb"
输出: 4
解释: s 的最长“好的回文子序列”是 "dccd"。
 

提示:

1 <= s.length <= 250
s 包含小写英文字母。

class Solution {
public:
	int dp(int s, int e, int c)
	{
		if (s >= e)return 0;
		if (ans[c][s][e]>=0)return ans[c][s][e];
		int r = max(dp(s + 1, e, c), dp(s, e - 1, c));
		if (str[s] == str[e] && str[s]-'a' != c)r = max(r, dp(s + 1, e - 1, str[s] - 'a') + 2);
		return ans[c][s][e] = r;
	}
	int longestPalindromeSubseq(string s) {
		str = s;
		for (int i = 0; i < 27; i++)for (int j = 0; j < s.length(); j++)memset(ans[i][j], -1, 4*s.length());
		return dp(0, s.length() - 1, 26);
	}
	string str;
	int ans[27][250][250];
};

力扣 741. 摘樱桃

给你一个 n x n 的网格 grid ,代表一块樱桃地,每个格子由以下三种数字的一种来表示:

  • 0 表示这个格子是空的,所以你可以穿过它。
  • 1 表示这个格子里装着一个樱桃,你可以摘到樱桃然后穿过它。
  • -1 表示这个格子里有荆棘,挡着你的路。

请你统计并返回:在遵守下列规则的情况下,能摘到的最多樱桃数:

  • 从位置 (0, 0) 出发,最后到达 (n - 1, n - 1) ,只能向下或向右走,并且只能穿越有效的格子(即只可以穿过值为 0 或者 1 的格子);
  • 当到达 (n - 1, n - 1) 后,你要继续走,直到返回到 (0, 0) ,只能向上或向左走,并且只能穿越有效的格子;
  • 当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为 0 );
  • 如果在 (0, 0) 和 (n - 1, n - 1) 之间不存在一条可经过的路径,则无法摘到任何一个樱桃。

示例 1:

输入:grid = [[0,1,-1],[1,0,-1],[1,1,1]]
输出:5
解释:玩家从 (0, 0) 出发:向下、向下、向右、向右移动至 (2, 2) 。
在这一次行程中捡到 4 个樱桃,矩阵变成 [[0,1,-1],[0,0,-1],[0,0,0]] 。
然后,玩家向左、向上、向上、向左返回起点,再捡到 1 个樱桃。
总共捡到 5 个樱桃,这是最大可能值。

示例 2:

输入:grid = [[1,1,-1],[1,-1,1],[-1,1,1]]
输出:0

提示:

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 50
  • grid[i][j] 为 -10 或 1
  • grid[0][0] != -1
  • grid[n - 1][n - 1] != -1
class Solution {
public:
    int cherryPickup(vector<vector<int>>& grid) {
        int n = grid.size();
        vector<vector<vector<int>>>v(n, vector<vector<int>>(n, vector<int>(n, 0)));
        v[0][0][0] = grid[0][0];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] < 0)continue;
                for (int k = j; k < n; k++) {
                    if (grid[i][k] < 0)continue;
                    if (i)v[i][j][k] = v[i - 1][j][k];
                    if(j) 
                }
            }
        }
        int ans = 0;
        return ans;
    }
};

力扣 1463. 摘樱桃 II(空间压缩)

给你一个 rows x cols 的矩阵 grid 来表示一块樱桃地。 grid 中每个格子的数字表示你能获得的樱桃数目。

你有两个机器人帮你收集樱桃,机器人 1 从左上角格子 (0,0) 出发,机器人 2 从右上角格子 (0, cols-1) 出发。

请你按照如下规则,返回两个机器人能收集的最多樱桃数目:

  • 从格子 (i,j) 出发,机器人可以移动到格子 (i+1, j-1)(i+1, j) 或者 (i+1, j+1) 。
  • 当一个机器人经过某个格子时,它会把该格子内所有的樱桃都摘走,然后这个位置会变成空格子,即没有樱桃的格子。
  • 当两个机器人同时到达同一个格子时,它们中只有一个可以摘到樱桃。
  • 两个机器人在任意时刻都不能移动到 grid 外面。
  • 两个机器人最后都要到达 grid 最底下一行。

示例 1:

输入:grid = [[3,1,1],[2,5,1],[1,5,5],[2,1,1]]
输出:24
解释:机器人 1 和机器人 2 的路径在上图中分别用绿色和蓝色表示。
机器人 1 摘的樱桃数目为 (3 + 2 + 5 + 2) = 12 。
机器人 2 摘的樱桃数目为 (1 + 5 + 5 + 1) = 12 。
樱桃总数为: 12 + 12 = 24 。

示例 2:

输入:grid = [[1,0,0,0,0,0,1],[2,0,0,0,0,3,0],[2,0,9,0,0,0,0],[0,3,0,5,4,0,0],[1,0,2,3,0,0,6]]
输出:28
解释:机器人 1 和机器人 2 的路径在上图中分别用绿色和蓝色表示。
机器人 1 摘的樱桃数目为 (1 + 9 + 5 + 2) = 17 。
机器人 2 摘的樱桃数目为 (1 + 3 + 4 + 3) = 11 。
樱桃总数为: 17 + 11 = 28 。

示例 3:

输入:grid = [[1,0,0,3],[0,0,0,3],[0,0,3,3],[9,0,3,3]]
输出:22

示例 4:

输入:grid = [[1,1],[1,1]]
输出:4

提示:

  • rows == grid.length
  • cols == grid[i].length
  • 2 <= rows, cols <= 70
  • 0 <= grid[i][j] <= 100 
class Solution {
public:
	int cherryPickup(vector<vector<int>>& grid) {
		int col = grid[0].size();
		vector<vector<int>>v(col, vector<int>(col, INT_MIN));
		v[0][col - 1] = grid[0][0] + grid[0][col - 1];
		for (int i = 1; i < grid.size(); i++) {
			auto v2=v;
			for (int j = 0; j < col; j++) {
				for (int k = j + 1; k < col; k++) {
					for (int j2 = max(j - 1, 0); j2 <= min(j + 1, col - 1); j2++) {
						for (int k2 = max(k - 1, 0); k2 <= min(k + 1, col - 1); k2++) {
							v2[j][k] = max(v2[j][k], v[j2][k2]);
						}
					}
					v2[j][k] += grid[i][j] + grid[i][k];
				}
			}
			v = v2;
		}
		int ans = 0;
		for (int j = 0; j < col; j++) {
			for (int k = 0; k < col; k++) {
				ans = max(ans, v[j][k]);
			}
		}
		return ans;
	}
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值