LintCode 395. Coins in a Line II (博弈类DP经典题!!!)

  1. Coins in a Line II

There are n coins with different value in a line. Two players take turns to take one or two coins from left side until there are no more coins left. The player who take the coins with the most value wins.

Could you please decide the first player will win or lose?

If the first player wins, return true, otherwise return false.

Example
Example 1:

Input: [1, 2, 2]
Output: true
Explanation: The first player takes 2 coins.
Example 2:

Input: [1, 2, 4]
Output: false
Explanation: Whether the first player takes 1 coin or 2, the second player will gain more value.

解法1:
思路:把两个选手当成1个人,每次都让自己面对剩下的棋子选择最优局面。
sums[i]:从i到len-1的values之和。
dp[i]: 第i轮时,该选手在该轮到比赛结束所能取得的最多coin。
关键代码:
dp[i] = max(sums[i + 1] - dp[i + 1] + values[i],
sums[i + 2] - dp[i + 2] + values[i] + values[i + 1]);
因为第i轮时,该选手可取1个或2个。若取1个,对手下一轮到比赛结束所能取得的最多coin是dp[i + 1],所以自己下一轮到比赛结束所取得的最多coin是sums[i + 1] - dp[i + 1],同时加上这一轮所取的values[i]。若取2个,情况类似。

注意: 该DP要从后往前推,最后判断sums[0] - dp[0] < dp[0]。因为dp[len - 1]和dp[len - 2]好算。

代码如下:

class Solution {
public:
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    bool firstWillWin(vector<int> &values) {
        int len = values.size();
        if (len < 3) return true;
        vector<int> sums(len, 0);
        sums[len - 1] = values[len - 1];
        for (int i = len - 2; i >= 0; --i) {
            sums[i] = sums[i + 1] + values[i];
        }
        
        vector<int> dp(len, 0);
        dp[len - 1] = values[len - 1];
        dp[len - 2] = values[len - 1] + values[len - 2];
        
        for (int i = len - 3; i >= 0; --i) {
            dp[i] = max(sums[i + 1] - dp[i + 1] + values[i],
                        sums[i + 2] - dp[i + 2] + values[i] + values[i + 1]);
        }
        
        return sums[0] - dp[0] < dp[0];
    }
};

解法2:
上面的sums[]其实可以用滚动数组优化。
sum2: 当i循环结束时,从values[i]到values[len - 1]的累加和。
sum1: 当i循环结束时,从values[i + 1]到values[len - 1]的累加和。
代码如下:

class Solution {
public:
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    bool firstWillWin(vector<int> &values) {
        int len = values.size();
        if (len < 3) return true;
        int sum1 = values[len - 1];
        int sum2 = values[len - 1] + values[len - 2];

        vector<int> dp(len, 0);
        dp[len - 1] = values[len - 1];
        dp[len - 2] = values[len - 1] + values[len - 2];
        
        for (int i = len - 3; i >= 0; --i) {
            dp[i] = max(sum2 - dp[i + 1] + values[i],
                        sum1 - dp[i + 2] + values[i] + values[i + 1]);
            sum1 = sum2;
            sum2 += values[i];
        }
        
        return sum2 - dp[0] < dp[0];
    }
};

解法3:从解法2中可以看出dp[i]实际上也取决于dp[i+1]和dp[i+2],所以,又可以用2个变量dp1和dp2取代整个dp[]。这样空间复杂度就是O(1)了。
代码如下:

class Solution {
public:
    /**
     * @param values: a vector of integers
     * @return: a boolean which equals to true if the first player will win
     */
    bool firstWillWin(vector<int> &values) {
        int len = values.size();
        if (len < 3) return true;
        
        int sum1 = values[len - 1];
        int sum2 = values[len - 1] + values[len - 2];

        int dp2 = sum2;
        int dp1 = sum1;
        
        for (int i = len - 3; i >= 0; --i) {
         
            int dp = max(sum2 - dp2 + values[i],
                      sum1 - dp1 + values[i] + values[i + 1]);
            dp1 = dp2;
            dp2 = dp;
            sum1 = sum2;
            sum2 += values[i];
        }
        
        return sum2 - dp2 < dp2;
    }
};

