您的位置:首页 > 其它

字符串转换成整数,字符串匹配问题

2013-06-05 21:12 537 查看

本文转自csdn大神v_JULY_v的博客

地址:
http://blog.csdn.net/v_july_v/article/details/9024123

阅读心得:自己原先想得太天真了。。。

第三十~三十一章:字符串转换成整数,字符串匹配问题

前言

    之前本一直想写写神经网络算法和EM算法,但写这两个算法实在需要大段大段的时间,而平时上班,周末则跑去北大教室自习看书(顺便以时间为序,说下过去半年看过的自觉还不错的几本书:《数理统计学简史》《微积分概念发展史》《微积分的历程:从牛顿到勒贝格》《数学恩仇录》《数学与知识的探求》《古今数学思想》《素数之恋》),故一直未曾有时间写。

     然最近在负责一款在线编程挑战平台:http://hero.pongo.cn/(简称hero,通俗理解是中国的topcoder,当然,一直在不断完善中,与一般OJ不同点在于,OJ侧重为参与ACM竞赛者提供刷题练习的场所,而hero则着重为企业招聘面试服务),在上面出了几道编程面试题,有些题目看似简单,但一coding,很多问题便立马都在hero上给暴露出来了,故就从hero上的编程挑战题切入,继续更新本程序员编程艺术系列吧。

    况且,前几天与一朋友聊天,他说他认识的今年360招进来的三四十人应届生包括他自己找工作时基本都看过我的博客,则更增加了更新此编程艺术系列的动力。

    OK,本文讲两个问题:

第三十章、字符串转换成整数;
第三十一章、字符串匹配问题
    还是这句老话,有问题恳请随时批评指正,感谢。

第三十章、字符串转换成整数

    先看题目:

输入一个表示整数的字符串,把该字符串转换成整数并输出,例如输入字符串"345",则输出整数345。

请完成函数StrToInt,实现字符串转换成整数的功能,不得用库函数atoi。

    我们来一步一步分析,直至写出第一份准确的代码:

1、本题考查的实际上就是字符串转换成整数的问题,或者说是要你自行实现atoi函数。那如何实现把表示整数的字符串正确地转换成整数呢?以"345"作为例子:

当我们扫描到字符串的第一个字符'3'时,由于我们知道这是第一位,所以得到数字3。
当扫描到第二个数字'4'时,而之前我们知道前面有一个3,所以便在后面加上一个数字4,那前面的3相当于30,因此得到数字:3*10+4=34。
继续扫描到字符'5','5'的前面已经有了34,由于前面的34相当于340,加上后面扫描到的5,最终得到的数是:34*10+5=345。
    因此,此题的思路便是:每扫描到一个字符,我们便把在之前得到的数字乘以10,然后再加上当前字符表示的数字。

    2、思路有了,有一些细节需要注意,如zhedahht所说:

“由于整数可能不仅仅之含有数字,还有可能以'+'或者'-'开头,表示整数的正负。因此我们需要把这个字符串的第一个字符做特殊处理。如果第一个字符是'+'号,则不需要做任何操作;如果第一个字符是'-'号,则表明这个整数是个负数,在最后的时候我们要把得到的数值变成负数。
接着我们试着处理非法输入。由于输入的是指针,在使用指针之前,我们要做的第一件是判断这个指针是不是为空。如果试着去访问空指针,将不可避免地导致程序崩溃。
另外,输入的字符串中可能含有不是数字的字符。每当碰到这些非法的字符,我们就没有必要再继续转换。
最后一个需要考虑的问题是溢出问题。由于输入的数字是以字符串的形式输入,因此有可能输入一个很大的数字转换之后会超过能够表示的最大的整数而溢出。”
    比如,当给的字符串是如左边图片所示的时候,有考虑到么?当然,它们各自对应的正确输出如右边图片所示(假定你是在32位系统下,且编译环境是VS2008以上):





    3、很快,可能你就会写下如下代码:

//copyright@zhedahht 2007    
enum Status {kValid = 0, kInvalid};  
int g_nStatus = kValid;  
  
