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)解法吗?

 

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