一、问题描述?

        给定一个整数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;
}

五、测试结果

        根据对应的几种可能情况进行测试:

小于n的最大素数python 小于n的最大整数_c++

六、番外:测试中发现缺少边界条件与查错

        第一次调试时,回溯漏了边界条件的判定,只要发生回溯,就直接把指针指往上一位,对应的C++代码段为:for (i++; i < n_digits.size(); i++),缺少了“若已经是最高位,无法进行回溯”的边界条件。这样一来,当最高位就确定出结果为减数量级情况时,无法唤起 resize,导致边界条件出错:

小于n的最大素数python 小于n的最大整数_算法_02

        检查后做了对应的边界限制,若第一位就直接唤起回溯时不会移动指针,而是直接确认结果。