找到数字和等于N的最小幸运数字

find the minimum lucky number that has the sum of digits equal to N

本文关键字:幸运数字 数字      更新时间:2023-10-16

幸运数字是正整数,其十进制表示形式仅包含数字4或7。在此处输入代码`例如,数字47、474、4是幸运的,3、13567不是如果没有这样的no,那么输出应该为-1。输入是数字的总和。我写了这个代码:

int main(){
long long int s,no=0,minimum=999999999999999999999;
cin>>s;
for(int i=0; i<=s; i++){
for(int j=0; j<=s; j++){
if(i*4+j*7==s){no=0;
for(int k=0; k<i; k++){
no=no*10+4;
}
for(int l=0; l<j; l++){
no=no*10+7;
}if(no<minimum){
minimum=no;}
}
}
}if(minimum==999999999999999999999){cout<<-1;}
else {cout<<minimum;}
}

它工作得很好,较小的和值,但输入很大,因此没有形成很大的值,因此我无法比较它们,和的约束是1<n<10^6

这个答案显示了一个过程,一个开发高效解决方案的精化过程。最有效的答案可以在底部的段落中找到,从文本";当然,你甚至可以更聪明&";。

我把整个过程都留了下来,这样你就可以理解算法开发中的思维方式。所以,让我们开始吧。


首先,在这种情况下,我不会尝试比较大的数字,这是完全没有必要的,并且限制了您想要处理的范围。相反,你只需要一些四分之一和七分之一,你可以很容易地将其转化为一个和:

sum = numFours * 4 + numSevens * 7

此外,意识到最小的数字首先是最小位数的数字,您需要绝对最小的四位数和最大的七位数。因此,从no四分球开始,根据需要选择七分球,直到达到或刚刚超过所需的总和。

然后,只要你不是的总和,执行以下互斥步骤:

  • 如果你超过了期望的总和,如果可能的话,去掉一个7。如果有没有七个可以拿走,你就完了,没有解决方案。记录该事实并退出
  • 否则(即,如果你在和下面),加一个四

此时,您将有一个解决方案(没有可能的解决方案意味着您已经在上面的第一个要点中执行了退出)。

因此,现在有一个四个和七个的计数,它们加起来就是所需的数字,所以最低的数字将是所有四个都在左边的数字(例如,447小于{474, 744}中的任何一个)。输出它,你就完成了。

通过这种方式,限制(例如,一个无符号的32位int)不再是你使用的数字(大约40亿,所以是9位数字),而是你可以用40亿(大约10亿位数字)保持的任何4位数。

这增加了大约110亿%,希望对你来说已经足够了,远远超过了指定的106最大总和。

事实上,你不会得到那么多的四分球,因为任何一组七个四分球总是可以用四个七分球代替,给出一个更小的数字(a7777b总是小于a4444444b,其中a是零或更多的四分,b是零或更多的七分球,两个数字的计数相同),所以四分球的最大计数总是六分。


这里有一些伪代码(实际上是Python代码)来展示它的实际操作。我选择了Python,尽管你说的是C++,但原因如下:

  • 这几乎是当然一个教育问题(在现实世界中很少有人需要这种程序)。这意味着你最好自己写代码,以确保你理解,也确保你不会因为从网上复制代码而失败
  • Python是有史以来最棒的伪代码语言。它可以像普通的英语伪代码一样轻松地读取,但还有一个额外的好处,即计算机实际上可以运行进行测试和验证:-)

Python代码是:

import sys
# Get desired sum from command line, with default.
try:
desiredSum = int(sys.argv[1])
except:
desiredSum = 22
# Init sevens to get at or beyond sum, fours to zero, and the sum.
(numSevens, numFours) = ((desiredSum + 6) // 7, 0)
thisSum = numSevens * 7 + numFours * 4
# Continue until a solution is found.
while thisSum != desiredSum:
if thisSum > desiredSum:
# Too high, remove a seven. If that's not possible, exit.
if numSevens == 0:
print(f"{desiredSum}: no solution")
sys.exit(0)
numSevens -= 1
thisSum -= 7
else:
# Too low, add a four.
numFours += 1
thisSum += 4
# Only get here if solution found, so print lowest
# possible number that matches four/seven count.
print(f"{desiredSum}: answer is {'4' * numFours}{'7' * numSevens}")

这是一个小样本范围的转录本:

pax:~> for i in {11..20} ; do ./test47.py ${i} ; done
11: answer is 47
12: answer is 444
13: no solution
14: answer is 77
15: answer is 447
16: answer is 4444
17: no solution
18: answer is 477
19: answer is 4447
20: answer is 44444

这是40亿,远远超过5亿数字的期望总和的(粗略)数字计数:

pax:~> export LC_NUMERIC=en_US.UTF8
pax:~> printf "%'.fn" $(./test47.py 4000000000 | wc -c)
571,428,597

如果真的需要C++解决方案,请参阅下文。如果这是一门课程,我不建议使用它,而是建议您将上面显示的算法转换为自己的代码(出于前面提到的原因)。这只是为了展示C++中类似的方法:

#include <iostream>
int main(int argc, char *argv[]) {
// Get desired sum from command line, defaulting to 22.
int desiredSum = 22;
if (argc >= 2) desiredSum = atoi(argv[1]);
// Init sevens to get at or beyond desired sum, fours to zero,
// and the sum based on that.
int numSevens = (desiredSum + 6) / 7, numFours = 0;
int thisSum = numSevens * 7 + numFours * 4;
// Continue until a solution is found.
while (thisSum != desiredSum) {
if (thisSum > desiredSum) {
// Too high, remove a seven if possible, exit if not.
if (numSevens == 0) {
std::cout << desiredSum << ": no solutionn";
return 0;
}
--numSevens; thisSum -= 7;
} else {
// Too low, add a four.
++numFours; thisSum += 4;
}
}
// Only get here if solution found, so print lowest
// possible number that matches four / seven count.
std::cout << desiredSum << ": answer is ";
while (numFours-- > 0)  std::cout << 4;
while (numSevens-- > 0)  std::cout << 7;
std::cout << 'n';
}

当然,当你意识到四个数字的最大数量将是六个,并且你可以通过去掉一个七和加上两个四来在数字和上加一时,你甚至会更聪明。

简单来说:

  • 计算出达到或仅低于所需总和所需的七位数
  • 如果仍然,则加一个四,使您保持在或低于所需的总和
  • 则通过足够的";去掉一个七,加上两个四;直到你得到所需的总和(记住你可能已经在那里了)。对于当前金额中的差额(低于所需金额的程度),每个单位将只执行一次,因此,如果差额为2,则您将删除2个7,并添加4个4(-14+16=2)。这意味着您可以使用一个简单的数学公式,而不是循环
  • 如果该公式导致计数为7,则没有解决方案,否则使用前面提到的计数来形成最低数(4后面跟着7)

这个解决方案只有Python,因为它很容易:

import sys
# Get desired number.
desiredNum = int(sys.argv[1])
# Work out seven and four counts as per description in text.
numSevens = int(desiredNum / 7)          # Now within six of desired sum.
shortFall = desiredNum - (numSevens * 7)
numFours = int(shortFall / 4)            # Now within three of desired sum.
shortFall = shortFall - numFours * 4
# Do enough '+7-4-4's to reach desired sum (none if already there).
numSevens = numSevens - shortFall
numFours = numFours + shortFall * 2
# Done, output solution, if any.
if numSevens < 0:
print(f"{desiredNum}: No solution")
else:
print(f"{desiredNum}: {'4' * numFours}{'7' * numSevens}")

这样,就根本不需要循环。这都是数学推理。

如果我正确理解问题,您正在搜索最小的数字x,它只包含数字47及其数字之和N。最小的数字肯定写为:

4...47...7

m4n次数7组成。因此我们知道N=N·4+m·7

以下是一些适用的规则:

  1. (n+m)·7≥n::这很明显,只需将所有4个替换为7个
  2. (n+m)·4≤n::这很明显,只需将所有7个替换为4个
  3. (n+m)·7−n=m·(7−4)::换句话说,需要被7−4整除

因此,有了这两个条件,我们现在可以非常快速地编写伪代码:

# always assume integer division
j = N/7                       # j resembles n+m (total digits)
if (N*7 < N) j++              # ensure rule 1
while (  (j*4 <= N) AND ((j*7 - N)%(7-4) != 0) ) j++  # ensure rule 2 and rule 3
m = (j*7 - N)/(7-4) # integer division
n = j-m
if (m>=0 AND n>=0 AND N==m*4 + n*7) result found

下面是一个快速的bash-awk实现:

$ for N in {1..30}; do 
awk -v N=$N '
BEGIN{ j=int(N/7) + (N%7>0);
while( j*4<=N && (j*7-N)%3) j++;
m=int((j*7-N)/3); n=j-m;
s="no solution";
if (m>=0 && n>=0 && m*4+n*7==N) {
s=""; for(i=1;i<=j;++i) s=s sprintf("%d",(i<=m?4:7))
}
print N,s
}'
done
1 no solution
2 no solution
3 no solution
4 4
5 no solution
6 no solution
7 7
8 44
9 no solution
10 no solution
11 47
12 444
13 no solution
14 77
15 447
16 4444
17 no solution
18 477
19 4447
20 44444
21 777
22 4477
23 44447
24 444444
25 4777
26 44477
27 444447
28 7777
29 44777
30 444477

求和的约束是1≤n≤106

这意味着您可能需要查找和打印超过105数字的数字(106/7Ş142857)。不能像long long那样将它们存储在固定大小的整数类型中,最好直接将它们生成为仅由47字符组成的std::strings。

一些数学性质可能有助于找到合适的算法。

  • 我们知道n=i*4+j*7。

  • i数字四和j位数七的每个组合生成的所有可能的数字中,最小的是所有四位都在所有七位左边的数字。例如44777<47477<47747<lt;77744.

  • 最小幸运数字最多有六个4数字,因为即使它们的数字之和相等,4444444>7777.

现在,让我们介绍s=n/7(整数除法)和r=n%7(余数)。

  • 如果n可被7整除(或者当r==0),则幸运数字仅由s数字组成(所有7)。

  • 如果余数不为零,我们需要引入一些4。注意

    • 如果r==4,我们可以在ssevens的左边放一个4
    • 每次我们用两个4s替换(如果我们可以)一个单个7时,数字之和增加1
    • 在没有循环的情况下,我们可以准确地计算出我们需要多少4个数字(最多6个)

这足以编写一个算法。

#include <string>
struct lucky_t
{
long fours, sevens;
};
// Find the minimum lucky number (composed by only 4 and 7 digits)
// that has the sum of digits equal to n.
// Returns it as a string, if exists, otherwise return "-1".
std::string minimum_lucky(long n)
{
auto const digits = [multiples = n / 7L, remainder = n % 7L] {
return remainder > 3
? lucky_t{remainder * 2 - 7, multiples - remainder + 4}
: lucky_t{remainder * 2, multiples - remainder};
} ();
if ( digits.fours < 0  ||  digits.sevens < 0 )
{
return "-1";
}
else
{
std::string result(digits.fours, '4');
result.append(digits.sevens, '7');
return result;
}
}

在这里测试。