一直很纠结算法的文章应该怎么写。最后觉得还是从最简单的level开始写吧,一开始就弄些重量级的,什么人工智能,机器学习的算法,还要有大量的数学以及优化的知识,小白们估计会很郁闷,当然我也不一定能做出来对吧。
我计划每题给出两种语言的解决方案,一种静态语言,一种动态语言。
我选择C语言,Python和Java作为实现语言,由于篇幅有限,其他语言的实现有兴趣的朋友请自己尝试。
LeetCode 788. 旋转数字(Rotated Digits)
问题描述:
我们称一个数 X 为好数, 如果它的每位数字逐个地被旋转 180 度后,我们仍可以得到一个有效的,且和 X 不同的数。要求每位数字都要被旋转。
如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。0, 1, 和 8 被旋转后仍然是它们自己;2 和 5 可以互相旋转成对方;6 和 9 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。
现在我们有一个正整数 N, 计算从 1 到 N 中有多少个数 X 是好数?
注:
· N 的取值范围是 [1, 10000]。
示例:
![af5e6ee8ea15e53152ee7339bb3b3974.png](https://i-blog.csdnimg.cn/blog_migrate/bb8527d39dfdae084baaa1b7b1f8c2f0.jpeg)
C语言实现:
这个问题可能有人没看懂。这其实说的就是火柴棒数字问题。在火柴棒数字中,2和5是互为镜像的,6和9是互为镜像的,0,1,8 旋转180度还是自身,3,4,7旋转后不是数字。
![3638b3a38a7fdce9505cf5c9f9e076e8.png](https://i-blog.csdnimg.cn/blog_migrate/460b5d698daad4d2d8f4652288784040.jpeg)
要解决这个问题,我们可以用暴力方法,遍历0~N的所有整数,检查它们数字中是否包含3,4,7,如果包含就忽略,然后再检查数字中是否保护2,5,6,9中某些数字,如果有就计数1.
这种方法,算法复杂度是O(N).
但是我觉得有更好的办法。
不知道大家有没有注意到这是一个排列组合问题。
![87afc307164a14fa2354abc8a9618831.png](https://i-blog.csdnimg.cn/blog_migrate/f0501fbc8b8f10061a22da5a91994d80.jpeg)
“好数”要满足条件:要有一个数是2,5,6,9,其他的数可以是0,1,8,2,5,6,9。
我们会发现对最大的Y位数,我们可以有公式:7*Count(Y-1)+4*3^(Y-1)。例如对于最大一位数9,有4个好数;对于两位数99,十位可以取0,1,2,5,6,8,9共6种,这时候个位可以取,2,5,6,9,另外还要加上一种情况:当十位取2,5,6,9的时候,后面个位可以取0,1,8,合在一起99的总的好数数量Count就等于40种。
我们还可以进一步扩展,对于任意一个数字后面跟多个9的情况。例如499。百位可以取0,1,2 三种数字,所有有3*Count(2) = 120 ,还要加上百位取[2,5,6,9]的情况,这里我们发现只能取到2,所以是一种,就有1*3*3 = 9,合计是129种好数。
我们再扩展,如上图,对于2948这个数,我们想计算前1999个数中的好数,计算玩以后千位数2就变成固定的了,然后计算899个数中的好数,这个时候百位的9也就被固定了。为什么是899?其实加上前面计算1999的步骤,实际上我们计算的就是2899个数中好数的笼统计算(为什么是笼统?因为我们还有一种情况没有考虑,后面接着说。)如此这样,直到计算完39之后(统计的是2939)我们发现遇到了[3,4,7]中的4,结束,因为有4的数都不是好数,所以没必要继续下去。这时候第一步结束了,下面开始第二步。
如前面所述,我们第一步只是考虑到一种情况,即,当前数在[0,1,2,5,6,8,9]中取一个数,后面的位数组成的数好数的情况。第二步我们要考虑的是,如果前面数有[2,5,6,9]的时候,注意,我们第一步的时候已经将它们固定了,也就是说,如果前面数是1和7的时候,那么只判断1和7,因为它们不是我们要的数,所以这一步跳过,如果是2,6或者2,1这样的的,那么需要这一步。
方法和第一步类似,只是这里计算的是各位取[0,1,8]后的组合数。
代码如下:
![1ba185022a1c1e5e1623ae6bb4f45fcc.png](https://i-blog.csdnimg.cn/blog_migrate/a3521d4754d1897af46c0fc1f69f2c0b.jpeg)
![19f1bf1c336d305bb39c52dc6cd7ee44.png](https://i-blog.csdnimg.cn/blog_migrate/3c15d954b6401cf3b0848e11f99024c6.jpeg)
首先定义了3个数组,用于快速查找定位。其中x表示的是取[0,1,8]的可能性。比如对于7,他可以取0,1两种。y是取[2,5,6,9]的可能性。z是为了定位数字是哪一种。
函数_func(),是用来计算f999这样的数出现的好数数量,其中f表示最大位的数,i表示有几位数,例如要计算499,那么_func(4, 3)。
代码14~20行,是将数N按位存储到数组中,方便后面操作。
代码23~27行,这个循环就是处理第一步的任务。
代码29~40行,处理第二部的任务。这里众多的判断其实多是考虑N中有0的情况。0出现在N的末尾和中间,处理起来是不同的。
可以看出,这个方案虽然复杂一些,但是效率很高,它之于N的位数有关。而N的位数是有限的,所以算法的时间复杂度是O(1)。
![9e946badfe43f0437bebe572d87cbe3e.png](https://i-blog.csdnimg.cn/blog_migrate/2f2d25f0f827841858fd70731025c203.jpeg)
python语言的实现:
python语言的依然可以用上面的原理来实现,但是代码可能会比较长。我认为如果考虑用python来解决的问题,算法的效率往往不是最重要的。简洁,易实现,容易维护可能更加重要,所以我这里用的是传统的方法。
代码如下:
![1afb00a9336009ca717e64a49d9ad5eb.png](https://i-blog.csdnimg.cn/blog_migrate/2db0c9f066a095a795ce5886fd2421b5.jpeg)
![e91287ef40e0d9fe50a1dcd0b256a59b.png](https://i-blog.csdnimg.cn/blog_migrate/6169375bfade1bb5f70d5c4cc19b724a.jpeg)
Java语言的实现:
Java的实现和C语言的实现相同。
代码如下:
![596e85c68ccb914005bc3b14c642e3f1.png](https://i-blog.csdnimg.cn/blog_migrate/da85a6ff2a842187a1dd34509a39f7db.jpeg)
![37d37d080c86d8793535bc81d0ba00b3.png](https://i-blog.csdnimg.cn/blog_migrate/18786ee29b591f8ca5d9dc01340dfcfb.jpeg)
![408954553d977c12ede3366a1f0fb36d.png](https://i-blog.csdnimg.cn/blog_migrate/456740d8d010988a9b39bcd26036af49.jpeg)
更新的有些晚,因为我想给大家一个更好的解决方案,而这个实现描述起来还有些复杂,没办法表达能力有限,如果有不懂得地方,尽管问吧。