• 算法:求比指定数大且最小的“不重复数”问题的高效实现


    问题:

      给定任意一个正整数,求比这个数大且最小的“不重复数”,“不重复数”的含义是相邻两位不相同,例如1101是重复数,而1201是不重复数。

    ——引自 百度2014校招笔试题目题解

    问题的提法:

      为代码简便,将问题等价地改为,求大于等于指定正整数的不重复数。由find()函数实现。

    调用:

      find( i + 1u )

    原型:

      unsigned find( unsigned );

    算法:

      以19922884u为例。

      首先确定高位是否是重复数。即依次判断

    1u

    19u 

    199u

    1992u

    19922u

    199228u 

    1992288u

     是否是重复数。

      如高位不是重复数,则当前数不变,并判断当前数是否是重复数。

      例如对19u,由于1u不是重复数(一位正整数不是重复数,是显而易见的事。(if ( n < 10u ) return n;),所以判断19u是否是重复数(通过简单地判断19u的个位和十位是否相同。n % 10u == n /10u %10u)。

      当当前数为199u时,高位(19u)不是重复数,当前数本身(119u)是重复数。

      此时,将当前数加1,问题变为求大于等于200u的不重复数。

      由于200u的高位不是重复数,而200u本身是重复数,所以经过了

    n:2

    n:20

    之后,问题变成了求大于等于201u的不重复数。

      201u不是重复数,所以回到求大于等于1992u的不重复数时,由于对于1992u来说,由于高位是重复数(返回值大于1992u/10u。 if ( n/10u <(t = find( n/10u)) )n = t * 10;),所以问题变成了求2010u的不重复数(n = t * 10;)。

      2010u是不重复数,求大于等于19922u的不重复数变成了求大于等于20100u的不重复数。

      但由于20100u的高位不是重复数,20100u本身是重复数(个位和十位相同),所以问题又变成了求大于等于20101u的不重复数的问题( if ( n % 10u == n /10u %10u ) return find( n + 1u );)。

      重复以上过程,可得结果为20101010。

    代码:

     1 #include <stdio.h>
     2 
     3 unsigned find( unsigned );
     4 
     5 int main( void )
     6 {
     7   unsigned i ;
     8   
     9   //测试 
    10   for ( i = 19922884u ; i < 19922884u + 1u ; i++ )
    11   {
    12      printf ( "%u %u
    " , i , find( i + 1u ) );
    13   } 
    14   
    15   return 0;
    16 }
    17 
    18 unsigned find( unsigned n )
    19 {
    20    unsigned t;
    21    
    22    printf( "n:%u
    " , n ) ;    //演示一下调用路径 
    23    
    24    if ( n < 10u )
    25       return n;
    26    
    27    if ( n / 10u < ( t = find( n / 10u ) ) )
    28       n = t * 10u ;
    29    
    30    if ( n % 10u == n /10u % 10u )
    31       return find( n + 1u );
    32 
    33    return n ;
    34 }

    提前回复:

      飞鸟_Asuka 网友大概又会问:“有没有不用递归的算法呢?”我提前回复,有。不过目前写得还很难看,实在拿不出手。等我改好了再拿出来献丑。

    非递归写法

    #include <stdio.h>
    
    typedef  struct
             {
                unsigned char t[ 20 ] ; //这个空间应该够了
                int top ;                 //记录第一位的下标 
             } 
             Map ;
    
    unsigned find( unsigned );
    void parse( Map * , unsigned ) ;
    int  search ( const Map * );
    void add_1( Map * , const int );
    void clear( Map * , const int , const int );
    unsigned combi( const Map * );
    
    int main( void )
    {
      unsigned iu ;
      
      //测试 
      for ( iu = 19922884u ; iu < 19922884u + 1u ; iu++ )
      {
         printf ( "%u %u
    " , iu , find( iu + 1u ) );
      } 
    
      return 0;
    }
    
    unsigned combi( const Map * p_m )
    {
       unsigned nu = 0u ;
       int i;
       for ( i = p_m->top ; i >= 0 ; i -- )
       {
          nu *= 10u ;
          nu += p_m->t[i] ;
       }
       return nu;
    }
    
    void clear( Map * p_m , const int from , const int to )
    {
       int i ;
       
       for ( i = from - 1 ; i > to - 1; i -- )
          p_m->t[i] = 0u ;
    }
    
    void add_1( Map * p_m , const int from )
    {
       int i ;
       
       p_m->t[from] ++;                              //最低位加1 
       
       for ( i = from ; i < p_m->top ; i ++ )        //进位处理 
       {
          p_m->t[i + 1] += p_m->t[i] / 10u ;
          p_m->t[i] %= 10u ;
       }
       
       if ( p_m->t[p_m->top] > 9u )                  //最高位有进位 
       {
          p_m->t[p_m->top + 1] = p_m->t[p_m->top] / 10u ;
          p_m->t[p_m->top ++ ] %= 10u ;
       }
    }
    
    int  search ( const Map * p_m )
    {
       int i ;
       
       for ( i = p_m->top ; i > 0  ; i-- )
       {
          if ( p_m->t[i] == p_m->t[i-1] )
             break ;
       }
       
       return i - 1 ; 
    }
    
    void parse( Map * p_m , unsigned n )
    {
       p_m->top = -1 ;
       while ( n > 0u )
       {
          p_m->t[ ++ p_m->top ] = n % 10u ;
          n /= 10u ;
       }
    }
    
    unsigned find( unsigned n )
    {
       Map map ;
       int end = 0 , b_point ;
       
       parse( &map , n ) ;                         //将n分解为数字
        
       while ( ( b_point = search ( &map ) ) > -1 )//为-1时说明不是重复数 
       {
          add_1( &map , b_point );                //重复数部分加1 
          clear( &map , b_point , end );          //后面改为0 
          end = b_point ;                         //确定下次循环的处理范围 
       }
       return combi( &map );
    }
    

    算法描述:

      以19922884为例,

    数据结构:用一数组及所使用到的最大下标表示。

    map:

    4 8 8 2 2 9 9 1

    7

    find():

    从19922884的高位开始查找不重复数,记录位置

    b_point = search ( &map )

    b_point :5

    b_point以后部分加1:add_1( &map , b_point );

    map:

    4 8 8 2 2 0 0 2

    7

    从 end到b_point-1之间的元素清零:clear( &map , b_point , end );

    map:

    0 0 0 0 0 0 0 2

    7

    记录b_point作为下次循环的end。

    end:5

    第二次循环,

    b_point = search ( &map )

    b_point :5

    b_point以后部分加1:add_1( &map , b_point );

    map:

    0 0 0 0 0 1 0 2

    7

    从 end到b_point-1之间的元素清零:由于此时end为5,所以没有任何元素清零

    记录b_point作为下次循环的end。

    end:5

    第三次循环,

    b_point = search ( &map )

    b_point :3

    b_point以后部分加1:add_1( &map , b_point );

    map:

    0 0 0 1 0 1 0 2

    7

    从 end到b_point-1之间的元素清零:由于此时end为5,b_point 为3,所以没有任何元素清零

    记录b_point作为下次循环的end。

    end:3     

    第四次循环,

    b_point = search ( &map )

    b_point :1

    b_point以后部分加1:add_1( &map , b_point );

    map:

    1 0 1 0 1 0 2

    7

    从 end到b_point-1之间的元素清零:由于此时end为3,b_point 为1,所以没有任何元素清零

    记录b_point作为下次循环的end。

    end:1

    第五次循环,

    b_point = search ( &map )

    b_point :-1

    循环结束。

    重新合成整数:return combi( &map );

    问题得解。

    总结

      非递归写法很难写,就这个问题而言,效率方面也不比递归方法好。

    后续:对Alexia(minmin)网友代码的评论及对“求比指定数大且最小的‘不重复数’问题”代码的改进

  • 相关阅读:
    知识点整理
    NGINX 内存池有感
    NGINX怎样处理惊群的
    NGINX 定时器
    制作linux内核安装包
    ES6变量的解构赋值
    jquery uploadify上传插件用法心得
    【转贴】J2EE中的13种技术规范
    【转帖】Servlet 3.0 新特性详解
    汉诺塔问题的一个C#实现
  • 原文地址:https://www.cnblogs.com/pmer/p/3351466.html
Copyright © 2020-2023  润新知