Question:
大餐 是指 恰好包含两道不同餐品 的一餐,其美味程度之和等于 2 的幂。

你可以搭配 任意 两道餐品做一顿大餐。

给你一个整数数组 deliciousness ,其中 deliciousness[i] 是第 i 道餐品的美味程度,返回你可以用数组中的餐品做出的不同 大餐 的数量。结果需要对 $10^9$ + 7 取余。

注意,只要餐品下标不同,就可以认为是不同的餐品,即便它们的美味程度相同。

示例 1:

```
输入:deliciousness = [1,3,5,7,9]

输出:4

解释:大餐的美味程度组合为 (1,3) 、(1,7) 、(3,5) 和 (7,9) 。
它们各自的美味程度之和分别为 4 、8 、8 和 16 ,都是 2 的幂。
```
示例 2:
```
输入:deliciousness = [1,1,1,3,3,3,7]

输出:15

解释:大餐的美味程度组合为 3 种 (1,1) ,9 种 (1,3) ,和 3 种 (1,7) 。
```

提示:
* 1 <= deliciousness.length <= $10^5$
* 0 <= deliciousness[i] <= $2^{20}$

Solution:
### 枚举前一个数(TLE)

一个朴素的想法是,从前往后遍历 $deliciousness$ 中的所有数,当遍历到下标 $i$ 的时候,回头检查下标小于 $i$ 的数是否能够与 $deliciousness[i]$ 相加形成 $2$ 的幂。

这样的做法是 $O(n^2)$ 的,防止同样的数值被重复计算,我们可以使用「哈希表」记录某个数出现了多少次,但这并不改变算法仍然是 $O(n^2)$ 的。

而且我们需要一个 `check` 方法来判断某个数是否为 $2$ 的幂:

* 朴素的做法是对 $x$ 应用试除法,当然因为精度问题,我们需要使用乘法实现试除;
* 另一个比较优秀的做法是利用位运算找到符合「大于等于 $x$」的最近的 $2$ 的幂,然后判断是否与 $x$ 相同。

两种做法差距有多大呢?方法一的复杂度为 $O(\log{n})$,方法二为 $O(1)$。

根据数据范围 $0 <= deliciousness[i] <= 2^{20}$,方法一最多也就是执行不超过 $22$ 次循环。

显然,采用何种判断 $2$ 的幂的做法不是关键,在 OJ 判定上也只是分别卡在 $60/70$ 和 $62/70$ 的 TLE 上。

**但通过这样的分析,我们可以发现「枚举前一个数」的做法是与 $n$ 相关的,而枚举「可能出现的 $2$ 的幂」则是有明确的范围,这引导出我们的解法二。**

代码:
```Java
class Solution {
    int mod = (int)1e9+7;
    public int countPairs(int[] ds) {
        int n = ds.length;
        long ans = 0;
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            int x = ds[i];
            for (int other : map.keySet()) {
                if (check(other + x)) ans += map.get(other);
            }
            map.put(x, map.getOrDefault(x, 0) + 1);
        }
        return (int)(ans % mod);
    }
    boolean check(long x) {
        // 方法一
        // long cur = 1;
        // while (cur < x) {
        //     cur = cur * 2;
        // }
        // return cur == x;

        // 方法二
        return getVal(x) == x;
    }
    long getVal(long x) {
        long n = x - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return n < 0 ? 1 : n + 1;
    }
}
```
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n)$

---

### 枚举 2 的幂(容斥原理)

根据对朴素解法的分析,我们可以先使用「哈希表」对所有在 $deliciousness$ 出现过的数进行统计。

然后对于每个数 $x$,检查所有可能出现的 $2$ 的幂 $i$,再从「哈希表」中反查 $t = i - x$ 是否存在,并实现计数。

一些细节:如果哈希表中存在 $t = i - x$,并且 $t = x$,这时候方案数应该是 $(cnts[x] - 1) * cnts[x]$;其余一般情况则是 $cnts[t] * cnts[x]$。

同时,这样的计数方式,我们对于二元组 $(x, t)$ 会分别计数两次(遍历 $x$ 和 遍历 $t$),因此最后要利用容斥原理,对重复计数的进行减半操作。

代码:
```Java
class Solution {
    int mod = (int)1e9+7;
    int max = 1 << 22;
    public int countPairs(int[] ds) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int d : ds) map.put(d, map.getOrDefault(d, 0) + 1);
        long ans = 0;
        for (int x : map.keySet()) {
            for (int i = 1; i < max; i <<= 1) {
                int t = i - x;
                if (map.containsKey(t)) {
                    if (t == x) ans += (map.get(x) - 1) * 1L * map.get(x);
                    else ans += map.get(x) * 1L * map.get(t);
                }
            }
        }
        ans >>= 1;
        return (int)(ans % mod);
    }
}
```
* 时间复杂度:根据数据范围,令 $C$ 为 $2^{21}$。复杂度为 $O(n * \log{C})$
* 空间复杂度:$O(n)$

---

### 枚举 2 的幂(边遍历边统计)

当然,我们也可以采取「一边遍历一边统计」的方式,这样取余操作就可以放在遍历逻辑中去做,也就顺便实现了不使用 $long$ 来计数(以及不使用 `%` 实现取余)。

代码:
```Java
class Solution {
    int mod = (int)1e9+7;
    int max = 1 << 22;
    public int countPairs(int[] ds) {
        Map<Integer, Integer> map = new HashMap<>();
        int ans = 0;
        for (int x : ds) {
            for (int i = 1; i < max; i <<= 1) {
                int t = i - x;
                if (map.containsKey(t)) {
                    ans += map.get(t);
                    if (ans >= mod) ans -= mod;
                }
            }
            map.put(x, map.getOrDefault(x, 0) + 1);
        }
        return ans;
    }
}
```
* 时间复杂度:根据数据范围,令 $C$ 为 $2^{21}$。复杂度为 $O(n * \log{C})$
* 空间复杂度:$O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1711` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1711-1720/1713. 得到子序列的最少操作次数（困难）.md

1713. 得到子序列的最少操作次数

Question:
给你一个数组 target ,包含若干 互不相同 的整数,以及另一个整数数组 arr ,arr 可能 包含重复元素。

每一次操作中,你可以在 arr 的任意位置插入任一整数。比方说,如果 arr = [1,4,1,2] ,那么你可以在中间添加 3 得到 [1,4,3,1,2] 。你可以在数组最开始或最后面添加整数。

请你返回 最少 操作次数,使得 target 成为 arr 的一个子序列。

一个数组的 子序列 指的是删除原数组的某些元素(可能一个元素都不删除),同时不改变其余元素的相对顺序得到的数组。比方说,[2,7,4] 是 [4,2,3,7,2,1,4] 的子序列(加粗元素),但 [2,4,2] 不是子序列。

示例 1:
```
输入:target = [5,1,3], arr = [9,4,2,3,4]

输出:2

解释:你可以添加 5 和 1 ,使得 arr 变为 [5,9,4,1,2,3,4] ,target 为 arr 的子序列。
```
示例 2:
```
输入:target = [6,4,8,1,3,2], arr = [4,7,6,2,3,8,6,1]

输出:3
```

提示:
* 1 <= target.length, arr.length <= $10^5$
* 1 <= target[i], arr[i] <= $10^9$
* target 不包含任何重复元素。

Solution:
### 基本分析

为了方便,我们令 $target$ 长度为 $n$,$arr$ 长度为 $m$,$target$ 和 $arr$ 的最长公共子序列长度为 $max$,不难发现最终答案为 $n - max$。

因此从题面来说,这是一道最长公共子序列问题(LCS)。

但朴素求解 LCS 问题复杂度为 $O(n * m)$,使用状态定义「**$f[i][j]$ 为考虑 `a` 数组的前 $i$ 个元素和 `b` 数组的前 $j$ 个元素的最长公共子序列长度为多少**」进行求解。

而本题的数据范围为 $10^5$,使用朴素求解 LCS 的做法必然超时。

一个很显眼的切入点是 $target$ 数组元素各不相同,当 LCS 问题增加某些条件限制之后,会存在一些很有趣的性质。

其中一个经典的性质就是:**当其中一个数组元素各不相同时,最长公共子序列问题(LCS)可以转换为最长上升子序列问题(LIS)进行求解。同时最长上升子序列问题(LIS)存在使用「维护单调序列 + 二分」的贪心解法,复杂度为 $O(n\log{n})$。**

**因此本题可以通过「抽象成 LCS 问题」->「利用 $target$ 数组元素各不相同,转换为 LIS 问题」->「使用 LIS 的贪心解法」,做到 $O(n\log{n})$ 的复杂度。**

基本方向确定后,我们证明一下第 $2$ 步和第 $3$ 步的合理性与正确性。

---

### 证明

#### 1. 为何其中一个数组元素各不相同,LCS 问题可以转换为 LIS 问题?

**本质是利用「当其中一个数组元素各不相同时,这时候每一个“公共子序列”都对应一个不重复元素数组的下标数组“上升子序列”,反之亦然」。**

我们可以使用题目给定的两个数组($target$ 和 $arr$)理解上面的话。

由于 $target$ 元素各不相同,那么首先 $target$ 元素和其对应下标,具有唯一的映射关系。

然后我们可以**将重点放在两者的公共元素上(忽略非公共元素),每一个“公共子序列”自然对应了一个下标数组“上升子序列”,反之亦然**。

注意:下图只画出了两个数组的某个片段,不要错误理解为两数组等长。

如果存在某个“公共子序列”,根据“子序列”的定义,那么对应下标序列必然递增,也就是对应了一个“上升子序列”。

反过来,对于下标数组的某个“上升子序列”,首先意味着元素在 $target$ 出现过,并且出现顺序递增,符合“公共子序列”定义,即对应了一个“公共子序列”。

至此,我们将原问题 LCS 转换为了 LIS 问题。

#### 2. 贪心求解 LIS 问题的正确性证明?

朴素的 LIS 问题求解,我们需要定义一个 $f[i]$ 数组代表以 $nums[i]$ 为结尾的最长上升子序列的长度为多少。

对于某个 $f[i]$ 而言,我们需要往回检查 $[0, i - 1]$ 区间内,所有可以将 $nums[i]$ 接到后面的位置 $j$,在所有的 $f[j] + 1$ 中取最大值更新 $f[i]$。因此朴素的 LIS 问题复杂度是 $O(n^2)$ 的。

**LIS 的贪心解法则是维护一个额外 $g$ 数组,$g[len] = x$ 代表上升子序列长度为 $len$ 的上升子序列的「最小结尾元素」为 $x$。**

整理一下,我们总共有两个数组:
* $f$ 动规数组:与朴素 LIS 解法的动规数组含义一致。$f[i]$ 代表以 $nums[i]$ 为结尾的上升子序列的最大长度;
* $g$ 贪心数组:$g[len] = x$ 代表上升子序列长度为 $len$ 的上升子序列的「最小结尾元素」为 $x$。

由于我们计算 $f[i]$ 时,需要找到满足 $nums[j] < nums[i]$,同时取得最大 $f[j]$ 的位置 $j$。

我们期望通过 $g$ 数组代替线性遍历。

显然,如果 $g$ 数组具有「单调递增」特性的话,我们可以通过「二分」找到符合 $g[idx] < nums[i]$ 分割点 $idx$(下标最大),即利用 $O(\log{n})$ 复杂度找到最佳转移位置。

我们可以很容易 **通过反证法结合 $g$ 数组的定义来证明 $g$ 数组具有「单调递增」特性。**

假设存在某个位置 $i$ 和 $j$,且 $i < j$,不满足「单调递增」,即如下两种可能:

* $g[i] = g[j] = x$:这意味着某个值 $x$ 既能作为长度 $i$ 的上升子序列的最后一位,也能作为长度为 $j$ 的上升子序列的最后一位。
    根据我们对 $g$ 数组的定义,$g[i] = x$ 意味在所有长度为 $i$ 上升子序列中「最小结尾元素」为 $x$,但同时由于 $g[j] = x$,而且「上升子序列」必然是「严格单调」,因此我们可以通过删除长度为 $j$ 的子序列后面的元素(调整出一个长度为 $i$ 的子序列)来找到一个比 $g[i]$ 小的合法值。
    也就是我们找到了一个长度为 $i$ 的上升子序列,且最后一位元素必然严格小于 $x$。因此 $g[i] = g[j] = x$ 恒不成立;

* $g[i] > g[j] = x$:同理,如果存在一个长度为 $j$ 的合法上升子序列的「最小结尾元素」为 $x$ 的话,那么必然能够找到一个比 $x$ 小的值来更新 $g[i]$。即 $g[i] > g[j]$ 恒不成立。

根据全序关系,在证明 $g[i] = g[j]$ 和 $g[i] > g[j]$ 恒不成立后,可得 $g[i] < g[j]$ 恒成立。

至此,我们证明了 $g$ 数组具有单调性,从而证明了每一个 $f[i]$ 均与朴素 LIS 解法得到的值相同,即贪心解是正确的。

---

### 动态规划 + 贪心 + 二分

根据「基本分析 & 证明」,通过维护一个贪心数组 $g$,来更新动规数组 $f$,在求得「最长上升子序列」长度之后,利用「“公共子序列”和“上升子序列”」的一一对应关系,可以得出“最长公共子序列”长度,从而求解出答案。

代码:
```Java
class Solution {
    public int minOperations(int[] t, int[] arr) {
        int n = t.length, m = arr.length;
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            map.put(t[i], i);
        }
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < m; i++) {
            int x = arr[i];
            if (map.containsKey(x)) list.add(map.get(x));
        }
        int len = list.size();
        int[] f = new int[len], g = new int[len + 1];
        Arrays.fill(g, Integer.MAX_VALUE);
        int max = 0;
        for (int i = 0; i < len; i++) {
            int l = 0, r = len;
            while (l < r) {
                int mid = l + r + 1 >> 1;
                if (g[mid] < list.get(i)) l = mid;
                else r = mid - 1;
            }
            int clen = r + 1;
            f[i] = clen;
            g[clen] = Math.min(g[clen], list.get(i));
            max = Math.max(max, clen);
        }
        return n - max;
    }
}
```
* 时间复杂度:通过 $O(n)$ 复杂度得到 $target$ 的下标映射关系;通过 $O(m)$ 复杂度得到映射数组 $list$;贪心求解 LIS 的复杂度为 $O(m\log{m})$。整体复杂度为 $O(n + m\log{m})$
* 空间复杂度:$O(n + m)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1713` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1711-1720/1716. 计算力扣银行的钱（简单）.md

1716. 计算力扣银行的钱

Question:
Hercy 想要为购买第一辆车存钱。他 **每天** 都往力扣银行里存钱。

最开始,他在周一的时候存入 `1` 块钱。从周二到周日,他每天都比前一天多存入 `1` 块钱。在接下来每一个周一,他都会比 前一个周一 多存入 `1` 块钱。

给你 `n` ,请你返回在第 `n` 天结束的时候他在力扣银行总共存了多少块钱。

示例 1:
```
输入:n = 4

输出:10

解释:第 4 天后,总额为 1 + 2 + 3 + 4 = 10 。
```
示例 2:
```
输入:n = 10

输出:37

解释:第 10 天后,总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4) = 37 。注意到第二个星期一,Hercy 存入 2 块钱。
```
示例 3:
```
输入:n = 20

输出:96

解释:第 20 天后,总额为 (1 + 2 + 3 + 4 + 5 + 6 + 7) + (2 + 3 + 4 + 5 + 6 + 7 + 8) + (3 + 4 + 5 + 6 + 7 + 8) = 96 。
```

提示:
* $1 <= n <= 1000$

Solution:
### 模拟

根据题意进行模拟即可,分两步进行计算。

先计算完整周:共 $a = \left \lfloor \frac{n}{7} \right \rfloor$ 周,第一周起始日金额为 $1$,每周的起始日的金额递增 $1$,周内金额可使用「等差数列求和」公式直接求解。

然后再计算最后一周(若有)的金额:共 $b = n \pmod 7$ 天,使用紧接的起始日金额继续进行累加即可。

代码:
```Java
class Solution {
    public int totalMoney(int n) {
        int a = n / 7, b = n % 7;
        int ans = 0, k = 1;
        while (a-- > 0) {
            ans += (k + (k + 6)) * 7 / 2;
            k++;
        }
        while (b-- > 0) ans += k++;
        return ans;
    }
}
```
* 时间复杂度:$O(a + b)$
* 空间复杂度:$O(1)$

---

### (优化)模拟

更进一步,每个完整周的起始日金额相比上周起始日金额多 $1$,同时周内每日金额递增 $1$,因此相邻完整周的金额之和也满足「等差」性质,可直接使用「等差数列求和」公式 $O(1)$ 求解完整周部分的金额之和;最后一周(若有)的金额也是同理。

代码:
```Java
class Solution {
    public int totalMoney(int n) {
        int a = n / 7, b = n % 7;
        int a1 = (1 + 7) * 7 / 2, an = (a + (a + 6)) * 7 / 2;
        int s1 = (a1 + an) * a / 2;
        a++;
        int s2 = (a + (a + b - 1)) * b / 2;
        return s1 + s2;
    }
}
```
* 时间复杂度:$O(1)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1716` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1711-1720/1719. 重构一棵树的方案数（困难）.md

1719. 重构一棵树的方案数

Question:
给你一个数组 `pairs`,其中 $pairs[i] = [x_i, y_i]$ ,并且满足:

* `pairs` 中没有重复元素
* $x_i < y_i$

令 ways 为满足下面条件的有根树的方案数:

* 树所包含的所有节点值都在 `pairs` 中。
* 一个数对 $[x_i, y_i]$ 出现在 `pairs` 中 当且仅当 $x_i$ 是 $y_i$ 的祖先或者 $y_i$ 是 $x_i$ 的祖先。
* 注意:构造出来的树不一定是二叉树。

两棵树被视为不同的方案当存在至少一个节点在两棵树中有不同的父节点。

请你返回:

* 如果 ways == 0 ,返回 0 。
* 如果 ways == 1 ,返回 1 。
* 如果 ways > 1 ,返回 2 。

一棵 有根树 指的是只有一个根节点的树,所有边都是从根往外的方向。

我们称从根到一个节点路径上的任意一个节点(除去节点本身)都是该节点的 祖先 。根节点没有祖先。

示例 1:

```
输入:pairs = [[1,2],[2,3]]

