豆包 LeetCode 1900.最佳运动员的比拼回合 JavaScript实现
1. 状态标准化:通过交换和对称转换,将所有状态统一为 a < b 且 a + b <= n+1 ,状态数量减少约75%// b的对手(必输)// 记忆化数组:minMemo[n][a][b] 存储n人时a和b最早相遇回合。2. 终止条件:当 a + b == n+1 时,两人本轮直接相遇,返回 [1,1]// 标准化状态:保证a < b 且 a + b <= n+1(利用对称性)// maxMe
LeetCode 1900 最佳运动员的比拼回合 JavaScript 实现
核心思路(与Java版一致)
采用记忆化递归,利用比赛对称性大幅减少状态数量:
1. 状态标准化:始终保证 a < b 且 a + b <= n+1 (对称位置结果相同)
2. 终止条件:当 a + b == n+1 时,两人本轮直接相遇,返回 [1,1]
3. 状态转移:枚举中间选手所有可能的晋级情况,递归计算下一轮结果
4. 记忆化缓存:存储已计算状态避免重复计算
完整JavaScript代码
javascript
/**
* @param {number} n
* @param {number} firstPlayer
* @param {number} secondPlayer
* @return {number[]}
*/
var earliestAndLatest = function(n, firstPlayer, secondPlayer) {
// 记忆化数组:minMemo[n][a][b] 存储n人时a和b最早相遇回合
// maxMemo[n][a][b] 存储n人时a和b最晚相遇回合
const minMemo = Array.from({ length: 30 }, () =>
Array.from({ length: 30 }, () =>
Array.from({ length: 30 }, () => null)
)
);
const maxMemo = Array.from({ length: 30 }, () =>
Array.from({ length: 30 }, () =>
Array.from({ length: 30 }, () => null)
)
);
// 标准化状态:保证a < b 且 a + b <= n+1(利用对称性)
const normalize = (n, a, b) => {
if (a > b) [a, b] = [b, a];
if (a + b > n + 1) {
const newA = n + 1 - b;
const newB = n + 1 - a;
a = newA;
b = newB;
}
return [a, b];
};
const dfs = (n, a, b) => {
// 已计算过直接返回
if (minMemo[n][a][b] !== null && maxMemo[n][a][b] !== null) {
return;
}
// 终止条件:两人在当前轮直接对决
if (a + b === n + 1) {
minMemo[n][a][b] = 1;
maxMemo[n][a][b] = 1;
return;
}
const bOpponent = n + 1 - b; // b的对手(必输)
let yMin = 0, yMax = 0;
// 计算中间选手(a+1 ~ b-1)晋级人数的范围
for (let i = a + 1; i <= b - 1; i++) {
if (i === bOpponent) continue; // b的对手必输,不晋级
const opp = n + 1 - i;
if (opp < i) continue; // 避免重复计算配对
if (opp === i) {
// 奇数轮中间选手自动晋级
yMin++;
yMax++;
} else if (opp > b) {
// 对手在b右侧,可选择中间选手晋级或不晋级
yMax++;
} else if (opp > i) {
// 两个中间选手比赛,必有一人晋级
yMin++;
yMax++;
}
}
const nextN = Math.floor((n + 1) / 2); // 下一轮人数
let minRes = Infinity;
let maxRes = -Infinity;
// 枚举所有可能的中间晋级人数y
const maxY = Math.min(yMax, nextN - 2); // a和b必晋级,剩余nextN-2个位置
for (let y = yMin; y <= maxY; y++) {
// 枚举所有可能的左侧晋级人数x
const xMax = Math.min(a - 1, nextN - y - 2);
if (xMax < 0) continue;
for (let x = 0; x <= xMax; x++) {
// 计算下一轮a和b的新位置
const aNew = x + 1;
const bNew = x + y + 2;
const [aNorm, bNorm] = normalize(nextN, aNew, bNew);
dfs(nextN, aNorm, bNorm);
const subMin = minMemo[nextN][aNorm][bNorm];
const subMax = maxMemo[nextN][aNorm][bNorm];
minRes = Math.min(minRes, 1 + subMin);
maxRes = Math.max(maxRes, 1 + subMax);
}
}
minMemo[n][a][b] = minRes;
maxMemo[n][a][b] = maxRes;
};
const [a, b] = normalize(n, firstPlayer, secondPlayer);
dfs(n, a, b);
return [minMemo[n][a][b], maxMemo[n][a][b]];
};
测试用例验证
javascript
// 官方示例1
console.log(earliestAndLatest(11, 2, 4)); // [3, 4]
// 直接相遇
console.log(earliestAndLatest(5, 1, 5)); // [1, 1]
// 必须3轮相遇
console.log(earliestAndLatest(5, 2, 3)); // [3, 3]
// 两人对决
console.log(earliestAndLatest(2, 1, 2)); // [1, 1]
// 4人情况
console.log(earliestAndLatest(4, 1, 3)); // [2, 2]
关键细节说明
1. 状态标准化:通过交换和对称转换,将所有状态统一为 a < b 且 a + b <= n+1 ,状态数量减少约75%
2. 晋级范围计算:精确计算中间选手可能的晋级人数上下界,避免无效枚举
3. 记忆化优化:使用三维数组缓存结果,每个状态仅计算一次
4. 边界处理:确保 a 和 b 永远晋级,因此下一轮至少需要2个位置
复杂度分析
- 时间复杂度:O(n^3),n最大为28,实际运行极快
- 空间复杂度:O(n^3),用于存储记忆化缓存
需要我给你提供迭代动态规划版本或者数学公式推导的O(log n)解法吗?
更多推荐




所有评论(0)