【数据结构与算法(二十)】

题目

数字序列中某一位的数字

数字以012345678910111213141516171819…的格式序列化到一个字符序列中。在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数,求任意第n位对应的数字。

思路

1、举例子,找规律。比如现在要找第1001位的数字是什么?
2、序列的前10位是个位数0~9都是只有一位的数字。显然第1001位在这10个数字之后,因此这10个数字可以直接跳过。我们再从后面紧跟着的序列中找第991(1001-10)位数字
3、接下来的180位数字是10~99共90个2位数。由于 991>180(=902) 991 > 180 ( = 90 ∗ 2 ) ,所以第991位在所有的两位数之后,我们再跳过90个两位数,继续从后面找811(=991-180)位
4、接下来的2700位数字是100~999共900位3位数。由于 811<2700(=9003) 811 < 2700 ( = 900 ∗ 3 ) ,所以第811位是某个三位数中的一个。由于 811=2703+1 811 = 270 ∗ 3 + 1 ,所以第811位是从100开始的第270个数字即370的第1位,即7(第0位是3)

int digitAtIndex(int index)
{
    if (index < 0)
        return -1;
    int digits = 1;
    while (true)
    {
        //计算digits长的数字有多少个
        int numbers = countOfIntegers(digits);
        if (index < numbers*digits)
            return digitAtIndex(index, digits);
    }
}
//计算m位长的数字一共由多少个,比如传入2,输出90
int countOfIntegers(int digits)
{
    if (digits == 1)
        return 10;
    int count = (int)pow(10, digits);
    return 9 * count;
}
//对核心函数进行重载
//当我们知道要找的那一位数字位于某m位数之后,使用该函数找出要找的那个数
int digitAtIndex(int index, int digits)
{   //得到的是最终的目标所在的那个数,例子说的370
    int number = beginNumber(digits) + index / digits;
    int indexFromRight = digits - index % digits;//3-811%3=3-1=2
    for (int i = 1; i < indexFromRight; i++)
        number /= 10;       //370--》37
    return number % 10;     //37--》7
}
//m位长的数的第一个是啥,比如2位是100,3位是1000
int beginNumber(int digits)
{
    if (digits == 1)
        return 1;
    return (int)pow(10, digits-1);
}

把数组排成最小的数

输入一个正整数数组,把数组里所有数组拼接起来排成一个数,打印能拼接出的所有数字中的最小的一个,例如,输入数组{3,32,321},则打印出这3个数字能排成的最小数字321323

思路

1、这道题其实是希望我们找到一个排序规则,数组根据这个规则排序之后能排成一个最小的数字。要确定排序规则,就要比较这两个数字,也就是给出两个数字m和n,我们需要确定一个规则判断m和n哪个应该排在前面,而不是仅仅比较这两个数字的值哪个更大
2、根据题目的要求,两个数字m和n能拼接成数字mn和nm。如果 mn<nm m n < n m ,那么我们应该打印出mn,也就是说m应该排在n的前面,我们定义此时的m小于n;反之,若 nm<mn n m < m n ,则定义n小于m;若 mn=nm m n = n m ,则m等于n。在下文中,符号“<、>、=”表示常规意义的数值的大小关系,而文字“大于、小于、等于”表示新定义的大小关系
3、接下来考虑怎么拼接数字,即给出数字m和n,怎么得到数字mn和nm并比较它们的大小。直接用数值去计算很简单,但需要考虑到一个潜在问题就是m和n都在int型能表达的范围内,但把它们拼接起来的数字mn和nm用int型表示就有可能溢出了,所以这是一个隐形的大数问题
4、大数问题,就想到字符串。另外,由于把数字m和n拼接起来得到mn和nm,它们的位数肯定是相同的,因此比较它们的大小只需按照字符串大小的比骄傲规则就可以了。

//int型变量的最长位数
const int g_MaxNumberLength = 10;
//后面还要加1?因为变为字符串后面多了一个字符串结束标志“\0”
char* strCombine_1 = new char[g_MaxNumberLength * 2 + 1];
char* strCombine_2 = new char[g_MaxNumberLength * 2 + 1];