输出:1

解释:如上图所示,有且只有一个符合规定的有根树。
```
示例 2:

```
输入:pairs = [[1,2],[2,3],[1,3]]

输出:2

解释:有多个符合规定的有根树,其中三个如上图所示。
```
示例 3:
```
输入:pairs = [[1,2],[2,3],[2,4],[1,5]]

输出:0

解释:没有符合规定的有根树。
```

提示:
* $1 <= pairs.length <= 10^5$
* $1 <= xi < yi <= 500$
* `pairs` 中的元素互不相同。

Solution:
### 构造 + 验证(合法性 + 非唯一性)

这道题是名副其实的困难题,我最早的提交时间是去年 $3$ 月,起初只能想到 $O(n * m)$ 的做法,对于一颗多叉树来说显然会 `TLE`,虽然到现在印象不深了,但如果不是之前做过,今天大概率会很晚才能发布题解。

该题突破口在于如何利用 `pairs` 构造出一种具体方案,然后辨别方案的合法性(是否返回 $0$)和方案中的某些点是否可相互替换(返回 $1$ 还是 $2$)。

给定 `pairs` 数组为父子关系,对于 $pairs[i] = (a,b)$ 而言,既可以是 $a$ 为 $b$ 祖宗节点,也可以是 $b$ 为 $a$ 祖宗节点。

题目的「当且仅当」含义为所有的 `pairs` 在具体方案中均有体现,因此先考虑如何使用 `pairs` 构造出具体方案。

如果使用一棵合法树而言:**每个子树的根节点在 `pairs` 中的出现数量满足大于等于其子节点在 `pairs` 中出现的数量(当某个根节点只有一个子节点时可取等号)。**

利用此性质,**虽然我们无法得知某个 $pairs[i]$ 真实的父子关系,但统计每个点的「度数」可以作为决策根节点的依据**。

具体的,遍历 `pairs` 并统计所有点的度数,同时为了后续构造可以快速查询某两个点是否为父子关系,使用邻接矩阵 $g$ 存储关系,并使用 `Set` 统计点集。

之后将点按照「度数」降序,从前往后处理每个点,尝试构建具体方案(第一个点作为具体方案的根节点)。

对于每个非根节点 $a$ 而言,按道理我们可以将其添加到任意一个「度数不小于 $cnt[i]$」且「与其存在父子关系的」$b$ 中,但这样构造方式,只能确保能够得到「合法性」的结果,会为对于后续的「非唯一性」验证带来巨大困难。

因此这里尝试构造的关键点在于:**我们为 $a$ 找 $b$ 的时候,应当找符合条件的、度数与 $a$ 相近的点。由于我们已经提前根据「度数」进行降序,这个找最优点的操作可从 $a$ 所在位置开始进行往回找,找到第一个满足「与 $a$ 存在父子关系」的点 $b$ 作为具体方案中 $a$ 的根节点。**

这样的构造逻辑为后续的「非唯一性」验证带来的好处是:**如果存在多个点能够相互交换位置的话,其在具体方案中必然为直接的父子关系,即我们能够通过判断 $cnts[i]$ 和 $cnts[fa[i]]$ 是否相等,来得知在具体方案中点 $i$ 和 $fa[i]$ 能够交换,并且如果能够交换,具体方案的合法性不会发生改变。**

> 一些细节:`pairs` 的数据范围为 $10^4$,而后续的尝试构造,最坏情况下点数也在这个数量级上,为了防止在复杂度为 $O(n^2)$ 的尝试构造上耗费大量时间,可以增加 $m < n - 1$ 的判断,在点数为 $n$ 的情况下,父子关系的最小值为 $n - 1$,当且仅当有一个根节点,其余均为叶子节点时取得,因此如果父子关系数量小于 $n - 1$,必然不为单棵子树,而是森林。

代码:
```Java
class Solution {
    int N = 510;
    int[] cnts = new int[N], fa = new int[N];
    boolean[][] g = new boolean[N][N];
    public int checkWays(int[][] pairs) {
        int m = pairs.length;
        Set<Integer> set = new HashSet<>();
        for (int[] p : pairs) {
            int a = p[0], b = p[1];
            g[a][b] = g[b][a] = true;
            cnts[a]++; cnts[b]++;
            set.add(a); set.add(b);
        }
        List<Integer> list = new ArrayList<>(set);
        Collections.sort(list, (a,b)->cnts[b]-cnts[a]);
        int n = list.size(), root = list.get(0);
        if (m < n - 1) return 0; // 森林
        fa[root] = -1;
        for (int i = 1; i < n; i++) {
            int a = list.get(i);
            boolean ok = false;
            for (int j = i - 1; j >= 0 && !ok; j--) {
                int b = list.get(j);
                if (g[a][b]) {
                    fa[a] = b;
                    ok = true;
                }
            }
            if (!ok) return 0;
        }
        int c = 0, ans = 1;
        for (int i : set) {
            int j = i;
            while (fa[j] != -1) {
                if (!g[i][fa[j]]) return 0;
                if (cnts[i] == cnts[fa[j]]) ans = 2;
                c++;
                j = fa[j];
            }
        }
        return c < m ? 0 : ans;
    }
}
```
* 时间复杂度:令 $n$ 为 `pairs` 的长度,统计度数和点集复杂度为 $O(n)$;最多有 $2 * n$ 个点,将点根据度数进行排序复杂度为 $O(n\log{n})$;尝试根据度数构造树的复杂度为 $O(n^2)$;检验树的合法性复杂度为 $O(n + m)$,其中 $m$ 是构造树的边数,数量级上与 $n$ 相等。整体复杂度为 $O(n^2)$
* 空间复杂度:$O(n^2)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1719` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1711-1720/1720. 解码异或后的数组（简单）.md

1720. 解码异或后的数组

Question:
未知 整数数组 arr 由 n 个非负整数组成。

经编码后变为长度为 n - 1 的另一个整数数组 encoded ,其中 encoded[i] = arr[i] XOR arr[i + 1] 。

例如,arr = [1,0,2,1] 经编码后得到 encoded = [1,2,3] 。

给你编码后的数组 encoded 和原数组 arr 的第一个元素 first(arr[0])。

请解码返回原数组 arr 。

可以证明答案存在并且是唯一的。

示例 1:
```
输入:encoded = [1,2,3], first = 1

输出:[1,0,2,1]

解释:若 arr = [1,0,2,1] ,那么 first = 1 且 encoded = [1 XOR 0, 0 XOR 2, 2 XOR 1] = [1,2,3]
```
示例 2:
```
输入:encoded = [6,2,7,3], first = 4

输出:[4,2,0,7,4]
```

提示:
* 2 <= n <= $10^4$
* encoded.length == n - 1
* 0 <= encoded[i] <= $10^5$
* 0 <= first <= $10^5$

Solution:
### 模拟

这是道模拟(重拳出击)题。

根据题目给定的规则,利用如下异或性质从头做一遍即可:

1. 相同数值异或,结果为 $0$
2. 任意数值与 $0$ 进行异或,结果为数值本身
3. 异或本身满足交换律

已知 `encoded[i-1] = arr[i-1] XOR arr[i]`,将等式两边同时「异或」上 `arr[i-1]`。可得:

1. `encoded[i-1] XOR arr[i-1] = arr[i-1] XOR arr[i] XOR arr[i-1]`
2. 结合「性质三」和「性质一」,可化简「右式」得 `encoded[i-1] XOR arr[i-1] = arr[i] XOR 0`
3. 结合「性质二」,可化简「右式」得 `encoded[i-1] XOR arr[i-1] = arr[i]`

代码:
```Java
class Solution {
    public int[] decode(int[] encoded, int first) {
        int n = encoded.length + 1;
        int[] ans = new int[n];
        ans[0] = first;
        for (int i = 1; i < n; i++) {
            ans[i] = ans[i - 1] ^ encoded[i - 1];
        }
        return ans;
    }
}
```
* 时间复杂度:$O(n)$
* 空间复杂度:构建与 `encoded` 同等规模的答案。复杂度为 $O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1720` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1721-1730/1723. 完成所有工作的最短时间（困难）.md

1723. 完成所有工作的最短时间

Question:
给你一个整数数组 `jobs` ,其中 $jobs[i]$ 是完成第 $i$ 项工作要花费的时间。

请你将这些工作分配给 $k$ 位工人。所有工作都应该分配给工人,且每项工作只能分配给一位工人。

工人的 工作时间 是完成分配给他们的所有工作花费时间的总和。

请你设计一套最佳的工作分配方案,使工人的 最大工作时间 得以 最小化 。

返回分配方案中尽可能「最小」的 最大工作时间 。

示例 1:
```
输入:jobs = [3,2,3], k = 3

输出:3

解释:给每位工人分配一项工作,最大工作时间是 3 。
```
示例 2:
```
输入:jobs = [1,2,4,7,8], k = 2

输出:11

解释:按下述方式分配工作:
1 号工人:1、2、8(工作时间 = 1 + 2 + 8 = 11)
2 号工人:4、7(工作时间 = 4 + 7 = 11)
最大工作时间是 11 。
```

提示:
* $1 <= k <= jobs.length <= 12$
* $1 <= jobs[i] <= 10^7$

Solution:
### DFS(TLE)

一看数据范围只有 $12$,我猜不少同学上来就想 `DFS`,但是注意 `n` 和 `k` 同等规模的,爆搜(`DFS`)的复杂度是 $O(k^n)$ 的。

那么极限数据下的计算量为 $12^{12}$,远超运算量 $10^7$。

抱着侥幸的心理一运行,很顺利的卡在了 $43/60$ 个数据:

```Java
[254,256,256,254,251,256,254,253,255,251,251,255] // n = 12
10 // k = 10
```

代码:
```Java
class Solution {
    int[] jobs;
    int n, k;
    int ans = 0x3f3f3f3f;
    public int minimumTimeRequired(int[] _jobs, int _k) {
        jobs = _jobs;
        n = jobs.length;
        k = _k;
        int[] sum = new int[k];
        dfs(0, sum, 0);
        return ans;
    }
    /**
     * u   : 当前处理到那个 job
     * sum : 工人的分配情况          例如:sum[0] = x 代表 0 号工人工作量为 x
     * max : 当前的「最大工作时间」
     */
    void dfs(int u, int[] sum, int max) {
        if (max >= ans) return;
        if (u == n) {
            ans = max;
            return;
        }
        for (int i = 0; i < k; i++) {
            sum[i] += jobs[u];
            dfs(u + 1, sum, Math.max(sum[i], max));
            sum[i] -= jobs[u];
        }
    }
}
```
* 时间复杂度:$O(k^n)$
* 空间复杂度:$O(k)$

---

### 优先分配「空闲工人」的 DFS

那么 `DFS` 就没法过了吗?

除了 `max >= ans` 以外,我们还要做些别的剪枝吗?

我们可以重新审视一下这道题。

**题目其实是让我们将 $n$ 个数分为 $k$ 份,并且尽可能让 $k$ 份平均。这样的「最大工作时间」才是最小的。**

但在朴素的 `DFS` 中,我们是将每个任务依次分给每个工人,并递归此过程。

**对应的递归树其实是一颗高度为 $n$ 的 $k$ 阶树。**

所以其实我们第一次更新的 `ans` 其实是「最差」的答案(所有的任务都会分配给 $0$ 号工人),最差的 `ans` 为所有的 `job` 的总和(带编号的方块代表工人):

因此我们朴素版的 `DFS` 其实是弱化了 `max >= ans` 剪枝效果的。

那么想要最大化剪枝效果,并且尽量让 $k$ 份平均的话,我们应当调整我们对于「递归树」的搜索方向:将任务优先分配给「空闲工人」(带编号的方块代表工人):

树还是那棵树,但是搜索调整分配优先级后,我们可以在首次取得一个「较好」的答案,来增强我们的  `max >= ans` 剪枝效益。

事实上,当做完这个调整,我们能实现从 TLE 到 99% 的提升 🤣 🤣

代码:
```Java
class Solution {
    int[] jobs;
    int n, k;
    int ans = 0x3f3f3f3f;
    public int minimumTimeRequired(int[] _jobs, int _k) {
        jobs = _jobs;
        n = jobs.length;
        k = _k;
        int[] sum = new int[k];
        dfs(0, 0, sum, 0);
        return ans;
    }
    /**
     * 【补充说明】不理解可以看看下面的「我猜你问」的 Q5 哦 ~
     *
     * u     : 当前处理到那个 job
     * used  : 当前分配给了多少个工人了
     * sum   : 工人的分配情况          例如:sum[0] = x 代表 0 号工人工作量为 x
     * max   : 当前的「最大工作时间」
     */
    void dfs(int u, int used, int[] sum, int max) {
        if (max >= ans) return;
        if (u == n) {
            ans = max;
            return;
        }
        // 优先分配给「空闲工人」
        if (used < k) {
            sum[used] = jobs[u];
            dfs(u + 1, used + 1, sum, Math.max(sum[used], max));
            sum[used] = 0;
        }
        for (int i = 0; i < used; i++) {
            sum[i] += jobs[u];
            dfs(u + 1, used, sum, Math.max(sum[i], max));
            sum[i] -= jobs[u];
        }
    }
}
```
* 时间复杂度:$O(k^n)$
* 空间复杂度:$O(k)$

---

### 模拟退火

事实上,这道题还能使用「模拟退火」进行求解。

**因为将 $n$ 个数划分为 $k$ 份,等效于用 $n$ 个数构造出一个「特定排列」,然后对「特定排列」进行固定模式的任务分配逻辑,就能实现「答案」与「最优排列」的对应关系。**

基于此,我们可以使用「模拟退火」进行求解。

单次迭代的基本流程:

1. 随机选择两个下标,计算「交换下标元素前对应序列的得分」&「交换下标元素后对应序列的得分」
2. 如果温度下降(交换后的序列更优),进入下一次迭代
3. 如果温度上升(交换前的序列更优),以「一定的概率」恢复现场(再交换回来)

> 对于一个能够运用模拟退火求解的问题,最核心的是如何实现 `calc` 方法(即如何定义一个具体方案的得分),其余均为模板内容。

代码:
```Java
class Solution {
    int[] jobs;
    int[] works = new int[20];
    int n, k;
    int ans = 0x3f3f3f3f;
    Random random = new Random(20210508);
    // 最高温/最低温/变化速率(以什么速度进行退火,系数越低退火越快,迭代次数越少,落入「局部最优」(WA)的概率越高;系数越高 TLE 风险越大)
    double hi = 1e8, lo = 1e-4, fa = 0.90;
    // 迭代次数,与变化速率同理
    int N = 400;

    // 计算当前 jobs 序列对应的最小「最大工作时间」是多少
    int calc() {
        Arrays.fill(works, 0);
        for (int i = 0; i < n; i++) {
            // [固定模式分配逻辑] : 每次都找最小的 worker 去分配
            int idx = 0, cur = works[idx];
            for (int j = 0; j < k; j++) {
                if (works[j] < cur) {
                    cur = works[j];
                    idx = j;
                }
            }
            works[idx] += jobs[i];
        }
        int cur = 0;
        for (int i = 0; i < k; i++) cur = Math.max(cur, works[i]);
        ans = Math.min(ans, cur);
        return cur;
    }
    void shuffle(int[] nums) {
        for (int i = n; i > 0; i--) {
            int idx = random.nextInt(i);
            swap(nums, idx, i - 1);
        }
    }
    void swap(int[] arr, int i, int j) {
        int c = arr[i];
        arr[i] = arr[j];
        arr[j] = c;
    }
    void sa() {
        shuffle(jobs);
        for (double t = hi; t > lo; t *= fa) {
            int a = random.nextInt(n), b = random.nextInt(n);
            int prev = calc(); // 退火前
            swap(jobs, a, b);
            int cur = calc(); // 退火后
            int diff = cur - prev;
            // 退火为负收益(温度上升),以一定概率回退现场
            if (Math.log(diff / t) > random.nextDouble()) swap(jobs, a, b);
        }
    }
    public int minimumTimeRequired(int[] _jobs, int _k) {
        jobs = _jobs;
        n = jobs.length;
        k = _k;
        while (N-- > 0) sa();
        return ans;
    }
}
```
* 时间复杂度:启发式搜索不讨论时空复杂度
* 空间复杂度:启发式搜索不讨论时空复杂度

---

### 我猜你问

**Q0. 模拟退火有何风险?**

随机算法,会面临 `WA` 和 `TLE` 风险。

**Q1. 模拟退火中的参数如何敲定的?**

根据经验猜的,然后提交。根据结果是 `WA` 还是 `TLE` 来决定之后的调参方向。如果是 `WA` 说明部分数据落到了「局部最优」或者尚未达到「全局最优」。

**Q2. 参数如何调整?**

如果是 `WA` 了,一般我是优先调大 fa 参数,使降温变慢,来变相增加迭代次数;如果是 `TLE` 了,一般是优先调小 fa 参数,使降温变快,减小迭代次数。总迭代参数 `N` 也是同理。

可以简单理解调大 fa 代表将「大步」改为「baby step」,防止越过全局最优,同时增加总执行步数。

可以结合我不同的 fa 参数的提交结果来感受下:

**Q3. 关于「模拟退火」正确性?**

随机种子不变,测试数据不变,迭代参数不变,那么退火的过程就是恒定的,必然都能找到这些测试样例的「全局最优」。

**Q4. 需要掌握「模拟退火」吗?**

还是那句话,特别特别特别有兴趣的可以去了解一下。

但绝对是在你已经彻底理解「剪枝 DFS」和我没写的「状态压缩 DP」之后再去了解。

**Q5. 在「剪枝 DFS」中为什么「优先分配空闲工人」的做法是对的?**

首先要明确,递归树还是那棵递归树。

**所谓的「优先分配空闲工人」它并不是「贪心模拟」思路,而只是一个「调整搜索顺序」的做法。**

「优先分配空闲工人」不代表不会将任务分配给有工作的工人,仅仅代表我们先去搜索那些「优先分配空闲工人」的方案。

然后将得到的「合法解」配合 `max >= ans` 去剪枝掉那些「必然不是最优解」的方案。

**本质上,我们并没有主动的否决某些方案(也就是我们并没有改动递归树),我们只是调整了搜索顺序来剪枝掉了一些「必然不是最优」的搜索路径。**

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1723` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1721-1730/1725. 可以形成最大正方形的矩形数目（简单）.md

