【C++算法】【数位dp】【动态规划】2719统计整数数目

作者推荐

视频算法专题

本文涉及知识点

动态规划汇总

题目

给你两个数字字符串 num1 和 num2 ,以及两个整数 max_sum 和 min_sum 。如果一个整数 x 满足以下条件,我们称它是一个好整数:
num1 <= x <= num2
min_sum <= digit_sum(x) <= max_sum.
请你返回好整数的数目。答案可能很大,请返回答案对 109 + 7 取余后的结果。
注意,digit_sum(x) 表示 x 各位数字之和。
示例 1:
输入:num1 = “1”, num2 = “12”, min_num = 1, max_num = 8
输出:11
解释:总共有 11 个整数的数位和在 1 到 8 之间,分别是 1,2,3,4,5,6,7,8,10,11 和 12 。所以我们返回 11 。
示例 2:
输入:num1 = “1”, num2 = “5”, min_num = 1, max_num = 5
输出:5
解释:数位和在 1 到 5 之间的 5 个整数分别为 1,2,3,4 和 5 。所以我们返回 5 。
提示:
1 <= num1 <= num2 <= 1022
1 <= min_sum <= max_sum <= 400

分析

a. 先考虑数位相等

DoSameLen函数处理数位相等。用动态规划,假定已经处理了i位,正在处理第i位。对于某种状态,我们只关心:一,位数和,取值范围[0, max_sum],共max_sum+1种,大于max_sum的状态抛弃。二,上下界。非边界、上边界、下边界、上下界。如:num1=”121”,num2=”142”则”1”是上下界,"12"是下界,"13"是非边界、"14"是上边界。如果上下界,则当前位只能取值[num1[i],nums2[i]]。如果上边界,则只能取值[0,nums2[i]]。如果是下边界,则能取[nums1[i],9]。如果是非边界,则可以任意取值。

前一轮状态当前条件新状态
上下界同时等于num1[i]和num2[i]上下界
上下界等于num1[i]下界
上下界等于num2[i]上界
上下界num1[i]和num2[i]之间非边界
上界等于num2[i]上界
上界小于num2[i]非边界
下界等于num1[i]下界
下界大于num1[i]非边界
非边界0到9非边界

b. 优化掉上下界

上下界一定出现再最前面,且数量为1。当i为0时,有新的上下界,那说明num1[0]和num2[i],只能选择num1[i]。当i大于0是,有新的上下界,那么num1[i]等于num2[i],由于(i-1)也是上下界,所有num1和num2的前i+1个元素相同。由于第i为只能num1[i],所以数量不变。
假定num1和num2的前i位完全相同,则前i位只有一个合法数。
这意味这num1和num2扔掉相同的前者,边界状态的数量不变。比如:
num1 = “112”和num2 = “115”, 下界:115,非边界:113和114 上界:115。
num1 = “12”和num2 = “15”, 下界:15,非边界:13和14 上界:15。
num1 = “2”和num2 = “5”, 下界:5,非边界:3和4 上界:5。

c. 考虑数位不等

