螺旋归纳DP

目录

螺旋归纳

[力扣 LCP 14. 切分数组](#力扣 LCP 14. 切分数组)

[力扣 3573. 买卖股票的最佳时机 V](#力扣 3573. 买卖股票的最佳时机 V)


螺旋归纳

数学归纳法中的螺旋归纳,参考归纳法

简单的说,就是2个dp函数交替求解。

力扣 LCP 14. 切分数组

给定一个整数数组 nums ,小李想将 nums 切割成若干个非空子数组,使得每个子数组最左边的数和最右边的数的最大公约数大于 1 。为了减少他的工作量,请求出最少可以切成多少个子数组。

示例 1:

输入:nums = [2,3,3,2,3,3]

输出:2

解释:最优切割为 2,3,3,23,3 。第一个子数组头尾数字的最大公约数为 2 ,第二个子数组头尾数字的最大公约数为 3 。

示例 2:

输入:nums = [2,3,5,7]

输出:4

解释:只有一种可行的切割:2, 3, 5, 7

限制:

  • 1 <= nums.length <= 10^5
  • 2 <= nums[i] <= 10^6

递推式:

  • 数组a的下标是从1到n
  • splitArray函数是一个数组的最小切分数
  • lcm(k)是数组a前k个数的最小公倍数
  • dpk=splitArray(a1...k),即前k个数的最小切分数,dp0=0, dp1=1
  • f(p,k) = min{dpi | 0<=i<k,p|ai+1} , 其中p|lcm(k), f(p,k)表示前k个数中,一个p的倍数前面的子数组的最小切分数
  • dpk = min{f(p,k)+1 | p|ak}

把形如f(......,k)的所有式子打包到一起,看成一个整体g(k)。

那么g(k)的求解依赖dp(0)到dp(k-1)这k项,而dp(k)的求解依赖g(k),所以整体是一个螺旋归纳法。

具体实现:

实际上,lcm也是一个动态规划的函数,是数列的一维动态规划。

也就是说,整体是3个动态规划函数的螺旋归纳。

在实现层面,还需要降维,把f降到1维,把lcm降到0维。

代码:

为了方便对照,写了个形式最贴近递推式的代码:

cpp 复制代码
class Solution {
public:
	int dp(int k) {
		if (k < 2)return k;
		if (m_dp[k])return m_dp[k];
		vector<int> v = GetFacs(nums[k]);
		g(v, k);
		int ans = INT_MAX;
		for (auto vi : v) ans = min(ans, f(vi, k) + 1);
		return m_dp[k]=ans;
	}
	void g(vector<int>&v,int k)
	{
		for (auto vi : v) {
			if (m_f.find(vi) == m_f.end())m_f[vi] = dp(k - 1);
			else m_f[vi] = min(m_f[vi], dp(k - 1));
		}
	}
	int f(int p, int k)
	{
		return m_f[p];
	}
	int splitArray(vector<int>& nums) {
		this->nums = nums;
		nums.insert(nums.begin(), 0);
		return dp(nums.size() - 1);
	}
	vector<int> nums;
	map<int, int>m_f;
	map<int, int>m_dp;
};

可惜代码是错的。

改正之后:

cpp 复制代码
class Solution {
public:
	int dp(int k) {
		if (k < 1)return 0;
		if (m_dp[k])return m_dp[k];
		vector<int> v = GetFacs(nums[k]);
		g(v, k);
		int ans = INT_MAX;
		for (auto vi : v) ans = min(ans, f(vi, k) + 1);
		return m_dp[k]=ans;
	}
	void g(vector<int>&v,int k)
	{
		for (auto vi : v) {
			int ans = dp(k - 1);
			if (m_f.find(vi) == m_f.end())m_f[vi] = ans;
			else m_f[vi] = min(m_f[vi], ans);
		}
	}
	int f(int p, int k)
	{
		return m_f[p];
	}
	int splitArray(vector<int>& nums) {
		this->nums = nums;
		this->nums.insert(this->nums.begin(), 0);
		auto x= dp(this->nums.size() - 1);
		return x;
	}
	vector<int> nums;
	map<int, int>m_f;
	map<int, int>m_dp;
};

改的很微妙,可能只有螺旋归纳才会出现这种现象。

逻辑对了,但是在极限用例下会超时。

把代码化简,顺便做个性能优化:

cpp 复制代码
class Solution {
public:
	int dp(int k) {
		if (k < 1)return 0;
		vector<int> v = GetFacs(nums[k]);
		for (auto vi : v) {
			int ans = m_dp[k - 1];
			if (m_f.find(vi) == m_f.end())m_f[vi] = ans;
			else m_f[vi] = min(m_f[vi], ans);
		}
		int ans = INT_MAX;
		for (auto vi : v) ans = min(ans, m_f[vi] + 1);
		return m_dp[k]=ans;
	}
	int splitArray(vector<int>& nums) {
		this->nums = nums;
		this->nums.insert(this->nums.begin(), 0);
		for (int i = 1; i < this->nums.size(); i++)dp(i);
		return m_dp[this->nums.size() - 1];
	}
	vector<int> nums;
	map<int, int>m_f;
	map<int, int>m_dp;
};

这样就不出意外的AC了。

力扣 3573. 买卖股票的最佳时机 V

给你一个整数数组 prices,其中 prices[i] 是第 i 天股票的价格(美元),以及一个整数 k

你最多可以进行 k 笔交易,每笔交易可以是以下任一类型:

  • 普通交易 :在第 i 天买入,然后在之后的第 j 天卖出,其中 i < j。你的利润是 prices[j] - prices[i]

  • 做空交易 :在第 i 天卖出,然后在之后的第 j 天买回,其中 i < j。你的利润是 prices[i] - prices[j]

注意:你必须在开始下一笔交易之前完成当前交易。此外,你不能在已经进行买入或卖出操作的同一天再次进行买入或卖出操作。

通过进行 最多 k 笔交易,返回你可以获得的最大总利润。

示例 1:

输入: prices = 1,7,9,8,2, k = 2

输出: 14

解释:

我们可以通过 2 笔交易获得 14 美元的利润:

  • 一笔普通交易:第 0 天以 1 美元买入,第 2 天以 9 美元卖出。
  • 一笔做空交易:第 3 天以 8 美元卖出,第 4 天以 2 美元买回。

示例 2:

输入: prices = 12,16,19,19,8,1,19,13,9, k = 3

输出: 36

解释:

我们可以通过 3 笔交易获得 36 美元的利润:

  • 一笔普通交易:第 0 天以 12 美元买入,第 2 天以 19 美元卖出。
  • 一笔做空交易:第 3 天以 19 美元卖出,第 4 天以 8 美元买回。
  • 一笔普通交易:第 5 天以 1 美元买入,第 6 天以 19 美元卖出。

提示:

  • 2 <= prices.length <= 103
  • 1 <= prices[i] <= 109
  • 1 <= k <= prices.length / 2

思路:螺旋归纳

首先按照单调性做区间分割,然后定义2个函数:

dp表示前vpId+1段折线内,产生最多n次交易的最大收益

dp2表示以vpId为结尾的,产生最多n次交易的最大收益

那么,dp的递推式就是,分为2种情况,即是否以vpId为结尾

而dp2的递推式就是,分为3种情况:

(1)vpId-1没有被采用,即vpId就是交易起点

(2)vpId-1有被采用,vpId不是交易起点,那么vpId-1也不是交易起点,即算上vpId之后至少是3段单调段连起来的交易(而且肯定是奇数段)

(3)vpId-1有被采用,vpId是交易起点,即交易类型反转(普通和做空的反转)

cpp 复制代码
class Solution {
public:
	long long maximumProfit(vector<int>& prices, int k) {
		this->prices = prices;
		this->vp = getBrokenLine(prices);
		m.clear();
		m2.clear();
		auto ans = dp(vp.size() - 1, k);
		return ans;
	}
	//前vpId+1段折线内,产生最多n次交易的最大收益
	long long dp(int vpId, int n)
	{
		if (vpId < 0)return 0;
		if (m[vpId].find(n) != m[vpId].end()) {
			return m[vpId][n];
		}
		return m[vpId][n] = max(dp(vpId - 1, n), dp2(vpId, n));
	}
	//以vpId为结尾的,产生最多n次交易的最大收益
	long long dp2(int vpId, int n)
	{
		if (vpId < 0)return 0;
		if (vpId == 0)return n == 0 ? 0 : abs(prices[vp[vpId].second] - prices[vp[vpId].first]);
		if (n == 0)return 0;
		if (m2[vpId].find(n) != m2[vpId].end()) {
			return m2[vpId][n];
		}
		long long ans = dp(vpId - 2, n - 1) + abs(prices[vp[vpId].second] - prices[vp[vpId].first]);
		if(vpId>1)ans = max(ans, dp2(vpId - 2, n) + getFlag(vpId) * (prices[vp[vpId].second] - prices[vp[vpId-2].second]));
		if (vpId > 0)ans = max(ans, dp2(vpId - 1, n - 1) + abs(prices[vp[vpId].second] - prices[vp[vpId].first]) - g(vp[vpId].first));
		return m2[vpId][n] = ans;
	}
	long long getFlag(int vpId)
	{
		if (prices[vp[vpId].second] > prices[vp[vpId].first])return 1;
		return -1;
	}
	long long g(int id)
	{
		return min(abs(prices[id] - prices[id + 1]), abs(prices[id] - prices[id - 1]));
	}
	vector<int> prices;
	vector<pair<int, int>> vp;
	unordered_map<int, unordered_map<int, long long>>m;
	unordered_map<int, unordered_map<int, long long>>m2;
};
相关推荐
Zldaisy3d6 分钟前
全球唯一仿真驱动自适应扫描路径新版本发布,金属3D打印工艺开发进入算法时代
算法·3d
小江的记录本9 分钟前
【JVM虚拟机】类加载机制:类加载全流程:加载→验证→准备→解析→初始化(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·算法·安全·spring·面试
故事和你9143 分钟前
洛谷-【动态规划2】线性状态动态规划4
开发语言·数据结构·c++·算法·动态规划·图论
不吃土豆的马铃薯1 小时前
Socket 网络编程实战教程
linux·服务器·开发语言·网络·c++·算法
weixin_468466852 小时前
图像滤波算法新手实战指南
图像处理·人工智能·算法·计算机视觉·ai·机器视觉·滤波
Ulyanov2 小时前
深入QML-Python通信 构建响应式交互界面的桥梁设计:QML+PySide6现代开发入门(五)
开发语言·python·算法·交互·qml·系统仿真
重生之我是Java开发战士2 小时前
【贪心算法】加油站,单调递增的数字,坏了的计算器,合并区间,用最少数量的箭引爆气球
算法·贪心算法
小欣加油2 小时前
leetcode 3300 替换为数位和后的最小元素
数据结构·c++·算法·leetcode
晚风予卿云月2 小时前
【枚举】普通枚举
数据结构·c++·算法·竞赛·算法随笔
IronMurphy2 小时前
【算法五十三】1143. 最长公共子序列
算法