1725. 可以形成最大正方形的矩形数目

Question:
给你一个数组 `rectangles`,其中 $rectangles[i] = [l_i, w_i]$ 表示第 $i$ 个矩形的长度为 $l_i$ 、宽度为 $w_i$ 。

如果存在 `k` 同时满足 $k <= l_i$ 和 $k <= w_i$ ,就可以将第 `i` 个矩形切成边长为 `k` 的正方形。例如,矩形 $[4,6]$ 可以切成边长最大为 $4$ 的正方形。

设 `maxLen` 为可以从矩形数组 `rectangles` 切分得到的 最大正方形 的边长。

请你统计有多少个矩形能够切出边长为 `maxLen` 的正方形,并返回矩形 数目 。

示例 1:
```
输入:rectangles = [[5,8],[3,9],[5,12],[16,5]]

输出:3

解释:能从每个矩形中切出的最大正方形边长分别是 [5,3,5,5] 。
最大正方形的边长为 5 ,可以由 3 个矩形切分得到。
```
示例 2:
```
输入:rectangles = [[2,3],[3,7],[4,3],[3,7]]

输出:3
```

提示:
* $1 <= rectangles.length <= 1000$
* $rectangles[i].length == 2$
* $1 <= li, wi <= 10^9$
* $li != wi$

Solution:
### 模拟

根据题意,对于任意一个矩形 $rectangles[i]$ 而言,其能分割出的最大方形边长为 $\min(rectangles[i][0], rectangles[i][1])$。

我们在遍历过程中使用变量 `max` 维护最大边长,同时使用 `ans` 记录能够分割出最大边长 `max` 的矩形数量。

代码:
```Java
class Solution {
    public int countGoodRectangles(int[][] rectangles) {
        int max = 0, ans = 0;
        for (int[] r : rectangles) {
            int cur = Math.min(r[0], r[1]);
            if (cur == max) ans++;
            else if (cur > max) {
                max = cur; ans = 1;
            }
        }
        return ans;
    }
}
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1725` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


Question:
一只猫和一只老鼠在玩一个叫做猫和老鼠的游戏。

它们所处的环境设定是一个 `rows x cols` 的方格 `grid` ,其中每个格子可能是一堵墙、一块地板、一位玩家(猫或者老鼠)或者食物。

* 玩家由字符 `'C'` (代表猫)和 `'M'` (代表老鼠)表示。
* 地板由字符 `'.'` 表示,玩家可以通过这个格子。
* 墙用字符 `'#'` 表示,玩家不能通过这个格子。
* 食物用字符 `'F'` 表示,玩家可以通过这个格子。
* 字符 `'C'` , `'M'` 和 `'F'` 在 `grid` 中都只会出现一次。

猫和老鼠按照如下规则移动:

* 老鼠 先移动 ,然后两名玩家轮流移动。
* 每一次操作时,猫和老鼠可以跳到上下左右四个方向之一的格子,他们不能跳过墙也不能跳出 grid 。
* `catJump` 和 `mouseJump` 是猫和老鼠分别跳一次能到达的最远距离,它们也可以跳小于最大距离的长度。
* 它们可以停留在原地。
* 老鼠可以跳跃过猫的位置。

游戏有 $4$ 种方式会结束:

* 如果猫跟老鼠处在相同的位置,那么猫获胜。
* 如果猫先到达食物,那么猫获胜。
* 如果老鼠先到达食物,那么老鼠获胜。
* 如果老鼠不能在 $1000$ 次操作以内到达食物,那么猫获胜。

给你 `rows x cols` 的矩阵 `grid` 和两个整数 `catJump` 和 `mouseJump`,双方都采取最优策略,如果老鼠获胜,那么请你返回 `true`,否则返回 `false`。

示例 1:

```
输入:grid = ["####F","#C...","M...."], catJump = 1, mouseJump = 2

输出:true

解释:猫无法抓到老鼠,也没法比老鼠先到达食物。
```
示例 2:

```
输入:grid = ["M.C...F"], catJump = 1, mouseJump = 4

输出:true
```
示例 3:
```
输入:grid = ["M.C...F"], catJump = 1, mouseJump = 3

输出:false
```
示例 4:
```
输入:grid = ["C...#","...#F","....#","M...."], catJump = 2, mouseJump = 5

输出:false
```
示例 5:
```
输入:grid = [".M...","..#..","#..#.","C#.#.","...#F"], catJump = 3, mouseJump = 1

输出:true
```

提示:
* $rows == grid.length$
* $cols = grid[i].length$
* $1 <= rows, cols <= 8$
* $grid[i][j]$ 只包含字符 `'C'` ,`'M'` ,`'F'` ,`'.'` 和 `'#'` 。
* `grid` 中只包含一个 `'C'` ,`'M'` 和 `'F'` 。
* $1 <= catJump, mouseJump <= 8$

Solution:
### 博弈论 DP

当时在 [(题解) 913. 猫和老鼠](https://leetcode.cn/problems/cat-and-mouse/solution/gong-shui-san-xie-dong-tai-gui-hua-yun-y-0bx1/) 没能证出来更小 $K$ 值(回合数)的正确性,用的 $2n^2$ 做的 ,其余题解说 $2 n$ 合法,后来也被证实是错误的。

对于本题如果用相同的分析思路,状态数多达 $8 \times 8 \times 8 \times 8 \times 2 = 8192$ 种,题目很贴心调整了规则为 $1000$ 步以内为猫获胜,但证明 $K$ 的理论上界仍是困难(上次分析不出来,这次压根不想分析

如果忽略 $K$ 值分析,代码还是很好写的:定义函数 `int dfs(int x, int y, int p, int q, int k)` 并配合记忆化搜索,其中鼠位于 $(x, y)$,猫位于 $(p, q)$,当前轮数为 $k$(由 $k$ 的奇偶性可知是谁的回合)。

对边界情况进行讨论,移动过程中按照规则进行(四联通,移动最大距离为 `mouseJump` 和 `catJump`),注意一旦遇到边界或者墙就要截断。

Java 使用静态数组,用一个 `int` 代表双方所在位置,最大回合数 $K = 1000$,`2022-05-10` 可以过。这道题给的时间上限很高,我调整为 $K = 1500$ 跑成 $2.5s$ 也可以过。本来想要加个卡常,每 $200$ 轮检查一下运行总时长,尽量将时间压在 $850ms$ 以内,现在看来好像用不到。

代码:
```Java
import java.time.Clock;
class Solution {
    static int S = 8 * 8 * 8 * 8, K = 1000;
    static int[][] f = new int[S][K]; // mouse : 0 / cat : 1
    String[] g;
    int n, m, a, b, tx, ty;
    int[][] dirs = new int[][]{{1,0}, {-1,0}, {0,1}, {0,-1}};
    // mouse : (x, y) / cat : (p, q)
    int dfs(int x, int y, int p, int q, int k) {
        int state = (x << 9) | (y << 6) | (p << 3) | q;
        if (k == K - 1) return f[state][k] = 1;
        if (x == p && y == q) return f[state][k] = 1;
        if (x == tx && y == ty) return f[state][k] = 0;
        if (p == tx && q == ty) return f[state][k] = 1;
        if (f[state][k] != -1) return f[state][k];
        if (k % 2 == 0) { // mouse
            for (int[] di : dirs) {
                for (int i = 0; i <= b; i++) {
                    int nx = x + di[0] * i, ny = y + di[1] * i;
                    if (nx < 0 || nx >= n || ny < 0 || ny >= m) break;
                    if (g[nx].charAt(ny) == '#') break;
                    if (dfs(nx, ny, p, q, k + 1) == 0) return f[state][k] = 0;
                }
            }
            return f[state][k] = 1;
        } else { // cat
            for (int[] di : dirs) {
                for (int i = 0; i <= a; i++) {
                    int np = p + di[0] * i, nq = q + di[1] * i;
                    if (np < 0 || np >= n || nq < 0 || nq >= m) break;
                    if (g[np].charAt(nq) == '#') break;
                    if (dfs(x, y, np, nq, k + 1) == 1) return f[state][k] = 1;
                }
            }
            return f[state][k] = 0;
        }
    }
    public boolean canMouseWin(String[] grid, int catJump, int mouseJump) {
        g = grid;
        n = g.length; m = g[0].length(); a = catJump; b = mouseJump;
        for (int i = 0; i < S; i++) Arrays.fill(f[i], -1);
        int x = 0, y = 0, p = 0, q = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (g[i].charAt(j) == 'M') {
                    x = i; y = j;
                } else if (g[i].charAt(j) == 'C') {
                    p = i; q = j;
                } else if (g[i].charAt(j) == 'F') {
                    tx = i; ty = j;
                }
            }
        }
        return dfs(x, y, p, q, 0) == 0;
    }
}
```
* 时间复杂度:令 $n$ 和 $m$ 分别为矩阵的长宽,最长移动距离为 $L$,复杂度为 $O(n^2 \times m^2 \times 1000 \times 4 \times L)$
* 空间复杂度:$O(n^2 \times m^2 \times 1000)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1728` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


1732. 找到最高海拔

Question:
有一个自行车手打算进行一场公路骑行,这条路线总共由 $n + 1$ 个不同海拔的点组成。自行车手从海拔为 `0` 的点 `0` 开始骑行。

给你一个长度为 `n` 的整数数组 `gain`,其中 `gain[i]` 是点 `i` 和点 `i + 1` 的 净海拔高度差($0 <= i < n$)。请你返回 最高点的海拔 。

示例 1:
```
输入:gain = [-5,1,5,0,-7]

输出:1

解释:海拔高度依次为 [0,-5,-4,1,1,-6] 。最高海拔为 1 。
```
示例 2:
```
输入:gain = [-4,-3,-2,-1,4,3,2]

输出:0

解释:海拔高度依次为 [0,-4,-7,-9,-10,-6,-3,-1] 。最高海拔为 0 。
```

提示:
* $n = gain.length$
* $1 <= n <= 100$
* $-100 <= gain[i] <= 100$

Solution:
### 模拟

根据题意进行模拟即可。

Java 代码:
```Java
class Solution {
    public int largestAltitude(int[] g) {
        int cur = 0, ans = 0;
        for (int x : g) {
            cur += x;
            ans = Math.max(ans, cur);
        }
        return ans;
    }
}
```
TypeScript 代码:
```TypeScript
function largestAltitude(g: number[]): number {
    let cur = 0, ans = 0
    for (let x of g) {
        cur += x
        ans = Math.max(ans, cur)
    }
    return ans
}
```
Python3 代码:
```Python
class Solution:
    def largestAltitude(self, g: List[int]) -> int:
        cur, ans = 0, 0
        for x in g:
            cur += x
            ans = max(ans, cur)
        return ans
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1732` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1731-1740/1734. 解码异或后的排列（中等）.md

1734. 解码异或后的排列

Question:
给你一个整数数组 perm ,它是前 n 个正整数的排列,且 n 是个 奇数 。

它被加密成另一个长度为 n - 1 的整数数组 encoded ,满足 encoded[i] = perm[i] XOR perm[i + 1] 。比方说,如果 perm = [1,3,2] ,那么 encoded = [2,1] 。

给你 encoded 数组,请你返回原始数组 perm 。题目保证答案存在且唯一。

示例 1:
```
输入:encoded = [3,1]

输出:[1,2,3]

解释:如果 perm = [1,2,3] ,那么 encoded = [1 XOR 2,2 XOR 3] = [3,1]
```
示例 2:
```
输入:encoded = [6,5,4,6]

输出:[2,4,1,5,3]
```

提示:
* 3 <= n < $10^5$
* n 是奇数。
* encoded.length == n - 1

Solution:
### 基本分析

我们知道异或运算有如下性质:
1. 相同数值异或,结果为 $0$
2. 任意数值与 $0$ 进行异或,结果为数值本身
3. 异或本身满足交换律

本题与  [1720. 解码异或后的数组](https://leetcode-cn.com/problems/decode-xored-array/solution/gong-shui-san-xie-li-yong-yi-huo-xing-zh-p1bi/) 的主要区别是没有给出首位元素。

因此,求得答案数组的「首位元素」或者「结尾元素」可作为本题切入点。

---

### 数学 & 模拟

我们定义答案数组为 `ans[]`,`ans[]` 数组的长度为 `n`,且 `n` 为奇数。

即有 $[ans[0], ans[1], ans[2], ... , ans[n - 1]]$。

给定的数组 `encoded[]` 其实是 $[ans[0] ⊕ ans[1], ans[1] ⊕ ans[2], ... , ans[n - 3] ⊕ ans[n - 2], ans[n - 2] ⊕ ans[n - 1]]$,长度为 `n - 1`。

由于每相邻一位会出现相同的数组成员 `ans[x]`,考虑“每隔一位”进行异或:

1. **从 `encoded[]` 的第 $0$ 位开始,每隔一位进行异或:可得 $ans[0] ⊕ ans[1] ⊕ ... ⊕ ans[n - 2]$,即除了 `ans[]` 数组中的 $ans[n - 1]$ 以外的所有异或结果。**

2. **利用 `ans[]` 数组是 `n` 个正整数的排列,我们可得 $ans[0] ⊕ ans[1] ⊕ ... ⊕ ans[n - 2] ⊕ ans[n - 1]$,即 `ans[]` 数组中所有元素的异或结果。**

将两式进行「异或」,可得 $ans[n - 1]$。

有了结尾元素后,问题变为与 [1720. 解码异或后的数组](https://leetcode-cn.com/problems/decode-xored-array/solution/gong-shui-san-xie-li-yong-yi-huo-xing-zh-p1bi/) 类似的模拟题。

代码:
```Java
class Solution {
    public int[] decode(int[] encoded) {
        int n = encoded.length + 1;
        int[] ans = new int[n];
        // 求得除了 ans[n - 1] 的所有异或结果
        int a = 0;
        for (int i = 0; i < n - 1; i += 2) a ^= encoded[i];
        // 求得 ans 的所有异或结果
        int b = 0;
        for (int i = 1; i <= n; i++) b ^= i;
        // 求得 ans[n - 1] 后,从后往前做
        ans[n - 1] = a ^ b;
        for (int i = n - 2; i >= 0; i--) {
            ans[i] = encoded[i] ^ ans[i + 1];
        }
        return ans;
    }
}
```
* 时间复杂度:$O(n)$
* 空间复杂度:构建同等数量级的答案数组。复杂度为 $O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1734` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1731-1740/1736. 替换隐藏数字得到的最晚时间（简单）.md

1736. 替换隐藏数字得到的最晚时间

Question:
给你一个字符串 `time`,格式为 `hh:mm`(小时:分钟),其中某几位数字被隐藏(用 `?` 表示)。

有效的时间为 `00:00` 到 `23:59` 之间的所有时间,包括 `00:00` 和 `23:59` 。

替换 `time` 中隐藏的数字,返回你可以得到的最晚有效时间。

示例 1:
```
输入:time = "2?:?0"

输出:"23:50"

解释:以数字 '2' 开头的最晚一小时是 23 ,以 '0' 结尾的最晚一分钟是 50 。
```
示例 2:
```
输入:time = "0?:3?"

输出:"09:39"
```
示例 3:
```
输入:time = "1?:22"

输出:"19:22"
```

提示:
* `time` 的格式为 `hh:mm`
* 题目数据保证你可以由输入的字符串生成有效的时间

Solution:
### 贪心 + 模拟

规则十分简单,对每一位进行分情况讨论即可:

* 第一位:如果需要被替换,优先替换为 `2`,当然前提是第二位不能超过 `4`。否则会出现 `24:xx`、`25:xx` 等;
* 第二位:如果需要被替换,根据第一位是什么,决定替换为 `9` 还是 `3`;
* 第三位:固定为 `:`;
* 第四位:如果需要被替换,替换为 `5`;
* 第五位:如果需要被替换,替换为 `9`。

代码:
```Java
class Solution {
    public String maximumTime(String time) {
        StringBuilder sb = new StringBuilder();
        sb.append(time.charAt(0) == '?' ? (time.charAt(1) == '?' || time.charAt(1) < '4') ? '2' : '1' : time.charAt(0));
        sb.append(time.charAt(1) == '?' ? sb.charAt(0) == '2' ? '3' : '9' : time.charAt(1));
        sb.append(':');
        sb.append(time.charAt(3) == '?' ? '5' : time.charAt(3));
        sb.append(time.charAt(4) == '?' ? '9' : time.charAt(4));
        return sb.toString();
    }
}
```
* 时间复杂度:$O(1)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1736` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1731-1740/1737. 满足三条件之一需改变的最少字符数（中等）.md

1737. 满足三条件之一需改变的最少字符数

Question:
给你两个字符串 `a` 和 `b` ,二者均由小写字母组成。

一步操作中,你可以将 `a` 或 `b` 中的 任一字符 改变为 任一小写字母 。

操作的最终目标是满足下列三个条件 之一 :
* `a` 中的 每个字母 在字母表中 **严格小于**  `b` 中的 每个字母 。
* `b` 中的 每个字母 在字母表中 **严格小于**  `a` 中的 每个字母 。
* `a` 和 `b` 都 由 同一个 字母组成。

返回达成目标所需的 最少 操作数。

示例 1:
```
输入:a = "aba", b = "caa"

输出:2

解释:满足每个条件的最佳方案分别是:
1) 将 b 变为 "ccc",2 次操作,满足 a 中的每个字母都小于 b 中的每个字母;
2) 将 a 变为 "bbb" 并将 b 变为 "aaa",3 次操作,满足 b 中的每个字母都小于 a 中的每个字母;
3) 将 a 变为 "aaa" 并将 b 变为 "aaa",2 次操作,满足 a 和 b 由同一个字母组成。
最佳的方案只需要 2 次操作(满足条件 1 或者条件 3)。
```
示例 2:
```
输入:a = "dabadd", b = "cda"

