C++ 查找字符串中不存在的最小子字符串

C++ 查找字符串中不存在的最小子字符串,c++,string,algorithm,substring,C++,String,Algorithm,Substring,我有一个只由数字0-9组成的字符串。字符串的长度可以在1到1000000个字符之间。我需要在线性时间内找到字符串中不存在的最小数。以下是一些例子: 1023456789 //Smallest number not in string is 11 1023479 //Smallest number not in string is 5 112131405678910 //Smallest number not in string is 15 大小为1000000时

我有一个只由数字0-9组成的字符串。字符串的长度可以在1到1000000个字符之间。我需要在线性时间内找到字符串中不存在的最小数。以下是一些例子:

1023456789       //Smallest number not in string is 11
1023479          //Smallest number not in string is 5
112131405678910  //Smallest number not in string is 15
大小为1000000时,我计算字符串中不存在的最小数字必须最多为6位

我的方法是生成从0到999999的所有数字,并将它们全部插入向量中(按顺序)。然后制作一张地图,标出已经看到的字符串。然后我遍历字符串,对于每个位置,我得到从它开始的所有子字符串,大小为1到6,我在映射中将所有这些子字符串标记为true。最后,我只需逐个检查所有键,当我在地图中找到第一个具有假值的键时,我将其打印出来

以下是一些代码片段:

string tmp="0";
string numbers[999999];

void increase(int pos)
{
    if(pos==-1)tmp.insert(0,"1");
    else if(tmp.at(pos)!='9')tmp.at(pos)++;
    else
    {
        tmp.at(pos)='0';
        increase(pos-1);
    }
}

//And later inside main
for(int j=0;j<999999;j++)
{
    numbers[j]=tmp;
    increase(tmp.size()-1);
}
string tmp=“0”;
字符串编号[999999];
无效增加(内部位置)
{
如果(位置==-1)tmp.插入(0,“1”);
否则如果(tmp.at(pos)!='9')tmp.at(pos)+;
其他的
{
tmp.at(位置)='0';
增加(1号职位);
}
}
//然后在主楼里面

