首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在给定的范围内有效地找到数字6×或8的每一个数字。

在给定的范围内有效地找到数字6×或8的每一个数字。
EN

Stack Overflow用户
提问于 2020-01-25 02:21:59
回答 1查看 127关注 0票数 0

我必须取两个数字,并找到这两个数字之间的所有数字(包括),其中有数字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之间的数字的值添加到特殊数字的数量中(添加的第一个值已保存在内存中的数组中)。

在任何情况下,我认为我能够演示我尝试的解决方案的最好方法是展示我的代码。不过,如果你有改善我的问题的建议,请随意提出。

代码语言:javascript
复制
#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;
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-01-26 00:25:21

完全解决方案位于本文的底部

做这个花了几个小时,但我想我有了最佳的解决方案。谢谢你提供了这个挑战。数字计算问题是我的业余爱好。

术语

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

NQ或“不合格”。NQ数字是指在其数字集合中既没有6也没有8的数字。

Q6或“由6限定”是指数字集合中有6的任何数字,但没有8。

Q8或“由8限定”是指数字集合中有8的任何数字,但没有6。

PNQ或“永久不合格”。这是一个同时包含6或8的数字。

任何NQ数字只要在其末尾附加一个68,就可以成为合格的。(例如,123是NQ,而1236是“合格”)

我之所以使用这个“限定”术语,是因为解决方案需要将数字从左到右构建为一个数字串。例如,一个非限定数字(如123 )可以通过在其上附加一个6而变得合格。通过将一个6附加到123上,这个数字就变成了1236,并且是Q6。

任何限定数字都可以通过向其附加一个68,使其具有两个数字而成为永久的非限定数。例如,654是Q6,但是如果我们在它上附加一个8,它就会永久不合格。任何额外的数字加在这个数字上仍将导致数字为PNQ。

慢溶液

让我们介绍第一批代码。我刚才讨论的一个形式化的枚举:

代码语言:javascript
复制
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位数,并告诉我们它是哪种类型:

代码语言:javascript
复制
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数的计数。

代码语言:javascript
复制
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)

  • 1
  • 7 NQ数(6)
  • 1 Q8数(8)
  • 0

取10到99之间的所有两位数。如果粗略扫描这些数字,您会发现以下统计数据是正确的:

16,26,36,46,56,76,96)

  • 16

  • 56 NQ数(例如23,45,79等)

  • 16 Q6数字(60-67,69, Q8编号(80-85,87-89,18,28,38,48,58,78,96)
  • 2 PNQ编号(68和86))

为了计算所有三个数字的统计数据,你不需要进行蛮力搜索。您将意识到以下几点:

  • 在2位范围内的所有NQ数都可以加上8位中的一位来计算NQ集。因此,3位数字的NQ总数仅为56*8。
  • 所有的Q6和Q8数字都可以通过在其上附加9个数字中的一个来保持限定。此外,只要添加6或8.

,就可以将来自2位范围的任何NQ数字添加到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(N1)+ countof_q8(N-1)

如果问题空间是“用6 xor 8计算N个数字的计数”,那么递归函数就很容易了。但是,如何计算不同数字边界内的数字范围呢?

示例分解

假设您希望在125455之间找到限定号的范围。

首先,递归地将问题从125-455中分离出来,切断数字。

首先解决12-45问题,这需要在此之前解决1-4问题。然后应用计算到下一个数字范围的公式。但是,您必须添加一个修复步骤,以不计算不在范围内的数字(120-124和456-459)。

代码语言:javascript
复制
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}

终解

代码语言:javascript
复制
#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;
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59905834

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档