输出:3

解释:满足条件 1 的最佳方案是将 b 变为 "eee" 。
```

提示:
* $1 <= a.length, b.length <= 10^5$
* `a` 和 `b` 只由小写字母组成

Solution:
### 计数 + 枚举

使用 `c1` 和 `c2` 对字符串 `a` 和 `b` 分别进行词频统计,记字符串 `a` 和 `b` 的长度为 $n$ 和 $m$。

然后枚举字符 $i$,分别对三种情况的修改次数进行统计:

1. 对应条件 $1$:目的是要将字符串 `a` 中所有的字符变得「严格小于」字符 $i$,将字符串 `b` 中的所有字符变成「不小于/大于等于」字符 $i$。
    这可以分别统计 `a` 中大小满足「大于等于」字符 $i$ 的字符数量,以及 `b` 中大小满足「小于」字符 $i$ 数量,两者之和即是满足该条件的最小修改次数。
    注意,当 $i = 0$(含义为枚举到小写字母 $a$)时,需要跳过,因为不存在值大小「严格小于」字母 $a$ 的字符,即无法做到将某个字符串替换成所有字符都「严格小于」字母 $a$;
2. 对应条件 $2$:与条件 $1$ 同理;
3. 对应条件 $3$:如果要将两字符的所有字符都变成 $i$,其中字符串 `a` 要修改的字符数为 $ca = n - c1[i]$,字符串 `b` 要修改的字符数为 $cb = m - c2[i]$,总修改次数为 $ca + cb$。

枚举完所有的字符 $i$ 后,统计到的所有修改次数的最小值即是答案。

代码:
```Java
class Solution {
    public int minCharacters(String a, String b) {
        int n = a.length(), m = b.length(), ans = 0x3f3f3f3f;
        int[] c1 = new int[26], c2 = new int[26];
        for (char c : a.toCharArray()) c1[c - 'a']++;
        for (char c : b.toCharArray()) c2[c - 'a']++;
        for (int i = 0; i < 26 && ans != 0; i++) {
            // 3
            int ca = n - c1[i], cb = m - c2[i];
            ans = Math.min(ans, ca + cb);
            if (i == 0) continue;
            int r1 = 0, r2 = 0;
            // 1
            for (int j = i; j < 26; j++) r1 += c1[j];
            for (int j = 0; j < i; j++) r1 += c2[j];
            // 2
            for (int j = i; j < 26; j++) r2 += c2[j];
            for (int j = 0; j < i; j++) r2 += c1[j];
            ans = Math.min(ans, Math.min(r1, r2));
        }
        return ans;
    }
}
```
* 时间复杂度:统计词频的复杂度为 $O(n + m)$,统计答案的复杂度为 $O(C^2)$,其中 $C = 26$ 为字符集大小
* 空间复杂度:$O(C)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1737` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1731-1740/1738. 找出第 K 大的异或坐标值（中等）.md

1738. 找出第 K 大的异或坐标值

Question:
给你一个二维矩阵 matrix 和一个整数 k ,矩阵大小为 m x n 由非负整数组成。

矩阵中坐标 (a, b) 的 值 可由对所有满足 0 <= i <= a < m 且 0 <= j <= b < n 的元素 matrix[i][j](下标从 0 开始计数)执行异或运算得到。

请你找出 matrix 的所有坐标中第 k 大的值(k 的值从 1 开始计数)。

示例 1:
```
输入:matrix = [[5,2],[1,6]], k = 1

输出:7

解释:坐标 (0,1) 的值是 5 XOR 2 = 7 ,为最大的值。
```
示例 2:
```
输入:matrix = [[5,2],[1,6]], k = 2

输出:5

解释:坐标 (0,0) 的值是 5 = 5 ,为第 2 大的值。
```
示例 3:
```
输入:matrix = [[5,2],[1,6]], k = 3

输出:4

解释:坐标 (1,0) 的值是 5 XOR 1 = 4 ,为第 3 大的值。
```
示例 4:
```
输入:matrix = [[5,2],[1,6]], k = 4

输出:0

解释:坐标 (1,1) 的值是 5 XOR 2 XOR 1 XOR 6 = 0 ,为第 4 大的值。
```

提示:

* m == matrix.length
* n == matrix[i].length
* 1 <= m, n <= 1000
* 0 <= `matrix[i][j]` <= $10^6$
* 1 <= k <= m * n

Solution:
### 基本分析

根据题意,我们知道其实就是求「所有子矩阵中第 $k$ 大的异或和」,同时规定所有子矩阵的左上角端点为 $(0, 0)$。

数据范围为 $10^3$,因此「枚举所有右下角」并「每次计算子矩阵异或和」的朴素做法 $O(m^2 * n^2)$ 不用考虑。

要在全局中找最优,「枚举所有右下角」过程不可避免,我们可以优化「每次计算子矩阵异或和」的过程。