对于(int j=0;j因为您只需要知道是否已经看到一个数字,所以使用
std::vector
存储该指示可能是最简单的方法。当您遍历输入数字时,您会在数组中将数字标记为true。完成后,您遍历数组,并打印出仍然为false的第一项的索引.

我们的想法是建立一个满足条件的数字树:

class Node {
public:
    Node() : count( 0 ) {}
    // create a tree from substring [from, to[ interval
    void build( const std::string &str, size_t from, size_t to )
    {
        Node *node = this;
        while( from != to )
            node = node->insert( str[from++] );
    }

    std::string smallestNumber(  bool root = true, int limit = 0 ) const;

 private:
    Node *insert( char c ) 
    {
        int idx = c - '0';
        if( !children[idx] ) {
            ++count;
            children[idx].reset( new Node );
        }
        return children[idx].get();
    }

    int count;
    std::unique_ptr<Node> children[10];

};

std::string Node::smallestNumber( bool root, int limit ) const
{
    std::string rez;
    if( count < 10 ) { // for this node string is one symbol length
        for( int i = 0; i < 10; ++i )
            if( !children[i] ) return std::string( 1, '0' + i );
        throw std::sruntime_error( "should not happen!" );
    }
    if( limit ) { 
        if( --limit == 1 ) return rez; // we cannot make string length 1
    }
    char digit = '0';
    for( int i = 0; i < 10; ++i ) {
        if( root && i == 0 ) continue;
        std::string tmp = children[i]->smallestNumber( false, limit );
        if( !tmp.empty() ) {
            rez = tmp;
            digit = '0' + i;
            limit = rez.length();
            if( limit == 1 ) break;
        }
    }
    return digit + rez;
}

void calculate( const std::string &str )
{
    Node root;
    for( size_t i = 0; i < str.length(); ++i ) {
        root.build( str, i, i + std::min( 6UL, str.length() - i ) );
    }
    std::cout << "smallest number is:" << root.smallestNumber() << std::endl;
}

int main()
{
    calculate( "1023456789" );
    calculate( "1023479" );
    calculate( "112131405678910" );
    return 0;
}

下面是我如何处理这个问题的。这个想法是生成一组具有特定长度的唯一子字符串,从最短的子字符串开始,然后在生成较长的子字符串之前测试这些子字符串。这使得代码不必对结果的上界进行假设,而且对于具有尽管如此,在最坏的情况下,大业绩并不一定更好

int find_shortest_subnumber(std::string str) {
    static int starts[10] = {
        0, 10, 100, 1000, 10000, 
        100000, 1000000, 10000000, 100000000, 1000000000
    };
    // can't find substrings longer than 9 (won't fit in int)
    int limit = std::min((int)str.size(), 9);
    for(int length = 1; length <= limit; length++) {
        std::set<std::string> uniques; // unique substrings of current length
        for(int i = 0; i <= (int)str.size() - length; i++) {
            auto start = str.begin() + i;
            uniques.emplace(start, start + length);
        }
        for(int i = starts[length - 1]; i < starts[length]; i++) {
            if(uniques.find(std::to_string(i)) == uniques.end())
                return i;
        }
    }
    return -1; // not found (empty string or too big result)
}
int查找最短子编号(std::string str){
静态整数开始[10]={
0, 10, 100, 1000, 10000, 
100000, 1000000, 10000000, 100000000, 1000000000
};
//找不到长于9的子字符串(不适合int)
int limit=std::min((int)str.size(),9);
对于(int-length=1;length,您可以在线性时间(和空间)中为字符串构造一个后缀树。一旦有了后缀树,您只需首先遍历它,按字典顺序扫描每个节点的子节点,并检查每个节点的所有10位数字。第一位缺失的数字是最小缺失数字中的最后一位


由于1000000位序列只有99995个六位数子序列,因此必须至少有五个六位数子序列不存在,因此广度优先搜索必须不晚于第六级终止;因此,它也是线性时间。

@a.Andevski,您是指与字符串长度相关的线性时间还是与t相关的线性时间o子字符串的数量(与字符串长度有关,是二次的)?我不确定前者是否可行。这听起来像是一个来自竞赛的问题——请链接到它,以便我们知道它不是最新的。我可以想出一个算法,它是输入字符数加上答案值的线性算法。我想知道你是如何得出170000的。我明白了。170000只是1000000/6(大概四舍五入)。这可能是低的。例如,考虑字符串<代码>“123456123”。
包含四个6位数字,只有九位数字。很可能会得出一个非常接近压缩比的排列方式。我怀疑您的最大值接近500000。@JimMischel:请参阅de Bruijn序列;对于大小为k和任何整数n的字母表,您可以构造一个大小为k^n的循环,其中包含所有k^n个n字符序列。对循环进行展开会产生长度为k^n+n-1的字符串,其第一个和最后的n-1个字符相同。一个1000000位序列只有99995个6位子序列,因此至少有五个6位子序列不存在。特别是de Bruijn序列(剪切而不是展开)我需要一直将每个子字符串转换为整数。这不会花费我更长的时间吗?在这样一个小字符串中,我会说类型转换所需的时间不是一个因素,但检查它的唯一方法是实现和执行基准测试。但我会使用数组,而不是向量。@user3564091:向量通常由数组支持,并且
vector
有一些(臭名昭著的)将数据打包到更少空间的优化。好的,但这次不是将数据打包到更少空间,而是快速访问它,这两个主题通常是相互矛盾的,但如果我在这里遗漏了什么,请更正。@user3564091:除非数组非常小(特别是足够小,可以放入缓存中),
vector
主要是用额外的CPU时间来节省内存访问时间。您可以使用大量的CPU时间来节省少量内存访问,但仍然领先。
class Node {
public:
    Node() : count( 0 ) {}
    // create a tree from substring [from, to[ interval
    void build( const std::string &str, size_t from, size_t to )
    {
        Node *node = this;
        while( from != to )
            node = node->insert( str[from++] );
    }

    std::string smallestNumber(  bool root = true, int limit = 0 ) const;

 private:
    Node *insert( char c ) 
    {
        int idx = c - '0';
        if( !children[idx] ) {
            ++count;
            children[idx].reset( new Node );
        }
        return children[idx].get();
    }

    int count;
    std::unique_ptr<Node> children[10];

};

std::string Node::smallestNumber( bool root, int limit ) const
{
    std::string rez;
    if( count < 10 ) { // for this node string is one symbol length
        for( int i = 0; i < 10; ++i )
            if( !children[i] ) return std::string( 1, '0' + i );
        throw std::sruntime_error( "should not happen!" );
    }
    if( limit ) { 
        if( --limit == 1 ) return rez; // we cannot make string length 1
    }
    char digit = '0';
    for( int i = 0; i < 10; ++i ) {
        if( root && i == 0 ) continue;
        std::string tmp = children[i]->smallestNumber( false, limit );
        if( !tmp.empty() ) {
            rez = tmp;
            digit = '0' + i;
            limit = rez.length();
            if( limit == 1 ) break;
        }
    }
    return digit + rez;
}

void calculate( const std::string &str )
{
    Node root;
    for( size_t i = 0; i < str.length(); ++i ) {
        root.build( str, i, i + std::min( 6UL, str.length() - i ) );
    }
    std::cout << "smallest number is:" << root.smallestNumber() << std::endl;
}

int main()
{
    calculate( "1023456789" );
    calculate( "1023479" );
    calculate( "112131405678910" );
    return 0;
}
smallest number is:11
smallest number is:5
smallest number is:15
int find_shortest_subnumber(std::string str) {
    static int starts[10] = {
        0, 10, 100, 1000, 10000, 
        100000, 1000000, 10000000, 100000000, 1000000000
    };
    // can't find substrings longer than 9 (won't fit in int)
    int limit = std::min((int)str.size(), 9);
    for(int length = 1; length <= limit; length++) {
        std::set<std::string> uniques; // unique substrings of current length
        for(int i = 0; i <= (int)str.size() - length; i++) {
            auto start = str.begin() + i;
            uniques.emplace(start, start + length);
        }
        for(int i = starts[length - 1]; i < starts[length]; i++) {
            if(uniques.find(std::to_string(i)) == uniques.end())
                return i;
        }
    }
    return -1; // not found (empty string or too big result)
}