暴力递归(二)(python实现)
1、数字转化字符串
问:规定1和A对应、2和B对应、3和C对应。那么一个数字字符串比如“111",就可以转化为"AAA"、"KA"和"AK"。给定一个只有数字字符组成的字符串str,返回有多少种转化结果。
解:假设i之前的位置,如何转化已经做过决定了,接下来:首先,i自己作为单独的部分,后续有多少种方法;再考虑(i和i+1)作为单独的部分,后续有多少种方法。如果可以抵达末尾,方法数+1,对于3-9,直接+1跳下一个,对于0,直接返回0可能,对于1,看i+1和i+2,对于2,看是不是20-26,是看i+1和i+2,不是看i+1。代码如下:
class Solution: def numConvertStr(self, str): if str == None or int(str) == 0: return res = self.process(str, 0) return res def process(self, str, i): if i == len(str): return 1 if str[i] == '0': return 0 if str[i] == '1': res = self.process(str, i + 1) # i作为单独的部分,后续有多少种方法 if i + 1 < len(str): res+=self.process(str,i+2) #(i和i+1)作为单独的部分,后续有多少种方法 return res elif str[i] == '2': res = self.process(str, i + 1) # i作为单独的部分,后续有多少种方法 # (i和i+1)作为单独的部分并且没有超过26,后续有多少种方法 if i + 1 < len(str) and str[i+1]>='0' and str[i+1]<='6': res += self.process(str, i + 2) return res else: return self.process(str,i+1)
2、求最大价值
给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物品不能超过这个重量。返回能装下最多的价值。
class Solution: def maxValue(self, weight, value, bag): res = self.process(weight, value, 0, 0, bag) return res def process(self, weight, value, i, alreadyWeight, bag): # i...的货物自由选择,形成的最大价值返回 # 重要永远不要超过bag # 之前做的决定,所达到的重量,alreadyweight if alreadyWeight > bag: return 0 if i > len(weight): return 0 return max( self.process(weight,value,i+1,alreadyWeight,bag), value[i] + self.process(weight,value,i+1,alreadyWeight+weight[i],bag) )
3、抽牌博弈问题
给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
【举例】
arr=[1,2,100,4]。
开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2,100,4],接下来玩家B可以拿走2或4,然后继续轮到玩家A...
如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继续轮到玩家A..
玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1,让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家A拿走。玩家A会获胜,分数为101。所以返回101。
arr=[1,100,2]。
开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜,分数为100。所以返回100。
思想:
用暴力递归的方法:
f(i,j): 表示如果arr[i…j]这个排列上的纸牌被绝顶聪明的人先拿,最终能够获得什么分数。
s(i,j): 表示如果arr[i…j] 这个排列上的纸牌被绝顶聪明的人后拿,最终能获得什么分数。
在先拿的f(i,j)中:
1.如果i == j(只有一张纸牌),会被先拿纸牌的人拿走,所以返回arr[i]或arr[j];
2.如果i != j,先拿纸牌的人有两种选择,要么拿走arr[i],要么拿走arr[j];
1. 如果拿走arr[i],剩下arr[i+1,j]。对于arr[i+1,j]的纸牌,当前玩家成了后拿的人,因此他后续能获得的分数为s(i+1,j)
2. 如果拿走arr[j],那么剩下arr[i,j-1],当前玩家后续能获得的分数为s(i,j-1).
3. 作为绝顶聪明的人,必然会在两种决策中选择最优的。所以返回max{arr[i]+s[i+1,j],arr[j]+s[i][j-1]}
在后拿的s(i,j)中:
1.如果i == j,后拿纸牌的人什么也拿不到,返回0
2.如果i!=j,玩家的对手会先拿纸牌。
1. 对手要么先拿走a[i],那么排列剩下arr[i+1,j],
2. 要么先拿走arr[j],剩下arr[i,j-1]
3. 对手也是绝顶聪明的人,所以也会把最差的情况留给玩家因此返回min{f(i+1,j),f(i,j-1)}
class Solution: def win(self, arr): if not arr: return None res = max(self.first(arr, 0, len(arr) - 1), self.second(arr, 0, len(arr) - 1)) def first(self, arr, left, right): if left == right: return arr[left] return max( arr[left] + self.second(arr, left + 1, right), arr[right] + self.second(arr, left, right - 1) ) def second(self, arr, left, right): if left == right: return 0 return min( self.first(arr, left + 1, right), self.first(arr, left, right - 1) )
4、纸牌博弈先手最优问题
问题:
一个纸牌明牌显示,牌上的数值就是分数:(就是一个数组,每个数均为非负整数),设置一个数为M,每个人都是绝顶聪明,都是按最优策略顺序拿牌,
一次可拿的牌数是大于等于1,小于等于M,问先手拿牌的人最多可以得几分。
答:这题使用贪心法绝对是不行的。
比如 1,1,1,100,设置M=2
那么第一个人绝对不能第一次拿两张牌,否则第二个人就可以拿到100.
这涉及到两个人博弈问题,但是博弈策略是一样的,所以可以使用同一个函数表示博弈策略
class Solution: def maxValue(self, arr, m): if arr == None: return 0 return self.first(arr, 0, m) def first(self, arr, index, m): if index >= len(arr): return 0 lens = len(arr) - index # 计算出数组剩余长度是否大于m value = 0 if lens <= m: # 如果数组长度小于m,那就全部拿过来 value = sum(arr[index:]) return value else: allValue = sum(arr) for i in range(1, m): if allValue - self.first(arr, index + i, m) > value: # 用总值减去第二个人的最大值,对m种情况都测试比较 value = allValue - self.first(arr, index + i, m) return value
5、n皇后问题(32以内)
N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列,也不在同一条斜线上。
给定一个整数n,返回n皇后的摆法有多少种。
n=1,返回1。
n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。
n=8,返回92。
class Solution: def nQueue(self, n): if n < 1: return 0 record = [0 for _ in range(n)] res = self.process(0, record, n) return res def process(self, i, record, n): if i == n: return 1 res = 0 for j in range(0, n): # 当前i行的皇后,放在列,会不会和之前(e..i - 1)的皇后,共行共列或者共斜线, # 如果是,认为无效 # 如果不是,认为有效 if self.isValid(record, i, j): record[i] = j res += self.process(i + 1, record, n) return res def isValid(self,record, i,j): for k in range(0,i): if j == record[k] or abs(record[k]-j) == abs(i-k): return False return True
使用位运算加速(理解)
class Solution: def nQueue(self, n): if n < 1 or n > 32: return 0 upperLim = 0 if n == 32: upperLim = -1 else: upperLim = (1 << n) - 1 res = self.process2(upperLim, 0, 0, 0) return res # colLim 列的限制,1的位置不能放皇后,0的位置可以 # leftDiaLim左斜线的限制,1的位置不能放皇后,0的位置可以 # rightDiaLim右斜线的限制,1的位置不能放皇后,0的位置可以 def process2(self, limit, colLim, leftDiaLim, rightDiaLim): if colLim == limit: return 1 pos = limit & (~(colLim | leftDiaLim | rightDiaLim)) #pos代表能在哪些位置上放皇后 res = 0 while pos != 0: mostRightOne = pos & (~pos + 1) # 提取pos最右侧的1 pos = pos - mostRightOne res += self.process2(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, (rightDiaLim | mostRightOne) >> 1) return res