为了兴趣,也为了保持coding的能力,重新开始写一些算法题;为了对算法了解的更透彻,也为了锻炼表达能力,干脆在博客里面对解题代码进行详解,代码是自己写的,但也不排除参考了别人的解法,消化吸收了就是自己的,不是吗?
题面:
太长了,就不贴了,链接在此
代码及详解:
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 |
class ZooExchangeProgram: def getNumber(self, label, lower, upper): l = list(label) # 这个r就是我们最后要通过各种现有label组的合并达到的目标集合 r = range(lower, upper+1) # 简单的将无解的情况排除 for i in r: if i not in l: return -1 # 按照给定的目标集合,将所有在包含于目标集合的label组都整理出来 groups = [] start = 0 for i,x in enumerate(l): if x not in r: if start < i: groups.append(set(l[start:i])) start = i + 1 elif i == len(l) - 1: groups.append(set(l[start:i+1])) # 去除重复的组,因为我们最终求解的是达成目标集合所需要最小的组数量,答案不用给出包含具体某个组的信息,所以去重不影响结果,只会加速求解;这一段代码体现了python的简洁,一个in关键字就可以代替C语言的compare函数,虽然背后的效率肯定比C低,但是这对让程序员关注算法本身提供了极大便利。当然还有其它更加高效的list of lists去重方法,比如使用库itertools,但是本题的限制了label的数量为50个之内,所以最多有50个组,双重循环一次也还能接受。 ng = [] for i in groups: if i not in ng: ng.append(i) # tricky part: 这里开始才是本题的精髓,使用的是动态规划;状态定义为某个组所覆盖的元素集合,或者多个组的并集所覆盖的元素集合,最终状态就是目标集合,而每个组,或者每种多组的并集所构成的所有状态都是中间状态;而且,达成同一种状态可能有多种组合形式;暴力求解方法就是搜索计算所有这些组构成的全部组合形式,将其中构成最终状态的组合记录下来,然后从中找到由最少组构成的组合;这种方法所需计算量非常大,因为其搜索过程中放弃了很多中间结果,为了搜索到一组可能解,过程中走过的弯路,在下一组求解中可能仍然要重新走一遍,这是极大的浪费;动态规划思想记录下搜索过程中的任何一次尝试结果。本题中,我们要纪录的就是每次搜索到某个中间状态时所需要的组合数,下次再组合出这种状态时,比较以前记录下来的值,如果本次组合数更小则更新记录;这样,只需遍历一次所有组合,就能得到所有可能的中间状态的最小组合数,届时只需要从中选择那个最终状态对应的组合数即可。 count = [0] * 50 state = {0:0} mode = [] mask = 0 ret = 0 # 这里是为了优化程序的执行时间,需要将那些必须选入组合的组排除出我们搜索的范围,反正无论如何它们都得入选,我们干嘛还要去考虑它们呢,博主一开始就是没有排除它们,在提交后被判失败,自己测试发现其中一个case居然耗费了12s之多。哪些是必选的呢,就是那些包含了别人都没有的元素的组,到这里我们所说的组都是包含于目标集合的,所以有独一无二的元素的组必须入选。 # 先计算所有目标集合中的元素哪些是只存在于一个组中的 for i in ng: for j in i: count[j] += 1 # 对独一无二的元素,我们单独处理,把它们排除在搜索范围(搜索范围的组构成mode)内,然后将它们所在的组的并集mask记录下来;其它的组计算出它们的状态值作为key保存在字典state里面,对应的value记为1,即这种状态显然只需要对应的单个组自己就能构成。 # 之所以用字典,而不用list,因为list的索引必须是连续的,即使中间很多item都没有用到,也要为他们分配内存,这样的话会浪费大量的内存,比如我们这题当中的label最大可以到50,也就是说从1到50,这50个label会有1<<50种可能的状态组合,那我们需要一个1<<50,即1*2^50,即使用最小的byte型数组(python好像不能指定类型),那也是1024TB的内存,博主开始也没有意识到这个问题,直接用了list,在测试过程中python报memory error才恍然发现了这么个大bug。而python提供了字典这么好的一个数据结构,使用字典可以做到用多少给多少,大大大的减少了内存浪费。 for i in ng: temp = 0 only = False for j in i: if count[j] == 1: only = True temp += 1<<(j-1) if not only: mode.append(temp) state[temp] = 1 else: mask |= temp ret += 1 if mask == (1<<upper)-(1<<(lower-1)): return ret best = 50 # 不会比50个组的组合更差的情况了 for m in mode: for i in state.keys(): # 新的状态可由老状态和m组成,但未必比历史记录更优 state[i|m] = min(state.get(i|m,50), state.get(i,50)+1) if mask|i|m == (1<<upper)-(1<<(lower-1)): best = min(best, ret+state[i|m]) return best |