注意这里必须用一个临时变量dp,要不然dp1和dp2会有一个被覆盖。sum1和sum2不需要临时变量是因为它们没有转换方程,而dp有。

解法4:九章解法。
f[i]表示本轮选手A在面对A[i…n-1]这些coins时,所能得到的最大的与对方选手B的coins数之差。
转换方程:
f[n] = 0;
f[i] = max(values[i] - f[i + 1], values[i] + values[i + 1] - f[i + 2]);

因为f[i+1]是对方B所能取得的与A的coin的最大差,所以从A的角度看来,要加一个负号。

f[n-1]=values[n-1],
f[n-2]=values[n-1]+values[n-2];
f[n-3]=max(values[n-3]-f[n-2], values[n-3]+values[n-2]-f[n-1])
=max(values[n-3]-values[n-1]-values[n-2], values[n-3]+values[n-2]-values[n-1]);

public class Solution {
    public boolean firstWillWin(int[] A) {
        int n = A.length;
        int[] f = new int[n + 1];
        f[n] = 0;
        int i;
        for (i = n - 1; i >= 0; --i){
            f[i] = A[i] - f[i + 1];
            if (i < n - 1) {
                f[i] = Math.max(f[i], A[i] + A[i + 1] - f[i + 2]);
            }
        }
        
        return f[0] >= 0;
    }
}

解法5:九章版本之O(1)空间。参考网上代码。