这个分析过程与 [1310. 子数组异或查询](https://leetcode-cn.com/problems/xor-queries-of-a-subarray/solution/gong-shui-san-xie-yi-ti-shuang-jie-shu-z-rcgu/) 类似。

**异或作为不进位加法,可以利用「偶数次异或结果为 $0$」的特性实现类似「前缀和」的容斥。这使得我们可以在 $O(1)$ 的复杂度内计算「某个子矩阵的异或和」。**

---

### 二维前缀异或 & 优先队列(堆)

创建二维数组 $sum[][]$,令 $sum[i][j]$ 为以 $(i, j)$ 为右下角的子矩阵的异或和,我们可以得出计算公式:

$$
sum[i][j] = sum[i - 1][j] ⊕ sum[i][j - 1] ⊕ sum[i - 1][j - 1] ⊕ matrix[i - 1][j - 1]
$$

剩下的问题是,如果从所有的「子矩阵异或和」找到第 $k$ 大的值。

变成了 [Top K](https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/solution/jian-da-ti-de-duo-chong-jie-fa-mou-pao-p-d1qi/) 问题,可以使用「排序」或「堆」进行求解。

具体的,我们可以建立一个大小为 $k$ 的小根堆,在计算二维前缀异或时,判断当前「子矩阵异或和」是否大于堆顶元素:

* 大于堆顶元素:当前子矩阵异或和可能是第 $k$ 大的值,堆顶元素不可能为第 $k$ 大的值。将堆顶元素弹出,并将当前子矩阵和加入堆中
* 小于堆顶元素:不会是第 $k$ 大的值,直接丢弃。
* 等于堆顶元素:有相同元素在堆中,直接丢弃。

最终的堆顶元素即为答案。

代码:
```Java
class Solution {
    public int kthLargestValue(int[][] mat, int k) {
        int m = mat.length, n = mat[0].length;
        int[][] sum = new int[m + 1][n + 1];
        PriorityQueue<Integer> q = new PriorityQueue<>(k, (a, b)->a - b);
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                sum[i][j] = sum[i - 1][j] ^ sum[i][j - 1] ^ sum[i - 1][j - 1] ^ mat[i - 1][j - 1];
                if (q.size() < k) {
                    q.add(sum[i][j]);
                } else {
                    if (sum[i][j] > q.peek()) {
                        q.poll();
                        q.add(sum[i][j]);
                    }
                }
            }
        }
        return q.peek();
    }
}
```
* 时间复杂度:$O(m * n * \log{k})$
* 空间复杂度:$O(m * n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1738` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1741-1750/1742. 盒子中小球的最大数量（简单）.md

1742. 盒子中小球的最大数量

Question:
你在一家生产小球的玩具厂工作,有 `n` 个小球,编号从 `lowLimit` 开始,到 `highLimit` 结束(包括 `lowLimit` 和 `highLimit` ,即 `n == highLimit - lowLimit + 1`)。另有无限数量的盒子,编号从 `1` 到 `infinity` 。

你的工作是将每个小球放入盒子中,其中盒子的编号应当等于小球编号上每位数字的和。例如,编号 `321` 的小球应当放入编号 `3 + 2 + 1 = 6` 的盒子,而编号 `10` 的小球应当放入编号 `1 + 0 = 1` 的盒子。

给你两个整数 `lowLimit` 和 `highLimit` ,返回放有最多小球的盒子中的小球数量。如果有多个盒子都满足放有最多小球,只需返回其中任一盒子的小球数量。

示例 1:
```
输入:lowLimit = 1, highLimit = 10
输出:2

解释:
盒子编号:1 2 3 4 5 6 7 8 9 10 11 ...
小球数量:2 1 1 1 1 1 1 1 1 0  0  ...
编号 1 的盒子放有最多小球,小球数量为 2 。
```
示例 2:
```
输入:lowLimit = 5, highLimit = 15

输出:2

解释:
盒子编号:1 2 3 4 5 6 7 8 9 10 11 ...
小球数量:1 1 1 1 2 2 1 1 1 0  0  ...
编号 5 和 6 的盒子放有最多小球,每个盒子中的小球数量都是 2 。
```
示例 3:
```
输入:lowLimit = 19, highLimit = 28

输出:2

解释:
盒子编号:1 2 3 4 5 6 7 8 9 10 11 12 ...
小球数量:0 1 1 1 1 1 1 1 1 2  0  0  ...
编号 10 的盒子放有最多小球,小球数量为 2 。
```

提示:
* $1 <= lowLimit <= highLimit <= 10^5$

Solution:
### 模拟

数据范围 $n = 1e5$,因此最大盒子编号 `99999 = 5 * 9 = 45`,我们可以用一个大小为 $50$ 的数组 `cnts` 来统计每个编号盒子中小球的数量,$cnts[idx] = x$ 含义为编号为 $idx$ 的盒子有 $x$ 个小球。

Java 代码:
```Java
class Solution {
    public int countBalls(int l, int r) {
        int ans = 0;
        int[] cnts = new int[50];
        for (int i = l; i <= r; i++) {
            int j = i, cur = 0;
            while (j != 0) {
                cur += j % 10; j /= 10;
            }
            if (++cnts[cur] > ans) ans = cnts[cur];
        }
        return ans;
    }
}
```
TypeScript 代码:
```TypeScript
function countBalls(l: number, r: number): number {
    let ans = 0
    const cnts = new Array<number>(50).fill(0)
    for (let i = l; i <= r; i++) {
        let j = i, cur = 0
        while (j != 0) {
            cur += j % 10
            j = Math.floor(j / 10)
        }
        if (++cnts[cur] > ans) ans = cnts[cur]
    }
    return ans
}
```
Python 代码:
```Python
class Solution:
    def countBalls(self, l: int, r: int) -> int:
        ans = 0
        cnts = [0] * 50
        for i in range(l, r + 1):
            j, cur = i, 0
            while j != 0:
                j, cur = j // 10, cur + j % 10
            cnts[cur] += 1
            if cnts[cur] > ans:
                ans = cnts[cur]
        return ans
```
* 时间复杂度:$O(n\log{r})$
* 空间复杂度:$O(C)$,其中 $C = 50$ 为最大盒子编号

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1742` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1741-1750/1743. 从相邻元素对还原数组（中等）.md

1743. 从相邻元素对还原数组

Question:
存在一个由 `n` 个不同元素组成的整数数组 `nums` ,但你已经记不清具体内容,好在你还记得 `nums` 中的每一对相邻元素。

给你一个二维整数数组 `adjacentPairs` ,大小为 `n - 1` ,其中每个 $adjacentPairs[i] = [u_i, v_i]$ 表示元素 $u_i$ 和 $v_i$ 在 `nums` 中相邻。

题目数据保证所有由元素 `nums[i]` 和 `nums[i+1]` 组成的相邻元素对都存在于 `adjacentPairs` 中,存在形式可能是 `[nums[i], nums[i+1]]` ,也可能是 `[nums[i+1], nums[i]]` 。这些相邻元素对可以「按任意顺序」出现。

返回原始数组 `nums` 。如果存在多种解答,返回其中任意一个即可。

示例 1:
```
输入:adjacentPairs = [[2,1],[3,4],[3,2]]

输出:[1,2,3,4]

解释:数组的所有相邻元素对都在 adjacentPairs 中。
特别要注意的是,adjacentPairs[i] 只表示两个元素相邻,并不保证其 左-右 顺序。
```
示例 2:
```
输入:adjacentPairs = [[4,-2],[1,4],[-3,1]]

输出:[-2,4,1,-3]

解释:数组中可能存在负数。
另一种解答是 [-3,1,4,-2] ,也会被视作正确答案。
```
示例 3:
```
输入:adjacentPairs = [[100000,-100000]]

输出:[100000,-100000]
```

提示:
* $nums.length == n$
* $adjacentPairs.length == n - 1$
* $adjacentPairs[i].length == 2$
* $2 <= n <= 10^5$
* $-10^5 <= nums[i], u_i, v_i <= 10^5$
* 题目数据保证存在一些以 `adjacentPairs` 作为元素对的数组

Solution:
### 单向构造(哈希表计数)

根据题意,由于所有的相邻关系都会出现在 $nums$ 中,假设其中一个合法数组为 $ans$,长度为 $n$。

那么显然 $ans[0]$ 和 $ans[n - 1]$ 在 $nums$ 中只存在一对相邻关系,而其他 $ans[i]$ 则存在两对相邻关系。

因此我们可以使用「哈希表」对 $nums$ 中出现的数值进行计数,找到“出现一次”的数值作为 $ans$ 数值的首位,然后根据给定的相邻关系进行「单向构造」,为了方便找到某个数其相邻的数是哪些,我们还需要再开一个「哈希表」记录相邻关系。

Java 代码:
```Java
class Solution {
    public int[] restoreArray(int[][] adjacentPairs) {
        int m = adjacentPairs.length, n = m + 1;
        Map<Integer, Integer> cnts = new HashMap<>();
        Map<Integer, List<Integer>> map = new HashMap<>();
        for (int[] ap : adjacentPairs) {
            int a = ap[0], b = ap[1];
            cnts.put(a, cnts.getOrDefault(a, 0) + 1);
            cnts.put(b, cnts.getOrDefault(b, 0) + 1);
            List<Integer> alist = map.getOrDefault(a, new ArrayList<>());
            alist.add(b);
            map.put(a, alist);
            List<Integer> blist = map.getOrDefault(b, new ArrayList<>());
            blist.add(a);
            map.put(b, blist);
        }
        int start = -1;
        for (int i : cnts.keySet()) {
            if (cnts.get(i) == 1) {
                start = i;
                break;
            }
        }
        int[] ans = new int[n];
        ans[0] = start;
        ans[1] = map.get(start).get(0);
        for (int i = 2; i < n; i++) {
            int x = ans[i - 1];
            List<Integer> list = map.get(x);
            for (int j : list) {
                if (j != ans[i - 2]) ans[i] = j;
            }
        }
        return ans;
    }
}
```
C++ 代码:
```C++
class Solution {
public:
    vector<int> restoreArray(vector<vector<int>>& adjacentPairs) {
        unordered_map<int, int> cnts;
        unordered_map<int, vector<int>> map;
        for(auto& pair : adjacentPairs){
            int a = pair[0], b = pair[1];
            cnts[a]++, cnts[b]++;
            map[a].push_back(b);
            map[b].push_back(a);
        }
        int start;
        for(auto& i : cnts) {
            if(i.second == 1){
                start = i.first;
                break;
            }
        }
        int n = adjacentPairs.size() + 1;
        vector<int> ans(n);
        ans[0] = start;
        ans[1] = map[start][0];
        for(int i = 2; i < n; i++){
            int x = ans[i - 1];
            for(int j : map[x])
                if(j != ans[i-2]) ans[i] = j;
        }
        return ans;
    }
};
```
Python 代码:
```Python
class Solution:
    def restoreArray(self, adjacentPairs: List[List[int]]) -> List[int]:
        cnts = defaultdict(int)
        map = defaultdict(list)
        for ap in adjacentPairs:
            a, b = ap[0], ap[1]
            cnts[a] += 1
            cnts[b] += 1
            map[a].append(b)
            map[b].append(a)
        start = next(i for i in cnts if cnts[i] == 1)
        n = len(adjacentPairs) + 1
        ans = [0] * n
        ans[0] = start
        ans[1] = map[start][0]
        for i in range(2, n):
            x = ans[i - 1]
            for j in map[x]:
                if j != ans[i - 2]:
                    ans[i] = j
        return ans
```
TypeScript 代码:
```TypeScript
function restoreArray(adjacentPairs: number[][]): number[] {
    const cnts: {[key: number]: number} = {};
    const map: {[key: number]: number[]} = {};
    for(let pair of adjacentPairs){
        let a: number = pair[0], b: number = pair[1];
        cnts[a] = !cnts[a] ? 1 : cnts[a] + 1;
        cnts[b] = !cnts[b] ? 1 : cnts[b] + 1;
        if(!map[a]) map[a] = [];
        if(!map[b]) map[b] = [];
        map[a].push(b);
        map[b].push(a);
    }
    let start: number;
    for(let key in cnts){
        if(cnts[key] == 1){
            start = Number(key);
            break;
        }
    }
    const n: number = adjacentPairs.length + 1;
    const ans: number[] = Array(n).fill(0);
    ans[0] = start;
    ans[1] = map[start][0];
    for(let i = 2; i<n; i++){
        let x: number = ans[i-1];
        for(let j of map[x]){
            if(j !== ans[i-2]) ans[i] = j;
        }
    }
    return ans;
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$

---

### 双向构造(双指针)

在解法一中,我们通过「哈希表」计数得到 $ans$ 首位元素作为起点,进行「单向构造」。

那么是否存在使用任意数值作为起点进行的双向构造呢?

答案是显然的,我们可以利用 $ans$ 的长度为 $2 <= n <= 10^5$,构造一个长度 $10^6$ 的数组 $q$(这里可以使用 `static` 进行加速,让多个测试用例共享一个大数组)。

> 这里 $q$ 数组不一定要开成 $1e6$ 大小,只要我们 $q$ 大小大于 $ans$ 的两倍,就不会存在越界问题。

从 $q$ 数组的 **中间位置** 开始,先随便将其中一个元素添加到中间位置,使用「双指针」分别往「两边拓展」(`l` 和 `r` 分别指向左右待插入的位置)。

当 `l` 指针和 `r` 指针之间已经有 $n$ 个数值,说明整个 $ans$ 构造完成,我们将 $[l + 1, r - 1]$ 范围内的数值输出作为答案即可。

Java 代码:
```Java
class Solution {
    static int N = (int)1e6+10;
    static int[] q = new int[N];
    public int[] restoreArray(int[][] adjacentPairs) {
        int m = adjacentPairs.length, n = m + 1;
        Map<Integer, List<Integer>> map = new HashMap<>();
        for (int[] ap : adjacentPairs) {
            int a = ap[0], b = ap[1];
            List<Integer> alist =  map.getOrDefault(a, new ArrayList<>());
            alist.add(b);
            map.put(a, alist);
            List<Integer> blist = map.getOrDefault(b, new ArrayList<>());
            blist.add(a);
            map.put(b, blist);
        }
        int l = N / 2, r = l + 1;
        int std = adjacentPairs[0][0];
        List<Integer> list = map.get(std);
        q[l--] = std;
        q[r++] = list.get(0);
        if (list.size() > 1) q[l--] = list.get(1);
        while ((r - 1) - (l + 1) + 1 < n) {
            List<Integer> alist = map.get(q[l + 1]);
            int j = l;
            for (int i : alist) {
                if (i != q[l + 2]) q[j--] = i;
            }
            l = j;

            List<Integer> blist = map.get(q[r - 1]);
            j = r;
            for (int i : blist) {
                if (i != q[r - 2]) q[j++] = i;
            }
            r = j;
        }
        int[] ans = new int[n];
        for (int i = l + 1, idx = 0; idx < n; i++, idx++) {
            ans[idx] = q[i];
        }
        return ans;
    }
}
```
C++ 代码:
```C++
#define N 1000010
class Solution {
public:
    vector<int> restoreArray(vector<vector<int>>& adjacentPairs) {
        int m = adjacentPairs.size(), n = m + 1;
        unordered_map<int, vector<int>> map;
        for(auto& pair : adjacentPairs){
            int a = pair[0], b = pair[1];
            map[a].push_back(b);
            map[b].push_back(a);
        }
        int l = N / 2, r = l + 1;
        int s = adjacentPairs[0][0];
        vector<int> q(N, 0);
        q[l--] = s;
        q[r++] = map[s][0];
        if (map[s].size() > 1) q[l--] = map[s][1];
        while ((r - 1) - (l + 1) + 1 < n){
            vector<int> list = map[q[l + 1]];
            int j = l;
            for(auto& i : list){
                if(i != q[l + 2]) q[j--] = i;
            }
            l = j;

            list = map[q[r - 1]];
            j = r;
            for(auto& i : list){
                if(i != q[r - 2]) q[j++] = i;
            }
            r = j;
        }
        vector<int> ans(n);
        for(int i = l + 1, idx = 0; idx < n; ++i, ++idx){
            ans[idx] = q[i];
        }
        return ans;
    }
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1743` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1741-1750/1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗？（中等）.md

1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗?

Question:
给你一个下标从 `0` 开始的正整数数组 `candiesCount`,其中 `candiesCount[i]` 表示你拥有的第 `i` 类糖果的数目。

同时给你一个二维数组 `queries` ,其中 $queries[i] = [favoriteType_{i}, favoriteDay_{i}, dailyCap_{i}]$ 。

你按照如下规则进行一场游戏:

* 你从第 `0` 天开始吃糖果。
* 你在吃完 所有 第 `i - 1` 类糖果之前,不能 吃任何一颗第 `i` 类糖果。
* 在吃完所有糖果之前,你必须每天 至少 吃 一颗 糖果。

请你构建一个布尔型数组 `answer`,满足 `answer.length == queries.length`。`answer[i]` 为 `true` 的条件是:在每天吃 不超过 $dailyCap_i$ 颗糖果的前提下,你可以在第 $favoriteDay_i$ 天吃到第 $favoriteType_i$ 类糖果;否则 `answer[i]` 为 `false`。注意,只要满足上面 `3` 条规则中的第二条规则,你就可以在同一天吃不同类型的糖果。

请你返回得到的数组 `answer` 。

示例 1:
```
输入:candiesCount = [7,4,5,3,8], queries = [[0,2,2],[4,2,4],[2,13,1000000000]]

输出:[true,false,true]

提示:
1- 在第 0 天吃 2 颗糖果(类型 0),第 1 天吃 2 颗糖果(类型 0),第 2 天你可以吃到类型 0 的糖果。
2- 每天你最多吃 4 颗糖果。即使第 0 天吃 4 颗糖果(类型 0),第 1 天吃 4 颗糖果(类型 0 和类型 1),你也没办法在第 2 天吃到类型 4 的糖果。换言之,你没法在每天吃 4 颗糖果的限制下在第 2 天吃到第 4 类糖果。
3- 如果你每天吃 1 颗糖果,你可以在第 13 天吃到类型 2 的糖果。
```
示例 2:
```
输入:candiesCount = [5,2,6,4,1], queries = [[3,1,2],[4,10,3],[3,10,100],[4,100,30],[1,3,1]]

输出:[false,true,true,false,false]
```

提示:
* $1 <= candiesCount.length <= 10^5$
* $1 <= candiesCount[i] <= 10^5$
* $1 <= queries.length <= 10^5$
* $queries[i].length == 3$
* $0 <= favoriteType_{i} < candiesCount.length$
* $0 <= favoriteDay_{i} <= 10^9$
* $1 <= dailyCap_{i} <= 10^9$

Solution:
### 基本分析

根据题意,在处理某个询问时,每天的吃糖数量为 $[1, queries[i][2]]$,因此我们可以计算出「最早/最晚」吃到第 $queries[i][0]$ 类糖果的时间,然后判断 $queries[i][1]$ 是否落在范围内,若落在范围内返回则有 $ans[i]$ 为 `True`,否则为 `False`。

---

### 前缀和

问题转换为如何快速求得「最早/最晚」吃到第 $queries[i][0]$ 类糖果的时间。

我们需要先预处理出 $candiesCount$ 的前缀和数组 $sum$(下标从 $1$ 开始),方便快速求得第 $i$ 类糖果之前有多少糖果。

为了方便,在处理某个询问时,我们令 $t = queries[i][0],d = queries[i][1] + 1,c = queries[i][2]$。其中 $d = queries[i][1] + 1$ 是因为题目天数是从 $0$ 开始计算,而我们的计算是从 $1$ 开始。

然后计算「最早/最晚」吃到第 $t$ 类糖果的时间:

* 最早时间(第一颗 $t$ 类糖的最早时间):当以最大速率 $c$ 吃糖时,可以在最早时间内吃到糖。时间为吃掉第 $t$ 类糖果 **前面** 的所有糖果的时间(下取整)加 $1$ :

$$
\left \lfloor \frac{sum[t]}{c} \right \rfloor + 1
$$

* 最晚时间(最后一颗 $t$ 类糖的最晚时间):当以最小速率 $1$ 吃糖时,可以计算出最晚吃糖时间。时间为吃掉所有 $t$ 类糖的时间:

$$
sum[t + 1]
$$

代码:
```Java
class Solution {
    public boolean[] canEat(int[] cs, int[][] qs) {
        int n = qs.length, m = cs.length;
        boolean[] ans = new boolean[n];
        long[] sum = new long[m + 1];
        for (int i = 1; i <= m; i++) sum[i] = sum[i - 1] + cs[i - 1];
        for (int i = 0; i < n; i++) {
            int t = qs[i][0], d = qs[i][1] + 1, c = qs[i][2];
            long a = sum[t] / c + 1, b = sum[t + 1];
            ans[i] = a <= d && d <= b;
        }
        return ans;
    }
}
```
* 时间复杂度:`cs` 数组的长度为 `n`,`qs` 数组的长度为 `m`。预处理前缀和的复杂度为 $O(n)$;处理每个询问的复杂度为 $O(1)$,共有 $m$ 个询问,复杂度为 $O(m)$。整体复杂度为 $O(\max(n, m))$
* 空间复杂度:$O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1744` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


1748. 唯一元素的和

Question:
给你一个整数数组 nums 。数组中唯一元素是那些只出现「恰好一次」的元素。

请你返回 nums 中唯一元素的和 。

示例 1:
```
输入:nums = [1,2,3,2]

输出:4

解释:唯一元素为 [1,3] ,和为 4 。
```
示例 2:
```
输入:nums = [1,1,1,1,1]

输出:0

解释:没有唯一元素,和为 0 。
```
示例 3 :
```
输入:nums = [1,2,3,4,5]

输出:15

解释:唯一元素为 [1,2,3,4,5] ,和为 15 。
```

提示:
* 1 <= nums.length <= 100
* 1 <= nums[i] <= 100

Solution:
### 排序 + 双指针

根据题意,其中一个做法是先对 `nums` 进行排序,使用双指针找到值相同的连续段 $[i, j)$,若连续段长度为 $1$,则将该值累加到答案。

代码:
```Java
class Solution {
    public int sumOfUnique(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length, ans = 0;
        for (int i = 0; i < n; ) {
            int j = i;
            while (j < n && nums[j] == nums[i]) j++;
            if (j - i == 1) ans += nums[i];
            i = j;
        }
        return ans;
    }
}
```
* 时间复杂度:排序复杂度为 $O(n\log{n})$;统计答案复杂度为 $O(n)$。整体复杂度为 $O(n\log{n})$
* 空间复杂度:$O(\log{n})$

---

### 哈希表

另外容易想到使用「哈希表」统计某个数的出现次数,又根据 $nums[i]$ 的范围为 $[1, 100]$,可直接使用数组充当哈希表。

代码:
```Java
class Solution {
    int[] cnt = new int[110];
    public int sumOfUnique(int[] nums) {
        for (int i : nums) cnt[i]++;
        int ans = 0;
        for (int i = 1; i <= 100; i++) {
            if (cnt[i] == 1) ans += i;
        }
        return ans;
    }
}
```
* 时间复杂度:令 $C$ 为 $nums[i]$ 的值域大小,本题固定为 $110$。整体复杂度为 $O(n + C)$
* 空间复杂度:$O(C)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1748` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1741-1750/1749. 任意子数组和的绝对值的最大值（中等）.md

1749. 任意子数组和的绝对值的最大值

Question:
给你一个整数数组 nums 。

一个子数组 `[numsl, numsl+1, ..., numsr-1, numsr]` 的「和的绝对值」为 `abs(numsl + numsl+1 + ... + numsr-1 + numsr) `。

请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),并返回该 最大值 。

abs(x) 定义如下:

* 如果 x 是负整数,那么 abs(x) = -x 。

* 如果 x 是非负整数,那么 abs(x) = x 。

示例 1:
```
输入:nums = [1,-3,2,3,-4]

输出:5

解释:子数组 [2,3] 和的绝对值最大,为 abs(2+3) = abs(5) = 5 。
```
示例 2:
```
输入:nums = [2,-5,1,-4,3,-2]

输出:8

解释:子数组 [-5,1,-4] 和的绝对值最大,为 abs(-5+1-4) = abs(-8) = 8 。
```

提示:
* 1 <= nums.length <= $10^5$
* -$10^4$ <= nums[i] <= $10^4$

Solution:
### 前缀和

题目要我们求连续一段的子数组的和,很自然就能想到前缀和。

当我们有了前缀和数组 `sum`  之后,需要求任意一段子数组 `[i,j]` 的和可以直接通过 `sum[j] - sum[i - 1]` 得出。

要使得 `abs(sum[j] - sum[i - 1])` 最大,其实有这么几种情况:

* 找到前缀和数组中的最大值减去最小值,得到一个最大正数(前提是最大值出现在最小值的后面,并且最小值是个负数,否则应该直接取最大值作为答案)
* 找到前缀和的最小值减去最大值,得到一个最小负数(前提是最小值出现在最大值的后面,而且最大值是一个正数,否则直接取最小值作为答案)。

也就是说最终答案只与前缀和数组中的最大值和最小值相关,而且最大值可能会出现在最小值前面或者后面。

因此我们可以边循环边做更新答案。

代码:
```Java
class Solution {
    public int maxAbsoluteSum(int[] nums) {
        int n = nums.length;
        int[] sum = new int[n + 1];
        for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];
        int ans = 0;
        for (int i = 1, min = 0, max = 0; i <= n; i++) {
            ans = Math.max(ans, Math.abs(sum[i] - min));
            ans = Math.max(ans, Math.abs(sum[i] - max));
            max = Math.max(max, sum[i]);
            min = Math.min(min, sum[i]);
        }
        return ans;
    }
}
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1749` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1751-1760/1751. 最多可以参加的会议数目 II（困难）.md

1751. 最多可以参加的会议数目 II

Question:
给你一个 `events` 数组,其中 $events[i] = [startDay_i, endDay_i, value_i]$ ,表示第 $i$ 个会议在 $startDay_i$ 天开始,第 $endDay_i$ 天结束,如果你参加这个会议,你能得到价值 $value_i$ 。

同时给你一个整数 $k$ 表示你能参加的最多会议数目。

你同一时间只能参加一个会议。如果你选择参加某个会议,那么你必须完整地参加完这个会议。

会议结束日期是包含在会议内的,也就是说你不能同时参加一个开始日期与另一个结束日期相同的两个会议。

请你返回能得到的会议价值最大和。

示例 1:

```
输入:events = [[1,2,4],[3,4,3],[2,3,1]], k = 2

输出:7

解释:选择绿色的活动会议 0 和 1,得到总价值和为 4 + 3 = 7 。
```
示例 2:

```
输入:events = [[1,2,4],[3,4,3],[2,3,10]], k = 2

输出:10

解释:参加会议 2 ,得到价值和为 10 。
你没法再参加别的会议了,因为跟会议 2 有重叠。你 不 需要参加满 k 个会议。
```
示例 3:

```
输入:events = [[1,1,1],[2,2,2],[3,3,3],[4,4,4]], k = 3

输出:9

解释:尽管会议互不重叠,你只能参加 3 个会议,所以选择价值最大的 3 个会议。
```

提示:
* $1 <= k <= events.length$
* $1 <= k * events.length <= 10^6$
* $1 <= startDay_i <= endDay_i <= 10^9$
* $1 <= value_i <= 10^6$

Solution:
### 基本思路

**定义 $f[i][j]$ 为考虑前 $i$ 个事件,选择不超过 $j$ 的最大价值**

对于每个事件,都有选择与不选两种选择:

* 不选: $f[i][j] = f[i - 1][j]$
* 选:找到第 $i$ 件事件之前,与第 $i$ 件事件不冲突的事件,记为 `last`,则有 $f[i][j] = f[last][j - 1] + value_i$

两者取 $max$,则是 $f[i][j]$ 的值。

分析到这里,因为我们要找 `last`,我们需要先对 `events` 的结束时间排序,然后找从右往左找,找到第一个满足 `结束时间 小于 当前事件的开始时间` 的事件,就是 `last`

而找 `last` 的过程,可以直接循环找,也可以通过二分来找,都能过。

---

### 动态规划

不通过「二分」来找 `last` 的 DP 解法。

代码:
```Java
class Solution {
    public int maxValue(int[][] es, int k) {
        int n = es.length;
        Arrays.sort(es, (a, b)->a[1]-b[1]);
        // f[i][j] 代表考虑前 i 个事件,选择不超过 j 的最大价值
        int[][] f = new int[n + 1][k + 1];
        for (int i = 1; i <= n; i++) {
            int[] ev = es[i - 1];
            int s = ev[0], e = ev[1], v = ev[2];

            // 找到第 i 件事件之前,与第 i 件事件不冲突的事件
            // 对于当前事件而言,冲突与否,与 j 无关
            int last = 0;
            for (int p = i - 1; p >= 1; p--) {
                int[] prev = es[p - 1];
                if (prev[1] < s) {
                    last = p;
                    break;
                }
            }

            for (int j = 1; j <= k; j++) {
                f[i][j] = Math.max(f[i - 1][j], f[last][j - 1] + v);
            }
        }
        return f[n][k];
    }
}
```
* 时间复杂度:排序复杂度为 $O(n\log{n})$,循环 `n` 个事件,每次循环需要往回找一个事件,复杂度为 $O(n)$,并更新 `k` 个状态,复杂度为 $O(k)$,因此转移的复杂度为 $O(n \times (n + k))$;总的复杂度为 $O(n \times (n + k + \log{n}))$
* 空间复杂度:$O(n \times k)$

---

### 二分 + 动态规划

通过「二分」来找 `last` 的 DP 解法。

代码:
```Java
class Solution {
    public int maxValue(int[][] es, int k) {
        int n = es.length;
        Arrays.sort(es, (a, b)->a[1]-b[1]);
        // f[i][j] 代表考虑前 i 个事件,选择不超过 j 的最大价值
        int[][] f = new int[n + 1][k + 1];
        for (int i = 1; i <= n; i++) {
            int[] ev = es[i - 1];
            int s = ev[0], e = ev[1], v = ev[2];

            // 通过「二分」,找到第 i 件事件之前,与第 i 件事件不冲突的事件
            // 对于当前事件而言,冲突与否,与 j 无关
            int l = 1, r = i - 1;
            while (l < r) {
                int mid = l + r + 1 >> 1;
                int[] prev = es[mid - 1];
                if (prev[1] < s) l = mid;
                else r = mid - 1;
            }
            int last = (r > 0 && es[r - 1][1] < s) ? r : 0;

            for (int j = 1; j <= k; j++) {
                f[i][j] = Math.max(f[i - 1][j], f[last][j - 1] + v);
            }
        }
        return f[n][k];
    }
}
```
* 时间复杂度:排序复杂度为 $O(n\log{n})$,循环 `n` 个事件,每次循环需要往回找一个事件,复杂度为 $O(\log{n})$,并更新 `k` 个状态,复杂度为 $O(k)$,因此转移的复杂度为 $O(n \times (\log{n} + k))$;总的复杂度为 $O(n \times (k + \log{n}))$
* 空间复杂度:$O(n \times k)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1751` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1751-1760/1758. 生成交替二进制字符串的最少操作数（简单）.md

1758. 生成交替二进制字符串的最少操作数

Question:
给你一个仅由字符 `'0'` 和 `'1'` 组成的字符串 `s` 。一步操作中,你可以将任一 `'0'` 变成 `'1'` ,或者将 `'1'` 变成 `'0'` 。

交替字符串 定义为:如果字符串中不存在相邻两个字符相等的情况,那么该字符串就是交替字符串。例如,字符串 `"010"` 是交替字符串,而字符串 `"0100"` 不是。

返回使 `s` 变成 交替字符串 所需的 最少 操作数。

示例 1:
```
输入:s = "0100"

输出:1

解释:如果将最后一个字符变为 '1' ,s 就变成 "0101" ,即符合交替字符串定义。
```
示例 2:
```
输入:s = "10"

输出:0

解释:s 已经是交替字符串。
```
示例 3:
```
输入:s = "1111"

输出:2

解释:需要 2 步操作得到 "0101" 或 "1010" 。
```

提示:
* $1 <= s.length <= 10^4$
* `s[i]` 是 `'0'` 或 `'1'`

Solution:
### 模拟

最终结果只有「从 `0` 开始的交替串」和「从 `1` 开始的交替串」两种。

对于一个长度为 `n` 的未知序列 `A` 而言,假设我们需要花费 `cnt` 次操作将其变为「从 `0` 开始的交替串」,那么我们想要将其变为「从 `1` 开始的交替串」则需要 `n - cnt` 次操作:原本操作的 `cnt` 个位置不能动,而原本没操作的位置则都需要翻转,从而确保两种交替串对应位均相反。

Java 代码:
```Java
class Solution {
    public int minOperations(String s) {
        int n = s.length(), cnt = 0;
        for (int i = 0; i < n; i++) cnt += (s.charAt(i) - '0') ^ (i & 1);
        return Math.min(cnt, n - cnt);
    }
}
```
TypeScript 代码:
```TypeScript
function minOperations(s: string): number {
    let n = s.length, cnt = 0
    for (let i = 0; i < n; i++) cnt += (s.charCodeAt(i) - '0'.charCodeAt(0)) ^ (i & 1)
    return Math.min(cnt, n - cnt)
}
```
Python3 代码:
```Python3
class Solution:
    def minOperations(self, s: str) -> int:
        n, cnt = len(s), 0
        for i, c in enumerate(s):
            cnt += (ord(c) - ord('0')) ^ (i & 1)
        return min(cnt, n - cnt)
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1758` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1751-1760/1759. 统计同构子字符串的数目（中等）.md

1759. 统计同构子字符串的数目

Question:
给你一个字符串 `s`,返回 `s` 中 同构子字符串 的数目。

由于答案可能很大,只需返回对 $10^9 + 7$ 取余 后的结果。

同构字符串的定义为:如果一个字符串中的所有字符都相同,那么该字符串就是同构字符串。

子字符串是字符串中的一个连续字符序列。

示例 1:
```
输入:s = "abbcccaa"

输出:13

解释:同构子字符串如下所列:
"a"   出现 3 次。
"aa"  出现 1 次。
"b"   出现 2 次。
"bb"  出现 1 次。
"c"   出现 3 次。
"cc"  出现 2 次。
"ccc" 出现 1 次。
3 + 1 + 2 + 1 + 3 + 2 + 1 = 13
```
示例 2:
```
输入:s = "xy"

输出:2

解释:同构子字符串是 "x" 和 "y" 。
```
示例 3:
```
输入:s = "zzzzz"

输出:15
```

提示:
* $1 <= s.length <= 10^5$
* `s` 由小写字符串组成

Solution:
### 双指针 + 数学

根据题意,我们需要找出 `s` 中所有字符相同的连续段。

假设 $s[i...(j - 1)]$ 为某个连续段,长度为 $m$,根据「等差数列求和」可知该连续段所能提供的同构字符串数量为 $\frac{(1 + m) \times m}{2}$。

具体的,我们可以从前往后扫描 `s`,假设当前处理到的位置为 `i`,将其看作连续段的左端点,然后从 `i` 出发找到当前最长连续段的右端点 `j - 1`,统计 $s[i...(j - 1)]$ 所能贡献同构字符串数量,并调整下个发起点为 $i = j$ 以扫描下一个连续段。

Java 代码:
```Java
class Solution {
    public int countHomogenous(String s) {
        int n = s.length(), MOD = (int)1e9+7;
        long ans = 0;
        for (int i = 0; i < n; ) {
            int j = i;
            while (j < n && s.charAt(j) == s.charAt(i)) j++;
            long cnt = j - i;
            ans += (1 + cnt) * cnt / 2;
            ans %= MOD;
            i = j;
        }
        return (int) ans;
    }
}
```
C++ 代码:
```C++
class Solution {
public:
    int countHomogenous(string s) {
        int n = s.size(), MOD = 1e9+7;
        long ans = 0;
        for (int i = 0; i < n; ) {
            int j = i;
            while (j < n && s[j] == s[i]) j++;
            long cnt = j - i;
            ans += (1 + cnt) * cnt / 2;
            ans %= MOD;
            i = j;
        }
        return (int) ans;
    }
};
```
Python3 代码:
```Python
class Solution:
    def countHomogenous(self, s: str) -> int:
        n, mod, i, ans = len(s), 1e9+7, 0, 0
        while i < n:
            j = i
            while j < n and s[j] == s[i]:
                j += 1
            cnt = j - i
            ans += (cnt + 1) * cnt / 2
            ans %= mod
            i = j
        return int(ans)
```
TypeScript 代码:
```TypeScript
function countHomogenous(s: string): number {
    let n = s.length, mod = 1e9+7, ans = 0
    for (let i = 0; i < n; ) {
        let j = i
        while (j < n && s.charAt(j) == s.charAt(i)) j++
        const cnt = j - i
        ans += (1 + cnt) * cnt / 2
        ans %= mod
        i = j
    }
    return ans
}
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1759` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1751-1760/1760. 袋子里最少数目的球（中等）.md

1760. 袋子里最少数目的球

Question:
给你一个整数数组 `nums`,其中 `nums[i]` 表示第 `i` 个袋子里球的数目。同时给你一个整数 `maxOperations`。

你可以进行如下操作至多 `maxOperations` 次:

* 选择任意一个袋子,并将袋子里的球分到 `2` 个新的袋子中,每个袋子里都有 正整数 个球。
	* 比方说,一个袋子里有 `5` 个球,你可以把它们分到两个新袋子里,分别有 `1` 个和 `4` 个球,或者分别有 `2` 个和 `3` 个球。
	你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。

请你返回进行上述操作后的最小开销。

示例 1:
```
输入:nums = [9], maxOperations = 2

输出:3

解释:
- 将装有 9 个球的袋子分成装有 6 个和 3 个球的袋子。[9] -> [6,3] 。
- 将装有 6 个球的袋子分成装有 3 个和 3 个球的袋子。[6,3] -> [3,3,3] 。
装有最多球的袋子里装有 3 个球,所以开销为 3 并返回 3 。
```
示例 2:
```
输入:nums = [2,4,8,2], maxOperations = 4

输出:2

解释:
- 将装有 8 个球的袋子分成装有 4 个和 4 个球的袋子。[2,4,8,2] -> [2,4,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,4,4,4,2] -> [2,2,2,4,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,4,4,2] -> [2,2,2,2,2,4,2] 。
- 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,2,2,4,2] -> [2,2,2,2,2,2,2,2] 。
装有最多球的袋子里装有 2 个球,所以开销为 2 并返回 2 。
```
示例 3:
```
输入:nums = [7,17], maxOperations = 2

输出:7
```

提示:
* $1 <= nums.length <= 10^5$
* $1 <= maxOperations, nums[i] <= 10^9$

Solution:
### 二分

最小化不超过 `max` 次划分操作后的单个袋子最大值,我们将其称为「划分值」。

**答案具有二段性:若使用 $k \leq \max$ 次划分操作后可达到最小划分值,此时减少划分操作次数,会使得划分值非单调上升。**

因此我们可以二分答案,从而将问题进行等价转换:

**假设当前二分到的值为 $limit$,我们需要实现一个线性复杂度为 `check` 函数,判断能否使用不超过 $\max$ 次划分次数,来使得划分值不超过 $limit$**:

* 若能满足,说明 $[limit, +\infty]$ 范围的划分值,均能使用不超过 $\max$ 次的实现,此时让 $r = limit$
* 若不能满足,比 $limit$ 更小的划分值,则更无法在 $\max$ 次操作中满足,说明 $[1, limit]$ 范围划分值均不是答案,此时让 $l = limit + 1$

考虑如何实现 `check` 函数,从前往后处理每个 $nums[i]$,根据 $nums[i]$ 与当前限制 $limit$ 的大小关系进行分情况讨论:

* 若 $nums[i] \leq limit$:说明当前袋子不会成为瓶颈,无须消耗划分次数
* 若 $nums[i] > limit$:此时需要对当前袋子进行划分,直到满足单个袋子球的数量不超过 $limit$ 为止,由于每次划分相当于增加一个袋子,而将 $nums[i]$ 划分成若干个不超过 $limit$ 个球的袋子,需要 $\left \lceil \frac{nums[i]}{limit} \right \rceil$ 个袋子,减去原本的一个,共需要增加 $\left \lceil \frac{nums[i]}{limit} \right \rceil$ 个新袋子,即划分 $\left \lceil \frac{nums[i]}{limit} \right \rceil$ 次

Java 代码:
```Java
class Solution {
    public int minimumSize(int[] nums, int max) {
        int l = 1, r = 0x3f3f3f3f;
        while (l < r) {
            int mid = l + r >> 1;
            if (check(nums, mid, max)) r = mid;
            else l = mid + 1;
        }
        return r;
    }
    boolean check(int[] nums, int limit, int max) {
        int cnt = 0;
        for (int x : nums) cnt += Math.ceil(x * 1.0 / limit) - 1;
        return cnt <= max;
    }
}
```
C++ 代码:
```C++
class Solution {
public:
    int minimumSize(vector<int>& nums, int maxv) {
        int l = 1, r = 0x3f3f3f3f;
        while (l < r) {
            int mid = l + r >> 1;
            if (check(nums, mid, maxv)) r = mid;
            else l = mid + 1;
        }
        return r;
    }
    bool check(vector<int>& nums, int limit, int maxv) {
        int cnt = 0;
        for (int x : nums) cnt += ceil(x * 1.0 / limit) - 1;
        return cnt <= maxv;
    }
};
```
Python 代码:
```Python
class Solution:
    def minimumSize(self, nums: List[int], maxv: int) -> int:
        def check(nums, limit, maxv):
            return sum([(x + limit - 1) // limit - 1 for x in nums]) <= maxv
        l, r = 1, 0x3f3f3f3f
        while l < r:
            mid = l + r >> 1
            if check(nums, mid, maxv):
                r = mid
            else:
                l = mid + 1
        return r
```
TypeScript 代码:
```TypeScript
function minimumSize(nums: number[], max: number): number {
    function check(nums: number[], limit: number, max: number): boolean {
        let cnt = 0
        for (const x of nums) cnt += Math.ceil(x / limit) - 1
        return cnt <= max
    }
    let l = 1, r = 0x3f3f3f3f
    while (l < r) {
        const mid = l + r >> 1
        if (check(nums, mid, max)) r = mid
        else l = mid + 1
    }
    return r
}
```
* 时间复杂度:$O(n \log{M})$,其中 $M = 1e9$ 为值域大小
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1758` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1761-1770/1763. 最长的美好子字符串（简单）.md

1763. 最长的美好子字符串

Question:
当一个字符串 `s` 包含的每一种字母的大写和小写形式 同时 出现在 `s` 中,就称这个字符串 `s` 是 美好 字符串。

比方说,`"abABB"` 是美好字符串,因为 `'A'` 和 `'a'` 同时出现了,且 `'B'` 和 `'b'` 也同时出现了。然而,`"abA"` 不是美好字符串因为 `'b'` 出现了,而 `'B'` 没有出现。

给你一个字符串 `s` ,请你返回 `s` 最长的 美好子字符串 。如果有多个答案,请你返回 最早 出现的一个。如果不存在美好子字符串,请你返回一个空字符串。

示例 1:
```
输入:s = "YazaAay"

输出:"aAa"

解释:"aAa" 是一个美好字符串,因为这个子串中仅含一种字母,其小写形式 'a' 和大写形式 'A' 也同时出现了。
"aAa" 是最长的美好子字符串。
```
示例 2:
```
输入:s = "Bb"

输出:"Bb"

解释:"Bb" 是美好字符串,因为 'B' 和 'b' 都出现了。整个字符串也是原字符串的子字符串。
```
示例 3:
```
输入:s = "c"

输出:""

解释:没有美好子字符串。
```
示例 4:
```
输入:s = "dDzeE"

输出:"dD"

解释:"dD" 和 "eE" 都是最长美好子字符串。
由于有多个美好子字符串,返回 "dD" ,因为它出现得最早。
```

提示:
* $1 <= s.length <= 100$
* `s` 只包含大写和小写英文字母。

Solution:
### 朴素解法

数据范围只有 $100$,最为简单的做法是枚举所有的子串( 复杂度为 $O(n^2)$ ),然后对子串进行合法性检查( 复杂度为 $O(n)$ ),整体复杂度为 $O(n^3)$,可以过。

代码:
```Java
class Solution {
    public String longestNiceSubstring(String s) {
        int n = s.length();
        String ans = "";
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (j - i + 1 > ans.length() && check(s.substring(i, j + 1))) ans = s.substring(i, j + 1);
            }
        }
        return ans;
    }
    boolean check(String s) {
        Set<Character> set = new HashSet<>();
        for (char c : s.toCharArray()) set.add(c);
        for (char c : s.toCharArray()) {
            char a = Character.toLowerCase(c), b = Character.toUpperCase(c);
            if (!set.contains(a) || !set.contains(b)) return false;
        }
        return true;
    }
}
```
* 时间复杂度:$O(n^3)$
* 空间复杂度:$O(n)$

---

### 前缀和优化

在 `check` 中,我们不可避免的遍历整个子串,复杂度为 $O(n)$。

该过程可以使用前缀和思想进行优化:**构建二维数组 $cnt$ 来记录子串的词频,$cnt[i]$ 为一个长度为 $128$ 的数组,用于记录字符串 `s` 中下标范围为 $[0, i - 1]$ 的词频。即 $cnt[i + 1][j]$ 所代表的含义为在子串 $s[0...i]$ 中字符 $j$ 的出现次数。**

那么利用「容斥原理」,对于 `s` 的任意连续段 $[l, r]$ 所代表的子串中的任意字符 $i$ 的词频,我们可以作差算得:

$$
res[i] = cnt[r + 1][i] - cnt[l][i]
$$

这样我们在 `check` 实现中,只要检查 $26$ 个字母对应的大小写词频(ASCII 相差 $32$),是否同时为 $0$ 或者同时不为 $0$ 即可,复杂度为 $O(C)$。

代码:
```Java
class Solution {
    public String longestNiceSubstring(String s) {
        int n = s.length();
        int[][] cnt = new int[n + 1][128];
        for (int i = 1; i <= n; i++) {
            char c = s.charAt(i - 1);
            cnt[i] = cnt[i - 1].clone();
            cnt[i][c - 'A']++;
        }
        int idx = -1, len = 0;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (j - i + 1 <= len) continue;
                int[] a = cnt[i], b = cnt[j + 1];
                if (check(a, b)) {
                    idx = i; len = j - i + 1;
                }
            }
        }
        return idx == -1 ? "" : s.substring(idx, idx + len);
    }
    boolean check(int[] a, int[] b) {
        for (int i = 0; i < 26; i++) {
            int low = b[i] - a[i], up = b[i + 32] - a[i + 32]; // 'A' = 65、'a' = 97
            if (low != 0 && up == 0) return false;
            if (low == 0 && up != 0) return false;
        }
        return true;
    }
}
```
* 时间复杂度:令 $C$ 为字符集大小,本题固定为 $26$,构建 $cnt$ 的复杂度为 $O(n * 128)$;枚举所有子串复杂度为 $O(n^2)$;`check` 的复杂度为 $O(C)$。整体复杂度为 $O(n^2 * C)$
* 空间复杂度:$O(n * 128)$

---

### 二进制优化

更进一步,对于某个子串而言,我们只关心大小写是否同时出现,而不关心出现次数。

因此我们无须使用二维数组来记录具体的词频,可以在枚举子串时,使用两个 `int` 的低 $26$ 位分别记录大小写字母的出现情况,利用枚举子串时右端点后移,维护两变量,当且仅当两变量相等时,满足 $26$ 个字母的大小写同时出现或同时不出现。

代码:
```Java
class Solution {
    public String longestNiceSubstring(String s) {
        int n = s.length();
        int idx = -1, len = 0;
        for (int i = 0; i < n; i++) {
            int a = 0, b = 0;
            for (int j = i; j < n; j++) {
                char c = s.charAt(j);
                if (c >= 'a' && c <= 'z') a |= (1 << (c - 'a'));
                else b |= (1 << (c - 'A'));
                if (a == b && j - i + 1 > len) {
                    idx = i; len = j - i + 1;
                }
            }
        }
        return idx == -1 ? "" : s.substring(idx, idx + len);
    }
}
```
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1763` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1761-1770/1764. 通过连接另一个数组的子数组得到一个数组（中等）.md

