一、问题描述?
给定一个整数n,并从1~9中给定若干个可以使用的数字,根据上述两个条件,得到每一位都为给定可使用数字的、最大的小于整数n的数。
例如,给定可以使用的数字为 {2,3,8} 三个数:
给定 n=3589,输出3333;给定 n=8234,输出8233;……
二、解题思路
1.问题分析
如果一个数小于另一个数,那么较小的那个数一定有至少一个数量级的位是小于大数的。而为了取到小于n又尽可能大的值,我们总是希望能够在数量级尽可能小的位置小于n。
假定可以使用的数字为 {2,3,8} ,在尝试过程中,我们可以发现,结果有以下几类:
1> 高位相等,最后一位取到了小于的值。例如:给定 n=8234,除最后一位外,我们都能够取到相等的值,最后一位也刚好能够取到一个小于n个位的值,输出8233;
2> 高位相等,中间一位取到了小于的值。例如:给定 n=8534,在第二位,我们已经只能取到小于n的数了,此时,数量级更小的位也随之确定,均为给定的最大数字,输出8388;
3> 在高位就已经无法相等,但最高位能取到更小的值。这种情况相当于第二种情况的高位位数退化到0。例如:给定 n=5555,将会输出3333;
4> 在高位就已经无法相等,且最高位也无法取到更小的值。此时,与n相同的数量级内已经不存在解,我们向下一个数量级输出最大的位数。例如:给定 n=1999,将会输出888;
2.算法选择
从上面的分析中不难看出,我们总是寄希望于在数量级尽可能小的位里才使我们的结果小于n,对应的,尝试使用贪心算法+回溯来求解这个问题。
3.运算示例
给定可以使用的数字为 {2,3,8}:
n1 = 2338,贪心,从高往低取:2 - 3 - 3 - ;此时到达最后一位,尝试取更小的,- 3;
n2 = 2399,贪心,从高往低取:2 - 3 - ;此时只能取更小的,- 8;剩余位数均为 - 8;
n3 = 2381,贪心,从高往低取:2 - 3 - 8 - ;此时无法取更小的,回溯,上一位能取到更小值,改 - 8 为 -3,后接max值:2 - 3 - 3 - 8;
n4 = 1999,贪心,最高位就无法取更小的,此时已经无需回溯也无法回溯,直接确认为少一数量级(第六段中有相关番外),得到8 - 8 - 8;
n5 = 2222,贪心,从高往低取:2 - 2 - 2 - ;此时到达最后一位,必须取更小值,已经无法取得,回溯,上一位无法取到更小值,继续回溯,仍无法取得更小值,继续回溯,无法取得,此时已经到达首位,无法再回溯,到此时,确认为少一个数量级,得到8 - 8 - 8;
4.数据结构
1>功能:记录给定数字; 对应结构:vector;
2>功能:记录结果; 对应结构:vector;
3>功能:拆分给定 n 并记录; 对应结构:vector;
4>功能:从给定数字集中快速查找目标是否存在; 对应结构:map :unordered_set;
三、伪代码与接口
//-----接口1-----//
//@brief : 从给定的集合中获取小于指定值的数
//@param : 一个已经从大到小排序的集合, 指定值
//@return : 若集合中存在合法结果,返回给定值;否则返回0;
int getMaxDigitLessThanGivenDigit(const vector<int>& digits, int d) {
//从大到小遍历
{
//若找到小于指定值的数,返回该数
}
//若遍历无结果,返回0
}
//-----接口2-----//
//@brief : 从给定的集合中获取小于指定值的数
//@param : 一个已经从大到小排序的集合, 指定值
//@return : 若集合中存在合法结果,返回给定值;否则返回0;
int getMaxNumLessThanGivenN(vector<int>& digits, int n) {
//拆分n为单位数形式
//排序给定值
//定义vector格式结果
//构建快速查找map
//从高位往低位遍历n,根据遍历到的每一位,尝试确定结果
{
//对非最高位,首先尝试给定集合中是否能取到相同值
{
//若能取到相同值,先将结果中相同位贪心为相同值,然后继续查找下一位
}
//若不能取到相同值,调用接口1尝试是否能取到较小值
{
//若能取到较小值,赋值该位为较小值,接着后续位数均为给定值中的最大值,跳出遍历
}
//若不能取到较小值,需要回溯
//若已经是最高位,无法进行回溯
{
//定义结果为小一数量级,跳出遍历
}
//进行回溯,向高一位移动
{
//每次回溯,先将旧结果(失效值)抹去
//此时只能找较小值(相等值已经贪心过了),调用接口1尝试回溯位是否能取到较小值
{
//若能取到较小值,赋值该位为较小值,接着后续位数均为给定值中的最大值,跳出遍历
}
//若已经回溯到了最高位,仍未找到较小值,此时可以断定需要减少一个数量级
{
//定义结果为小一数量级,跳出遍历
}
}
//若回溯已经执行完但仍执行到了这里,强制跳出遍历,否则会发生死循环(指针在高低位往返移动)
}
//转换单位数格式的结果为数据的结果,返回
}
//-----main-----//
//@brief : 调用接口2并传参
//@param :
//@output : 输出结果
//@return :
四、C++代码
#include <iostream>
#include<vector>
#include<unordered_set>
#include<algorithm>
using namespace std;
//Get max digit which is less than d from given digits which is sorted from big to small.
int getMaxDigitLessThanGivenDigit(const vector<int>& digits, int d) {
for (auto it = digits.rbegin(); it < digits.rend(); it++) { //Searching from big to small.
if (*it < d) { //The first one to be less than d is the max one among the less set.
return *it;
}
}
//If miss, return 0;
return 0;
}
//Get max num consisted of digits in vector digits. The num is expected to be less than n.
int getMaxNumLessThanGivenN(vector<int>& digits, int n) {
//-----Condition due and result delare.-----//
//Devide n into digits.
vector<int> n_digits;
for (; n > 0; n /= 10)
n_digits.push_back(n % 10);
//Sort given digits.
sort(digits.begin(), digits.end());
//Declare target digits. Format {0,0,...,0}. Target format is {0,0,...,0,1,2,3}. At the end, 0 will be replace by max digit given.
vector<int> t_digits(n_digits.size(), 0);
//-----Construct auxiliary variable.-----//
//Make map for fast consistence judge.
unordered_set<int> set;
for (auto di : digits)
set.insert(di);
//Begin to fill target digits. From high to low.
for (int i = n_digits.size() - 1; i >= 0; i--) {
//Case 1 : For those who are not highest, chioce the same num if exists.
if (i > 0 && set.count(n_digits[i])) { //Exist.
t_digits[i] = n_digits[i]; //This digit use it temp, continue to go through.
continue;
}
//Case 2 : If not exist same num, try to get max digit which is less than n's current digit.
int less_d = getMaxDigitLessThanGivenDigit(digits, n_digits[i]);
//If got one num less than n's current digit, the reslt will be sure and there is no need to continue traverse.
if (less_d > 0) {
t_digits[i] = less_d;
break; //Format {0,0,...,0,1,2,3}. 0 is excepted to be replaced by max digit given.
}
//Case 3 : If there is no digit less than n's current digit, flash back.
//-----Boundary condition : include Method 1 and Method 2, only use one.-----//
//Method 1 : if hightest, directly resize.
if (i == n_digits.size() - 1) {
t_digits.resize(i);
break;
}
//Method 2 : move cursor only not in hightest. If not highest, move back the cursor.
else {
i++;
}
for (; i < n_digits.size(); i++) {
//If the digit is flashed back, corresponding t_digits need to be clear. Reassign it 0.
t_digits[i] = 0;
//Try to be smaller while flashing back. Turn to case 2.
less_d = getMaxDigitLessThanGivenDigit(digits, n_digits[i]);
if (less_d > 0) {
t_digits[i] = less_d;
break; //Format {0,0,...,0,1,2,3}. 0 is excepted to be replaced by max digit given.
}
//If fail to lessen this digit but this digit isn't the highest, just continue to flash back.
//But if this digit is the highest, it means there are no answer in the same order of magnitude.
if (i == n_digits.size() - 1)
t_digits.resize(i);
}
//If Case 3 finished, the result has been sure here. Stop. Otherwise endless looping will happen.
break;
}
//Turn target from digit format to num format.
int target = 0;
//From high to low, replace 0 to max.
for (int t_d = t_digits.size() - 1; t_d >= 0; t_d--)
target = 10 * target + (t_digits[t_d] == 0 ? digits.back() : t_digits[t_d]);
return target;
}
int main()
{
cout << "Testing begin..." << endl;
//Unordered for sort test.
vector<int> digits = { 2,8,3 };
cout << "Given digits is: ";
for (auto it : digits)
cout << it << " ";
cout << endl;
//Case 1 : Smaller in last, same the head. Can be same but avoid. n = 2338.
cout << "n = 2338 excepted output : 2333 actually output : " << getMaxNumLessThanGivenN(digits, 2338) << endl;
//Case 2 : Smaller in last, same the head. Get smaller. n = 2339.
cout << "n = 2339 excepted output : 2338 actually output : " << getMaxNumLessThanGivenN(digits, 2339) << endl;
//Case 3 : Smaller in middle, same the head, tail the max, no need to flash back. n = 2349.
cout << "n = 2399 excepted output : 2388 actually output : " << getMaxNumLessThanGivenN(digits, 2399) << endl;
//Case 4 : Smaller in middle, same the head, tail the max, need to flash back. n = 2381.
cout << "n = 2381 excepted output : 2338 actually output : " << getMaxNumLessThanGivenN(digits, 2381) << endl;
//Case 5 : Smaller in order, no need to flash back. Fail in head. n = 1999.
cout << "n = 1999 excepted output : 888 actually output : " << getMaxNumLessThanGivenN(digits, 1999) << endl;
//Case 6 : Smaller in order, need to flash back. Fail not in head. n = 2221.
cout << "n = 2221 excepted output : 888 actually output : " << getMaxNumLessThanGivenN(digits, 2221) << endl;
//Case 7 : Smaller in order, need to flash back. Special test. n = 2222.
cout << "n = 2222 excepted output : 888 actually output : " << getMaxNumLessThanGivenN(digits, 2222) << endl;
return 0;
}
五、测试结果
根据对应的几种可能情况进行测试:
六、番外:测试中发现缺少边界条件与查错
第一次调试时,回溯漏了边界条件的判定,只要发生回溯,就直接把指针指往上一位,对应的C++代码段为:for (i++; i < n_digits.size(); i++),缺少了“若已经是最高位,无法进行回溯”的边界条件。这样一来,当最高位就确定出结果为减数量级情况时,无法唤起 resize,导致边界条件出错:
检查后做了对应的边界限制,若第一位就直接唤起回溯时不会移动指针,而是直接确认结果。