注:以下题目来自《程序员的算法趣题》– [日]增井敏克著,原书解法主要用Ruby实现,最近在学Python,随便找点东西写写当做练习,准备改成Python3实现,顺便增加一些自己的理解。
6.考拉兹猜想
考拉兹猜想:
对自然数n:
若n为偶数,n /= 2
若n为奇数,n = n*3 + 1
循环操作,无论初始值为多少,最终得到1(1->4->2->1循环)
修改:
即使初始值为偶数,也 n = n*3 + 1,后面操作不变,问能回到初始值的偶数的个数。
求小于10000的偶数中,能回到初始值的数的个数。
思路:第一步变了,但后面没变,最终还是会得到1,所以如果到了1还没回到初始值,那么后面将会是(1, 4, 2, 1)循环,就再也回不到初始值了,当然需要特殊看看4,2
2,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2
4,13,40,20,10,5,16,8,4
发现2,4先到初始值,后才到1,所以无需特殊处理
def solve(n):
x = n*3 + 1
while True:
x = x // 2 if x % 2 == 0 else x*3 + 1
if x == 1:
return False
if x == n:
return True
return False
cnt = 0
for i in range(0, 10001, 2):
if solve(i):
cnt += 1
print(cnt)
34
7.日期的二进制转换
把日期转换为YYYYMMDD这样的8位十进制数,然后用二进制表示,逆序排列二进制,再转成10进制,求与原来日期一致的日期。
求1964-10-10到2020-07-24之间满足上述条件的日期。
思路:等价于转成二进制之后,二进制表示是回文串的日期
from datetime import datetime, date, timedelta
def solve1(date_start, date_end):
ans = []
step = timedelta(days=1)
d = datetime.strptime(date_start, '%Y-%m-%d').date()
while d < datetime.strptime(date_end, '%Y-%m-%d').date():
n = int(d.strftime('%Y%m%d'))
s2 = format(n, 'b')
if s2 == s2[::-1]:
ans.append(n)
d += step
return ans
ans = solve1('1964-10-10', '2020-07-24')
print(ans)
使用python3 的datetime
[19660713, 19660905, 19770217, 19950617, 20020505, 20130201]
思路2:实现类似range的方法:
def date_range(start, stop, step):
while start < stop:
yield start
start += step
def solve2(date_start, date_end):
ans = []
start = datetime.strptime(date_start, '%Y-%m-%d').date()
end = datetime.strptime(date_end, '%Y-%m-%d').date()
step = timedelta(days=1)
for d in date_range(start, end, step):
n = int(d.strftime('%Y%m%d'))
s2 = format(n, 'b')
if s2 == s2[::-1]:
ans.append(n)
d += step
return ans
ans = solve2('1964-10-10', '2020-07-24')
print(ans)
[19660713, 19660905, 19770217, 19950617, 20020505, 20130201]
思路3:先找满足条件的数,再转成日期
def solve3(date_start, date_end):
ans = []
start = int(datetime.strptime(date_start, '%Y-%m-%d').date().strftime('%Y%m%d'))
end = int(datetime.strptime(date_end, '%Y-%m-%d').date().strftime('%Y%m%d'))
for i in range(start, end):
s2 = format(i, 'b')
if s2 == s2[::-1]:
try:
d = datetime.strptime(str(i), '%Y%m%d').date()
ans.append(i)
except:
...
return ans
ans = solve3('1964-10-10', '2020-07-24')
print(ans)
[19660713, 19660905, 19770217, 19950617, 20020505, 20130201]
需要异常处理,因为有的数转不成合法日期,如2018-19-88
8.扫地机器人
假设有一款不会清扫同一位置的机器人,它只会前后左右移动,问移动12次时,路径数。
思路:不会清扫同一位置,意味着走过的位置不会再走。
def solve1(log, n):
if len(log) == n+1:
return 1
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
count = 0
for d in directions:
next = log[-1][0] + d[0], log[-1][1] + d[1]
if next not in log:
count += solve1(log + [next], n)
return count
start_time = time.clock()
ans = solve1([(0, 0)], n)
end_time = time.clock()
print('solve1: ', ans, 'time cost: ', end_time - start_time)
这是书中给出的方法,用一个list存已走过的位置,每次判断在list中的位置不再走。
优化:用二维数组表示走过的位置,加快判断是否走过的速度
def solve2(graph, pos, left):
if left == 0:
return 1
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
count = 0
for d in directions:
next = pos[0] + d[0], pos[1] + d[1]
if graph[next[0]][next[1]] == 0:
graph[next[0]][next[1]] = 1
count += solve2(graph, next, left-1)
graph[next[0]][next[1]] = 0
return count
start_time = time.clock()
graph = [[0] * n*2 for j in range(0, n*2)]
graph[0][0] = 1
ans = solve2(graph, (0, 0), n)
end_time = time.clock()
print('solve2: ', ans, 'time cost: ', end_time - start_time)
时间比较:
➜ n=13
solve1: 881500 time cost: 1.24
solve2: 881500 time cost: 0.76
➜ n=14
solve1: 2374444 time cost: 3.45
solve2: 2374444 time cost: 2.0300000000000002
➜ n=15
solve1: 6416596 time cost: 9.57
solve2: 6416596 time cost: 5.5
solve2快一些。
9.落单的男女
若干男女站成一排,求无论从哪个位置划分,都无法使两部分中男女人数均等的排列数。
求20男,10女满足条件的排列数。
如3男3女:
MMFMFF
如此排列无论从哪分开,两组的男女人数都不相等
思路:如果从左往右,出现了MF | xxxxx,MFFM | xxxxx, MMMFFF | xxxx这样的情况,即从左边开始出现了男女人数相等的情况,这个分组肯定不满足条件,同理右边存在这样的情况也不满足条件。
def solve():
girls, boys = 10, 20
dp = [ [0 for _ in range(boys+1)] for _ in range(girls+1) ]
dp[0][0] = 1
for g in range(girls+1):
for b in range(boys+1):
if g != b and boys-b != girls-g:
dp[g][b] += dp[g-1][b] if g >= 1 else 0
dp[g][b] += dp[g][b-1] if b >= 1 else 0
return dp[girls-1][boys] + dp[girls][boys-1]
ans = solve()
print(ans)
2417416
def solve1(girls, boys):
dp = [ [0 for _ in range(boys+1)] for _ in range(girls+1) ]
dp[0][0] = 1
for g in range(girls+1):
for b in range(boys+1):
if g != b and boys-b != girls-g:
dp[g][b] += dp[g-1][b] if g >= 1 else 0
dp[g][b] += dp[g][b-1] if b >= 1 else 0
return dp[girls-1][boys] + dp[girls][boys-1]
ans = solve1(10, 20)
print(ans)
ans = solve1(20, 10)
print(ans)
ans = solve1(3, 3)
print(ans)
2417416
2417416
4
10.轮盘的最大值
欧式规则轮盘数字:
0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26
美式规则:
0, 28, 9, 26, 30, 11, 7, 20, 32, 17, 5, 22, 34, 15, 3, 24, 36, 13, 1, 00, 27, 10, 25, 29, 12, 8, 19, 31, 18, 6, 21, 33, 16, 4, 23, 35, 14, 2
问,当2<=n<=36时,求连续n个数的和最大的情况,满足“欧式规则下和小于美式规则的和”的n的个数
def max_sum(nums, n):
s = ans = sum(nums[:n])
length = len(nums)
for i in range(length):
s += nums[(i+n) % length] - nums[i]
ans = max(ans, s)
return ans
def solve():
european = [
0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36,
11, 30, 8, 23, 10, 5, 24, 16, 33, 1, 20, 14, 31, 9,
22, 18, 29, 7, 28, 12, 35, 3, 26 ]
american = [
0, 28, 9, 26, 30, 11, 7, 20, 32, 17, 5, 22, 34, 15,
3, 24, 36, 13, 1, 0, 27, 10, 25, 29, 12, 8, 19, 31,
18, 6, 21, 33, 16, 4, 23, 35, 14, 2 ]
ans = []
for i in range(2, 37):
if max_sum(european, i) < max_sum(american, i):
ans += [i]
return ans
ans = solve()
print(ans)
print(len(ans))
思路:模拟