题目

给你两个数字字符串 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);}

其它

视频课程

如果你觉得复杂,想从简单的算法开始,可以学习我的视频课程。

测试环境

win7 VS2019 C++17

相关下载

算法精讲《闻缺陷则喜算法册》doc版

C++数位动态规划算法:统计整数数目_dp