我们称一个数 X 为好数, 如果它的每位数字逐个地被旋转 180 度后,我们仍可以得到一个有效的,且和 X 不同的数。要求每位数字都要被旋转。
如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。0, 1, 和 8 被旋转后仍然是它们自己;2 和 5 可以互相旋转成对方;6 和 9 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。
现在我们有一个正整数 N
, 计算从 1
到 N
中有多少个数 X 是好数?
示例:
输入: 10
输出: 4
解释:
在[1, 10]中有四个好数: 2, 5, 6, 9。
注意 1 和 10 不是好数, 因为他们在旋转之后不变。
注意:
- N 的取值范围是
[1, 10000]
。
解题思路
我们首先可以想到一个比较笨的方法。我们可以对[1,N]
的所有数都进行判断是不是好数。那现在问题就变成了怎么判断一个数是不是好数?首先这个数里面必须没有[3,4,7]
,在这个前提下我们的数字中必须包含[2,5,6,9]
中的数。
class Solution:
def rotatedDigits(self, N: int) -> int:
s1 = set([3, 4, 7])
s2 = set([2, 5, 6, 9])
def isGood(n):
res = False
while n:
t = n % 10
if t in s1:
return False
if t in s2:
res = True
n //= 10
return res
ret = 0
for i in range(1, N+1):
if isGood(i):
ret += 1
return ret
其实你可以很清晰的认识到这种做法不足,我们有什么更好的做法呢?有,我们可以先思考一个三位数,也就是[0,999]
有多少个好数呢?我们知道好数必须从[0,1,2,5,6,8,9]
中选,所以就是7^3
,但是这其中有一些不是,那就是只包含[0,1,8]
的数,这些数有多少呢?3^3
。所以我们最后的结果就是7^3-3^3=316
。但是这种策略会碰到这样的问题?如果我们N=1234
怎么办?我们显然不能先计算7^4
了,因为这种情况下数的范围是[1,9999]
。我们可以先考虑[1,999]
,再考虑[1000,1199]
,接着考虑[1200,1229]
和[1230,1234]
。好,现在问题变成了思考[1000,1199]
怎么处理?显然我们的第二个数只有两种策略[0,1]
,而后面的两个数会有7
种策略,也就是我们会有2*7^2
,但是这其中会有2*3^2
种是无效的,所以结果就是2*7^2-2*3^2=80
,同理我们可以思考后面的两种范围,最后的结果就是316+80+21=417
。这里需要注意关于21
的计算,我们通过3*7
得到,我们并没有减一个数因为我们之前碰到了2
,而2
非常特殊。
现在我们需要将上面这个策略整理为算法。我们首先需要将N
切分成list
。接着我们就要遍历N-list
,如果遍历到的元素v==0
,我们继续遍历下一个元素,如果不是0
,我们就可以通过res += validRotations[t]*validRotations[9]**(n - i - 1)
(其中t=v-1
,validRotations
表示前t
个元素中包含[0,1,8,2,5,6,9]
的个数)。同时我们需要判断减去什么,此时就要分两种情况,如果之前碰到过[2,5,6,9]
中的数话,我们就不用减任何数了,否则的话我们需要res -= sameRotations[t]*sameRotations[9]**(n - i - 1)
。
接着思考边界问题,当我们遍历到最后一个数的时候,我们只需res += validRotations[v]
,同时我们需要依照是否访问过[2,5,6,9]
中元素的策略,考虑是不是需要res -= sameRotations[v]
。如果我们遍历到了[3,4,7]
中的元素,我们直接返回结果就可以了。
class Solution:
def rotatedDigits(self, N: int) -> int:
s1 = set([3, 4, 7])
s2 = set([2, 5, 6, 9])
validRotations = [1,2,3,3,3,4,5,5,6,7] # Cumulative: Valid number
sameRotations = [1,2,2,2,2,2,2,2,3,3] # Cumulative: Same number
res = 0
N = list(map(int, str(N)))
n = len(N)
flag = True
for i, v in enumerate(N):
if i == n-1:
res += validRotations[v]
if flag:
res -= sameRotations[v]
else:
if v == 0:
continue
t = v - 1
res += validRotations[t]*validRotations[9]**(n - i - 1)
if flag:
res -= sameRotations[t]*sameRotations[9]**(n - i - 1)
if v in s2:
flag = False
if v in s1:
return res
return res
一个更简洁的写法
class Solution:
def rotatedDigits(self, N: int) -> int:
s1 = set([0, 1, 8])
s2 = set([0, 1, 8, 2, 5, 6, 9])
s = set()
res = 0
N = map(int, str(N))
for i, v in enumerate(N):
for j in range(v):
if s.issubset(s2) and j in s2:
res += 7**(len(N) - i - 1)
if s.issubset(s1) and j in s1:
res -= 3**(len(N) - i - 1)
if v not in s2:
return res
s.add(v)
return res + (s.issubset(s2) and not s.issubset(s1))
reference:
http://www.frankmadrid.com/ALudicFallacy/2018/02/28/rotated-digits-leet-code-788/
https://leetcode.com/problems/rotated-digits/discuss/116530/O(logN)-Solution
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!