从火柴棍生成数字的算法
Algorithm to make numbers from match sticks
我从ACM上编写了一个程序来解决这个问题。
火柴棒是表示数字的理想工具。用火柴棍表示10位十进制数字的常用方法如下:这与普通闹钟显示数字的方式相同。使用给定数量的火柴棒,您可以生成各种数字。我们想知道用你所有的火柴棍能产生的最小和最大的数字是多少。
输入第一行一个正数:测试用例的数量,最多100个。之后每个测试用例:
一行整数n(2≤n≤100):你拥有的火柴的数量。输出
每testcase:一行,用一个空格分隔最小和最大的数字。两个数字都应该是正数,并且不包含前导零。示例输入
43.6715样例输出
7 7111年6711年8108 7111111
问题是解决100根火柴棍的问题太慢了。搜索树太大了,不能使用暴力。
以下是前10名的结果:
2: 1 1
3: 7 7
4: 4 11
5:2 71
6: 6 111
7: 8 711
8:10 1111
9:18 7111
10:22 11111
最大值的模式很简单,但我没有看到最小值的快捷方式。有人能提出一个更好的方法来解决这个问题吗?下面是我使用的代码:
#include <iostream>
#include <string>
using namespace std;
#define MAX 20 //should be 100
//match[i] contains number of matches needed to form i
int match[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
string mi[MAX+1], ma[MAX+1];
char curr[MAX+1] = "";
//compare numbers saved as strings
int mycmp(string s1, string s2)
{
int n = (int)s1.length();
int m = (int)s2.length();
if (n != m)
return n - m;
else
return s1.compare(s2);
}
//i is the current digit, used are the number of matchsticks so far
void fill(int i, int used)
{
//check for smaller and bigger values
if (mycmp(curr, mi[used]) < 0) mi[used] = curr;
if (mycmp(curr, ma[used]) > 0) ma[used] = curr;
//recurse further, don't start numbers with a zero
for (int a = i ? '0' : '1'; a <= '9'; a++) {
int next = used + match[a-'0'];
if (next <= MAX) {
curr[i] = a;
curr[i+1] = ' ';
fill(i + 1, next);
}
}
}
int main()
{
//initialise
for (int i = 0; i <= MAX; i++) {
mi[i] = string(MAX, '9');
ma[i] = "0";
}
//precalculate the values
fill(0, 0);
int n;
cin >> n;
//print those that were asked
while (n--) {
int num;
cin >> num;
cout << mi[num] << " " << ma[num] << endl;
}
return 0;
}
EDIT:我最终使用了动态规划解决方案。我之前用dp试过,但是我用的是二维状态数组。这里的解决方案要好得多。谢谢!
您可以使用动态规划解决方案。
假设你有n个匹配,并且你知道如何解决所有n-k
匹配集的问题(最小数量),其中k
取每个数字使用的匹配数量对应的所有值(2为1,5为3,等等)
(best solution with n-2 matches) 1
。假设以2结尾,最好的解是(best solution with n-5 matches) 2
。等等;最后你可以比较这十个数字,选出最好的一个。
那么现在,你所要做的就是为所有小于你输入的n
设计最佳解决方案,递归地。
EDIT:如果你以一种直接的方式实现这个算法,你最终会得到一个指数级的复杂性。这里的技巧是要注意,如果您的核心函数MinimumGivenNMatches
只接受一个参数n
。因此,你最终会用相同的值调用它很多次。
要使复杂度线性化,你只需要记住(即。记住)每个n
的解决方案,使用一个辅助数组。
为了查找结果:
- 首先找到最小数字的最小位数
- 则从最高有效位到最低有效位。
每个数字都应该被选中,以便对剩余的数字存在一个解。每个数字需要2到7次匹配。因此,您必须选择最小的第n个"顶部"数字,使剩余匹配数在2*(N-1)和7*(N-1)之间。
不要忘记,在搜索结果的最高有效数字时,必须将0排除在外。
作为旁注,使该算法工作的一个原因是,在2到7之间的每个值(或匹配)至少有一个对应的数字。
EDIT:示例为10个匹配10个匹配-> 2位
最高数字的可接受匹配次数=在3到7之间。
需要匹配3到7次的最小数字-> 2(需要匹配5次),0被排除。
第一个数字= 2
剩余5场比赛-->
第二个(在本例中是最后一个)数字的可接受匹配数= 5
需要5次匹配的最小数字-> 2
第二位数字= 2
结果= 22.
编辑此问题的代码
#include <iostream>
#include <vector>
std::vector<int> nbMatchesForDigit;
long long minNumberForMatches(unsigned int nbMatches)
{
int nbMaxMatchesForOneDigit = 7;
int nbMinMatchesForOneDigit = 2;
int remainingMatches = nbMatches;
int nbDigits = 1 + nbMatches / nbMaxMatchesForOneDigit;
long long result = 0;
for (int idDigit = 0 ; idDigit < nbDigits ; ++idDigit )
{
int minMatchesToUse = std::max(nbMinMatchesForOneDigit, remainingMatches - nbMaxMatchesForOneDigit * (nbDigits - 1 - idDigit));
int maxMatchesToUse = std::min(nbMaxMatchesForOneDigit, remainingMatches - nbMinMatchesForOneDigit * (nbDigits - 1 - idDigit));
for (int digit = idDigit > 0 ? 0 : 1 ; digit <= 9 ; ++digit )
{
if( nbMatchesForDigit[digit] >= minMatchesToUse &&
nbMatchesForDigit[digit] <= maxMatchesToUse )
{
result = result * 10 + digit;
remainingMatches -= nbMatchesForDigit[digit];
break;
}
}
}
return result;
}
int main()
{
nbMatchesForDigit.push_back(6);
nbMatchesForDigit.push_back(2);
nbMatchesForDigit.push_back(5);
nbMatchesForDigit.push_back(5);
nbMatchesForDigit.push_back(4);
nbMatchesForDigit.push_back(5);
nbMatchesForDigit.push_back(6);
nbMatchesForDigit.push_back(3);
nbMatchesForDigit.push_back(7);
nbMatchesForDigit.push_back(6);
for( int i = 2 ; i <= 100 ; ++i )
{
std::cout << i << " " << minNumberForMatches(i) << std::endl;
}
}
使用动态规划代替递归。存储计算值并重用它们的速度要快得多。事实上,它把指数型的运行时间变成了多项式型的。
基本思想是有一个数组min
,它跟踪可以使用n
火柴棒的最小数量。所以
min[0] = ""
min[1] = infinity
min[2] = "1"
min[3] = min("1+"min[3-2],"7")
min[4] = min("1"+min[4-2],"7"+min[4-3])
etc
对于最小值,请注意,因为不允许前导零,所以您希望最小化位数。最小位数为ceil(n/7)
那么就很容易计算出必须在前导数字上使用的最小火柴棍数,从中你可以得到前导数字的最小可能值。
我能够解决O(d)
的问题,其中d是位数。这个想法是,首先我们计算最小位数的最小位数。这可以通过int nofDigits = n/7+ ((0 == n%7) ? 0 : 1)
来计算,其中n
是火柴棒的数量。现在创建一个nofDigits
数组。现在我们开始填充最大可能的火柴棒(7),从最低有效数字到最高有效数字(MSD)之前的一位,最后将所有剩余的火柴棒分配给最高有效数字(MSD)。现在有3种改进的可能性,这取决于MSD的火柴棒数量:
1如果MSD的火柴棍数是1,那么我们可以从相邻的数字上借一根火柴棍使它成为2。这将使相邻数字6对应的火柴棍数等于0
2d如果MSD的火柴棒数量是4,那么同样和之前的情况一样,我们可以将MSD的火柴棒数量增加到5,这相当于2
第3条如果MSD的火柴棍数为3,那么我们必须看看总位数是否大于2那么我们可以从MSD的两个相邻数字中减去1或者我们将相邻数字减去两次并将MSD的火柴棍数增加2
最后通过遍历数组并用对应的数字替换火柴棍的数目。
完整的程序:void minNumWithNMatches_no_dp (int n) {
if (n < 2) return ;
int digits[] = {-1, -1, 1, 7, 4, 2, 0, 8};
int nofDigits = n/7+ ((0 == n%7) ? 0 : 1);
int *matchesArr = new int [nofDigits];
int tmp = n;
for (int i = 0; i < nofDigits; ++i) {
matchesArr[i] = tmp/7 ? 7 : tmp%7;
tmp -= matchesArr[i];
}
if (nofDigits > 1)
{
switch (matchesArr[nofDigits - 1]) {
case 1:
case 4:
{
--matchesArr[nofDigits - 2];
++matchesArr[nofDigits - 1];
break;
}
case 3:
{
if (nofDigits > 2) {
--matchesArr[nofDigits - 2];
--matchesArr[nofDigits - 3];
} else {
matchesArr[nofDigits - 2] -= 2;
}
matchesArr[nofDigits - 1] += 2;
break;
}
case 2:
case 5:
case 6:
case 7:
default:
{
;
}
}
}
for (int i = nofDigits - 1; i >= 0; --i) {
int digit = digits[matchesArr[i]];
if ((i == nofDigits - 1) && (digit == 0)) digit = 6;
std::cout<< digit;
}
}
#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
#include <map>
using namespace std;
int main()
{
// freopen("22.txt", "r", stdin);
vector<char> count(10);
map<int, char> Min_1;
Min_1[0] = '8';
Min_1[2] = '1';
Min_1[3] = '7';
Min_1[4] = '4';
Min_1[5] = '2';
count[0] = '6';
count[1] = '2';
count[2] = '5';
count[3] = '5';
count[4] = '4';
count[5] = '5';
count[6] = '6';
count[7] = '3';
count[8] = '7';
count[9] = '6';
int N = 99, P = 2;
cin >> N;
while(N --)
{
cin >> P;
vector<char> min, max;
int a = P, b = P;
int total = (a + 6) / 7;
int left = a % 7;
bool first = true;
char lastInsert = 0;
while(a != 0)
{
if(total == 1)
{
if(left != 6)
{
min.push_back(Min_1[left]);
}else if(left == 6)
{
if(!first)
min.push_back('0');
else
min.push_back('6');
}
break;
}
if(left == 0)
{
min.insert(min.end(), total, '8');
break;
}else{
if(first && left <= 2)
{
min.push_back('1');
lastInsert = 1;
}else if(first && left < 6)
{
min.push_back('2');
lastInsert = 2;
}else if(first && left == 6)
{
min.push_back('6');
lastInsert = 6;
}else if(!first)
{
min.push_back('0');
lastInsert = 0;
}
}
int temp = count[lastInsert] - '0';
a -= temp;
left = a % 7;
total = (a + 6) / 7;
first = false;
}
if(b % 2 == 1)
{
max.push_back('7');
max.insert(max.end(), (b - 3) / 2, '1');
}else{
max.insert(max.end(), b / 2, '1');
}
string res_min = string(min.begin(), min.end());
string res_max = string(max.begin(), max.end());
cout << res_min << " " << res_max << endl;
P ++;
}
return 0;
}
这是我的答案,希望对大家有所帮助
- 检查数字是否为素数的算法
- 使用C++具有两个数字的最短路径算法.(C++)
- 算法设计:用边界数字表示 2D 网格的最佳方式,以C++?
- 如何在不导致堆栈溢出的情况下计算非常大的数字和很小的 HCF.我正在使用欧几里得算法
- 我可以在移动平台上有效地使用用C/C 编写的数字算法
- 函数以相反的顺序输出输入问题,并改进算法以解释相等的数字
- 使用位移算法计算平方根始终输出相同的数字
- 计数交换/比较合并排序算法的数字
- 接收数字和数字的算法并检查数字内部数字的次数
- C 搜索算法第一个数字= n
- 如何在较高和较低数字的阵列中适应搜索算法(3N / 2)-2
- 生成跨系统唯一数字的算法
- 更好的算法来检查一个数字是否既不是素数也不是单个素数的幂
- 给定数字与重复的组合的算法?C
- 需要一个高效的减法算法取模一个数字
- 优化算法以查找满足某些属性的六位数字的数量
- 算法-检查数字是否已经在列表中
- 以从 a 到 b 的数字的二进制表示形式计算 "1" 位数字的算法
- FFT Cooley Tukey 算法 - 不适用于多个数字
- 最小数字 -- 算法