我必须取两个数字,并找到这两个数字之间的所有数字(包括),其中有数字6或8,但不是两者兼有。因此,6 8的输入将返回2,而60 69的输入将返回9 (因为不计算68 )。
这是一件非常简单的事情,但我必须在不到一秒钟的时间内做到这一点,即使是在数为万亿的情况下。事实上,这是不可能这样做的,这让我怀疑,是否有一些基本的技巧,可以跳过检查99%的数字,我错过了。我所能做的最优的就是能够跳到10^[two less than the number of digits in the difference between the two numbers],让我们把这个指数定义为d。当数字有数字6和8,或者没有这些数字时,我的程序进行了这些跳跃,并以至少d零结尾。如果该数字没有数字6和8,则将6或8在1和10^d之间的数字的值添加到特殊数字的数量中(添加的第一个值已保存在内存中的数组中)。
在任何情况下,我认为我能够演示我尝试的解决方案的最好方法是展示我的代码。不过,如果你有改善我的问题的建议,请随意提出。
#include <iostream>
int main() {
int_fast64_t low; //starting number
int_fast64_t high; //ending number
std::cin >> low >> high; std::cin.ignore();
/*Starts out holding the difference between high and low,
then will hold two less than the number of digits in that difference*/
int_fast64_t lowHighDifference = high - low;
//10 to various powers used to not have to itterate over every number between low & high
static int_fast64_t pow10[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
//The number of numbers with 6 xor 8 between 1 and 10^index
static int_fast64_t numspecial[] = {0, 2, 34, 434, 4930, 52562, 538594, 5371634, 52539010, 506405522};
//Find how large the jumps one can make are based on the difference between high and low
for (int_fast8_t itterator = 9; itterator > 0; itterator--) {
if (lowHighDifference > pow10[itterator]) {
/*This needs to have two fewer digits than the difference between low & high,
otherwise it risks not being used*/
lowHighDifference = itterator - 1;
itterator = 0;
}
}
/*Incremented by one every time a number with 6 xor 8 is ecountered,
and by numspecial[lowHighDifference] every time a number without 6 or 8,
and at least as many zeros at the end as pow10[lowHighDifference] is encountered*/
int_fast64_t specialNumbers = 0;
for (int_fast64_t itterator = low; itterator <= high; itterator++) {
char stringNum[21]; //long ints can't have more than 20 digits
/*Copy itterator into a string to check each digit individually,
while recording the length that string ends up being. */
int_fast8_t stringNumLength = sprintf(stringNum, "%ld", itterator),
/*Starts at 0, will turn to 6 or 8 with the first of those found,
then will be set to -1 if the other number is found */
sixOrEight = 0;
for (int_fast8_t stringNumItterator = 0; stringNumItterator < stringNumLength; stringNumItterator++) {
if (stringNum[stringNumItterator] == '8') {
if (sixOrEight == 6) {
sixOrEight = -1;
stringNumItterator = stringNumLength;
}
else
sixOrEight = 8;
}
else if (stringNum[stringNumItterator] == '6') {
if (sixOrEight == 8) {
sixOrEight = -1;
stringNumItterator = stringNumLength;
}
else
sixOrEight = 6;
}
}
/*The basic case in which a six or an eight was found. I don't know how I can optimize this,
but it's the main part slowing this program down. */
if (sixOrEight > 0) {
specialNumbers++;
}
else if (lowHighDifference > 0) {
//If neither six or eight were found
if (sixOrEight == 0) {
if (itterator % pow10[lowHighDifference] == 0 && pow10[lowHighDifference] < (high - itterator)) {
specialNumbers += numspecial[lowHighDifference];
itterator += pow10[lowHighDifference] - 1;
}
} //If sixOrEight is < 0 and itterator has at least as many zeros at the end as pow10[lowHighDifference]
else if (itterator % pow10[lowHighDifference] == 0) {
//Add 10^lowHighDifference to itterator without adding to specialNumbers
itterator += pow10[lowHighDifference] - 1;
}
}
}
std::cout << specialNumbers;
return 0;
}发布于 2020-01-26 00:25:21
完全解决方案位于本文的底部
做这个花了几个小时,但我想我有了最佳的解决方案。谢谢你提供了这个挑战。数字计算问题是我的业余爱好。
术语
首先,我为这个问题发明了一些术语:
NQ或“不合格”。NQ数字是指在其数字集合中既没有6也没有8的数字。
Q6或“由6限定”是指数字集合中有6的任何数字,但没有8。
Q8或“由8限定”是指数字集合中有8的任何数字,但没有6。
PNQ或“永久不合格”。这是一个同时包含6或8的数字。
任何NQ数字只要在其末尾附加一个6或8,就可以成为合格的。(例如,123是NQ,而1236是“合格”)
我之所以使用这个“限定”术语,是因为解决方案需要将数字从左到右构建为一个数字串。例如,一个非限定数字(如123 )可以通过在其上附加一个6而变得合格。通过将一个6附加到123上,这个数字就变成了1236,并且是Q6。
任何限定数字都可以通过向其附加一个6或8,使其具有两个数字而成为永久的非限定数。例如,654是Q6,但是如果我们在它上附加一个8,它就会永久不合格。任何额外的数字加在这个数字上仍将导致数字为PNQ。
慢溶液
让我们介绍第一批代码。我刚才讨论的一个形式化的枚举:
enum class NumberType : int
{
NQ, // NQ is "non-qualifying". It's a number with neither a six or an eight
Q6, // Q6 is "qualified by 6". It's a number with at least a single six digit, but no eights
Q8, // Q8 is "qualified by 8". It's a number with at least a single eight digit, but no sixes
PNQ // PNQ is "permanently non qualified". It's a number with both a six and an eight in it
};让我们介绍一个辅助函数,它可以接受任何64位数,并告诉我们它是哪种类型:
NumberType GetNumberType(uint64_t value)
{
uint64_t sixCount = 0;
uint64_t eightCount = 0;
NumberType result = NumberType::NQ;
while (value > 0)
{
uint64_t lastDigit = value % 10;
value /= 10;
if (lastDigit == 6)
{
sixCount++;
}
else if (lastDigit == 8)
{
eightCount++;
}
}
if (sixCount && eightCount)
{
result = NumberType::PNQ;
}
else if (sixCount)
{
result = NumberType::Q6;
}
else if (eightCount)
{
result = NumberType::Q8;
}
else
{
result = NumberType::NQ;
}
return result;
}此时,我们可以很容易地构建一个蛮力解决方案来计算给定范围内的Q6或Q8数的计数。
uint64_t GetSixXorEightCount_BruteForceSlow(uint64_t start, uint64_t end)
{
uint64_t count = 0;
for (auto x = start; x <= end; x++)
{
NumberType nt = GetNumberType(x);
count += ((nt == NumberType::Q6) || (nt == NumberType::Q8)) ? 1 : 0;
}
return count;
}但一旦我们开始进入一个数十亿的范围内,这些数字是不会扩大的。
数模式
我注意到了一些数字。
取不包括零的所有个位数:
(1,2,3,4,5,7,9)
取10到99之间的所有两位数。如果粗略扫描这些数字,您会发现以下统计数据是正确的:
16,26,36,46,56,76,96)
为了计算所有三个数字的统计数据,你不需要进行蛮力搜索。您将意识到以下几点:
,就可以将来自2位范围的任何NQ数字添加到Q8或集中。
下面是计算任意N个数字集合中有多少类型的数字的一般形式:
如果问题空间是“用6 xor 8计算N个数字的计数”,那么递归函数就很容易了。但是,如何计算不同数字边界内的数字范围呢?
示例分解
假设您希望在125和455之间找到限定号的范围。
首先,递归地将问题从125-455中分离出来,切断数字。
首先解决12-45问题,这需要在此之前解决1-4问题。然后应用计算到下一个数字范围的公式。但是,您必须添加一个修复步骤,以不计算不在范围内的数字(120-124和456-459)。
SolveFor(125-455):
SolveFor(12-45)
SolveFor(1-4) => {Q6:0, Q8:0, NQ: 4}
Compute for 10-49 => {Q6: 4, Q8: 4, NQ: 32}
Repair for 12-45 by uncounting 10,11,46,47,48,and 49 from the stats set => {{Q6: 3, Q8: 3, NQ: 28}
Compute for 120-459 => {Q6:55, Q8:55, NQ: 224}
Repair for 125-455 by uncounting 120-124 and uncounting for 456-459 => {Q6:54, Q8:54, NQ: 217}终解
#include <iostream>
using namespace std;
enum class NumberType : int
{
NQ, // NQ is "non-qualifying". It's a number with neither a six or an eight
Q6, // Q6 is "qualified by 6". It's a number with at least a single six digit, but no eights
Q8, // Q8 is "qualified by 8". It's a number with at least a single eight digit, but no sixes
PNQ // PNQ is "permanently non qualified". It's a number with both a six and an eight in it
};
struct range_stats
{
uint64_t nq; // number of "non-qualifying numbers" that have neither 6 nor 8 in any digit
uint64_t pnq; // number of "permanently non-qualifying" numbers tht have both a 6 or 8
uint64_t q6; // number of qualifying numbers that have at least one occurance of 6, but not 8
uint64_t q8; // number of qualifying numbers that have at least one occurance of 8, but not 6
};
NumberType GetNumberType(uint64_t value)
{
uint64_t sixCount = 0;
uint64_t eightCount = 0;
NumberType result;
while (value > 0)
{
uint64_t lastDigit = value % 10;
value /= 10;
if (lastDigit == 6)
{
sixCount++;
}
else if (lastDigit == 8)
{
eightCount++;
}
}
if (sixCount && eightCount)
{
result = NumberType::PNQ;
}
else if (sixCount)
{
result = NumberType::Q6;
}
else if (eightCount)
{
result = NumberType::Q8;
}
else
{
result = NumberType::NQ;
}
return result;
}
void DeductStats(range_stats& stats, NumberType nt)
{
switch (nt)
{
case NumberType::Q6:
{
stats.q6--;
break;
}
case NumberType::Q8:
{
stats.q8--;
break;
}
case NumberType::NQ:
{
stats.nq--;
break;
}
case NumberType::PNQ:
{
stats.pnq--;
break;
}
}
}
void AddStats(range_stats& stats, NumberType nt)
{
switch (nt)
{
case NumberType::Q6:
{
stats.q6++;
break;
}
case NumberType::Q8:
{
stats.q8++;
break;
}
case NumberType::NQ:
{
stats.nq++;
break;
}
case NumberType::PNQ:
{
stats.pnq++;
break;
}
}
}
void GetStatsForRange(uint64_t start, uint64_t end, range_stats& stats)
{
// ASSUMES START AND END HAVE THE SAME NUMBER OF DIGITS
if (end < 10)
{
uint64_t sum = 0;
stats = {};
for (auto i = start; i <= end; i++)
{
if (i == 6)
{
stats.q6++;
}
else if (i == 8)
{
stats.q8++;
}
else if (i > 0)
{
stats.nq++;
}
}
}
else
{
uint64_t newStart = start / 10;
uint64_t newEnd = end / 10;
uint64_t repairStart = (start / 10) * 10; // convert 123 to 120
uint64_t repairEnd = (end / 10) * 10 + 9; // convert 567 to 569
range_stats lowerStats = {};
GetStatsForRange(newStart, newEnd, lowerStats);
stats.nq = lowerStats.nq * 8;
stats.q6 = lowerStats.q6 * 9 + lowerStats.nq;
stats.q8 = lowerStats.q8 * 9 + lowerStats.nq;
stats.pnq = lowerStats.pnq * 10 + lowerStats.q6 + lowerStats.q8;
// now repair the stats by accounting for the ones that aren't in range
for (uint64_t i = repairStart; i < start; i++)
{
auto nt = GetNumberType(i);
DeductStats(stats, nt);
}
for (uint64_t i = repairEnd; i > end; i--)
{
auto nt = GetNumberType(i);
DeductStats(stats, nt);
}
}
}
bool InRange(uint64_t value, uint64_t minimum, uint64_t maximum)
{
return ((value >= minimum) && (value <= maximum));
}
bool IntersectionOfRanges(uint64_t levelStart, uint64_t levelEnd, uint64_t start, uint64_t end, uint64_t& newStart, uint64_t& newEnd)
{
// ASSERT START <= END
bool startInRange = InRange(start, levelStart, levelEnd);
bool endInRange = InRange(end, levelStart, levelEnd);
if ((end < levelStart) || (start > levelEnd))
{
return false; // no intersection
}
if (!startInRange && endInRange)
{
newStart = levelStart;
newEnd = end;
}
else if (startInRange && endInRange)
{
newStart = start;
newEnd = end;
}
else if (startInRange)
{
newStart = start;
newEnd = levelEnd;
}
else
{
newStart = levelStart;
newEnd = levelEnd;
}
return true;
}
void bruteForceTest(uint64_t start, uint64_t end, range_stats& stats)
{
stats = {};
for (auto i = start; i <= end; i++)
{
auto nt = GetNumberType(i);
AddStats(stats, nt);
}
}
uint64_t GetSixXorEightCount(uint64_t start, uint64_t end)
{
range_stats stats = {};
range_stats expected = {};
range_stats results = {};
if (start == 0)
{
start = 1;
}
if (start > end)
{
return 0;
}
uint64_t levelStart = 1;
uint64_t levelEnd = 9;
while (levelStart <= end)
{
uint64_t normalizedStart;
uint64_t normalizedEnd;
if (IntersectionOfRanges(levelStart, levelEnd, start, end, normalizedStart, normalizedEnd))
{
stats = {};
GetStatsForRange(normalizedStart, normalizedEnd, stats);
results.nq += stats.nq;
results.q6 += stats.q6;
results.q8 += stats.q8;
results.pnq += stats.pnq;
}
levelStart = levelStart * 10;
levelEnd = levelEnd * 10 + 9;
}
cout << "For the range of numbers between " << start << " and " << end << endl;
cout << "Q6: " << results.q6 << endl;
cout << "Q8: " << results.q8 << endl;
cout << "NQ: " << results.nq << endl;
cout << "PNQ: " << results.pnq << endl;
//cout << "Brute force computing expected" << endl;
//bruteForceTest(start, end, expected);
//cout << "Expected Q6: " << expected.q6 << endl;
//cout << "Expected Q8: " << expected.q8 << endl;
//cout << "Expected NQ: " << expected.nq << endl;
//cout << "Expected PNQ: " << expected.pnq << endl;
return results.q6 + results.q8;
}
int main()
{
uint64_t start = 0;
uint64_t end = 0;
cout << "Starting number:\n";
cin >> start;
cout << "Ending number:\n";
cin >> end;
uint64_t result = GetSixXorEightCount(start, end);
std::cout << "There are " << result << " qualifying numbers in range that have a six or eight, but not both\n";
return 0;
}https://stackoverflow.com/questions/59905834
复制相似问题