我正在研究我的一个CS课程的问题,我非常有信心我有正确的想法,就在我实施它时,我没有得到正确答案。
游戏是:有两个玩家和一个数字列表(即[3,7,8,1,6,4,5])。每个玩家轮流从列表的任一端挑选一个数字。一旦选择了一个号码,它就会从列表中删除,然后对手可以从这个新列表中选择他们想要的结尾。目标是在列表为空时获得最大的数字总和。
我的想法:我们假设我们从简单列表[1,2,3,4,5]开始。当玩家1从开头或结尾(1或5)中选择一个号码时,我们现在有一个较小的列表可供对手选择。那么让我举一个使用这个列表的例子:
我选择5.新的列表是[1,2,3,4],对手可以选择。我不知道他们会选择列表的哪一端,但我知道它只能是1或者4.如果是1,那么当轮到我时,我就离开了[2,3,4]。如果他们选4,我就离开了[1,2,3]。如果我选择1,它们会被[2,3]保留,如果我选择3,它们会被[1,2]等留下,直到列表中没有数字为止。对手也在尽最大努力获得最高分,所以他们不会只是贪婪地选择更多的数字。球员同样聪明,所以他们都会使用完全相同的策略来获得最高分。
每次在较小的列表上这是一个明显的递归问题。
注意:我不是在寻找代码。因为它是一个cs课程,我真的很想给我一些关于我可能做错的提示,以便我可以学习而不是给出代码。
这是我写的代码:
def Recursive(A):
# check if there is only one item left. If so, return it
if len(A) == 1:
return A[0]
# take the left item and recurse on the list if the opponent
# were to take the left side, and the list if the opponent
# were to take the right number
takeLeftSide = A[0] + max(Recursive(A[1:-1]), Recursive(A[2:len(A)]))
takeRightSide = A[-1] + max(Recursive(A[0:-2]), Recursive(A[1:-1]))
return max(takeLeftSide, takeRightSide)
if __name__ == '__main__':
A = [5,8,1,3,6]
print Recursive(A)
我相信我应该期待12,但在某些情况下我的输出给了我19和14。
我很感激帮助,我已经在这里工作了几个小时,我知道一旦你尝试深入了解递归的东西就会变得混乱和混乱。
答案 0 :(得分:0)
这是我尝试的代码(使用您的输入)。它与你的几乎完全相同。
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
mBluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
if(mBluetoothGatt != null){
mBluetoothGatt.close();
mBluetoothGatt = null;
}
broadcastUpdate(intentAction);
}
}
正确答案是19。
原因是:假设我们正在寻找最高的可能分数(不是两个人都使用最佳策略的分数),那么游戏将如下:
def r(a):
if len(a) == 1:
return a[0]
takeLeft = a[0] + max(r(a[1:-1]), r(a[2:]))
takeRight = a[-1] + max(r(a[:-2]), r(a[1:-1]))
return max(takeLeft, takeRight)
所以P1有19,P2有4。
编辑:这是在使用GIVEN(略有改动)的递送代码的最佳情况下发生的事情
P1: 6 Leftover: [5,8,1,3]
P2: 3 Leftover: [5,8,1]
P1: 6 + 5 Leftover: [8,1]
P2: 3 + 1 Leftover: [8]
P1: 6 + 5 + 8 Leftover: []
以下是游戏在这种情况下的表现:
def r(a):
if len(a) == 1:
return a[0]
if len(a) == 2 or len(a) == 3:
if a[0] > a[-1]:
return True
return False
takeLeft = a[0] + max(r(a[1:-1]), r(a[2:]))
takeRight = a[-1] + max(r(a[:-2]), r(a[1:-1]))
if takeLeft > takeRight:
return True
return False
def game(a):
p1 = 0
p2 = 0
p1Turn = True
while len(a) > 0:
isLeft = r(a)
nextValue = -1
if isLeft: #if it's from the left
nextValue = a[0]
a = a[1:]
else: #if it's from the right
nextValue = a[-1]
a = a[:-1]
if p1Turn:
p1 += nextValue
else:
p2 += nextValue
p1Turn = not p1Turn
print "P1:", p1, "P2:", p2
答案 1 :(得分:0)
为了让两个玩家“同样聪明”,他们应该使用相同的方法(算法)来选择一个侧面,每轮。
为了对其进行编程,您应该根据给定的列表开发用于选择边(线的前面或后面)的算法,并且每次为不同的播放器调用此算法。
为了证明这一点,我选择了一种相对原始的贪婪算法,它总是选择两者之间的较大:
def pick_greedy(A):
if len(A) == 0:
result = 0
elif len(A) == 1:
result = A.pop()
elif A[0] > A[-1]:
result = A[0]
A = A[1:]
else:
result = A[-1]
A = A[:-1]
return A, result
if __name__ == '__main__':
A = [5,8,1,3,6]
x = 0
y = 0
while A:
A, _ = pick_greedy(A)
x += _
A, _ = pick_greedy(A)
y += _
print "Player 1: {}; Player 2: {}".format(x, y)
<强>输出强>
Player 1: 6; Player 2: 5
Player 1: 14; Player 2: 8
Player 1: 15; Player 2: 8
答案 2 :(得分:0)
您当前实施的一个问题是,播放器1应该获得次要呼叫的 min ,而不是最大值(播放器2将获得这些呼叫的最大值)。如果进行了更改,则会得到预期结果:12
。
takeLeftSide = A[0] + min(Recursive(A[1:-1]), Recursive(A[2:len(A)]))
takeRightSide = A[-1] + min(Recursive(A[0:-2]), Recursive(A[1:-1]))
当前实现的其他三个问题:(a)输入列表的大小增加非常慢; (b)如果输入列表具有偶数个值,则引发IndexError
; (c)如果输入列表为空,则引发RuntimeError
。
但是,这是另一种思考问题的方法:
如果列表中存在偶数个值,则玩家1可以通过决定是使用偶数索引获取所有值还是使用奇数索引<的所有值来保证获胜或平局/强>
如果初始列表中有奇数个值,则播放器1只需要在左与右之间做出决定。然后,初始选择会为玩家2创建一个大小合适的列表情况,玩家2将回归到上述策略。
因此,玩家1可以预测玩家2的移动,并且应该相应地进行初始左右选择。
def f(xs):
evens = sum(xs[i] for i in range(0, len(xs), 2))
odds = sum(xs[i] for i in range(1, len(xs), 2))
if len(xs) % 2 == 0:
return max(evens, odds)
else:
lft = xs[0]
rgt = xs[-1]
return max(
lft + min(odds, evens - lft),
rgt + min(odds, evens - rgt),
)
if __name__ == '__main__':
from random import randint
tests = [
(0, []),
(5, [5]),
(30, [20, 1, 15, 9, 19]),
(12, [5, 8, 1, 3, 6]),
('big', [randint(0, 100) for _ in xrange(0, 99999)]),
]
for exp, vals in tests:
print exp, f(vals)
与递归实现不同,此方法为O(N)
,因此具有能够处理非常大的列表的优势。