16.1 活动选择
最优子结构
活动选择问题的最优子结构意味着问题的最优解包含了其子问题的最优解。具体来说,如果我们有一个按结束时间排序的活动集合 S={a1,a2,...,an},并且 S’ 是 S 的最大兼容活动子集,那么对于 S′ 中的任何活动aj,S′ 也包含了在活动aj 之前结束的所有活动中的最大兼容子集。
这个属性允许我们使用贪心策略来构建解决方案,因为在选择了当前最优的活动之后,剩余的选择可以独立于已经做出的选择,并且仍然达到最优。
贪心策略
活动选择问题的贪心策略基于一个简单的原则:总是选择结束时间最早的活动,然后从剩余的与已选择活动不冲突的活动中继续应用这一策略。这种方法的有效性在于它能够最大化剩余时间,从而增加选择其他活动的机会。
- 排序:首先按照活动的结束时间对活动进行排序。
- 选择:选择结束时间最早的活动,并将其加入到最优解集合中。
- 剔除:移除与已选择活动时间上冲突的所有活动。
- 重复:重复步骤2和3,直到没有更多的活动可以选择。
Python 实现
迭代:
def activity_selection(activities):
# 按结束时间对活动进行排序
sorted_activities = sorted(activities, key=lambda x: x[1])
# 选择第一个活动
n = len(sorted_activities)
last_selected = 0
selected_activities = [sorted_activities[0]]
# 遍历剩余的活动
for i in range(1, n):
# 如果当前活动的开始时间大于或等于上一个选择的活动的结束时间
if sorted_activities[i][0] >= sorted_activities[last_selected][1]:
# 选择当前活动
selected_activities.append(sorted_activities[i])
last_selected = i
return selected_activities
递归:
def recursive_activity_selector(s, f, k, n):
# 查找第一个结束时间在k之后开始的活动
m = k + 1
while m <= n and s[m] < f[k]:
m += 1
if m <= n:
# 如果找到这样的活动,选择它,并递归地寻找下一个活动
return [m] + recursive_activity_selector(s, f, m, n)
else:
# 如果没有更多活动可以选择,返回空列表
return []
877. 石子游戏 感觉被狠狠地耍了!!
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
n = len(piles)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = piles[i]
for length in range(2, n + 1):
for start in range(n - length + 1):
end = start + length - 1
dp[start][end] = max(piles[start] - dp[start + 1][end], piles[end] - dp[start][end - 1])
return dp[0][n - 1] > 0
真不看题解很难理解dp数组的定义,已经能够想到pi...pj分选左还是选右了,但是dp定义差值是真想不到。
定义最优子结构
- 问题的分解:石子游戏可以被分解成一系列更小的子问题,每个子问题都涉及到从一系列石子堆中选择石子的决策。对于任何一对给定的起始和结束点
i
和j
,问题变成了在这个子序列中选择石子以最大化当前玩家相对于对手的分数差。 - 子问题的最优解:每个子问题的最优解是指在给定的子序列中,玩家可以获得的最大分数差。这个最优解依赖于玩家在子序列的两端选择石子的决策。
最优子结构的体现
- 当一个玩家面对一个石子堆序列
[i...j]
时,他们的目标是最大化自己相对于对手的分数差。这个目标可以通过选择序列两端的石子来实现,每次选择后,问题就缩小到一个更小的子序列,要么是[i+1...j]
,要么是[i...j-1]
。 - 对于每个这样的子序列,玩家都面临着同样的问题:如何选择石子以最大化自己的分数差。因此,大问题的最优解依赖于这些子问题的最优解。
状态转移方程
- 最优子结构允许我们通过状态转移方程来表达问题的解:
dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])
。这个方程说明了当前玩家在子序列[i...j]
中的最优选择依赖于在缩小的子序列[i+1...j]
和[i...j-1]
中的最优选择。
但是实际上对于偶数堆石子的情况,Alice 可以通过选择奇数位置的石子堆或偶数位置的石子堆(从1开始计数)来强迫bob只能选另一种位置的石子堆,确保自己能够获得更多的石子。因为石子总数是奇数,所以奇数位置的石子总数和偶数位置的石子总数不可能相等,Alice 可以选择较多的那一组。
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
return True
所以这个问题先手必胜...
也算练习一下dp吧...想休息了...