题目
数字序列中某一位的数字
数字以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(=90∗2)
991
>
180
(
=
90
∗
2
)
,所以第991位在所有的两位数之后,我们再跳过90个两位数,继续从后面找811(=991-180)位
4、接下来的2700位数字是100~999共900位3位数。由于
811<2700(=900∗3)
811
<
2700
(
=
900
∗
3
)
,所以第811位是某个三位数中的一个。由于
811=270∗3+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;
}
}
}
}