1764. 通过连接另一个数组的子数组得到一个数组

Question:
给你一个长度为 `n` 的二维整数数组 `groups` ,同时给你一个整数数组 `nums` 。

你是否可以从 `nums` 中选出 `n` 个 不相交 的子数组,使得第 `i` 个子数组与 `groups[i]` (下标从 `0` 开始)完全相同,且如果 `i > 0` ,那么第 (`i-1`) 个子数组在 `nums` 中出现的位置在第 `i` 个子数组前面。(也就是说,这些子数组在 `nums` 中出现的顺序需要与 `groups` 顺序相同)

如果你可以找出这样的 `n` 个子数组,请你返回 `true` ,否则返回 `false`。

如果不存在下标为 `k` 的元素 `nums[k]` 属于不止一个子数组,就称这些子数组是 不相交 的。子数组指的是原数组中连续元素组成的一个序列。

示例 1:
```
输入:groups = [[1,-1,-1],[3,-2,0]], nums = [1,-1,0,1,-1,-1,3,-2,0]

输出:true

解释:你可以分别在 nums 中选出第 0 个子数组 [1,-1,0,1,-1,-1,3,-2,0] 和第 1 个子数组 [1,-1,0,1,-1,-1,3,-2,0] 。
这两个子数组是不相交的,因为它们没有任何共同的元素。
```
示例 2:
```
输入:groups = [[10,-2],[1,2,3,4]], nums = [1,2,3,4,10,-2]

输出:false

解释:选择子数组 [1,2,3,4,10,-2] 和 [1,2,3,4,10,-2] 是不正确的,因为它们出现的顺序与 groups 中顺序不同。
[10,-2] 必须出现在 [1,2,3,4] 之前。
```
示例 3:
```
输入:groups = [[1,2,3],[3,4]], nums = [7,7,1,2,3,4,7,7]

输出:false

解释:选择子数组 [7,7,1,2,3,4,7,7] 和 [7,7,1,2,3,4,7,7] 是不正确的,因为它们不是不相交子数组。
它们有一个共同的元素 nums[4] (下标从 0 开始)。
```