// Convert a string into an integer  
int StrToInt(const char* str)  
{  
    g_nStatus = kInvalid;  
    long long num = 0;  
  
    if(str != NULL)  
    {  
        const char* digit = str;  
  
        // the first char in the string maybe '+' or '-'  
        bool minus = false;  
        if(*digit == '+')  
            digit ++;  
        else if(*digit == '-')  
        {  
            digit ++;  
            minus = true;  
        }  
  
        // the remaining chars in the string  
        while(*digit != '\0')  
        {  
            if(*digit >= '0' && *digit <= '9')  
            {  
                num = num * 10 + (*digit - '0');  
  
                // overflow    
                if(num > std::numeric_limits<int>::max())  
                {  
                    num = 0;  
                    break;  
                }  
  
                digit ++;  
            }  
            // if the char is not a digit, invalid input  
            else  
            {  
                num = 0;  
                break;  
            }  
        }  
  
        if(*digit == '\0')  
        {  
            g_nStatus = kValid;  
            if(minus)  
                num = 0 - num;  
        }  
    }  
    return static_cast<int>(num);  
}  

    run下上述程序,会发现当输入字符串是下图中红叉叉部分所对应的时候,程序结果出错:  

    


    两个问题:

当输入的字符串不是数字,而是字符的时候,比如“1a”,上述程序直接返回了0(而正确的结果应该是得到1):

// if the char is not a digit, invalid input  
                  else  
                  {  
                      num = 0;  
                      break;  
                  }  

处理溢出时,有问题。

    4、把代码做下微调,如下:

//copyright@SP_daiyq 2013/5/29  
int StrToInt(const char* str)  
{  
    int res = 0; // result  
    int i = 0; // index of str  
    int signal = '+'; // signal '+' or '-'  
    int cur; // current digit  
  
    if (!str)  
        return 0;  
  
    // skip backspace  
    while (isspace(str[i]))  
        i++;  
  
    // skip signal  
    if (str[i] == '+' || str[i] == '-')  
    {  
        signal = str[i];  
        i++;  
    }  
  
    // get result  
    while (str[i] >= '0' && str[i] <= '9')  
    {  
        cur = str[i] - '0';  
  
        // judge overlap or not  
        if ( (signal == '+') && (cur > INT_MAX - res*10) )  
        {  
            res = INT_MAX;  
            break;  
        }  
        else if ( (signal == '-') && (cur -1 > INT_MAX - res*10) )  
        {  
            res = INT_MIN;  
            break;  
        }  
  
        res = res * 10 + cur;  
        i++;  
    }  
  
    return (signal == '-') ? -res : res;  
}  

    会发现,上面第4小节所述的第1个问题解决了:

    


    但,即使这样,上述代码也还是有问题的。当给定下述测试数据的时候,问题就来了:

需要转换的字符串                         代码运行结果      
  理应得到的正确结果

"    10522545459"
1932610867
2147483647

"         +10523538441s"
1933603849
2147483647

"   +10432359437"
1842424845
2147483647

    什么问题呢?比如说用上述代码转换这个字符串:"    10522545459",它本应得到的正确结果应该是2147483647,但程序实际得到的结果却是:1932610867。故很明显,程序没有很好的解决上面的第2个小问题:溢出问题。

    5、上面说给的程序没有“很好的解决溢出问题。由于输入的数字是以字符串的形式输入,因此有可能输入一个很大的数字转换之后会超过能够表示的最大的整数而溢出。”那么,到底代码该如何写呢?

//copyright@淹死鲨鱼ronkins 2013/5/29  
//挑战题目:http://hero.pongo.cn/Question/Details?ID=47&ExamID=45  
int atoi(const char* str)    
{    
    long long res = 0;    
    int sign = 1;    
  
    while(isspace(*str))++str;    
    if('+' == *str){    
        ++str;    
    }else if('-' == *str){    
        sign = -1;    
        ++str;    
    }    
  
    for(; isdigit(*str); ++str){    
        res *= 10;    
        if(sign > 0)    
            res += (*str - '0');    
        else    
            res -= (*str - '0');    
        if(res >= INT_MAX)return INT_MAX;    
        else if(res <= INT_MIN)return INT_MIN;    
    }    
  
    return res;    
}    

    上面的代码看似能处理数据溢出的问题,其实它只是做了个取巧,即把返回的值res定义成了long long,如下所示:

long long res = 0;    

    故严格说来,我们依然未写出准确的规范代码。

    6、那到底该如何解决这个数据溢出的问题呢?库函数atoi的规定超过int值,按最大值maxint:2147483647来,超过-int按最小值minint:-2147483648来。咱们先来看看Microsoft是如何实现atoi的吧:

//atol函数  
//Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.  
long __cdecl atol(  
    const char *nptr  
    )  
{  
    int c; /* current char */  
    long total; /* current total */  
    int sign; /* if ''-'', then negative, otherwise positive */  
  
    /* skip whitespace */  
    while ( isspace((int)(unsigned char)*nptr) )  
        ++nptr;  
  
    c = (int)(unsigned char)*nptr++;  
    sign = c; /* save sign indication */  
    if (c == ''-'' || c == ''+'')  
        c = (int)(unsigned char)*nptr++; /* skip sign */  
  
    total = 0;  
  
    while (isdigit(c)) {  
        total = 10 * total + (c - ''0''); /* accumulate digit */  
        c = (int)(unsigned char)*nptr++; /* get next char */  
    }  
  
    if (sign == ''-'')  
        return -total;  
    else  
        return total; /* return result, negated if necessary */  
}  

    其中,isspace和isdigit函数的实现代码为:

isspace(int x)    
{    
    if(x==' '||x=='/t'||x=='/n'||x=='/f'||x=='/b'||x=='/r')    
        return 1;    
    else     
        return 0;    
}    
  
isdigit(int x)    
{    
    if(x<='9'&&x>='0')             
        return 1;     
    else     
        return 0;    
}   

    然后atoi调用上面的atol函数,如下所示:

//atoi调用上述的atol  
int __cdecl atoi(  
    const char *nptr  
    )  
{  
    //Overflow is not detected. Because of this, we can just use  
    return (int)atol(nptr);  
}  

    但很遗憾的是,上述atoi标准代码依然返回的是long:

long total; /* current total */  
if (sign == ''-'')  
    return -total;  
else  
    return total; /* return result, negated if necessary */  

    再者,下面这里定义成long的total与10相乘,即total*10很容易溢出:

long total; /* current total */  
total = 10 * total + (c - ''0''); /* accumulate digit */  

   
     7、继续寻找。接下来,咱们来看看linux内核中是如何实现此字符串转换为整数的问题的。
linux内核中提供了以下几个函数:

simple_strtol,把一个字符串转换为一个有符号长整数;
simple_strtoll,把一个字符串转换为一个有符号长长整数;
simple_strtoul,把一个字符串转换为一个无符号长整数;
simple_strtoull,把一个字符串转换为一个无符号长长整数

    相关源码及分析如下。
    首先,atoi调下面的strtol:

//linux/lib/vsprintf.c  
//Copyright (C) 1991, 1992  Linus Torvalds  
//simple_strtol - convert a string to a signed long  
long simple_strtol(const char *cp, char **endp, unsigned int base)  
{  
    if (*cp == '-')  
        return -simple_strtoul(cp + 1, endp, base);  
  
    return simple_strtoul(cp, endp, base);  
}  
EXPORT_SYMBOL(simple_strtol);  

    然后,上面的strtol调下面的strtoul:

//simple_strtoul - convert a string to an unsigned long  
unsigned long simple_strtoul(const char *cp, char **endp, unsigned int base)  
{  
    return simple_strtoull(cp, endp, base);  
}  
EXPORT_SYMBOL(simple_strtoul);  

    接着,上面的strtoul调下面的strtoull:

//simple_strtoll - convert a string to a signed long long  
long long simple_strtoll(const char *cp, char **endp, unsigned int base)  
{  
    if (*cp == '-')  
        return -simple_strtoull(cp + 1, endp, base);  
  
    return simple_strtoull(cp, endp, base);  
}  
EXPORT_SYMBOL(simple_strtoll);  

    最后,strtoull调_parse_integer_fixup_radix和_parse_integer来处理相关逻辑:

