【动态规划 逆向】837. 新 21 点

本文涉及知识点

C++动态规划

LeetCode837. 新 21 点

爱丽丝参与一个大致基于纸牌游戏 “21点” 规则的游戏,描述如下:
爱丽丝以 0 分开始,并在她的得分少于 k 分时抽取数字。 抽取时,她从 [1, maxPts] 的范围中随机获得一个整数作为分数进行累计,其中 maxPts 是一个整数。 每次抽取都是独立的,其结果具有相同的概率。
当爱丽丝获得 k 分 或更多分 时,她就停止抽取数字。
爱丽丝的分数不超过 n 的概率是多少?
与实际答案误差不超过 10-5 的答案将被视为正确答案。
示例 1:
输入:n = 10, k = 1, maxPts = 10
输出:1.00000
解释:爱丽丝得到一张牌,然后停止。
示例 2:
输入:n = 6, k = 1, maxPts = 10
输出:0.60000
解释:爱丽丝得到一张牌,然后停止。 在 10 种可能性中的 6 种情况下,她的得分不超过 6 分。
示例 3:
输入:n = 21, k = 17, maxPts = 10
输出:0.73278
提示:
0 <= k <= n <= 104
1 <= maxPts <= 104

动态规划

动态规划的状态表示

dp[i] 表示抽取i的可能数。空间复杂度:O(n+maxPts)

动态规划的状态方程

枚举后置状态,每种后置状态枚举最后一次抽取的数字。
dp[i] += F o r j : 1 m a x P t s d p [ i − k ] For_{j:1}^{maxPts}dp[i-k] Forj:1maxPtsdp[ik] st i-k < n
用前缀和或滑动窗口,单个状态转移的时间复杂度为:O(1)
故总时间复杂度为:O(n+maxPts)

动态规划的初始值

dp[0]为0

动态规划的填表顺序

从1到n-1+maxPts

动态规划的返回值

sum(dp[1…k-1])/sum(dp[1…])

此题歧义

如:k=3 n = 3 maxPts=2 ,共有五种可能111 112 12 21 22
dp[3]=3 dp[4]=2,故概率应该是0.6
答案是:0.625

我的代码

class Solution {
public:
	double new21Game(int n, int k, int maxPts) {
		vector<long long> dp (k + maxPts);
		dp[0] = 1;
		vector<long long> preSum = { 0,1 };
		for (int i = 1; i < k + maxPts; i++) {
			int begin = max(i - maxPts,0);
			dp[i] = preSum.back() - preSum[begin];
			if (i < k) {
				preSum.emplace_back(preSum.back() + dp[i]);
			}			
		}
		auto sum1 = accumulate(dp.begin()+k , dp.begin() + n+1,0.0);
		auto sum2 = accumulate(dp.begin()+k, dp.end(), 0.0);
		return sum1 / sum2;
	}
};

官方的解法

dp[4] 无法继续 必定超n,故0
dp[3] 无法继续 几率1
dp[2] 可能是1或2,则结果是(dp[3]+dp[4])/2 = 0.5
dp[1]可能是1或2 ,则结果是0.75
dp[0]是0.625

动态规划的状态表示

dp[x]表示已经x分后,不超的记录。

动态规划的转移方程。

{ 0 ( x > = k ) 且 ( x > n ) 1 ( x > = k ) 且 ( x < n ) ( ∑ j : x + 1 m d p [ j ] ) / ( m − x ) 其中 m = m i n ( k − 1 + m a x P t s , x + m a x P t s ) o t h e r \begin{cases} 0 && (x >= k ) 且 (x > n )\\ 1 && ( x>=k )且(x < n ) \\ (\sum_{j:x+1}^{m}dp[j] )/(m-x) 其中 m = min(k-1+maxPts,x+maxPts) && other\\ \end{cases} 01(j:x+1mdp[j])/(mx)其中m=min(k1+maxPts,x+maxPts)(x>=k)(x>n)(x>=k)(x<n)other

动态规划的填表顺序

i = k-1 : 0

动态规划的初始值

初始化x >= k

动态规划的返回值

dp[0]

代码

class Solution {
public:
	double new21Game(int n, int k, int maxPts) {
		vector<double> dp (k + maxPts);
		double sum = 0;
		for (int i = k; i <= min(n,(int)dp.size()-1); i++) {
			dp[i] = 1;
			sum++;
		}		
		for (int i = k - 1; i >= 0; i--) {
			const int m = min(k - 1 + maxPts, i + maxPts);
			if (m + 1 < dp.size()) {
				sum -= dp[m + 1];
			}
			dp[i] = sum / (m - i);
			sum += dp[i];
		}
		return dp[0];
	}
};

单元测试


template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
	Assert::AreEqual(t1, t2);
}
void AssertEx( double t1,  double t2)
{
	auto str = std::to_wstring(t1) + std::wstring(1,32) + std::to_wstring(t2);
	Assert::IsTrue(abs(t1 - t2) < 1e-5,str.c_str() );
}

template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{
	Assert::AreEqual(v1.size(), v2.size());
	for (int i = 0; i < v1.size(); i++)
	{
		Assert::AreEqual(v1[i], v2[i]);
	}
}

template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{
	sort(vv1.begin(), vv1.end());
	sort(vv2.begin(), vv2.end());
	Assert::AreEqual(vv1.size(), vv2.size());
	for (int i = 0; i < vv1.size(); i++)
	{
		AssertEx(vv1[i], vv2[i]);
	}
}

namespace UnitTest
{
	int n,  k,  maxPts;
	TEST_CLASS(UnitTest)
	{
	public:
		TEST_METHOD(TestMethod00)
		{
			n = 10, k = 1, maxPts = 10;
			auto res = Solution().new21Game(n, k, maxPts);
			AssertEx(1.0, res);
		}
		TEST_METHOD(TestMethod01)
		{
			n = 6, k = 1, maxPts = 10;
			auto res = Solution().new21Game(n, k, maxPts);
			AssertEx(0.6, res);
		}
		TEST_METHOD(TestMethod02)
		{
			n = 3, k = 3, maxPts = 2;
			auto res = Solution().new21Game(n, k, maxPts);
			AssertEx(0.62500, res);
		}
		TEST_METHOD(TestMethod03)
		{
			n = 1, k = 0, maxPts = 1;
			auto res = Solution().new21Game(n, k, maxPts);
			AssertEx(1.0, res);
		}
		TEST_METHOD(TestMethod04)
		{
			n = 21, k = 17, maxPts = 10;
			auto res = Solution().new21Game(n, k, maxPts);
			AssertEx(0.73278, res);
		}
	};
}

扩展阅读

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关推荐

我想对大家说的话
喜缺全书算法册》以原理、正确性证明、总结为主。
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闻缺陷则喜何志丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值