在给定范围内有效地找到数字六乘八的每个数字

时间:2020-01-25 02:21:59

标签: c++

我必须取两个数字并找到两个数字之间的所有数字(包括两个数字)(其中包括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;
}

1 个答案:

答案 0 :(得分:0)

此文章底部的完整解决方案

花了几个小时才完成,但是我认为我有最佳的解决方案。感谢您提供这一挑战。数字计算问题是我的爱好(对故障)。

术语

首先我为这个问题发明了一些术语:

NQ 或“不合格”。 NQ数字是一组数字中既没有6也没有8的数字。

Q6 或“被6限定”是指数字中带有6,但不能为8的任何数字。

Q8 或“由8限定”是在其数字集中带有8,但不包含六进制的任何数字。

PNQ 或“永久不合格”。这是一个既有6也有8的数字。

只要在其末尾附加68,任何NQ号码都可以成为合格号码。 (例如123是NQ,但1236是“合格”)

之所以使用此“限定”术语是因为需要从将数字构造为从左到右的数字字符串的角度来考虑解决方案。例如,诸如123之类的非限定数字可以通过将6附加到其上而变得合格。通过将6附加到123上,数字变为1236并成为Q6。

任何合格数字都可以通过在其后面附加68使其成为两位数字而永久变为不合格。例如,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;
}

但是一旦我们开始涉足数十亿个无法扩展的数字范围。

数字模式

现在我注意到一些有关数字的事情。

取除零以外的所有一位数字:

  • 7个NQ编号(1,2,3,4,5,7,9)
  • 1个Q6数字(6)
  • 1个Q8数字(8)
  • PNQ中的0

取10到99之间的所有两位数字。如果对这些数字进行粗略扫描,则会发现以下统计信息成立:

  • 56个NQ编号(例如23、45、79等)
  • 16个Q6数字(60-67、69、16、26、36、46、56、76、96)
  • 16个Q8数字(80-85、87-89、18,28,38,48,58,78,96)
  • 2个PNQ号码(68和86)

要计算所有3位数字的统计信息,您不必进行蛮力搜索。您将了解以下内容:

  • 2位范围内的所有NQ编号都可以附加8位之一来计算NQ集。因此,三位数的NQ总数仅为56 * 8。

  • 所有Q6和Q8编号都可以通过在其后面附加9位数字之一来保持合格。此外,只需添加6或8,就可以将2位数范围内的任何NQ编号添加到Q6或Q8集。

因此,接下来是一个通用公式,用于确定任何N个数字集中有多少种数字:

  • countof_nq(N)== countof_nq(N-1)* 8
  • countof_q6(N)== countof_q6(N-1)* 9 + countof_nq(N-1)
  • countof_q8(N)== countof_q8(N-1)* 9 + countof_nq(N-1)
  • countof_pnq(N)== countof_pnq(N-1)* 10 + countof_q6(N-1)+ countof_q8(N-1)

现在,如果问题空间是“用6 x或8计算N个数字的计数”,则递归函数将很容易。但是如何计算不同数字边界内的数字范围?

示例细分

假设您要查找125455之间的合格数字范围。

您首先要通过切掉数字来递归地将问题从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;
}