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

问题:

  给定任意一个正整数,求比这个数大且最小的“不重复数”,“不重复数”的含义是相邻两位不相同,例如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\n" , 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" , 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 网友大概又会问:“有没有不用递归的算法呢?”我提前回复,有。不过目前写得还很难看,实在拿不出手。等我改好了再拿出来献丑。

非递归写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#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\n" , 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)网友代码的评论及对“求比指定数大且最小的‘不重复数’问题”代码的改进

posted @   garbageMan  阅读(3273)  评论(105编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2012-10-04 《品悟C——抛弃 C程序设计 中的谬误与恶习》勘误
点击右上角即可分享
微信分享提示