//simple_strtoull - convert a string to an unsigned long long  
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)  
{  
    unsigned long long result;  
    unsigned int rv;  
  
    cp = _parse_integer_fixup_radix(cp, &base);  
    rv = _parse_integer(cp, base, &result);  
    /* FIXME */  
    cp += (rv & ~KSTRTOX_OVERFLOW);  
  
    if (endp)  
        *endp = (char *)cp;  
  
    return result;  
}  
EXPORT_SYMBOL(simple_strtoull);  

    重头戏来了。接下来,我们来看上面strtoull函数中的parse_integer_fixup_radix和_parse_integer两段代码。如鲨鱼所说

“真正的处理逻辑主要是在_parse_integer里面,关于溢出的处理,_parse_integer处理的很优美,
而_parse_integer_fixup_radix是用来自动根据字符串判断进制的”。

    先来看_parse_integer函数:

//lib/kstrtox.c, line 39    
//Convert non-negative integer string representation in explicitly given radix to an integer.    
//Return number of characters consumed maybe or-ed with overflow bit.    
//If overflow occurs, result integer (incorrect) is still returned.    
unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p)    
{    
    unsigned long long res;    
    unsigned int rv;    
    int overflow;    
    
    res = 0;    
    rv = 0;    
    overflow = 0;    
    while (*s) {    
        unsigned int val;    
    
        if ('0' <= *s && *s <= '9')    
            val = *s - '0';    
        else if ('a' <= _tolower(*s) && _tolower(*s) <= 'f')    
            val = _tolower(*s) - 'a' + 10;    
        else    
            break;    
    
        if (val >= base)    
            break;    
        /*  
         * Check for overflow only if we are within range of  
         * it in the max base we support (16)  
         */    
        if (unlikely(res & (~0ull << 60))) {    
            if (res > div_u64(ULLONG_MAX - val, base))    
                overflow = 1;    
        }    
        res = res * base + val;    
        rv++;    
        s++;    
    }    
    *p = res;    
    if (overflow)    
        rv |= KSTRTOX_OVERFLOW;    
    return rv;    
}  

    解释下两个小细节:

上头出现了个unlikely,其实unlikely和likely经常出现在linux相关内核源码中

if(likely(value)){  
    //等价于if(likely(value)) == if(value)  
}  
else{  
}  

likely表示value为真的可能性更大,而unlikely表示value为假的可能性更大,这两个宏被定义成:

//include/linux/compiler.h  
# ifndef likely  
#  define likely(x) (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 1))  
# endif  
# ifndef unlikely  
#  define unlikely(x)   (__builtin_constant_p(x) ? !!(x) : __branch_check__(x, 0))  
# endif  

呈现下div_u64的代码:

//include/linux/math64.h  
//div_u64  
static inline u64 div_u64(u64 dividend, u32 divisor)  
{  
    u32 remainder;  
    return div_u64_rem(dividend, divisor, &remainder);  
}  
  
//div_u64_rem  
static inline u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder)  
{  
    *remainder = dividend % divisor;  
    return dividend / divisor;  
}  

    最后看下_parse_integer_fixup_radix函数:

//lib/kstrtox.c, line 23  
const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)  
{  
    if (*base == 0) {  
        if (s[0] == '0') {  
            if (_tolower(s[1]) == 'x' && isxdigit(s[2]))  
                *base = 16;  
            else  
                *base = 8;  
        } else  
            *base = 10;  
    }  
    if (*base == 16 && s[0] == '0' && _tolower(s[1]) == 'x')  
        s += 2;  
    return s;  
}  

    OK,至此,字符串转换成整数的问题算是已经解决。如果面试官继续问你,如何把整数转换成字符串呢?请读者思考,同时也欢迎于本文评论下或hero上show your code。

第三十一章、字符串匹配问题

字符串匹配问题,给定一串字符串,按照指定规则对其进行匹配,并将匹配的结果保存至output数组中,多个匹配项用空格间隔,最后一个不需要空格。

要求:

匹配规则中包含通配符?和*,其中?表示匹配任意一个字符,*表示匹配任意多个(>=0)字符。
匹配规则要求匹配最大的字符子串,例如a*d,匹配abbdd而非abbd,即最大匹配子串。
匹配后的输入串不再进行匹配,从当前匹配后的字符串重新匹配其他字符串。
请实现函数:char* my_find(char  input[],   char rule[])