void PrintMinNumber(int* numbers, int length)
{
    if (numbers == nullptr || length == 0)
        return;
    char** strNumbers = (char**)(new int[length]);
    //①将int型数组中所有的int转换为char*字符串
    for (int i = 0; i < length; i++) {
        strNumbers[i] == new char[g_MaxNumberLength + 1];
        //sprintf函数的功能与printf函数的功能基本一样,只是它把结果输出到指定的字符串中
        sprintf(strNumbers[i], "%d", numbers[i]);
    }
    //②对字符串数组strNumbers进行排序,对数组中所有的字符串进行排序
    qsort(strNumbers, length, sizeof(char*), compare);
    //③输出已经排好序的字符串数组char**
    for (int i = 0; i < length; i++)
        cout << strNumbers[i];
    cout << endl;
    //④要注意把请求的内存空间给释放了delete[]
    for (int i = 0; i < length; i++)
        delete[] strNumbers[i];
    delete[] strNumbers;
}
// 如果[strNumber1][strNumber2] > [strNumber2][strNumber1], 返回值大于0
// 如果[strNumber1][strNumber2] = [strNumber2][strNumber1], 返回值等于0
// 如果[strNumber1][strNumber2] < [strNumber2][strNumber1], 返回值小于0
int compare(const void* strNumber1, const void* strNumber2)
{
    //mn
    strcpy(strCombine_1, *(const char**)strNumber1);
    strcpy(strCombine_1, *(const char**)strNumber2);
    //nm
    strcpy(strCombine_2, *(const char**)strNumber2);
    strcpy(strCombine_1, *(const char**)strNumber1);
    //mn>nm?   mn<nm?   mn=nm?
    return strcmp(strCombine_1, strCombine_2);
}

这道题有一个容易被忽视的点就是大数问题!!

把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0翻译成“a”,1翻译成“b”,……,11翻译成“l”,……,25翻译成“z”。一个数字可能有多种翻译。例如,12258有5种不同的翻译,分别是“bccfi”、“bwfi”、“bczi”、“mcfi”和“mzi”。请实现一个函数,用来计算一个数字有多少种不同的翻译方法

思路【以12258为例】

1、有两种不同的选择来翻译第一个数字1.第一种是数字1单独翻译成“b”,后面剩下数字2558;第二种是1和紧挨着的2组成12翻译成“m”,后面剩下数字258
2、当最开始的一个或两个数字被翻译成一个字符后,我们接着翻译后面剩下的数字。这很明显又是一个递归的问题。
3、定义函数 f(i) f ( i ) 表示从第i个数字开始的不同翻译的数目,那么 f(i)=f(i+1)+g(i,i+1)f(i+2) f ( i ) = f ( i + 1 ) + g ( i , i + 1 ) ∗ f ( i + 2 ) 。当第i位和第i+1位两位数字拼接起来的数字在10~25的范围内,函数 g(i,i+1) g ( i , i + 1 ) 的值为1;否则为0
4、由于存在重复的子问题,递归并不是解决这个问题的最佳方法。还是以12258为例。如前所属,翻译12258可以分解成两个子问题:翻译1和2558。接下来我们翻译第一个子问题中剩下的2558,同样也可以分解成两个子问题:翻译2和258,以及翻译22和58.注意到子问题翻译258重复出现了
5、递归从最大的问题开始自上而下解决问题。我们也可以从最小的子问题开始自下而上解决问题,这样就可以消除重复的子问题。也就是说,我们从数字的末尾开始,然后从右到左翻译并计算不同翻译的数目。

int GetTranslationCount(int number)
{
    if (number < 0)
        return 0;
    string numberInString = std::to_string(number);
    return GetTranslationCount(numberInString);
}
//重载,参数是字符串
int GetTranslationCount(const string& number)
{
    int length = number.length();
    //用于存放第i个数字开始的翻译种类数
    int* counts = new int[length];  
    int count = 0;
    for (int i = length - 1; i >= 0; i--) {
        count = 0;
        //如果是最后一个数字,那么只有一种翻译
        //如果不是最后一个数字,那么翻译的种类数就要看后面的数字,比如123,就要看23
        if (i < length - 1)
            count = counts[i + 1];
        else count = 1;

        if (i < length - 1) {
            //这里其实是将字符串转换为int
            int digit1 = number[i] - '0';
            int digit2 = number[i + 1] - '0';
            int coverted = digit1 * 10 + digit2;
            //如果和后一个数字组合刚好在范围内,那就要加上后一个数字之后的种类书
            //比如2558,25符合要求,那就要看58能组成的种类数
            if (coverted >= 10 && coverted <= 25) {
                if (i < length - 2)
                    count += counts[i + 2];
                else
                    count += 1;
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值