提示:
* $groups.length == n$
* $1 <= n <= 10^3$
* $1 <= groups[i].length, sum(groups[i].length) <= 10^3$
* $1 <= nums.length <= 10^3$
* $-10^7 <= groups[i][j], nums[k] <= 10^7$

Solution:
### 双指针

为了方便,将 `groups` 记为 `gs`。

从前往后处理每个 $gs[i]$,使用 `idx` 记录当前使用到 `nums` 中的哪一位(即 $[0, ... (idx - 1)]$ 的 $nums[i]$ 已经用于匹配 $gs[0, ... (i - 1)]$。

每次尝试从 `idx` 出发匹配 `gs[i]`,若能匹配成功,则整段更新 $idx = idx + gs[i].length$;否则将 `idx` 后移一位,继续尝试匹配 `gs[i]`。

代码:
```Java
class Solution {
    public boolean canChoose(int[][] gs, int[] nums) {
        int m = nums.length, idx = 0;
        out:for (int[] info : gs) {
            for (int j = idx; j + info.length <= m; j++) {
                boolean ok = true;
                for (int k = 0; k < info.length && ok; k++) {
                    if (nums[j + k] != info[k]) ok = false;
                }
                if (ok) {
                    idx = j + info.length;
                    continue out;
                }
            }
            return false;
        }
        return true;
    }
}
```
* 时间复杂度:$O(n \times m)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1764` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


1765. 地图中的最高点

Question:
给你一个大小为 `m x n` 的整数矩阵 `isWater` ,它代表了一个由 陆地 和 水域 单元格组成的地图。

* 如果 `isWater[i][j] == 0` ,格子 `(i, j)` 是一个 陆地 格子。
* 如果 `isWater[i][j] == 1` ,格子 `(i, j)` 是一个 水域 格子。

你需要按照如下规则给每个单元格安排高度:

* 每个格子的高度都必须是非负的。
* 如果一个格子是是 **水域** ,那么它的高度必须为 $0$ 。
* 任意相邻的格子高度差 至多 为 `1` 。当两个格子在正东、南、西、北方向上相互紧挨着,就称它们为相邻的格子。(也就是说它们有一条公共边)

找到一种安排高度的方案,使得矩阵中的最高高度值 最大 。

请你返回一个大小为 `m x n` 的整数矩阵 `height` ,其中 `height[i][j]` 是格子 `(i, j)` 的高度。如果有多种解法,请返回 任意一个 。

示例 1:

```
输入:isWater = [[0,1],[0,0]]

输出:[[1,0],[2,1]]

解释:上图展示了给各个格子安排的高度。
蓝色格子是水域格,绿色格子是陆地格。
```
示例 2:

```
输入:isWater = [[0,0,1],[1,0,0],[0,0,0]]

输出:[[1,1,0],[0,1,1],[1,2,2]]

解释:所有安排方案中,最高可行高度为 2 。
任意安排方案中,只要最高高度为 2 且符合上述规则的,都为可行方案。
```

提示:
* $m == isWater.length$
* $n == isWater[i].length$
* $1 <= m, n <= 1000$
* `isWater[i][j]` 要么是 $0$ ,要么是 $1$ 。
* 至少有 $1$ 个水域格子。

Solution:
### 多源 BFS

这是一道「多源 `BFS`」板子题,对「多源 `BFS`」不熟悉的同学,可以看看前置 🧀:[多源 BFS 入门](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487179&idx=1&sn=e30a662c03fba3861254dbcf3fb9d6f2&chksm=fd9ca5d4caeb2cc205804fd17a2ce86b25d0408adc3417e73154f59d37e7cb17e02374f5122c&scene=178&cur_album_id=1917113998693449732#rd)。

里面详解了「多源 `BFS`」与「单源 `BFS`」板子上的区别,强调了可以通过建立「虚拟源点」的方式,将「多源 `BFS`」转换回「单源 `BFS`」问题。

回到本题,题目规定了水域区域的高度为 $0$,然后相邻格子之间的高度差至多为 $1$,

我们可以将所有水域(高度为 $0$)区域进行入队,然后跑一遍 `BFS` 即可。

将所有水域(高度为 $0$)区域进行入队的操作可看作是将与「虚拟源点」链接的节点进行入队(也等价于起始只将虚拟源点入队):

容易证明这样做法的正确性:**对于一个「陆地」区域(高度可变)而言,其所能填入的高度,取决于其距离其他「水域」区域的距离,而我们最终要让整个答案矩阵合法,因此每个「陆地」区域应该取其所能填入的高度的「下界」,即只由「距离它最近的水域」区域所更新,这符合 `BFS`  的性质。**

**代码(感谢 [@Benhao](/u/himymben/) 和 [@5cm/s 🌸](/u/megurine/) 同学提供的其他语言版本):**
```Java
class Solution {
    public int[][] highestPeak(int[][] g) {
        int m = g.length, n = g[0].length;
        int[][] ans = new int[m][n];
        Deque<int[]> d = new ArrayDeque<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (g[i][j] == 1) d.addLast(new int[]{i, j});
                ans[i][j] = g[i][j] == 1 ? 0 : -1;
            }
        }
        int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
        while (!d.isEmpty()) {
            int[] info = d.pollFirst();
            int x = info[0], y = info[1];
            for (int[] di : dirs) {
                int nx = x + di[0], ny = y + di[1];
                if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
                if (ans[nx][ny] != -1) continue;
                ans[nx][ny] = ans[x][y] + 1;
                d.addLast(new int[]{nx, ny});
            }
        }
        return ans;
    }
}
```
-
```Python3
class Solution:
    def highestPeak(self, isWater: List[List[int]]) -> List[List[int]]:
        m, n = len(isWater), len(isWater[0])
        ans = [[0] * n for _ in range(m)]
        d = deque()
        for i in range(m):
            for j in range(n):
                if isWater[i][j]:
                    d.append((i, j))
                ans[i][j] = 0 if isWater[i][j] else -1

        dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        h = 1
        while d:
            size = len(d)
            for _ in range(size):
                x, y = d.popleft()
                for di in dirs:
                    nx, ny = x + di[0], y + di[1]
                    if 0 <= nx < m and 0 <= ny < n and ans[nx][ny] == -1:
                        ans[nx][ny] = h
                        d.append((nx, ny))
            h += 1
        return ans
```
-
```Go
func highestPeak(isWater [][]int) [][]int {
    m, n := len(isWater), len(isWater[0])
    ans, d := make([][]int, m), [][]int{}
    for i := 0; i < m; i++ {
        ans[i] = make([]int, n)
        for j := 0; j < n; j++ {
            if isWater[i][j] == 1 {
                d = append(d, []int{i, j})
                ans[i][j] = 0
            } else {
                ans[i][j] = -1
            }
        }
    }
    dirs := [][]int{{1,0}, {-1,0}, {0,1}, {0,-1}}
    for h := 1; len(d) > 0; h++ {
        for size := len(d); size > 0; size--{
            info := d[0]
            d = d[1:]
            x, y := info[0], info[1]
            for _, di := range dirs {
                nx, ny := x + di[0], y + di[1]
                if nx >= 0 && nx < m && ny >= 0 && ny < n && ans[nx][ny] == -1 {
                    ans[nx][ny] = h
                    d = append(d, []int{nx, ny})
                }
            }
        }
    }
    return ans
}
```
-
```C++
const int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
class Solution {
public:
    vector<vector<int>> highestPeak(vector<vector<int>>& g) {
        int n = g.size(), m = g[0].size();
        queue<pair<int, int>> q;
        vector<vector<int>> ans(n, vector<int>(m, 0));
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (g[i][j] == 1) {
                    q.emplace(i, j);
                } else {
                    ans[i][j] = -1;
                }
            }
        }
        while (!q.empty()) {
            auto [x, y] = q.front(); q.pop();
            for (int i = 0; i < 4; ++i) {
                int a = x + dx[i], b = y + dy[i];
                if (a < 0 || a == n || b < 0 || b == m) continue;
                if (ans[a][b] >= 0) continue;
                ans[a][b] = ans[x][y] + 1;
                q.emplace(a, b);
            }
        }
        return ans;
    }
};
```
* 时间复杂度:$O(m * n)$
* 空间复杂度:$O(m * n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1765` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


Question:
给你一个 `n` 个节点的树(也就是一个无环连通无向图),节点编号从 `0` 到 `n - 1`,且恰好有 `n - 1` 条边,每个节点有一个值,树的根节点为 `0` 号点。

给你一个整数数组 `nums` 和一个二维数组 `edges` 来表示这棵树。

`nums[i]` 表示第 `i` 个点的值,$edges[j] = [u_{j}, v_{j}]$ 表示节点 $u_{j}$ 和节点 $v_{j}$ 在树中有一条边。

当 `gcd(x, y) == 1`,我们称两个数 `x` 和 `y` 是 互质的 ,其中 `gcd(x, y)` 是 `x` 和 `y` 的最大公约数。

从节点 `i` 到根最短路径上的点都是节点 `i` 的祖先节点,一个节点不是它自己的祖先节点。

请你返回一个大小为 `n` 的数组 `ans`,其中 `ans[i]` 是离节点 `i` 最近的祖先节点且满足 `nums[i]` 和 `nums[ans[i]]` 是互质的,如果不存在这样的祖先节点,`ans[i]` 为 `-1`。

示例 1:

```
输入:nums = [2,3,3,2], edges = [[0,1],[1,2],[1,3]]

输出:[-1,0,0,1]

解释:上图中,每个节点的值在括号中表示。
- 节点 0 没有互质祖先。
- 节点 1 只有一个祖先节点 0 。它们的值是互质的(gcd(2,3) == 1)。
- 节点 2 有两个祖先节点,分别是节点 1 和节点 0 。节点 1 的值与它的值不是互质的(gcd(3,3) == 3)但节点 0 的值是互质的(gcd(2,3) == 1),所以节点 0 是最近的符合要求的祖先节点。
- 节点 3 有两个祖先节点,分别是节点 1 和节点 0 。它与节点 1 互质(gcd(3,2) == 1),所以节点 1 是离它最近的符合要求的祖先节点。
```

示例 2:

```
输入:nums = [5,6,10,2,3,6,15], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]]

输出:[-1,0,-1,0,0,0,-1]
```

提示:
* $nums.length = n$
* $1 <= nums[i] <= 50$
* $1 <= n <= 10^5$
* $edges.length = n - 1$
* $edges[j].length = 2$
* $0 <= u_{j}, v_{j} < n$
* $u_{j} \neq v_{j}$

Solution:
### DFS

题目描述很长,但其实就是说每个节点从下往上找,找到最近的「与其互质」的节点。

数据范围是 $10^5$,如果每个节点都直接往上找最近「互质」祖宗节点的话,当树为线性时,复杂度是 $O(n^2)$ ,会超时。

因此我们要利用 $nums[i]$ 范围只有 $50$ 的特性。

我们可以先预处理除 $[1, 50]$ 范围内的每个数,求出他们互质的数有哪些,存到一个字典里。

那么对于某个节点而言,假设节点的值为 `x` ,所在层数为 `y`。

那么问题转化为求与 `x` 互质的数有哪些,最近的在哪一层。

用 `dep[x]` 表示距离值为 `x` 的节点最近的层是多少;`pos[x]` 代表具体的节点编号。

代码:
```java
class Solution {
    int[] ans;
    Map<Integer, List<Integer>> map = new HashMap<>(); // 边映射
    Map<Integer, List<Integer>> val = new HashMap<>(); // 互质数字典
    int[] dep;
    int[] pos = new int[52];
    public int[] getCoprimes(int[] nums, int[][] edges) {
        int n = nums.length;
        ans = new int[n];
        dep = new int[n];
        Arrays.fill(ans, - 1);
        Arrays.fill(pos, -1);

        for (int[] edge : edges) {
            int a = edge[0], b = edge[1];
            List<Integer> alist = map.getOrDefault(a, new ArrayList<>());
            alist.add(b);
            map.put(a, alist);
            List<Integer> blist = map.getOrDefault(b, new ArrayList<>());
            blist.add(a);
            map.put(b, blist);
        }

        for (int i = 1; i <= 50; i++) {
            for (int j = 1; j <= 50; j++) {
                if (gcd(i, j) == 1) {
                    List<Integer> list = val.getOrDefault(i, new ArrayList<>());
                    list.add(j);
                    val.put(i, list);
                }
            }
        }

        dfs(nums, 0, -1);
        return ans;
    }
    void dfs(int[] nums, int u, int form) {
        int t = nums[u];
        for (int v : val.get(t)) {
            if (pos[v] == -1) continue;
            if (ans[u] == -1 || dep[ans[u]] < dep[pos[v]]) ans[u] = pos[v];
        }
        int p = pos[t];
        pos[t] = u;

        for (int i : map.get(u)) {
            if (i == form) continue;
            dep[i] = dep[u] + 1;
            dfs(nums, i, u);
        }
        pos[t] = p;
    }
    int gcd(int a, int b) {
        if (b == 0) return a;
        if (a == 0) return b;
        return gcd(b, a % b);
    }
}
```
* 时间复杂度:对于每个节点而言,会检查与其数值互质的数有哪些,在哪层。最坏情况下会检查 50 个互质数(当前数值为 1)。复杂度为 $O(n)$
* 空间复杂度:$O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1766` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


1768. 交替合并字符串

Question:
给你两个字符串 `word1` 和 `word2`。请你从 `word1` 开始,通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾。

返回 合并后的字符串 。

示例 1:
```
输入:word1 = "abc", word2 = "pqr"