举例说明

input:abcadefg
rule:a?c
output:abc

input :newsadfanewfdadsf
rule: new
output: new new

input :breakfastfood
rule: f*d
output:fastfood

注意事项:

自行实现函数my_find,勿在my_find函数里夹杂输出,且不准用C、C++库,和Java的String对象;
请注意代码的时间,空间复杂度,及可读性,简洁性;
input=aaa,rule=aa时,返回一个结果aa,即可。
    1、本题与上述第三十章的题不同,上题字符串转换成整数更多考察对思维的全面性和对细节的处理,本题则更多的是编程技巧。闲不多说,直接上代码:

//copyright@cao_peng 2013/4/23  
int str_len(char *a) {  //字符串长度  
    if (a == 0) {  
        return 0;  
    }  
    char *t = a;  
    for (;*t;++t)  
        ;  
    return (int) (t - a);  
}  
  
void str_copy(char *a,const char *b,int len) {  //拷贝字符串 a = b  
    for (;len > 0; --len, ++b,++a) {  
        *a = *b;  
    }  
    *a = 0;  
}  
  
char *str_join(char *a,const char *b,int lenb) { //连接字符串 第一个字符串被回收  
    char *t;  
    if (a == 0) {  
        t = (char *) malloc(sizeof(char) * (lenb + 1));   
        str_copy(t, b, lenb);  
        return t;  
    }  
    else {  
        int lena = str_len(a);  
        t = (char *) malloc(sizeof(char) * (lena + lenb + 2));  
        str_copy(t, a, lena);  
        *(t + lena) = ' ';  
        str_copy(t + lena + 1, b, lenb);  
        free(a);  
        return t;  
    }  
}  
  
int canMatch(char *input, char *rule) { // 返回最长匹配长度 -1表示不匹配   
    if (*rule == 0) { //已经到rule尾端  
        return 0;  
    }  
    int r = -1 ,may;  
    if (*rule == '*') {  
        r = canMatch(input, rule + 1);  // *匹配0个字符  
        if (*input) {  
            may = canMatch(input + 1, rule);  // *匹配非0个字符  
            if ((may >= 0) && (++may > r)) {  
                r = may;  
            }  
        }  
    }  
    if (*input == 0) {  //到尾端  
        return r;  
    }  
    if ((*rule == '?') || (*rule == *input)) {  
        may = canMatch(input + 1, rule + 1);  
        if ((may >= 0) && (++may > r)) {  
            r = may;  
        }  
    }  
    return r;  
}  
  
char * my_find(char  input[],   char rule[]) {  
    int len = str_len(input);  
    int *match = (int *) malloc(sizeof(int) * len);  //input第i位最多能匹配多少位 匹配不上是-1  
    int i,max_pos = - 1;  
    char *output = 0;  
  
    for (i = 0; i < len; ++i) {  
        match[i] = canMatch(input + i, rule);  
        if ((max_pos < 0) || (match[i] > match[max_pos])) {  
            max_pos = i;  
        }  
    }  
    if ((max_pos < 0) || (match[max_pos] <= 0)) {  //不匹配  
        output = (char *) malloc(sizeof(char));  
        *output = 0;   // \0  
        return output;  
    }  
    for (i = 0; i < len;) {  
        if (match[i] == match[max_pos]) { //找到匹配  
            output = str_join(output, input + i, match[i]);  
            i += match[i];  
        }  
        else {  
            ++i;  
        }  
    }  
    free(match);  
    return output;  
}  

     2、本题也可以直接写出DP方程,如下代码所示:

//copyright@chpeih 2013/4/23  
char * my_find(char  input[],   char rule[]) {  
    int len = str_len(input);  
    int *match = (int *) malloc(sizeof(int) * len);  //input第i位最多能匹配多少位 匹配不上是-1  
    int i,max_pos = - 1;  
    char *output = 0;  
  
    for (i = 0; i < len; ++i) {  
        match[i] = canMatch(input + i, rule);  
        if ((max_pos < 0) || (match[i] > match[max_pos])) {  
            max_pos = i;  
        }  
    }  
    if ((max_pos < 0) || (match[max_pos] <= 0)) {  //不匹配  
        output = (char *) malloc(sizeof(char));  
        *output = 0;   // \0  
        return output;  
    }  
    for (i = 0; i < len;) {  
        if (match[i] == match[max_pos]) { //找到匹配  
            output = str_join(output, input + i, match[i]);  
            i += match[i];  
        }  
        else {  
            ++i;  
        }  
    }  
    free(match);  
    return output;  
}  
  
  
char* my_find(char  input[],   char rule[])  
{  
    //write your code here  
    int len1,len2;  
    for(len1 = 0;input[len1];len1++);  
    for(len2 = 0;rule[len2];len2++);  
    int MAXN = len1>len2?(len1+1):(len2+1);  
    int  **dp;  
  
    //dp[i][j]表示字符串1和字符串2分别以i j结尾匹配的最大长度  
    //记录dp[i][j]是由之前那个节点推算过来  i*MAXN+j  
    dp = new int *[len1+1];  
    for (int i = 0;i<=len1;i++)  
    {  
        dp[i] = new int[len2+1];  
    }  
    dp[0][0] = 0;  
  
    for(int i = 1;i<=len2;i++)dp[0][i] = -1;  
    for(int i = 1;i<=len1;i++)dp[i][0] = 0;  
    for (int i = 1;i<=len1;i++)  
    {  
        for (int j = 1;j<=len2;j++)  
        {  
            if(rule[j-1]=='*'){  
                dp[i][j] = -1;  
                if (dp[i-1][j-1]!=-1)  
                {  
                    dp[i][j] = dp[i-1][j-1]+1;  
  
                }  
                if (dp[i-1][j]!=-1 && dp[i][j]<dp[i-1][j]+1)  
                {  
                    dp[i][j] = dp[i-1][j]+1;  
  
                }  
            }else if (rule[j-1]=='?')  
            {  
                if(dp[i-1][j-1]!=-1){  
                    dp[i][j] = dp[i-1][j-1]+1;  
  
                }else dp[i][j] = -1;  
            }   
            else  
            {  
                if(dp[i-1][j-1]!=-1 && input[i-1]==rule[j-1]){  
                    dp[i][j] = dp[i-1][j-1]+1;  
                }else dp[i][j] = -1;  
            }  
        }  
    }  
  
    int m = -1;//记录最大字符串长度  
    int *ans = new int[len1];  
    int count_ans = 0;//记录答案个数  
    char *returnans = new char[len1+1];  
    int count = 0;  
  
    for(int i = 1;i<=len1;i++)  
        if (dp[i][len2]>m){  
            m = dp[i][len2];  
            count_ans = 0;  
            ans[count_ans++] = i-m;  
        }else if(dp[i][len2]!=-1 &&dp[i][len2]==m){  
            ans[count_ans++] = i-m;  
        }  
        if (count_ans!=0)  
        {      
            int len = ans[0];  
            for (int i = 0;i<m;i++)  
            {  
                printf("%c",input[i+ans[0]]);  
                returnans[count++] = input[i+ans[0]];  
            }  
            for (int j = 1;j<count_ans;j++)  
            {  
                printf(" ");  
                returnans[count++] = ' ';  
                len = ans[j];  
                for (int i = 0;i<m;i++)  
                {  
                    printf("%c",input[i+ans[j]]);  
                    returnans[count++] = input[i+ans[j]];  
                }  
            }  
            printf("\n");  
            returnans[count++] = '\0';  
        }  
  
        return returnans;  
}  

     欢迎于本文评论下或hero上show your code。

参考文献及推荐阅读

http://zhedahht.blog.163.com/blog/static/25411174200731139971/

http://hero.pongo.cn/,本文大部分代码都取自左边hero上参与答题者提交的代码,欢迎你也去挑战;

字符串转换成整数题目完整描述:http://hero.pongo.cn/Question/Details?ID=47&ExamID=45
字符串匹配问题题目完整描述:http://hero.pongo.cn/Question/Details?ID=28&ExamID=28
linux3.8.4版本下的相关字符串整数转换函数概览:https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/lib/vsprintf.c?id=refs/tags/v3.9.4
关于linux中的likely和unlikely:http://blog.21ic.com/user1/5593/archives/2010/68193.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法
相关文章推荐