假定num1的长度为l1,num2的长度为l2,则分别:
Do(num1,string(l1,’9’)
for(int i = l1+1; i < l2 ;i++)
{
Do(‘1’+string(i-1,’0’),string(i,’9’);
}
Do(‘1’+string(i2-1,’0’),num2)
比如:
nums1 = “1”,nums2=”19”
Do(“1”,”9”)
Do(“10”,”19”)
再如:
nums1 = “1”,nums2=”123”
Do(“1”,”9”)
Do(“10,”99”)
Do(“100,”123”)
注意:
l1等于l2时要分别处理。

核心代码

class Solution {
public:
int count(string num1, string num2, int min_sum, int max_sum) {
m_min_sum = min_sum;
m_max_sum = max_sum;
const int l1 = num1.length();
const int l2 = num2.length();
if (l1 == l2)
{
return DoSameLen(num1, num2).ToInt();
}
C1097Int biRet;
biRet += DoSameLen(num1, string(l1, ‘9’));
for (int i = l1 + 1; i < l2; i++)
{
biRet += DoSameLen(“1” + string(i - 1, ‘0’), string(i,‘9’));
}
biRet += DoSameLen(“1” + string(l2-1,‘0’), num2);
return biRet.ToInt();
}
C1097Int<> DoSameLen(string num1, string num2)
{
int n = num1.size();//只处理相等位数
int i = 0;
int sum = 0;
for (; (i < n) && (num1[i] == num2[i]); i++)
{
sum += num1[i] - ‘0’;
}
if (i >= n)
{
return ((sum >= m_min_sum)&&( sum <= m_max_sum)) ? 1 : 0;
}
//注[1]
vector<vector<C1097Int<>>> pre(3, vector<C1097Int<>>(m_max_sum+1));
Set(pre, 1, sum + num1[i]-‘0’, 1);
for (int j = num1[i] + 1; j < num2[i]; j++)
{
Set(pre, 0, sum +j - ‘0’, 1);
}
Set(pre, 2, sum + num2[i] - ‘0’, 1);
for (i++; i < n; i++)
{
vector<vector<C1097Int<>>> dp(3, vector<C1097Int<>>(m_max_sum + 1));
for (int preSum = 0; preSum <= m_max_sum; preSum++)
{
//处理下界
Set(dp, 1, preSum + num1[i] - ‘0’, pre[1][preSum]);
for (int j = num1[i] + 1; j <= ‘9’; j++)
{
Set(dp, 0, preSum + j - ‘0’, pre[1][preSum]);
}
//处理无边界
for (int j = ‘0’; j <= ‘9’; j++)
{
Set(dp, 0, preSum + j - ‘0’, pre[0][preSum]);
}
//处理上界
for (int j = ‘0’; j < num2[i]; j++)
{
Set(dp, 0, preSum + j - ‘0’, pre[2][preSum]);
}
Set(dp,2, preSum + num2[i] - ‘0’, pre[2][preSum]);
}
pre.swap(dp);
}
return Cal(pre);
}
void Set(vector<vector<C1097Int<>>>& dp, int status, int sum,C1097Int<> iAddNum)
{
if (sum > m_max_sum)
{
return;
}
dp[status][sum] += iAddNum;
}
C1097Int<> Cal(const vector<vector<C1097Int<>>>& pre)
{
C1097Int<> biRet;
for (int i = 0; i < 3; i++)
{
for (int j = m_min_sum; j <= m_max_sum; j++)
{
biRet += pre[i][j];
}
}
return biRet;
}
int m_min_sum, m_max_sum;
};

测试代码

int main()
{
int res = 0;
res = Solution().count(“2”, “5”, 0, 100);
assert(res == 4);
res = Solution().count(“12”, “15”, 0, 100);
assert(res == 4);
res = Solution().count(“112”, “115”, 0, 100);
assert(res == 4);
res = Solution().count(“112”, “115”, 5, 100);
assert(res == 3);
res = Solution().count(“112”, “115”, 0, 6);
assert(res == 3);
res = Solution().count(“112”, “115”, 5, 6);
assert(res == 2);
res = Solution().count(“1”, “12”, 1, 8);
assert(res == 11);

CConsole::Out(res);

}
在这里插入图片描述

其它

视频课程

如果你觉得复杂,想从简单的算法开始,可以学习我的视频课程。
https://edu.csdn.net/course/detail/38771
我的其它课程
[https://edu.csdn.net/lecturer/6176]

(https://edu.csdn.net/lecturer/6176)

测试环境

win7 VS2019 C++17

相关下载

算法精讲《闻缺陷则喜算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

在这里插入图片描述

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
数位 DP 是一个非常实用的算法,可以用于求解数字的各种性质。下面以计算 $[1,n]$ 中所有整数数字 $d$ 出现次数的问题为例,介绍数位 DP 的实现方法。 假设我们要求出数字 $d$ 在 $[1,n]$ 中出现的次数,可以考虑将 $n$ 拆分成若干个数位,然后逐位统计 $d$ 出现的次数。具体地,我们可以使用 $f_{i,j,k}$ 表示考虑前 $i$ 位,当前数值为 $j$,是否达到上界 $k$ 的方案数。然后按照从高到低的顺序逐位进行统计。 状态转移方程为: $$ f_{i,j,k}=\sum_{p=0}^9 f_{i-1,p,k \ \&\&\ p=j} + [k=1]\times [j=d] \times (\text{数位 DP 的计算结果}) $$ 其中 $[x]$ 表示 Iverson 括号,当 $x$ 成立时为 $1$,否则为 $0$。第一项表示上一个数位传递下来的方案数,第二项表示当前数位对答案的贡献。 最终的答案为 $f_{\text{len}(n),0,1}$。 代码实现如下: ```cpp int f[20][10][2]; // f[i][j][k] 表示考虑到第 i 位,当前数字为 j,是否达到上界的方案数 int digit[20]; // 存储 n 的每一位数字 int dfs(int pos, int pre, bool limit, int d) { if (pos == -1) return 0; if (!limit && f[pos][pre][d] != -1) return f[pos][pre][d]; int up = limit ? digit[pos] : 9; int res = 0; for (int i = 0; i <= up; i++) { res += dfs(pos - 1, i, limit && (i == up), d) + (pre == d && i == d && !limit) ; } if (!limit) f[pos][pre][d] = res; return res; } int solve(int n, int d) { memset(f, -1, sizeof(f)); int pos = 0; while (n) { digit[pos++] = n % 10; n /= 10; } return dfs(pos - 1, 0, true, d); } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

闻缺陷则喜何志丹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值