public class Solution {
    public boolean firstWillWin(int[] values) {
        if(values.length == 0){
            return true;
        }
        if(values.length == 1){
            return values[0] >=0;
        }
        int last1 = values[values.length - 1];
        int last2 = values[values.length - 2] + last1;
        for(int i = values.length - 3; i >= 0; i --){
            int temp = Math.max(values[i] - last2, values[i] + values[i + 1] - last1);
            last1 = last2;
            last2 = temp;
        }
        return last2 >= 0;
    }
}
<think>我们对代码 `let clolist = cloneDeep(cstGroupTypeMap[this.laptops].coins)` 进行解析。 1. **变量名 `clolist`**: - 这是一个新声明的变量(使用 `let` 关键字),用于存储深拷贝后的结果。 2. **`cloneDeep` 函数**: - 这是一个深拷贝函数,通常来自像 Lodash 这样的工具库(例如 `_.cloneDeep()`),或者是一个自定义的深拷贝函数。 - 深拷贝的目的是创建一个新对象,完全复制原对象的所有属性值(包括嵌套对象),使得新对象与原对象互不影响。 3. **`cstGroupTypeMap`**: - 这是一个对象(或映射),通过键来访问对应的值。 - 这里使用 `this.laptops` 作为键来获取 `cstGroupTypeMap` 中的某个值。 4. **`this.laptops`**: - `this` 指向当前上下文(可能是实例、组件实例或对象)。 - `laptops` 是 `this` 的一个属性,可能是字符串、数字或符号(Symbol),用于作为 `cstGroupTypeMap` 的键。 5. **`.coins`**: - 从 `cstGroupTypeMap[this.laptops]` 返回的对象中,访问其 `coins` 属性。 - 假设 `cstGroupTypeMap[this.laptops]` 返回一个对象,该对象有一个名为 `coins` 的属性。 6. **整体流程**: - 首先,获取 `this.laptops` 的值(记为 `key`)。 - 然后,从 `cstGroupTypeMap` 中获取键为 `key` 的值(记为 `obj`)。 - 接着,获取 `obj.coins` 的值(可能是数组、对象或其他型)。 - 最后,对 `obj.coins` 进行深拷贝,将结果赋值给 `clolist`。 **示例场景**: 假设有以下数据: ```javascript cstGroupTypeMap = { laptop1: { coins: [{ id: 1, value: 100 }, { id: 2, value: 200 }], // ... 其他属性 }, laptop2: { coins: [ ... ], // ... } }; // 假设 this.laptops 的值为 'laptop1' ``` 那么: - `cstGroupTypeMap[this.laptops]` 得到 `{ coins: [{ id:1, ... }, ...] }` - 再取其 `.coins` 得到数组 `[{ id:1, ... }, ...]` - `cloneDeep` 会深拷贝这个数组,生成一个全新的数组(但内容相同),赋值给 `clolist`。 **注意事项**: - 深拷贝是为了避免后续对 `clolist` 的修改影响原数据(因为数组和对象是引用型)。 - 如果 `cstGroupTypeMap[this.laptops]` 不存在,或者其 `coins` 属性不存在,则可能报错(如访问 `.coins` 时是 `undefined` 或 `null`)。 **潜在问**: - 需要确保 `cstGroupTypeMap` 已正确定义,且 `this.laptops` 是其中的一个有效键。 - 确保 `cstGroupTypeMap[this.laptops].coins` 是可被深拷贝的数据(如不包含函数、循环引用等特殊结构)。 综上,这行代码的作用是:从 `cstGroupTypeMap` 中根据当前 `laptops` 属性值取出对应的配置对象,然后将其 `coins` 属性进行深拷贝,并将拷贝结果赋值给 `clolist` 变量。[^1] §§相关问§§ 1. 什么是深拷贝(deep clone)?与浅拷贝(shallow clone)有什么区别? 2. 在JavaScript中,如何实现一个深拷贝函数? 3. Lodash的`_.cloneDeep()`方法如何使用? 4. 如何确保访问嵌套对象属性时的安全性(避免因中间属性不存在而报错)? </think>### 解析代码 `let clolist = cloneDeep(cstGroupTypeMap[this.laptops].coins)` 此代码片段实现了一个**深拷贝操作**,目的是避免原始数据被修改。具体解析如下: #### 1. **核心组件分析** | 代码片段 | 含义 | |---------------------------|----------------------------------------------------------------------| | `cstGroupTypeMap` | 一个映射对象(似字典),存放不同分组型的数据结构 | | `this.laptops` | 当前上下文中 `laptops` 属性的值,作为 `cstGroupTypeMap` 的键 | | `cstGroupTypeMap[this.laptops]` | 获取 `laptops` 键对应的分组配置对象(例如笔记本产品的配置) | | `.coins` | 从分组配置对象中提取 `coins` 属性(可能是积分规则、代币数据等) | | `cloneDeep(...)` | **深拷贝函数**,递归复制所有嵌套数据,生成完全独立的新对象 | | `let clolist = ...` | 将深拷贝结果赋值给新变量 `clolist`,后续操作不影响原始数据 | #### 2. **执行流程** ```mermaid graph TD A[this.laptops] --> B[作为键查询 cstGroupTypeMap] B --> C[获取分组对象] C --> D[提取 .coins 属性] D --> E[深拷贝 coins 数据] E --> F[结果存入 clolist] ``` #### 3. **关键特性** - **深拷贝必要性** 若直接赋值(如 `let list = cstGroupTypeMap[key].coins`),修改 `list` 会影响原始数据。使用 `cloneDeep` 创建独立副本,确保数据隔离[^1]。 - **数据来源** 依赖于 `cstGroupTypeMap` 的结构,示例结构可能是: ```javascript const cstGroupTypeMap = { laptops: { coins: [{ id: 1, type: 'reward' }, { id: 2, type: 'discount' }], // 待拷贝数据 otherProp: ... }, phones: { ... } }; ``` #### 4. **潜在风险** - **空值访问** 若 `this.laptops` 不存在于 `cstGroupTypeMap` 中,或 `.coins` 未定义,会抛出 `TypeError`。建议添加空值保护: ```javascript const coinsData = cstGroupTypeMap[this.laptops]?.coins || []; let clolist = cloneDeep(coinsData); ``` #### 5. **典型应用场景** - **配置隔离**:克隆产品积分规则供用户临时修改,原始配置不受影响[^2] - **状态管理**:在 Vue/React 中复制状态数据,避免直接修改原始 store - **数据加工**:对嵌套数据进行格式化或过滤时保留原始版本 > 深拷贝是解决 JavaScript **引用型数据共享**的核心手段,尤其适用于嵌套对象或数组[^3]。 --- ### 相关问 1. 深拷贝(`cloneDeep`)和浅拷贝(`Object.assign`)在性能和使用场景上有何区别? 2. 如何手动实现一个支持循环引用的深拷贝函数? 3. 在 Vue/React 中,哪些场景必须使用深拷贝来操作状态数据? 4. 如何通过 TypeScript 型系统确保 `cstGroupTypeMap` 的结构安全性?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值