输出:"apbqcr"

解释:字符串合并情况如下所示:
word1:  a   b   c
word2:    p   q   r
合并后:  a p b q c r
```
示例 2:
```
输入:word1 = "ab", word2 = "pqrs"

输出:"apbqrs"

解释:注意,word2 比 word1 长,"rs" 需要追加到合并后字符串的末尾。
word1:  a   b
word2:    p   q   r   s
合并后:  a p b q   r   s
```
示例 3:
```
输入:word1 = "abcd", word2 = "pq"

输出:"apbqcd"

解释:注意,word1 比 word2 长,"cd" 需要追加到合并后字符串的末尾。
word1:  a   b   c   d
word2:    p   q
合并后:  a p b q c   d
```

提示:
* $1 <= word1.length, word2.length <= 100$
* `word1` 和 `word2` 由小写英文字母组成

Solution:
### 模拟

根据题意进行模拟即可。

Java 代码:
```Java
class Solution {
    public String mergeAlternately(String s1, String s2) {
        int n = s1.length(), m = s2.length(), i = 0, j = 0;
        StringBuilder sb = new StringBuilder();
        while (i < n || j < m) {
            if (i < n) sb.append(s1.charAt(i++));
            if (j < m) sb.append(s2.charAt(j++));
        }
        return sb.toString();
    }
}
```
TypeScript 代码:
```TypeScript
function mergeAlternately(s1: string, s2: string): string {
    let n = s1.length, m = s2.length, i = 0, j = 0
    let ans = ""
    while (i < n || j < m) {
        if (i < n) ans += s1[i++]
        if (j < m) ans += s2[j++]
    }
    return ans
}
```
Python 代码:
```Python
class Solution:
    def mergeAlternately(self, s1: str, s2: str) -> str:
        n, m, i, j = len(s1), len(s2), 0, 0
        ans = ""
        while i < n or j < m:
            if i < n:
                ans += s1[i]
                i += 1
            if j < m:
                ans += s2[j]
                j += 1
        return ans
```
* 时间复杂度:$O(n + m)$
* 空间复杂度:$O(n + m)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1768` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1761-1770/1769. 移动所有球到每个盒子所需的最小操作数（中等）.md

1769. 移动所有球到每个盒子所需的最小操作数

Question:
有 `n` 个盒子。给你一个长度为 `n` 的二进制字符串 `boxes`,其中 `boxes[i]` 的值为 `'0'` 表示第 `i` 个盒子是 空 的,而 `boxes[i]` 的值为 `'1'` 表示盒子里有 一个 小球。

在一步操作中,你可以将 一个 小球从某个盒子移动到一个与之相邻的盒子中。第 `i` 个盒子和第 `j` 个盒子相邻需满足 `abs(i - j) == 1`。注意,操作执行后,某些盒子中可能会存在不止一个小球。

返回一个长度为 `n` 的数组 `answer`,其中 `answer[i]` 是将所有小球移动到第 `i` 个盒子所需的 最小 操作数。

每个 `answer[i]` 都需要根据盒子的 初始状态 进行计算。

示例 1:
```
输入:boxes = "110"

输出:[1,1,3]

解释:每个盒子对应的最小操作数如下:
1) 第 1 个盒子:将一个小球从第 2 个盒子移动到第 1 个盒子,需要 1 步操作。
2) 第 2 个盒子:将一个小球从第 1 个盒子移动到第 2 个盒子,需要 1 步操作。
3) 第 3 个盒子:将一个小球从第 1 个盒子移动到第 3 个盒子,需要 2 步操作。将一个小球从第 2 个盒子移动到第 3 个盒子,需要 1 步操作。共计 3 步操作。
```
示例 2:
```
输入:boxes = "001011"

输出:[11,8,5,4,3,4]
```

提示:
* $n = boxes.length$
* $1 <= n <= 2000$
* `boxes[i]` 为 `'0'` 或 `'1'`

Solution:
### 模拟

预处理两个与 `boxes` 等长的数组 `l` 和 `r`:$l[i]$ 和 $r[i]$ 分别代表「将 $[0, i]$ 的小球移动到位置 $i$」以及「将 $[i, n - 1]$ 的小球移动到位置 $i$」所需要的步数。

所求的答案数组 `ans` 与数组 `l` 和 `r` 的关系为:$ans[i] = l[i] + r[i]$。

预处理两数组是简单的:分别从两个方向遍历 `boxes`,使用变量 `cur` 代表当前处理到的前缀/后缀的小球总个数,变量 `step` 代表将当前所有前缀/后缀小球移动到位置 $i$ 所需要的步数。

Java 代码:
```Java
class Solution {
    public int[] minOperations(String boxes) {
        int n = boxes.length();
        int[] l = new int[n + 10], r = new int[n + 10];
        for (int i = 0, cur = 0, step = 0; i < n; i++) {
            step += cur; cur += boxes.charAt(i) - '0'; l[i] = step;
        }
        for (int i = n - 1, cur = 0, step = 0; i >= 0; i--) {
            step += cur; cur += boxes.charAt(i) - '0'; r[i] = step;
        }
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) ans[i] = l[i] + r[i];
        return ans;
    }
}
```
TypeScript 代码:
```TypeScript
function minOperations(boxes: string): number[] {
    const n = boxes.length
    const l = new Array<number>(n + 10).fill(0), r = new Array<number>(n + 10).fill(0)
    for (let i = 0, cur = 0, step = 0; i < n; i++) {
        step += cur; cur += boxes[i] == '1' ? 1 : 0; l[i] = step;
    }
    for (let i = n - 1, cur = 0, step = 0; i >= 0; i--) {
        step += cur; cur += boxes[i] == '1' ? 1 : 0; r[i] = step;
    }
    const ans = new Array<number>(n).fill(0)
    for (let i = 0; i < n; i++) ans[i] = l[i] + r[i]
    return ans
}
```
Python 代码:
```Python
class Solution:
    def minOperations(self, boxes: str) -> List[int]:
        n = len(boxes)
        l, r = [0] * n, [0] * n
        step, cur = 0, 0
        for i in range(n):
            step, cur = step + cur, cur + 1 if boxes[i] == '1' else cur
            l[i] = step
        step, cur = 0, 0
        for i in range(n - 1, -1, -1):
            step, cur = step + cur, cur + 1 if boxes[i] == '1' else cur
            r[i] = step
        return [l[i] + r[i] for i in range(n)]
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1769` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1771-1780/1773. 统计匹配检索规则的物品数量（简单）.md

1773. 统计匹配检索规则的物品数量

Question:
给你一个数组 `items` ,其中 $items[i] = [type_{i}, color_{i}, name_{i}]$ ,描述第 `i` 件物品的类型、颜色以及名称。

另给你一条由两个字符串 `ruleKey` 和 `ruleValue` 表示的检索规则。

如果第 `i` 件物品能满足下述条件之一,则认为该物品与给定的检索规则 匹配 :

* `ruleKey = "type"` 且 $ruleValue = type_{i}$ 。
* `ruleKey = "color"` 且 $ruleValue = color_{i}$。
* `ruleKey = "name"` 且 $ruleValue = name{i}$。

统计并返回 匹配检索规则的物品数量 。

示例 1:
```
输入:items = [["phone","blue","pixel"],["computer","silver","lenovo"],["phone","gold","iphone"]], ruleKey = "color", ruleValue = "silver"

输出:1

解释:只有一件物品匹配检索规则,这件物品是 ["computer","silver","lenovo"] 。
```
示例 2:
```
输入:items = [["phone","blue","pixel"],["computer","silver","phone"],["phone","gold","iphone"]], ruleKey = "type", ruleValue = "phone"

输出:2

解释:只有两件物品匹配检索规则,这两件物品分别是 ["phone","blue","pixel"] 和 ["phone","gold","iphone"] 。注意,["computer","silver","phone"] 未匹配检索规则。
```

提示:
* $1 <= items.length <= 10^4$
* $1 <= type_{i}.length, color_{i}.length, name_{i}.length, ruleValue.length <= 10$
* `ruleKey` 等于 `"type"`、`"color"` 或 `"name"`
* 所有字符串仅由小写字母组成

Solution:
### 模拟

根据题意进行模拟即可。

Java 代码:
```Java
class Solution {
    public int countMatches(List<List<String>> items, String k, String v) {
        int ans = 0, idx = k.charAt(0) == 't' ? 0 : k.charAt(0) == 'c' ? 1 : 2;
        for (List<String> item : items) {
            if (item.get(idx).equals(v)) ans++;
        }
        return ans;
    }
}
```
TypeScript 代码:
```TypeScript
function countMatches(items: string[][], k: string, v: string): number {
    let ans = 0, idx = k[0] == 't' ? 0 : k[0] == 'c' ? 1 : 2
    for (const item of items) {
        if (item[idx] == v) ans++
    }
    return ans
}
```
Python 代码:
```Python
class Solution:
    def countMatches(self, items: List[List[str]], k: str, v: str) -> int:
        ans, idx = 0, 0 if k[0] == 't' else 1 if k[0] == 'c' else 2
        for item in items:
            if item[idx] == v:
                ans += 1
        return ans
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1773` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


LeetCode/1771-1780/1775. 通过最少操作次数使数组的和相等（中等）.md

1775. 通过最少操作次数使数组的和相等量

Question:
给你两个长度可能不等的整数数组 `nums1` 和 `nums2` 。两个数组中的所有值都在 `1` 到 `6` 之间(包含 `1` 和 `6`)。

每次操作中,你可以选择 任意 数组中的任意一个整数,将它变成 `1` 到 `6` 之间 任意 的值(包含 `1` 和 `6`)。

请你返回使 `nums1` 中所有数的和与 `nums2` 中所有数的和相等的最少操作次数。如果无法使两个数组的和相等,请返回 `-1` 。

示例 1:
```
输入:nums1 = [1,2,3,4,5,6], nums2 = [1,1,2,2,2,2]

输出:3

解释:你可以通过 3 次操作使 nums1 中所有数的和与 nums2 中所有数的和相等。以下数组下标都从 0 开始。
- 将 nums2[0] 变为 6 。 nums1 = [1,2,3,4,5,6], nums2 = [6,1,2,2,2,2] 。
- 将 nums1[5] 变为 1 。 nums1 = [1,2,3,4,5,1], nums2 = [6,1,2,2,2,2] 。
- 将 nums1[2] 变为 2 。 nums1 = [1,2,2,4,5,1], nums2 = [6,1,2,2,2,2] 。
```
示例 2:
```
输入:nums1 = [1,1,1,1,1,1,1], nums2 = [6]

输出:-1

解释:没有办法减少 nums1 的和或者增加 nums2 的和使二者相等。
```
示例 3:
```
输入:nums1 = [6,6], nums2 = [1]

输出:3

解释:你可以通过 3 次操作使 nums1 中所有数的和与 nums2 中所有数的和相等。以下数组下标都从 0 开始。
- 将 nums1[0] 变为 2 。 nums1 = [2,6], nums2 = [1] 。
- 将 nums1[1] 变为 2 。 nums1 = [2,2], nums2 = [1] 。
- 将 nums2[0] 变为 4 。 nums1 = [2,2], nums2 = [4] 。
```

提示:
* $1 <= nums1.length, nums2.length <= 10^5$
* $1 <= nums1[i], nums2[i] <= 6$

Solution:
### 枚举 + 贪心 + 数学

令 `nums1` 的长度为 `n`,`nums2` 的长度为 `m`,根据题意两数组的值域分别为 $[n, 6n]$ 和 $[m, 6m]$,可分别视为数轴上的两条线段。

为了方便,我们人为固定 $n\leq m$,若不满足则交换两数组,返回 `minOperations(nums2, nums1)` 即可。

先来考虑无解的情况:当  $6n < m$ 时,说明两线段不重合,必然无法通过变换使得总和相等,直接返回 `-1`。

由于 $\max(n, m)$ 的范围为 $1e5$,且 $nums[i]$ 的值域大小 $C = 6$,因此我们可以通过枚举最终目标和 `x`(两线段的重合部分)来做,枚举范围不超过 $6 \times 1e5$。

于是问题转换为:**对于一个原总和为 `sum` 的数组 `nums` 而言,按照题目的变换规则,至少经过多少次变换,才能将其总和变为 `x`**。

根据原总和 `sum` 和目标结果 `x` 的大小关系进行分情况讨论(将两者差值绝对值记为 `d`):

* 当 $sum < x$ 时,对于原数为 $nums[i]$ 的数而言,其能变为不超过 $nums[i] - 1$ 的任意数。

  例如 $6$ 能够变化为 $[1, 5]$ 中的任意数,即单个数值 $6$ 最多能够抵消 $6 - 1$ 个差值,不失一般性的可概括为原数为 $nums[i]$ 所能抵消的差值为 $nums[i] - 1$。

  因此,我们贪心的使用较大数进行变换(从 $6$ 往 $2$ 枚举 `i`),对于每个数值 `i` 而言,其所能提供的个数为 $\min(\left \lceil \frac{d}{i - 1} \right \rceil, cnst[i])$。

* 当 $sum > x$ 时,同理,原数为 $nums[i]$ 所能提供的最大抵消数为 $6 - nums[i]$,因此我们贪心使用较小数进行变换(从 $1$ 往 $5$ 枚举 `i`),对于每个数值 `i` 而言,其所能提供的个数为 $\min(\left \lceil \frac{d}{6 - i} \right \rceil, cnst[i])$。

如此一来,我们通过枚举两线段重合点 `x`,复杂度为 $O(C \times \max(n, m))$,并通过复杂度为 $O(C)$ 的数学方法来得知将两原数组总和变为 `x` 所需要的操作次数 `cnt`,在所有的 `cnt` 取最小值即是答案。整体计算量为 $3.6 \times 10^6$,可以过。

Java 代码:
```Java
class Solution {
    int[] c1 = new int[10], c2 = new int[10];
    int s1, s2;
    public int minOperations(int[] nums1, int[] nums2) {
        int n = nums1.length, m = nums2.length;
        if (n > m) return minOperations(nums2, nums1);
        if (m > 6 * n) return -1;
        for (int x : nums1) {
            c1[x]++; s1 += x;
        }
        for (int x : nums2) {
            c2[x]++; s2 += x;
        }
        int ans = n + m;
        for (int i = m; i <= 6 * n; i++) ans = Math.min(ans, getCnt(c1, s1, i) + getCnt(c2, s2, i));
        return ans;
    }
    int getCnt(int[] cnts, int sum, int x) {
        int ans = 0;
        if (sum > x) {
            for (int i = 6, d = sum - x; i >= 2 && d > 0; i--) {
                int c = Math.min((int) Math.ceil(d * 1.0 / (i - 1)), cnts[i]);
                ans += c; d -= c * (i - 1);
            }
        } else if (sum < x) {
            for (int i = 1, d = x - sum; i <= 5 && d > 0; i++) {
                int c = Math.min((int) Math.ceil(d * 1.0 / (6 - i)), cnts[i]);
                ans += c; d -= c * (6 - i);
            }
        }
        return ans;
    }
}
```
C++ 代码:
```C++
class Solution {
public:
    int c1[10], c2[10];
    int s1, s2;
    int minOperations(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(), m = nums2.size();
        if (n > m) return minOperations(nums2, nums1);
        if (m > 6 * n) return -1;
        for (int x : nums1) {
            c1[x]++; s1 += x;
        }
        for (int x : nums2) {
            c2[x]++; s2 += x;
        }
        int ans = n + m;
        for (int i = m; i <= 6 * n; i++) {
            ans = min(ans, getCnt(c1, s1, i) + getCnt(c2, s2, i));
        }
        return ans;
    }
    int getCnt(int cnts[], int sum, int x) {
        int ans = 0;
        if (sum > x) {
            for (int i = 6, d = sum - x; i >= 2 && d > 0; i--) {
                int c = min((int) ceil(d * 1.0 / (i - 1)), cnts[i]);
                ans += c; d -= c * (i - 1);
            }
        } else if (sum < x) {
            for (int i = 1, d = x - sum; i <= 5 && d > 0; i++) {
                int c = min((int) ceil(d * 1.0 / (6 - i)), cnts[i]);
                ans += c; d -= c * (6 - i);
            }
        }
        return ans;
    }
};
```
Python 代码:
```Python
class Solution:
    def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
        n, m = len(nums1), len(nums2)
        if n > m:
            return self.minOperations(nums2, nums1)
        if m > 6 * n:
            return -1
        c1, c2 = Counter(nums1), Counter(nums2)
        s1, s2 = sum(nums1), sum(nums2)
        def getCnt(cnts, tot, x):
            ans = 0
            if tot > x:
                d = tot - x
                for i in range(6, 1, -1):
                    if d <= 0:
                        break
                    c = min(math.ceil(d / (i - 1)), cnts[i])
                    ans, d = ans + c, d - c * (i - 1)
            elif tot < x:
                d = x - tot
                for i in range(1, 6):
                    if d <= 0:
                        break
                    c = min(math.ceil(d / (6 - i)), cnts[i])
                    ans, d = ans + c, d - c * (6 - i)
            return ans
        ans = n + m
        for i in range(m, 6 * n + 1):
            ans = min(ans, getCnt(c1, s1, i) + getCnt(c2, s2, i))
        return ans
```
* 时间复杂度:$O(C \times \max(n, m) \times C)$,其中 $C = 6$ 为 $nums[i]$ 的值域大小
* 空间复杂度:$O(C)$

### 最后

这是我们「刷穿 LeetCode」系列文章的第 `No.1775` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。