From c1a446ba0df93b24b3eccc216438e7cc181dd50a Mon Sep 17 00:00:00 2001 From: AC_Oier Date: Thu, 21 Nov 2024 10:19:59 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=9F=20feat:=20add=2010=E3=80=811004?= =?UTF-8?q?=E3=80=811006=E3=80=811038=E3=80=811052=E3=80=81115=E3=80=81119?= =?UTF-8?q?=E3=80=811190=E3=80=811206=E3=80=811208=E3=80=811233=E3=80=8112?= =?UTF-8?q?82=E3=80=811334=E3=80=811403=E3=80=81150=E3=80=811413=E3=80=811?= =?UTF-8?q?422=E3=80=811438=E3=80=811442=E3=80=811482=E3=80=811486?= =?UTF-8?q?=E3=80=811503=E3=80=81153=E3=80=81154=E3=80=81155=E3=80=811603?= =?UTF-8?q?=E3=80=811631=E3=80=811662=E3=80=811584=E3=80=81173=E3=80=81179?= =?UTF-8?q?=E3=80=811748=E3=80=811749=E3=80=811763=E3=80=811764=E3=80=8117?= =?UTF-8?q?66=E3=80=811786=E3=80=811787=E3=80=81190=E3=80=81203=E3=80=8120?= =?UTF-8?q?8=E3=80=8123=E3=80=8126=E3=80=8130=E3=80=81213=E3=80=81220?= =?UTF-8?q?=E3=80=812216=E3=80=812304=E3=80=81240=E3=80=812477=E3=80=81255?= =?UTF-8?q?8=E3=80=81263=E3=80=81274=E3=80=8134=E3=80=8138=E3=80=81338?= =?UTF-8?q?=E3=80=81341=E3=80=81354=E3=80=81363=20...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...10\345\233\260\351\232\276\357\274\211.md" | 94 +++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 142 +++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 87 +++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 2 + ...10\344\270\255\347\255\211\357\274\211.md" | 72 ++++- ...10\345\233\260\351\232\276\357\274\211.md" | 68 ++++- ...10\347\256\200\345\215\225\357\274\211.md" | 183 ++++++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 81 +++++- ...10\345\233\260\351\232\276\357\274\211.md" | 57 +++- ...10\344\270\255\347\255\211\357\274\211.md" | 105 ++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 6 +- ...10\344\270\255\347\255\211\357\274\211.md" | 94 ++++--- ...10\344\270\255\347\255\211\357\274\211.md" | 6 +- ...10\347\256\200\345\215\225\357\274\211.md" | 32 +++ ...10\344\270\255\347\255\211\357\274\211.md" | 122 ++++++--- ...10\347\256\200\345\215\225\357\274\211.md" | 31 ++- ...10\347\256\200\345\215\225\357\274\211.md" | 61 ++++- ...10\344\270\255\347\255\211\357\274\211.md" | 90 +++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 20 +- ...10\344\270\255\347\255\211\357\274\211.md" | 185 ++++++++++--- ...10\347\256\200\345\215\225\357\274\211.md" | 106 +++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 21 +- ...10\344\270\255\347\255\211\357\274\211.md" | 84 ++++-- ...10\345\233\260\351\232\276\357\274\211.md" | 45 +++- ...10\347\256\200\345\215\225\357\274\211.md" | 112 ++++++-- ...10\347\256\200\345\215\225\357\274\211.md" | 193 ++++++++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 111 +++++++- ...10\347\256\200\345\215\225\357\274\211.md" | 6 +- ...10\347\256\200\345\215\225\357\274\211.md" | 14 +- ...10\344\270\255\347\255\211\357\274\211.md" | 94 ++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 10 +- ...10\347\256\200\345\215\225\357\274\211.md" | 95 ++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 68 ++++- ...10\347\256\200\345\215\225\357\274\211.md" | 169 +++++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 65 ++++- ...10\345\233\260\351\232\276\357\274\211.md" | 3 + ...10\344\270\255\347\255\211\357\274\211.md" | 23 +- ...10\345\233\260\351\232\276\357\274\211.md" | 125 +++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 4 +- ...10\347\256\200\345\215\225\357\274\211.md" | 120 ++++++--- ...10\347\256\200\345\215\225\357\274\211.md" | 107 ++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 68 ++--- ...10\345\233\260\351\232\276\357\274\211.md" | 24 +- ...10\347\256\200\345\215\225\357\274\211.md" | 148 ++++++++++- ...10\345\233\260\351\232\276\357\274\211.md" | 58 ++++- ...10\344\270\255\347\255\211\357\274\211.md" | 166 +++++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 110 +++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 2 +- ...10\344\270\255\347\255\211\357\274\211.md" | 9 +- ...10\344\270\255\347\255\211\357\274\211.md" | 14 +- ...10\344\270\255\347\255\211\357\274\211.md" | 14 +- ...10\347\256\200\345\215\225\357\274\211.md" | 4 +- ...10\347\256\200\345\215\225\357\274\211.md" | 47 +++- ...10\344\270\255\347\255\211\357\274\211.md" | 4 +- ...10\344\270\255\347\255\211\357\274\211.md" | 87 ++++++- ...10\347\256\200\345\215\225\357\274\211.md" | 77 +++++- ...10\347\256\200\345\215\225\357\274\211.md" | 110 ++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 104 +++++++- ...10\345\233\260\351\232\276\357\274\211.md" | 93 ++++--- ...10\345\233\260\351\232\276\357\274\211.md" | 31 +-- ...10\344\270\255\347\255\211\357\274\211.md" | 88 +++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 60 ++++- ...10\345\233\260\351\232\276\357\274\211.md" | 53 +++- ...10\344\270\255\347\255\211\357\274\211.md" | 95 ++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 70 ++--- ...10\344\270\255\347\255\211\357\274\211.md" | 133 ++++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 183 ++++++++++++- ...10\347\256\200\345\215\225\357\274\211.md" | 87 ++++++- ...10\347\256\200\345\215\225\357\274\211.md" | 80 ++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 100 ++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 91 +++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 19 +- ...10\347\256\200\345\215\225\357\274\211.md" | 56 +++- ...10\347\256\200\345\215\225\357\274\211.md" | 4 +- ...10\344\270\255\347\255\211\357\274\211.md" | 90 +++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 106 +++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 5 +- ...10\344\270\255\347\255\211\357\274\211.md" | 106 ++++++++ ...10\347\256\200\345\215\225\357\274\211.md" | 49 +++- ...10\344\270\255\347\255\211\357\274\211.md" | 121 ++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 75 +++++- ...10\345\233\260\351\232\276\357\274\211.md" | 72 ++++- ...10\344\270\255\347\255\211\357\274\211.md" | 39 ++- ...10\347\256\200\345\215\225\357\274\211.md" | 149 +++++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 42 ++- ...10\347\256\200\345\215\225\357\274\211.md" | 132 +++++++--- ...10\347\256\200\345\215\225\357\274\211.md" | 5 +- ...10\344\270\255\347\255\211\357\274\211.md" | 123 ++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 245 ++++++++++++++---- ...10\347\256\200\345\215\225\357\274\211.md" | 119 +++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 27 +- ...10\345\233\260\351\232\276\357\274\211.md" | 136 ++++++++-- ...10\347\256\200\345\215\225\357\274\211.md" | 139 ++++++++-- ...10\345\233\260\351\232\276\357\274\211.md" | 213 +++++++++++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 143 ++++++++-- ...10\347\256\200\345\215\225\357\274\211.md" | 12 +- ...10\345\233\260\351\232\276\357\274\211.md" | 34 ++- ...10\344\270\255\347\255\211\357\274\211.md" | 152 +++++++++-- ...10\345\233\260\351\232\276\357\274\211.md" | 133 +++++++--- ...10\347\256\200\345\215\225\357\274\211.md" | 2 +- ...10\347\256\200\345\215\225\357\274\211.md" | 48 ++-- ...10\347\256\200\345\215\225\357\274\211.md" | 175 ++++++++++++- ...10\345\233\260\351\232\276\357\274\211.md" | 14 +- ...10\347\256\200\345\215\225\357\274\211.md" | 182 ++++++++++--- ...10\347\256\200\345\215\225\357\274\211.md" | 106 +++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 131 +++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 58 +++++ ...10\347\256\200\345\215\225\357\274\211.md" | 98 ++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 59 ++++- ...10\344\270\255\347\255\211\357\274\211.md" | 4 +- ...10\345\233\260\351\232\276\357\274\211.md" | 86 ++++++ ...10\344\270\255\347\255\211\357\274\211.md" | 143 +++++++++- ...10\344\270\255\347\255\211\357\274\211.md" | 4 +- ...10\345\233\260\351\232\276\357\274\211.md" | 60 ++++- ...10\345\233\260\351\232\276\357\274\211.md" | 99 +++++-- ...10\344\270\255\347\255\211\357\274\211.md" | 59 ++++- ...10\347\256\200\345\215\225\357\274\211.md" | 133 +++++++--- 117 files changed, 7969 insertions(+), 1361 deletions(-) diff --git "a/LeetCode/1-10/10. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/1-10/10. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215\357\274\210\345\233\260\351\232\276\357\274\211.md" index f4ec1b79..f375150c 100644 --- "a/LeetCode/1-10/10. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/1-10/10. \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -74,41 +74,28 @@ Tag : 「动态规划」、「序列 DP」 本题可以使用动态规划进行求解: * 状态定义:`f(i,j)` 代表考虑 `s` 中以 `i` 为结尾的子串和 `p` 中的 `j` 为结尾的子串是否匹配。最终我们要求的结果为 `f[n][m]` 。 - * 状态转移:也就是我们要考虑 `f(i,j)` 如何求得,前面说到了 `p` 有三种字符,所以这里的状态转移也要分三种情况讨论: - - 1. `p[j]` 为普通字符:匹配的条件是前面的字符匹配,同时 `s` 中的第 `i` 个字符和 `p` 中的第 `j` 位相同。 - - `f(i,j) = f(i - 1, j - 1) && s[i] == p[j]` 。 - - 2. `p[j]` 为 `'.'`:匹配的条件是前面的字符匹配, `s` 中的第 `i` 个字符可以是任意字符。 - - `f(i,j) = f(i - 1, j - 1) && p[j] == '.'`。 - - 3. `p[j]` 为 `'*'`:读得 `p[j - 1]` 的字符,例如为字符 `a`。 然后根据 `a*` 实际匹配 `s` 中 `a` 的个数是 $0$ 个、$1$ 个、$2$ 个 ... - - 3.1. 当匹配为 $0$ 个:`f(i,j) = f(i, j - 2)` - - 3.2. 当匹配为 $1$ 个:`f(i,j) = f(i - 1, j - 2) && (s[i] == p[j - 1] || p[j - 1] == '.')` - - 3.3. 当匹配为 $2$ 个:`f(i,j) = f(i - 2, j - 2) && ((s[i] == p[j - 1] && s[i - 1] == p[j - 1]) || p[j] == '.')` + 1. `p[j]` 为普通字符:匹配的条件是前面的字符匹配,同时 `s` 中的第 `i` 个字符和 `p` 中的第 `j` 位相同。 即 `f(i,j) = f(i-1, j-1) && s[i] == p[j]` 。 + 2. `p[j]` 为 `'.'`:匹配的条件是前面的字符匹配, `s` 中的第 `i` 个字符可以是任意字符。即 `f(i,j) = f(i-1, j-1) && p[j] == '.'`。 + 3. `p[j]` 为 `'*'`:读得 `p[j-1]` 的字符,例如为字符 `a`。 然后根据 `a*` 实际匹配 `s` 中 `a` 的个数是 $0$ 个、$1$ 个、$2$ 个 ... + * 当匹配为 $0$ 个:`f(i,j) = f(i,j-2)` + * 当匹配为 $1$ 个:`f(i,j) = f(i-1,j-2) && (s[i] == p[j-1] || p[j-1] == '.')` + * 当匹配为 $2$ 个:`f(i,j) = f(i-2, j-2) && ((s[i] == p[j-1] && s[i-1] == p[j-1]) || p[j] == '.')` **我们知道,通过「枚举」来确定 `*` 到底匹配多少个 `a` 这样的做法,算法复杂度是很高的。** **我们需要挖掘一些「性质」来简化这个过程。** -![640.png](https://pic.leetcode-cn.com/1611397993-lmpHIZ-640.png) +![](https://pic.leetcode-cn.com/1611397993-lmpHIZ-640.png) -代码: +Java 代码: ```Java class Solution { public boolean isMatch(String ss, String pp) { // 技巧:往原字符头部插入空格,这样得到 char 数组是从 1 开始,而且可以使得 f[0][0] = true,可以将 true 这个结果滚动下去 int n = ss.length(), m = pp.length(); - ss = " " + ss; - pp = " " + pp; - char[] s = ss.toCharArray(); - char[] p = pp.toCharArray(); + ss = " " + ss; pp = " " + pp; + char[] s = ss.toCharArray(), p = pp.toCharArray(); // f(i,j) 代表考虑 s 中的 1~i 字符和 p 中的 1~j 字符 是否匹配 boolean[][] f = new boolean[n + 1][m + 1]; f[0][0] = true; @@ -132,6 +119,67 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool isMatch(string s, string p) { + int n = s.length(), m = p.length(); + s = " " + s; p = " " + p; + vector> f(n + 1, vector(m + 1, false)); + f[0][0] = true; + for (int i = 0; i <= n; i++) { + for (int j = 1; j <= m; j++) { + if (j + 1 <= m && p[j + 1] == '*' && p[j] != '*') continue; + if (i - 1 >= 0 && p[j] != '*') { + f[i][j] = f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '.'); + } else if (p[j] == '*') { + f[i][j] = (j - 2 >= 0 && f[i][j - 2]) || (i - 1 >= 0 && f[i - 1][j] && (s[i] == p[j - 1] || p[j - 1] == '.')); + } + } + } + return f[n][m]; + } +}; +``` +Python 代码: +```Python +class Solution: + def isMatch(self, s: str, p: str) -> bool: + n, m = len(s), len(p) + s, p = " " + s, " " + p + f = [[False] * (m + 1) for _ in range(n + 1)] + f[0][0] = True + for i in range(n + 1): + for j in range(1, m + 1): + if j + 1 <= m and p[j + 1] == '*' and p[j] != '*': + continue + if i - 1 >= 0 and p[j] != '*': + f[i][j] = f[i - 1][j - 1] and (s[i] == p[j] or p[j] == '.') + elif p[j] == '*': + f[i][j] = (j - 2 >= 0 and f[i][j - 2]) or (i - 1 >= 0 and f[i - 1][j] and (s[i] == p[j - 1] or p[j - 1] == '.')) + return f[n][m] +``` +TypeScript 代码: +```TypeScript +function isMatch(s: string, p: string): boolean { + let n: number = s.length, m: number = p.length + s = " " + s; p = " " + p; + let f: boolean[][] = new Array(n + 1).fill(false).map(() => new Array(m + 1).fill(false)); + f[0][0] = true; + for (let i: number = 0; i <= n; i++) { + for (let j: number = 1; j <= m; j++) { + if (j + 1 <= m && p.charAt(j + 1) === '*' && p.charAt(j) !== '*') continue; + if (i - 1 >= 0 && p.charAt(j) !== '*') { + f[i][j] = f[i - 1][j - 1] && (s.charAt(i) === p.charAt(j) || p.charAt(j) === '.'); + } else if (p.charAt(j) === '*') { + f[i][j] = (j - 2 >= 0 && f[i][j - 2]) || (i - 1 >= 0 && f[i - 1][j] && (s.charAt(i) === p.charAt(j - 1) || p.charAt(j - 1) === '.')); + } + } + } + return f[n][m]; +}; +``` * 时间复杂度:$n$ 表示 `s` 的长度,$m$ 表示 `p` 的长度,总共 $n \times m$ 个状态。复杂度为 $O(n \times m)$ * 空间复杂度:使用了二维数组记录结果。复杂度为 $O(n \times m)$ diff --git "a/LeetCode/1001-1010/1004. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 III\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1001-1010/1004. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 III\357\274\210\344\270\255\347\255\211\357\274\211.md" index 15a9dcf4..1f559a0a 100644 --- "a/LeetCode/1001-1010/1004. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 III\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1001-1010/1004. \346\234\200\345\244\247\350\277\236\347\273\2551\347\232\204\344\270\252\346\225\260 III\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,15 +6,17 @@ Tag : 「双指针」、「滑动窗口」、「二分」、「前缀和」 -给定一个由若干 $0$ 和 $1$ 组成的数组 `A`,我们最多可以将 $K$ 个值从 $0$ 变成 $1$ 。 +给定一个由若干 0 和 1 组成的数组 `A`,我们最多可以将 `K` 个值从 0 变成 1。 -返回仅包含 $1$ 的最长(连续)子数组的长度。 +返回仅包含 1 的最长(连续)子数组的长度。 示例 1: ``` 输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2 + 输出:6 + 解释: [1,1,1,0,0,1,1,1,1,1,1] 粗体数字从 0 翻转到 1,最长的子数组长度为 6。 @@ -22,7 +24,9 @@ Tag : 「双指针」、「滑动窗口」、「二分」、「前缀和」 示例 2: ``` 输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3 + 输出:10 + 解释: [0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1] 粗体数字从 0 翻转到 1,最长的子数组长度为 10。 @@ -50,7 +54,7 @@ Tag : 「双指针」、「滑动窗口」、「二分」、「前缀和」 * 如果 $A[i]$ 本身就为 1 的话,无须消耗翻转次数,$f[i][j] = f[i - 1][j] + 1$。 * 如果 $A[i]$ 本身不为 1 的话,由于定义是必须以 $A[i]$ 为结尾,因此必须要选择翻转该位置,$f[i][j] = f[i - 1][j - 1] + 1$。 -代码: +Java 代码: ```Java class Solution { public int longestOnes(int[] nums, int k) { @@ -96,23 +100,19 @@ class Solution { **因此,对于某个确定的「左端点/右端点」而言,以「其最远右端点/最远左端点」为分割点的前缀和数轴,具有「二段性」。可以通过二分来找分割点。** -代码: +Java 代码: ```Java class Solution { public int longestOnes(int[] nums, int k) { - int n = nums.length; - int ans = 0; + int n = nums.length, ans = 0; int[] sum = new int[n + 1]; for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1]; for (int i = 0; i < n; i++) { int l = 0, r = i; while (l < r) { int mid = l + r >> 1; - if (check(sum, mid, i, k)) { - r = mid; - } else { - l = mid + 1; - } + if (check(sum, mid, i, k)) r = mid; + else l = mid + 1; } if (check(sum, r, i, k)) ans = Math.max(ans, i - r + 1); } @@ -124,6 +124,80 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int longestOnes(vector& nums, int k) { + int n = nums.size(), ans = 0; + vector sum(n + 1, 0); + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1]; + for (int i = 0; i < n; i++) { + int l = 0, r = i; + while (l < r) { + int mid = l + r >> 1; + if (check(sum, mid, i, k)) r = mid; + else l = mid + 1; + } + if (check(sum, r, i, k)) ans = max(ans, i - r + 1); + } + return ans; + } + bool check(const vector& sum, int l, int r, int k) { + int tol = sum[r + 1] - sum[l]; + int len = r - l + 1; + return len - tol <= k; + } +}; +``` +Python 代码: +```Python +class Solution: + def longestOnes(self, nums: List[int], k: int) -> int: + n, ans = len(nums), 0 + sumv = [0] * (n + 1) + for i in range(1, n + 1): + sumv[i] = sumv[i - 1] + nums[i - 1] + for i in range(n): + l, r = 0, i + while l < r: + mid = l + r >> 1 + if self.check(sumv, mid, i, k): + r = mid + else: + l = mid + 1 + if self.check(sumv, l, i, k): + ans = max(ans, i - l + 1) + return ans + + def check(self, sumv, l, r, k): + tol = sumv[r + 1] - sumv[l] + lenv = r - l + 1 + return lenv - tol <= k +``` +TypeScript 代码: +```TypeScript +function check(sum: number[], l: number, r: number, k: number): boolean { + const tol = sum[r + 1] - sum[l]; + const len = r - l + 1; + return len - tol <= k; +} +function longestOnes(nums: number[], k: number): number { + let n = nums.length, ans = 0; + const sum = new Array(n + 1).fill(0); + for (let i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1]; + for (let i = 0; i < n; i++) { + let l = 0, r = i; + while (l < r) { + let mid = l + r >> 1; + if (check(sum, mid, i, k)) r = mid; + else l = mid + 1; + } + if (check(sum, l, i, k)) ans = Math.max(ans, i - l + 1); + } + return ans; +}; +``` * 时间复杂度:$O(n\log{n})$ * 空间复杂度:$O(n)$ @@ -143,12 +217,11 @@ class Solution { 右端点一直右移,左端点在窗口不满足「`len - tol <= k`」的时候进行右移,即可做到线程扫描的复杂度。 -代码: +Java 代码: ```Java class Solution { public int longestOnes(int[] nums, int k) { - int n = nums.length; - int ans = 0; + int n = nums.length, ans = 0; for (int i = 0, j = 0, tot = 0; i < n; i++) { tot += nums[i]; while ((i - j + 1) - tot > k) tot -= nums[j++]; @@ -158,6 +231,47 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int longestOnes(vector& nums, int k) { + int n = nums.size(), ans = 0; + for (int i = 0, j = 0, tot = 0; i < n; i++) { + tot += nums[i]; + while ((i - j + 1) - tot > k) tot -= nums[j++]; + ans = max(ans, i - j + 1); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def longestOnes(self, nums: List[int], k: int) -> int: + n, ans = len(nums), 0 + j, tot = 0, 0 + for i in range(n): + tot += nums[i] + while (i - j + 1) - tot > k: + tot -= nums[j] + j += 1 + ans = max(ans, i - j + 1) + return ans +``` +TypeScript 代码: +```TypeScript +function longestOnes(nums: number[], k: number): number { + let n = nums.length, ans = 0; + for (let i = 0, j = 0, tot = 0; i < n; i++) { + tot += nums[i]; + while ((i - j + 1) - tot > k) tot -= nums[j++]; + ans = Math.max(ans, i - j + 1); + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/1001-1010/1006. \347\254\250\351\230\266\344\271\230\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1001-1010/1006. \347\254\250\351\230\266\344\271\230\357\274\210\344\270\255\347\255\211\357\274\211.md" index fb6f9b9c..28dd34b6 100644 --- "a/LeetCode/1001-1010/1006. \347\254\250\351\230\266\344\271\230\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1001-1010/1006. \347\254\250\351\230\266\344\271\230\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,17 +6,17 @@ Tag : 「数学」、「栈」 -通常,正整数 n 的阶乘是所有小于或等于 n 的正整数的乘积。 +通常,正整数 `n` 的阶乘是所有小于或等于 `n` 的正整数的乘积。 -例如,factorial(10) = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1。 +例如,`factorial(10) = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1`。 -相反,我们设计了一个笨阶乘 clumsy:在整数的递减序列中,我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(*),除法(/),加法(+)和减法(-)。 +相反,我们设计了一个笨阶乘 `clumsy`:在整数的递减序列中,我们以一个固定顺序的操作符序列来依次替换原有的乘法操作符:乘法(`*`),除法(`/`),加法(`+`)和减法(`-`)。 -例如,clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1。然而,这些运算仍然使用通常的算术运算顺序:我们在任何加、减步骤之前执行所有的乘法和除法步骤,并且按从左到右处理乘法和除法步骤。 +例如,`clumsy(10) = 10 * 9 / 8 + 7 - 6 * 5 / 4 + 3 - 2 * 1`。然而,这些运算仍然使用通常的算术运算顺序:我们在任何加、减步骤之前执行所有的乘法和除法步骤,并且按从左到右处理乘法和除法步骤。 -另外,我们使用的除法是地板除法(floor division),所以 10 * 9 / 8 等于 11。这保证结果是一个整数。 +另外,我们使用的除法是地板除法(`floor division`),所以 `10 * 9 / 8` 等于 `11`。这保证结果是一个整数。 -实现上面定义的笨函数:给定一个整数 N,它返回 N 的笨阶乘。 +实现上面定义的笨函数:给定一个整数 `N`,它返回 `N` 的笨阶乘。   @@ -39,8 +39,8 @@ Tag : 「数学」、「栈」 提示: -* 1 <= N <= 10000 -* -$2^{31}$ <= answer <= $2^{31}$ - 1  (答案保证符合 32 位整数) +* $1 <= N <= 10000$ +* $-2^{31} <= answer <= 2^{31} - 1$  (答案保证符合 `32` 位整数) --- @@ -48,7 +48,7 @@ Tag : 「数学」、「栈」 第一种解法是我们的老朋友解法了,使用「双栈」来解决通用表达式问题。 -**事实上,我提供这套解决方案不仅仅能解决只有 `+ - ( )`([224. 基本计算器](https://leetcode-cn.com/problems/basic-calculator/)) 或者 `+ - * /`([227. 基本计算器 II](https://leetcode-cn.com/problems/basic-calculator-ii/solution/)) 的表达式问题,还能能解决 `+ - * / ^ % ( )` 的完全表达式问题。** +**事实上,我提供这套解决方案不仅仅能解决只有 `+ - ( )`([224. 基本计算器](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247492602&idx=1&sn=135fd5b530189f13e0395414a6b47893&chksm=fd9f48e5cae8c1f3ee1fc83f3410ebb9b8fb24209bf6f08640ba3ddaf4db27d338b9d3fd3cbe#rd)) 或者 `+ - * /`([227. 基本计算器 II](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247492653&idx=1&sn=53db69e634307b64b5d8d1845fb7baf5&chksm=fd9f4f32cae8c624a8a2eef50a6917bdd834edc9e1bb18e16014062346506b2a3df3d24adb0e#rd)) 的表达式问题,还能能解决 `+ - * / ^ % ( )` 的完全表达式问题。** 甚至支持自定义运算符,只要在运算优先级上进行维护即可。 @@ -72,10 +72,9 @@ Tag : 「数学」、「栈」 1. 如果后面出现的 `+ 2` 或者 `- 1` 的话,满足「栈内运算符」比「当前运算符」优先级高/同等,可以将 `2 + 1` 算掉,把结果放到 `nums` 中; 2. 如果后面出现的是 `* 2` 或者 `/ 1` 的话,不满足「栈内运算符」比「当前运算符」优先级高/同等,这时候不能计算 `2 + 1`。 -更为详细的讲解可以看这篇题解 :[使用「双栈」解决「究极表达式计算」问题](https://leetcode-cn.com/problems/basic-calculator-ii/solution/shi-yong-shuang-zhan-jie-jue-jiu-ji-biao-c65k/) - -代码: +更为详细的讲解可以看这篇题解 :[使用「双栈」解决「究极表达式计算」问题](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247492653&idx=1&sn=53db69e634307b64b5d8d1845fb7baf5&chksm=fd9f4f32cae8c624a8a2eef50a6917bdd834edc9e1bb18e16014062346506b2a3df3d24adb0e#rd) +Java 代码: ```Java class Solution { public int clumsy(int n) { @@ -114,10 +113,46 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int clumsy(int n) { + stack nums; + stack ops; + // 维护运算符优先级 + map map = {{'*', 2}, {'/', 2}, {'+', 1}, {'-', 1}}; + char opsSeq[] = {'*', '/', '+', '-'}; + for (int i = n, j = 0; i > 0; i--, j++) { + char op = opsSeq[j % 4]; + nums.push(i); + // 如果「当前运算符优先级」不高于「栈顶运算符优先级」,说明栈内的可以算 + while (!ops.empty() && map[ops.top()] >= map[op]) { + calc(nums, ops); + } + if (i != 1) ops.push(op); + } + // 如果栈内还有元素没有算完,继续算 + while (!ops.empty()) calc(nums, ops); + return nums.top(); + } + void calc(stack& nums, stack& ops) { + int b = nums.top(); nums.pop(); + int a = nums.top(); nums.pop(); + char op = ops.top(); ops.pop(); + int ans = 0; + if (op == '+') ans = a + b; + else if (op == '-') ans = a - b; + else if (op == '*') ans = a * b; + else if (op == '/') ans = a / b; + nums.push(ans); + } +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ -*** +--- ### 数学解法(打表技巧分析) @@ -135,7 +170,7 @@ class Solution { } ``` -![image.png](https://pic.leetcode-cn.com/1617241518-IgbeTk-image.png) +![](https://pic.leetcode-cn.com/1617241518-IgbeTk-image.png) 似乎 $n$ 与 答案比较接近,我们考虑将两者的差值输出: @@ -147,7 +182,7 @@ class Solution { } ``` -![image.png](https://pic.leetcode-cn.com/1617241458-qndmFZ-image.png) +![](https://pic.leetcode-cn.com/1617241458-qndmFZ-image.png) 咦,好像发现了什么不得了的东西。似乎每四个数,差值都是 [1, 2, 2, -1] @@ -164,16 +199,14 @@ class Solution { } } ``` -![image.png](https://pic.leetcode-cn.com/1617241727-VssKmQ-image.png) +![](https://pic.leetcode-cn.com/1617241727-VssKmQ-image.png) 只有前四个数字被输出,其他数字都是符合我们的猜想规律的。 - **到这里我们已经知道代码怎么写可以 AC 了,十分简单。** -代码: - -```java +Java 代码: +```Java class Solution { public int clumsy(int n) { int[] special = new int[]{1,2,6,7}; @@ -183,10 +216,22 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int clumsy(int n) { + array special = {1, 2, 6, 7}; + array diff = {1, 2, 2, -1}; + if (n <= 4) return special[(n - 1) % 4]; + return n + diff[n % 4]; + } +}; +``` * 时间复杂度:$O(1)$ * 空间复杂度:$O(1)$ -*** +--- ### 证明 diff --git "a/LeetCode/1031-1040/1038. \344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1031-1040/1038. \344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" index e71b4a6e..8dbf376c 100644 --- "a/LeetCode/1031-1040/1038. \344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1031-1040/1038. \344\273\216\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\345\210\260\346\233\264\345\244\247\345\222\214\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -15,7 +15,9 @@ Tag : 「BST」、「中序遍历」 * 左右子树也必须是二叉搜索树。 示例 1: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/05/03/tree.png) + ``` 输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] diff --git "a/LeetCode/1051-1060/1052. \347\210\261\347\224\237\346\260\224\347\232\204\344\271\246\345\272\227\350\200\201\346\235\277\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1051-1060/1052. \347\210\261\347\224\237\346\260\224\347\232\204\344\271\246\345\272\227\350\200\201\346\235\277\357\274\210\344\270\255\347\255\211\357\274\211.md" index b78e9a0b..12917c84 100644 --- "a/LeetCode/1051-1060/1052. \347\210\261\347\224\237\346\260\224\347\232\204\344\271\246\345\272\227\350\200\201\346\235\277\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1051-1060/1052. \347\210\261\347\224\237\346\260\224\347\232\204\344\271\246\345\272\227\350\200\201\346\235\277\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,11 +6,13 @@ Tag : 「滑动窗口」、「双指针」 -今天,书店老板有一家店打算试营业 $customers.length$ 分钟。每分钟都有一些顾客($customers[i]$)会进入书店,所有这些顾客都会在那一分钟结束后离开。 +今天,书店老板有一家店打算试营业 `customers.length` 分钟。每分钟都有一些顾客(`customers[i]`)会进入书店,所有这些顾客都会在那一分钟结束后离开。 -在某些时候,书店老板会生气。 如果书店老板在第 $i$ 分钟生气,那么 $grumpy[i] = 1$,否则 $grumpy[i] = 0$。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。 +在某些时候,书店老板会生气。 如果书店老板在第 `i` 分钟生气,那么 `grumpy[i] = 1`,否则 `grumpy[i] = 0`。 -书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 $X$ 分钟不生气,但却只能使用一次。 +当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。 + +书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 `X` 分钟不生气,但却只能使用一次。 请你返回这一天营业下来,最多有多少客户能够感到满意的数量。 @@ -36,11 +38,11 @@ Tag : 「滑动窗口」、「双指针」 由于「技巧」只会将情绪将「生气」变为「不生气」,不生气仍然是不生气。 -1. 我们可以先将原本就满意的客户加入答案,同时将对应的 $customers[i]$ 变为 $0$。 +1. 我们可以先将原本就满意的客户加入答案,同时将对应的 `customers[i]` 变为 0。 -2. 之后的问题转化为:在 $customers$ 中找到连续一段长度为 $minutes$ 的子数组,使得其总和最大。这部分就是我们应用技巧所得到的客户。 +2. 之后的问题转化为:在 `customers` 中找到连续一段长度为 `minutes` 的子数组,使得其总和最大。这部分就是我们应用技巧所得到的客户。 -代码: +Java 代码: ```Java class Solution { public int maxSatisfied(int[] customers, int[] grumpy, int minutes) { @@ -61,6 +63,64 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxSatisfied(vector& customers, vector& grumpy, int minutes) { + int n = customers.size(), ans = 0; + for (int i = 0; i < n; i++) { + if (grumpy[i] == 0) { + ans += customers[i]; + customers[i] = 0; + } + } + int cur = 0, max_val = 0; + for (int i = 0; i < n; i++) { + cur += customers[i]; + if (i >= minutes) cur -= customers[i - minutes]; + max_val = max(max_val, cur); + } + return ans + max_val; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int: + n, ans = len(customers), 0 + for i in range(n): + if grumpy[i] == 0: + ans += customers[i] + customers[i] = 0 + cur, maxv = 0, 0 + for i in range(n): + cur += customers[i] + if i >= minutes: + cur -= customers[i - minutes] + maxv = max(maxv, cur) + return ans + maxv +``` +TypeScript 代码: +```TypeScript +function maxSatisfied(customers: number[], grumpy: number[], minutes: number): number { + let n = customers.length, ans = 0; + for (let i = 0; i < n; i++) { + if (grumpy[i] === 0) { + ans += customers[i]; + customers[i] = 0; + } + } + let cur = 0, maxv = 0; + for (let i = 0; i < n; i++) { + cur += customers[i]; + if (i >= minutes) cur -= customers[i - minutes]; + maxv = Math.max(maxv, cur); + } + return ans + maxv; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/111-120/115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/111-120/115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227\357\274\210\345\233\260\351\232\276\357\274\211.md" index aa5312ef..74b36df0 100644 --- "a/LeetCode/111-120/115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/111-120/115. \344\270\215\345\220\214\347\232\204\345\255\220\345\272\217\345\210\227\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -66,7 +66,7 @@ babgbag 字符串匹配也不具有二段性质,不可能有 $log$ 级别的算法,那么复杂度再往下优化就是 $O(n * m)$ 的递推 DP 做法了。 -*** +--- ### 动态规划 @@ -83,8 +83,8 @@ DP 的状态定义猜测通常是一门经验学科。 最终 $f[i][j]$ 就是两者之和。 -代码: -```java [] +Java 代码: +```Java class Solution { public int numDistinct(String s, String t) { // 技巧:往原字符头部插入空格,这样得到 char 数组是从 1 开始 @@ -103,21 +103,71 @@ class Solution { // 不使用 cs[i] 进行匹配,则有 f[i][j] = f[i - 1][j] f[i][j] = f[i - 1][j]; // 使用 cs[i] 进行匹配,则要求 cs[i] == ct[j],然后有 f[i][j] += f[i - 1][j - 1] - if (cs[i] == ct[j]) { - f[i][j] += f[i - 1][j - 1]; - } + if (cs[i] == ct[j]) f[i][j] += f[i - 1][j - 1]; } } return f[n][m]; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int numDistinct(string s, string t) { + int n = s.length(), m = t.length(); + s = " " + s; + t = " " + t; + vector> f(n + 1, vector(m + 1, 0)); + for (int i = 0; i <= n; i++) f[i][0] = 1; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + f[i][j] = f[i - 1][j]; + if (s[i] == t[j]) f[i][j] += f[i - 1][j - 1]; + } + } + return f[n][m]; + } +}; +``` +Python 代码: +```Python +class Solution: + def numDistinct(self, s: str, t: str) -> int: + n, m = len(s), len(t) + s = " " + s + t = " " + t + f = [[0]*(m + 1) for _ in range(n + 1)] + for i in range(n + 1): + f[i][0] = 1 + for i in range(1, n + 1): + for j in range(1, m + 1): + f[i][j] = f[i - 1][j] + if s[i] == t[j]: + f[i][j] += f[i - 1][j - 1] + return f[n][m] +``` +TypeScript 代码: +```TypeScript +function numDistinct(s: string, t: string): number { + const n: number = s.length, m: number = t.length; + const f: number[][] = new Array(n + 1).fill(0).map(() => new Array(m + 1).fill(0)); + for (let i: number = 0; i <= n; i++) f[i][0] = 1; + for (let i: number = 1; i <= n; i++) { + for (let j: number = 1; j <= m; j++) { + f[i][j] = f[i - 1][j]; + if (s.charAt(i - 1) === t.charAt(j - 1)) f[i][j] += f[i - 1][j - 1]; + } + } + return f[n][m]; +}; +``` * 时间复杂度:$O(n \times m)$ * 空间复杂度:$O(n \times m)$ -*PS. 需要说明的是,由于中间结果会溢出,CPP 中必须使用 long long,而 Java 不用。由于 Java 中 int 的存储机制,只要在运算过程中只要不涉及取 min、取 max 或者其他比较操作的话,中间结果溢出不会影响最终结果。* +*PS. 需要说明的是,由于中间结果会溢出,`CPP` 中必须使用 `unsigned long long`,而 `Java` 不用。由于 `Java` 中 `int` 的存储机制,只要在运算过程中只要不涉及取 `min`、取 `max` 或者其他比较操作的话,中间结果溢出不会影响最终结果。* -*** +--- ### 总结 @@ -128,7 +178,7 @@ class Solution { 2. 往两个字符串的头部追加「不存在」的字符,目的是为了能够构造出可以滚动(被累加)下去的初始化值 -*** +--- ### 进阶 diff --git "a/LeetCode/111-120/119. \346\235\250\350\276\211\344\270\211\350\247\222 II\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/111-120/119. \346\235\250\350\276\211\344\270\211\350\247\222 II\357\274\210\347\256\200\345\215\225\357\274\211.md" index d14b64fd..bf2037ec 100644 --- "a/LeetCode/111-120/119. \346\235\250\350\276\211\344\270\211\350\247\222 II\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/111-120/119. \346\235\250\350\276\211\344\270\211\350\247\222 II\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,23 +7,31 @@ Tag : 「数学」、「线性 DP」 -给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。 +给定一个非负索引 `k`,其中 `k ≤ 33`,返回杨辉三角的第 `k` 行。 在杨辉三角中,每个数是它左上方和右上方的数的和。 +[](https://pic.leetcode-cn.com/1626927345-DZmfxB-PascalTriangleAnimated2.gif) + 示例: ``` 输入: 3 + 输出: [1,3,3,1] ``` 进阶: -* 你可以优化你的算法到 O(k) 空间复杂度吗? +* 你可以优化你的算法到 $O(k)$ 空间复杂度吗? --- ### 动态规划 -```java +动态规划裸题。 + +定义 $f[i][j]$ 为杨辉三角中第 $i$ 行第 $j$ 列的值,然后根据杨辉三角规则(每个数是它左上方和右上方的数的和)进行转移,初始化有 $f[0][0] = 1$。 + +Java 代码: +```Java class Solution { public List getRow(int idx) { int[][] f = new int[idx + 1][idx + 1]; @@ -41,18 +49,71 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector getRow(int idx) { + vector> f(idx + 1, vector(idx + 1, 0)); + f[0][0] = 1; + for (int i = 1; i < idx + 1; i++) { + for (int j = 0; j < i + 1; j++) { + f[i][j] = f[i - 1][j]; + if (j - 1 >= 0) f[i][j] += f[i - 1][j - 1]; + if (f[i][j] == 0) f[i][j] = 1; + } + } + vector ans; + for (int i = 0; i < idx + 1; i++) ans.push_back(f[idx][i]); + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def getRow(self, idx: int) -> List[int]: + f = [[0] * (idx + 1) for i in range(idx + 1)] + f[0][0] = 1 + for i in range(1, idx + 1): + for j in range(i + 1): + f[i][j] = f[i - 1][j] + if j - 1 >= 0: + f[i][j] += f[i - 1][j - 1] + if f[i][j] == 0: + f[i][j] = 1 + return [f[idx][i] for i in range(idx + 1)] +``` +TypeScript 代码: +```TypeScript +function getRow(idx: number): number[] { + const f: number[][] = new Array(idx + 1).fill(0).map(() => new Array(idx + 1).fill(0)); + f[0][0] = 1; + for (let i = 1; i < idx + 1; i++) { + for (let j = 0; j < i + 1; j++) { + f[i][j] = f[i - 1][j]; + if (j - 1 >= 0) f[i][j] += f[i - 1][j - 1]; + if (f[i][j] == 0) f[i][j] = 1; + } + } + const ans: number[] = []; + for (let i = 0; i < idx + 1; i++) ans.push(f[idx][i]); + return ans; +}; +``` * 时间复杂度:$O(n^2)$ * 空间复杂度:$O(n^2)$ -*** +--- ### 滚动数组 滚动数组优化十分机械,直接将滚动的维度从 `i` 改造为 `i % 2` 或 `i & 1` 即可。 -`i & 1` 相比于 `i % 2` 在不同架构的机器上,效率会更稳定些 ~ +`i & 1` 相比于 `i % 2` 在不同架构的机器上,效率会更稳定些。 -```java +Java 代码: +```Java class Solution { public List getRow(int idx) { int[][] f = new int[2][idx + 1]; @@ -70,16 +131,71 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector getRow(int idx) { + vector> f(2, vector(idx + 1, 0)); + f[0][0] = 1; + for (int i = 1; i < idx + 1; i++) { + for (int j = 0; j < i + 1; j++) { + f[i & 1][j] = f[(i - 1) & 1][j]; + if (j - 1 >= 0) f[i & 1][j] += f[(i - 1) & 1][j - 1]; + if (f[i & 1][j] == 0) f[i & 1][j] = 1; + } + } + vector ans; + for (int i = 0; i < idx + 1; i++) ans.push_back(f[idx & 1][i]); + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def getRow(self, idx: int) -> List[int]: + f = [[0] * (idx + 1) for i in range(2)] + f[0][0] = 1 + for i in range(1, idx + 1): + for j in range(i + 1): + f[i & 1][j] = f[(i - 1) & 1][j] + if j - 1 >= 0: + f[i & 1][j] += f[(i - 1) & 1][j - 1] + if f[i & 1][j] == 0: + f[i & 1][j] = 1 + return [f[idx & 1][i] for i in range(idx + 1)] +``` +TypeScript 代码: +```TypeScript +function getRow(idx: number): number[] { + const f: number[][] = new Array(2).fill(0).map(() => new Array(idx + 1).fill(0)); + f[0][0] = 1; + for (let i = 1; i < idx + 1; i++) { + for (let j = 0; j < i + 1; j++) { + f[i & 1][j] = f[(i - 1) & 1][j]; + if (j - 1 >= 0) f[i & 1][j] += f[(i - 1) & 1][j - 1]; + if (f[i & 1][j] == 0) f[i & 1][j] = 1; + } + } + const ans: number[] = []; + for (let i = 0; i < idx + 1; i++) ans.push(f[idx & 1][i]); + return ans; +}; +``` * 时间复杂度:$O(n^2)$ * 空间复杂度:$O(n)$ -*** +--- ### 一维优化 -只有第 `i` 行的更新只依赖于 `i - 1` 行,因此可以直接消除行的维度: +只有第 `i` 行的更新只依赖于 `i - 1` 行,因此可以直接消除行的维度。 -```java +但需要注意,为了防止在计算当前行的第 `j` 列时,所依赖于前一行的第 `j - 1` 列被覆盖,我们需要调整列循环的方向(从大到小转移)。 + +Java 代码: +```Java class Solution { public List getRow(int idx) { int[] f = new int[idx + 1]; @@ -96,6 +212,55 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector getRow(int idx) { + vector f(idx + 1, 0); + f[0] = 1; + for (int i = 1; i < idx + 1; i++) { + for (int j = i; j >= 0; j--) { + if (j - 1 >= 0) f[j] += f[j - 1]; + if (f[j] == 0) f[j] = 1; + } + } + vector ans; + for (int i = 0; i < idx + 1; i++) ans.push_back(f[i]); + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def getRow(self, idx: int) -> List[int]: + f = [0] * (idx + 1) + f[0] = 1 + for i in range(1, idx + 1): + for j in range(i, 0, -1): + if j - 1 >= 0: + f[j] += f[j - 1] + if f[j] == 0: + f[j] = 1 + return [f[i] for i in range(idx + 1)] +``` +TypeScript 代码: +```TypeScript +function getRow(idx: number): number[] { + const f: number[] = new Array(idx + 1).fill(0) + f[0] = 1; + for (let i = 1; i < idx + 1; i++) { + for (let j = i; j >= 0; j--) { + if (j - 1 >= 0) f[j] += f[j - 1]; + if (f[j] == 0) f[j] = 1; + } + } + const ans: number[] = []; + for (let i = 0; i < idx + 1; i++) ans.push(f[i]); + return ans; +}; +``` * 时间复杂度:$O(n^2)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/1181-1190/1190. \345\217\215\350\275\254\346\257\217\345\257\271\346\213\254\345\217\267\351\227\264\347\232\204\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1181-1190/1190. \345\217\215\350\275\254\346\257\217\345\257\271\346\213\254\345\217\267\351\227\264\347\232\204\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" index a21dfbf0..cb0d4f9d 100644 --- "a/LeetCode/1181-1190/1190. \345\217\215\350\275\254\346\257\217\345\257\271\346\213\254\345\217\267\351\227\264\347\232\204\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1181-1190/1190. \345\217\215\350\275\254\346\257\217\345\257\271\346\213\254\345\217\267\351\227\264\347\232\204\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -10,7 +10,7 @@ Tag : 「双端队列」、「栈」 请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。 -注意,您的结果中 不应 包含任何括号。 +注意,您的结果中不应包含任何括号。 示例 1: ``` @@ -58,7 +58,7 @@ Tag : 「双端队列」、「栈」 在 `Java` 中,双端队列可以使用自带的 `ArrayDeque`, 也可以直接使用数组进行模拟。 -代码(使用 `ArrayDeque` ): +Java 代码(使用 `ArrayDeque` ): ```Java class Solution { public String reverseParentheses(String s) { @@ -88,7 +88,7 @@ class Solution { } } ``` -代码(数组模拟双端队列): +Java 代码(数组模拟双端队列): ```Java class Solution { int N = 2010, he = 0, ta = 0; @@ -118,6 +118,81 @@ class Solution { } } ``` +C++ 代码(数组模拟双端队列): +```C++ +class Solution { +public: + string reverseParentheses(string s) { + int N = 2010; + vector d(N), path(N); + int he = 0, ta = 0; + for (char c : s) { + if (c == ')') { + int idx = 0; + while (he < ta) { + if (d[ta - 1] == '(' && --ta >= 0) { + for (int j = 0; j < idx; j++) d[ta++] = path[j]; + break; + } else { + path[idx++] = d[--ta]; + } + } + } else { + d[ta++] = c; + } + } + return string(d.begin(), d.begin() + ta); + } +}; +``` +Python 代码(数组模拟双端队列): +```Python +class Solution: + def reverseParentheses(self, s: str) -> str: + N, he, ta = 2010, 0, 0 + d, path = [''] * N, [''] * N + for c in s: + if c == ')': + idx = 0 + while he < ta: + ta -= 1 + if d[ta] == '(' and ta >= 0: + for j in range(idx): + d[ta] = path[j] + ta += 1 + break + else: + path[idx] = d[ta] + idx += 1 + else: + d[ta] = c + ta += 1 + return ''.join(d[:ta]) +``` +TypeScript 代码(数组模拟双端队列): +```TypeScript +function reverseParentheses(s: string): string { + let N = 201, he = 0, ta = 0; + const d: string[] = new Array(N), path = new Array(N); + for (let i = 0; i < s.length; i++) { + let c = s.charAt(i); + if (c === ')') { + let idx = 0; + while (he < ta) { + if (d[ta - 1] === '(' && --ta >= 0) { + for (let j = 0; j < idx; j++) d[ta++] = path[j]; + break; + } else { + path[idx++] = d[--ta]; + } + } + } else { + d[ta++] = c; + } + } + return d.slice(0, ta).join(''); +}; +``` * 时间复杂度:每个 `(` 字符只会进出队列一次;`)` 字符串都不会进出队列,也只会被扫描一次;分析的重点在于普通字符,可以发现每个普通字符进出队列的次数取决于其右边的 `)` 的个数,最坏情况下每个字符右边全是右括号,因此复杂度可以当做 $O(n^2)$,但实际计算量必然取不满 $n^2$,将普通字符的重复弹出均摊到整个字符串处理过程,可以看作是每个字符串都被遍历常数次,复杂度为 $O(n)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/1201-1210/1206. \350\256\276\350\256\241\350\267\263\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/1201-1210/1206. \350\256\276\350\256\241\350\267\263\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" index 2d309f4c..8c4dd8b7 100644 --- "a/LeetCode/1201-1210/1206. \350\256\276\350\256\241\350\267\263\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/1201-1210/1206. \350\256\276\350\256\241\350\267\263\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -6,15 +6,19 @@ Tag : 「链表」、「数据结构」 -不使用任何库函数,设计一个 跳表 。 +不使用任何库函数,设计一个跳表。 -跳表 是在 $O(log{n})$ 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。 +跳表 是在 $O(\log{n})$ 时间内完成增加、删除、搜索操作的数据结构。 + +跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。 例如,一个跳表包含 `[30, 40, 50, 60, 70, 90]`,然后增加 `80`、`45` 到跳表中,以下图的方式操作: ![](https://assets.leetcode.com/uploads/2019/09/27/1506_skiplist.gif) -跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 $O(n)$。跳表的每一个操作的平均时间复杂度是 $O(\log{n})$,空间复杂度是 $O(n)$。 +跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 $O(n)$。 + +跳表的每一个操作的平均时间复杂度是 $O(\log{n})$,空间复杂度是 $O(n)$。 在本题中,你的设计应该要包含这些函数: @@ -48,7 +52,7 @@ skiplist.search(1); // 返回 false,1 已被擦除 提示: * $0 <= num, target <= 2 \times 10^4$ -* 调用 `search`, `add`,  `erase` 操作次数不大于 $5 \times 10^44$ +* 调用 `search`, `add`,  `erase` 操作次数不大于 $5 \times 10^4$ --- @@ -121,6 +125,51 @@ class Skiplist { } } ``` +Python 代码: +```Python +class Skiplist: + def __init__(self, level=10): + self.level = level + class Node: + def __init__(self, val): + self.val = val + self.ne = [None] * level + self.Node = Node + self.he = Node(-1) + + def find(self, t, ns): + cur = self.he + for i in range(self.level - 1, -1, -1): + while cur.ne[i] is not None and cur.ne[i].val < t: + cur = cur.ne[i] + ns[i] = cur + + def search(self, t): + ns = [None] * self.level + self.find(t, ns) + return ns[0].ne[0] is not None and ns[0].ne[0].val == t + + def add(self, t): + ns = [None] * self.level + self.find(t, ns) + node = self.Node(t) + for i in range(self.level): + node.ne[i] = ns[i].ne[i] + ns[i].ne[i] = node + if random.randint(0, 1) == 0: + break + + def erase(self, t): + ns = [None] * self.level + self.find(t, ns) + node = ns[0].ne[0] + if node is None or node.val != t: + return False + for i in range(self.level): + if ns[i].ne[i] == node: + ns[i].ne[i] = ns[i].ne[i].ne[i] + return True +``` TypeScript 代码: ```TypeScript const level: number = 10 diff --git "a/LeetCode/1201-1210/1208. \345\260\275\345\217\257\350\203\275\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1201-1210/1208. \345\260\275\345\217\257\350\203\275\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211\357\274\210\344\270\255\347\255\211\357\274\211.md" index 5ccca8f0..21ef2db4 100644 --- "a/LeetCode/1201-1210/1208. \345\260\275\345\217\257\350\203\275\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1201-1210/1208. \345\260\275\345\217\257\350\203\275\344\275\277\345\255\227\347\254\246\344\270\262\347\233\270\347\255\211\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -7,15 +7,17 @@ Tag : 「前缀和」、「二分」、「滑动窗口」 -给你两个长度相同的字符串,s 和 t。 +给你两个长度相同的字符串,`s` 和 `t`。 -将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。 +将 `s` 中的第 `i` 个字符变到 `t` 中的第 `i` 个字符需要 `|s[i] - t[i]|` 的开销(开销可能为 0),也就是两个字符的 `ASCII` 码值的差的绝对值。 -用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。 +用于变更字符串的最大预算是 `maxCost`。 -如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。 +在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。 -如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。 +如果你可以将 `s` 的子字符串转化为它在 `t` 中对应的子字符串,则返回可以转化的最大长度。 + +如果 `s` 中没有子字符串可以转化成 `t` 中对应的子字符串,则返回 0。   @@ -45,9 +47,9 @@ Tag : 「前缀和」、「二分」、「滑动窗口」 ``` 提示: -* 1 <= s.length, t.length <= $10^5$ -* 0 <= maxCost <= $10^6$ -* s 和 t 都只含小写英文字母。 +* $1 <= s.length, t.length <= 10^5$ +* $0 <= maxCost <= 10^6$ +* `s` 和 `t` 都只含小写英文字母。 --- @@ -92,25 +94,20 @@ Tag : 「前缀和」、「二分」、「滑动窗口」 1. 为了方便的预处理前缀和和减少边界处理,我会往字符串头部添加一个空格,使之后的数组下标从 1 开始 2. 二分出来滑动窗口长度,需要在返回时再次检查,因为可能没有符合条件的有效滑动窗口长度 -代码: +Java 代码: ```Java class Solution { public int equalSubstring(String ss, String tt, int max) { int n = ss.length(); - ss = " " + ss; - tt = " " + tt; - char[] s = ss.toCharArray(); - char[] t = tt.toCharArray(); + ss = " " + ss; tt = " " + tt; + char[] s = ss.toCharArray(), t = tt.toCharArray(); int[] sum = new int[n + 1]; for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + Math.abs(s[i] - t[i]); int l = 1, r = n; while (l < r) { int mid = l + r + 1 >> 1; - if (check(sum, mid, max)) { - l = mid; - } else { - r = mid - 1; - } + if (check(sum, mid, max)) l = mid; + else r = mid - 1; } return check(sum, r, max) ? r : 0; } @@ -123,6 +120,78 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int equalSubstring(string ss, string tt, int max) { + int n = ss.length(); + ss = " " + ss; tt = " " + tt; + vector sum(n + 1, 0); + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + abs(ss[i] - tt[i]); + int l = 1, r = n; + while (l < r) { + int mid = l + r + 1 >> 1; + if (check(sum, mid, max)) l = mid; + else r = mid - 1; + } + return check(sum, r, max) ? r : 0; + } + bool check(const vector& nums, int mid, int max) { + for (int i = mid; i < nums.size(); i++) { + int tot = nums[i] - nums[i - mid]; + if (tot <= max) return true; + } + return false; + } +}; +``` +Python 代码: +```Python +class Solution: + def equalSubstring(self, ss: str, tt: str, maxv: int) -> int: + n = len(ss) + ss, tt = " " + ss, " " + tt + sumv = [0] * (n + 1) + for i in range(1, n + 1): + sumv[i] = sumv[i - 1] + abs(ord(ss[i]) - ord(tt[i])) + l, r = 1, n + while l < r: + mid = l + r + 1 >> 1 + if self.check(sumv, mid, maxv): l = mid + else: r = mid - 1 + return l if self.check(sumv, r, maxv) else 0 + + def check(self, nums, mid, maxv): + for i in range(mid, len(nums)): + tot = nums[i] - nums[i - mid] + if tot <= maxv: + return True + return False +``` +TypeScript 代码: +```TypeScript +function check(nums: number[], mid: number, max: number): boolean { + for (let i: number = mid; i <= nums.length; i++) { + const tot: number = nums[i] - nums[i - mid]; + if (tot <= max) return true; + } + return false; +} +function equalSubstring(ss: string, tt: string, max: number): number { + const n: number = ss.length; + ss = " " + ss; tt = " " + tt; + const sum: number[] = [0]; + for (let i: number = 1; i <= n; i++) sum[i] = sum[i - 1] + Math.abs(ss.charCodeAt(i) - tt.charCodeAt(i)); + let l: number = 1, r: number = n; + while (l < r) { + const mid: number = l + r + 1 >> 1; + if (check(sum, mid, max)) l = mid; + else r = mid - 1; + } + return check(sum, r, max) ? r : 0; +}; +``` * 时间复杂度:预处理出前缀和的复杂度为 $O(n)$;二分出「滑动窗口长度」的复杂度为 $O(\log{n})$,对于每个窗口长度,需要扫描一遍数组进行检查,复杂度为 $O(n)$,因此二分的复杂度为 $O(n\log{n})$。整体复杂度为 $O(n\log{n})$ * 空间复杂度:使用了前缀和数组。复杂度为 $O(n)$ diff --git "a/LeetCode/1221-1230/1233. \345\210\240\351\231\244\345\255\220\346\226\207\344\273\266\345\244\271\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1221-1230/1233. \345\210\240\351\231\244\345\255\220\346\226\207\344\273\266\345\244\271\357\274\210\344\270\255\347\255\211\357\274\211.md" index 7344e621..255bde65 100644 --- "a/LeetCode/1221-1230/1233. \345\210\240\351\231\244\345\255\220\346\226\207\344\273\266\345\244\271\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1221-1230/1233. \345\210\240\351\231\244\345\255\220\346\226\207\344\273\266\345\244\271\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,13 +6,13 @@ Tag : 「字典树」 -你是一位系统管理员,手里有一份文件夹列表 `folder`,你的任务是要删除该列表中的所有 子文件夹,并以 任意顺序 返回剩下的文件夹。 +你是一位系统管理员,手里有一份文件夹列表 `folder`,你的任务是要删除该列表中的所有子文件夹,并以任意顺序返回剩下的文件夹。 -如果文件夹 `folder[i]` 位于另一个文件夹 `folder[j]` 下,那么 `folder[i]` 就是 `folder[j]` 的 子文件夹 。 +如果文件夹 `folder[i]` 位于另一个文件夹 `folder[j]` 下,那么 `folder[i]` 就是 `folder[j]` 的子文件夹。 文件夹的「路径」是由一个或多个按以下格式串联形成的字符串:`'/'` 后跟一个或者多个小写英文字母。 -例如,`"/leetcode"` 和 `"/leetcode/problems"` 都是有效的路径,而空字符串和 "/" 不是。 +例如,`"/leetcode"` 和 `"/leetcode/problems"` 都是有效的路径,而空字符串和 `"/"` 不是。 示例 1: ``` diff --git "a/LeetCode/1281-1290/1282. \347\224\250\346\210\267\345\210\206\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1281-1290/1282. \347\224\250\346\210\267\345\210\206\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" index d3a8dd71..b3032a33 100644 --- "a/LeetCode/1281-1290/1282. \347\224\250\346\210\267\345\210\206\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1281-1290/1282. \347\224\250\346\210\267\345\210\206\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -2,11 +2,11 @@ 这是 LeetCode 上的 **[1282. 用户分组](https://leetcode.cn/problems/group-the-people-given-the-group-size-they-belong-to/solution/by-ac_oier-z1bg/)** ,难度为 **中等**。 -Tag : 「哈希表」、「模拟」 +Tag : 「哈希表」、「模拟」、「构造」 -有 `n` 个人被分成数量未知的组。每个人都被标记为一个从 $0$ 到 $n - 1$ 的唯一`ID` 。 +有 `n` 个人被分成数量未知的组。每个人都被标记为一个从 $0$ 到 $n - 1$ 的唯一 `ID` 。 给定一个整数数组 `groupSizes`,其中 `groupSizes[i]` 是第 $i$ 个人所在的组的大小。例如,如果 `groupSizes[1] = 3`,则第 $1$ 个人必须位于大小为 $3$ 的组中。 @@ -40,55 +40,81 @@ Tag : 「哈希表」、「模拟」 --- -### 哈希表 + 模拟 +### 哈希表 + 构造 -我们可以使用「哈希表」将所属组大小相同的下标放到一起。假设组大小为 $k$ 的元素有 $m$ 个,然后我们再将这 $m$ 个元素按照 $k$ 个一组进行划分即可。 +答案确保有解,可直接进行构造:**假设组大小为 $k$ 的元素有 $m$ 个,则将这 $m$ 个元素按照 $k$ 个一组进行划分**。 + +具体的,我们可以使用哈希表 `map` 记录当前组大小为 `x` 的数组容器 `cur`。即组大小数值为 `key`,对应容器引用为 `value`。 + +从前往后处理每个 $x = groupSizes[i]$,若 `x` 不在 `map` 或 `map[x]` 大小为 `x`,起一个新的组存入 `map` 和 `ans` 中,然后每次都将 `i` 存入到 `map[x]` 当中即可。 Java 代码: ```Java class Solution { - public List> groupThePeople(int[] gs) { + public List> groupThePeople(int[] groupSizes) { Map> map = new HashMap<>(); - for (int i = 0; i < gs.length; i++) { - List list = map.getOrDefault(gs[i], new ArrayList<>()); - list.add(i); - map.put(gs[i], list); - } List> ans = new ArrayList<>(); - for (int k : map.keySet()) { - List list = map.get(k), cur = new ArrayList<>(); - for (int i = 0; i < list.size(); i++) { - cur.add(list.get(i)); - if (cur.size() == k) { - ans.add(cur); - cur = new ArrayList<>(); - } + for (int i = 0; i < groupSizes.length; i++) { + int x = groupSizes[i]; + if (!map.containsKey(x) || map.get(x).size() == x) { + List cur = new ArrayList<>(); + map.put(x, cur); + ans.add(cur); } + map.get(x).add(i); } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector> groupThePeople(vector& groupSizes) { + unordered_map> map; + vector> ans; + for (int i = 0; i < groupSizes.size(); i++) { + int x = groupSizes[i]; + map[x].push_back(i); + if (map[x].size() == x) { + ans.push_back(map[x]); + map[x].clear(); + } + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def groupThePeople(self, groupSizes: List[int]) -> List[List[int]]: + mapping = {} + ans = [] + for i, x in enumerate(groupSizes): + if x not in mapping or len(mapping[x]) == x: + cur = [] + mapping[x] = cur + ans.append(cur) + mapping[x].append(i) + return ans +``` Typescript 代码: ```Typescript -function groupThePeople(gs: number[]): number[][] { - const map = new Map>() - for (let i = 0; i < gs.length; i++) { - if (!map.has(gs[i])) map.set(gs[i], new Array()) - map.get(gs[i]).push(i) - } - const ans = new Array>() - for (let k of map.keys()) { - let list = map.get(k), cur = new Array() - for (let i = 0; i < list.length; i++) { - cur.push(list[i]) - if (cur.length == k) { - ans.push(cur) - cur = new Array() - } +function groupThePeople(groupSizes: number[]): number[][] { + const map = new Map>(); + const ans = new Array>(); + for (let i = 0; i < groupSizes.length; i++) { + const x = groupSizes[i]; + if (!map.has(x) || map.get(x).length == x) { + const cur = new Array(); + map.set(x, cur); + ans.push(cur); } + map.get(x).push(i); } - return ans + return ans; }; ``` * 时间复杂度:$O(n)$ diff --git "a/LeetCode/1331-1340/1334. \351\230\210\345\200\274\350\267\235\347\246\273\345\206\205\351\202\273\345\261\205\346\234\200\345\260\221\347\232\204\345\237\216\345\270\202\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1331-1340/1334. \351\230\210\345\200\274\350\267\235\347\246\273\345\206\205\351\202\273\345\261\205\346\234\200\345\260\221\347\232\204\345\237\216\345\270\202\357\274\210\344\270\255\347\255\211\357\274\211.md" index e1783f78..61bb1ed7 100644 --- "a/LeetCode/1331-1340/1334. \351\230\210\345\200\274\350\267\235\347\246\273\345\206\205\351\202\273\345\261\205\346\234\200\345\260\221\347\232\204\345\237\216\345\270\202\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1331-1340/1334. \351\230\210\345\200\274\350\267\235\347\246\273\345\206\205\351\202\273\345\261\205\346\234\200\345\260\221\347\232\204\345\237\216\345\270\202\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -10,11 +10,14 @@ Tag : 「最短路」、「图」 给你一个边数组 `edges`,其中 $edges[i] = [from_{i}, to_{i}, weight_{i}]$ 代表 $from_{i}$ 和 $to_{i}$ 两个城市之间的双向加权边,距离阈值是一个整数 `distanceThreshold`。 -返回能通过某些路径到达其他城市数目最少、且路径距离最大为 `distanceThreshold` 的城市。如果有多个这样的城市,则返回编号最大的城市。 +返回能通过某些路径到达其他城市数目最少,且路径距离最大为 `distanceThreshold` 的城市。 + +如果有多个这样的城市,则返回编号最大的城市。 注意,连接城市 $i$ 和 $j$ 的路径的距离等于沿该路径的所有边的权重之和。 示例 1: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/26/find_the_city_01.png) ``` @@ -31,6 +34,7 @@ Tag : 「最短路」、「图」 城市 0 和 3 在阈值距离 4 以内都有 2 个邻居城市,但是我们必须返回城市 3,因为它的编号最大。 ``` 示例 2: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/01/26/find_the_city_02.png) ``` diff --git "a/LeetCode/1401-1410/1403. \351\235\236\351\200\222\345\242\236\351\241\272\345\272\217\347\232\204\346\234\200\345\260\217\345\255\220\345\272\217\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1401-1410/1403. \351\235\236\351\200\222\345\242\236\351\241\272\345\272\217\347\232\204\346\234\200\345\260\217\345\255\220\345\272\217\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" index 37b551f8..c392c0ea 100644 --- "a/LeetCode/1401-1410/1403. \351\235\236\351\200\222\345\242\236\351\241\272\345\272\217\347\232\204\346\234\200\345\260\217\345\255\220\345\272\217\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1401-1410/1403. \351\235\236\351\200\222\345\242\236\351\241\272\345\272\217\347\232\204\346\234\200\345\260\217\345\255\220\345\272\217\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -64,6 +64,38 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector minSubsequence(vector& nums) { + sort(nums.begin(), nums.end()); + int sumv = 0, cur = 0, idx = nums.size() - 1; + for (int x : nums) sumv += x; + vector ans; + while (cur <= sumv) { + sumv -= nums[idx]; + cur += nums[idx]; + ans.push_back(nums[idx--]); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def minSubsequence(self, nums: List[int]) -> List[int]: + nums.sort() + sumv, cur, idx = sum(nums), 0, len(nums) - 1 + ans = [] + while cur <= sumv: + sumv -= nums[idx] + cur += nums[idx] + ans.append(nums[idx]) + idx -= 1 + return ans +``` TypeScript 代码: ```TypeScript function minSubsequence(nums: number[]): number[] { diff --git "a/LeetCode/141-150/150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/141-150/150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" index 8c2fdc52..032a6d13 100644 --- "a/LeetCode/141-150/150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/141-150/150. \351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -9,7 +9,7 @@ Tag : 「表达式计算」 根据 逆波兰表示法,求表达式的值。 -有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 +有效的算符包括 `+`、`-`、`*`、`/` 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 说明: @@ -19,19 +19,25 @@ Tag : 「表达式计算」 示例 1: ``` 输入:tokens = ["2","1","+","3","*"] + 输出:9 + 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9 ``` 示例 2: ``` 输入:tokens = ["4","13","5","/","+"] + 输出:6 + 解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 ``` 示例 3: ``` 输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] + 输出:22 + 解释: 该算式转化为常见的中缀算术表达式为: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 @@ -44,20 +50,20 @@ Tag : 「表达式计算」 ``` 提示: -* 1 <= tokens.length <= $10^4$ -* tokens[i] 要么是一个算符("+"、"-"、"*" 或 "/"),要么是一个在范围 [-200, 200] 内的整数 +* $1 <= tokens.length <= 10^4$ +* tokens[i] 要么是一个算符(`"+"`、`"-"`、`"*"` 或 `"/"`),要么是一个在范围 $[-200, 200]$ 内的整数 逆波兰表达式: 逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。 -* 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。 -* 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。 +* 平常使用的算式则是一种中缀表达式,如 `( 1 + 2 ) * ( 3 + 4 ) `。 +* 该算式的逆波兰表达式写法为 `( ( 1 2 + ) ( 3 4 + ) * ) `。 逆波兰表达式主要有以下两个优点: -* 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。 +* 去掉括号后表达式无歧义,上式即便写成 `1 2 + 3 4 + *` 也可以依据次序计算出正确结果。 * 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。 --- @@ -72,28 +78,20 @@ Tag : 「表达式计算」 而栈的实现通常有两种:使用数组继续模拟 & 使用系统自带的栈结构 -*** - -### 数组模拟栈解法 - -![image.png](https://pic.leetcode-cn.com/1616171750-FpfcGZ-image.png) - -代码: - -```java [] +Java 代码(自带栈): +```Java class Solution { public int evalRPN(String[] ts) { - int[] d = new int[ts.length]; - int hh = 0, tt = -1; + Deque d = new ArrayDeque<>(); for (String s : ts) { if ("+-*/".contains(s)) { - int b = d[tt--], a = d[tt--]; - d[++tt] = calc(a, b, s); - } else { - d[++tt] = Integer.parseInt(s); + int b = d.pollLast(), a = d.pollLast(); + d.addLast(calc(a, b, s)); + } else { + d.addLast(Integer.parseInt(s)); } } - return d[tt]; + return d.pollLast(); } int calc(int a, int b, String op) { if (op.equals("+")) return a + b; @@ -104,29 +102,21 @@ class Solution { } } ``` -* 时间复杂度:$O(n)$ -* 空间复杂度:$O(n)$ - -*** - -### 自带栈解法 - -![image.png](https://pic.leetcode-cn.com/1616171844-nzbuMx-image.png) - -代码: -```java +Java 代码(数组模拟栈): +```Java class Solution { public int evalRPN(String[] ts) { - Deque d = new ArrayDeque<>(); + int[] d = new int[ts.length]; + int tt = -1; for (String s : ts) { if ("+-*/".contains(s)) { - int b = d.pollLast(), a = d.pollLast(); - d.addLast(calc(a, b, s)); - } else { - d.addLast(Integer.parseInt(s)); + int b = d[tt--], a = d[tt--]; + d[++tt] = calc(a, b, s); + } else { + d[++tt] = Integer.parseInt(s); } } - return d.pollLast(); + return d[tt]; } int calc(int a, int b, String op) { if (op.equals("+")) return a + b; @@ -137,10 +127,62 @@ class Solution { } } ``` +C++ 代码(数组模拟栈): +```C++ +class Solution { +public: + int evalRPN(vector& ts) { + vector d(ts.size()); + int tt = -1; + for (string& s : ts) { + if (s.size() == 1 && (s[0] == '+' || s[0] == '-' || s[0] == '*' || s[0] == '/')) { + int b = d[tt--], a = d[tt--]; + d[++tt] = calc(a, b, s); + } else { + d[++tt] = stoi(s); + } + } + return d[tt]; + } + int calc(int a, int b, const string& op) { + if (op == "+") return a + b; + if (op == "-") return a - b; + if (op == "*") return a * b; + if (op == "/") return a / b; + return -1; + } +}; +``` +Python 代码(数组模拟栈): +```Python +class Solution: + def evalRPN(self, ts): + d = [0] * len(ts) + tt = -1 + for s in ts: + if s in "+-*/": + b = d[tt] + tt -= 1 + a = d[tt] + tt -= 1 + d[tt + 1] = self.calc(a, b, s) + tt += 1 + else: + d[tt + 1] = int(s) + tt += 1 + return d[tt] + + def calc(self, a, b, op): + if op == '+': return a + b + elif op == '-': return a - b + elif op == '*': return a * b + elif op == '/': return int(a / b) + else: return -1 +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ -*** +--- ### 其他 diff --git "a/LeetCode/1411-1420/1413. \351\200\220\346\255\245\346\261\202\345\222\214\345\276\227\345\210\260\346\255\243\346\225\260\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1411-1420/1413. \351\200\220\346\255\245\346\261\202\345\222\214\345\276\227\345\210\260\346\255\243\346\225\260\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\347\256\200\345\215\225\357\274\211.md" index 2b66a5d4..a67eaafa 100644 --- "a/LeetCode/1411-1420/1413. \351\200\220\346\255\245\346\261\202\345\222\214\345\276\227\345\210\260\346\255\243\346\225\260\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1411-1420/1413. \351\200\220\346\255\245\346\261\202\345\222\214\345\276\227\345\210\260\346\255\243\346\225\260\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -10,7 +10,7 @@ Tag : 「模拟」 你需要从左到右遍历 `nums` 数组,并将 `startValue` 依次累加上 `nums` 数组中的值。 -请你在确保累加和始终大于等于 $1$ 的前提下,选出一个最小的 正数 作为 `startValue`。 +请你在确保累加和始终大于等于 $1$ 的前提下,选出一个最小的正数作为 `startValue`。 示例 1: ``` @@ -63,19 +63,44 @@ class Solution { public int minStartValue(int[] nums) { int n = nums.length, min = 0x3f3f3f3f; for (int i = 0, j = 1; i < n; i++) { - j = j + nums[i]; + j += nums[i]; min = Math.min(min, j); } return min < 1 ? 2 - min : 1; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int minStartValue(vector& nums) { + int n = nums.size(), minv = 0x3f3f3f3f; + for (int i = 0, j = 1; i < n; i++) { + j += nums[i]; + minv = min(minv, j); + } + return minv < 1 ? 2 - minv : 1; + } +}; +``` +Python 代码: +```Python +class Solution: + def minStartValue(self, nums: List[int]) -> int: + n, minv = len(nums), 0x3f3f3f3f + j = 0 + for i in range(n): + j += nums[i] + minv = min(minv, j) + return 1 if minv > 0 else 1 - minv +``` TypeScript 代码: ```TypeScript function minStartValue(nums: number[]): number { let n = nums.length, min = 0x3f3f3f3f for (let i = 0, j = 1; i < n; i++) { - j = j + nums[i] + j += nums[i] min = Math.min(min, j) } return min < 1 ? 2 - min : 1 diff --git "a/LeetCode/1421-1430/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1421-1430/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206\357\274\210\347\256\200\345\215\225\357\274\211.md" index 01a988d4..7dd8080b 100644 --- "a/LeetCode/1421-1430/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1421-1430/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -47,7 +47,7 @@ Tag : 「前缀和」、「模拟」 ### 前缀和 -构建前缀和数组来记录每个前缀中 $1$ 个个数,复杂度为 $O(n)$,枚举每个分割点,搭配前缀和数组计算左串中 $0$ 的数量和右串中 $1$ 的数量,取所有得分的最大值即是答案。 +构建前缀和数组来记录每个前缀中 $1$ 的个数,复杂度为 $O(n)$,枚举每个分割点,搭配前缀和数组计算左串中 $0$ 的数量和右串中 $1$ 的数量,取所有得分的最大值即是答案。 Java 代码: ```Java @@ -64,6 +64,35 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxScore(string s) { + int n = s.length(), ans = 0; + vector sum(n + 10, 0); + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + s[i - 1] - '0'; + for (int i = 1; i <= n - 1; i++) { + int a = i - sum[i], b = sum[n] - sum[i]; + ans = max(ans, a + b); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxScore(self, s: str) -> int: + n, ans = len(s), 0 + sumv = [0] * (n + 10) + for i in range(1, n + 1): + sumv[i] = sumv[i - 1] + int(s[i - 1]) + for i in range(1, n): + a, b = i - sumv[i], sumv[n] - sumv[i] + ans = max(ans, a + b) + return ans +``` Typescript 代码: ```Typescript function maxScore(s: string): number { @@ -84,7 +113,7 @@ function maxScore(s: string): number { ### 模拟 -更进一步,利用 `s` 中有只有 $0$ 和 $1$,我们可以遍遍历 `s` 边计算得分(而无须预处理前缀和数组),起始分割点为 `s[0]`,此时得分为 `s[0]` 中 $0$ 的个数加上 `s[1...(n-1)]` 中 $1$ 的个数。 +更进一步,利用 `s` 中有只有 $0$ 和 $1$,我们可以边遍历 `s` 边计算得分(而无须预处理前缀和数组),起始分割点为 `s[0]`,此时得分为 `s[0]` 中 $0$ 的个数加上 `s[1...(n-1)]` 中 $1$ 的个数。 然后继续往后处理 `s`,当 $s[i] = 0$,说明有一个 $0$ 从右串中移到了左串,并且 $0$ 在右串中不得分,在左串中得分,因此总得分加一;而当 $s[i] = 1$,说明有一个 $1$ 从右串中移到了左串,而 $1$ 在右串中得分,在左串中不得分,因此总得分减一。在所有得分中取最大值即是答案。 @@ -103,6 +132,34 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxScore(string s) { + int n = s.length(), cur = s[0] == '0' ? 1 : 0; + for (int i = 1; i < n; i++) cur += s[i] - '0'; + int ans = cur; + for (int i = 1; i < n - 1; i++) { + cur += s[i] == '0' ? 1 : -1; + ans = max(ans, cur); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxScore(self, s: str) -> int: + n, cur = len(s), 1 if s[0] == '0' else 0 + for i in range(1, n): cur += int(s[i]) + ans = cur + for i in range(1, n - 1): + cur += 1 if s[i] == '0' else -1 + ans = max(ans, cur) + return ans +``` TypeScript 代码: ```TypeScript function maxScore(s: string): number { diff --git "a/LeetCode/1431-1440/1438. \347\273\235\345\257\271\345\267\256\344\270\215\350\266\205\350\277\207\351\231\220\345\210\266\347\232\204\346\234\200\351\225\277\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1431-1440/1438. \347\273\235\345\257\271\345\267\256\344\270\215\350\266\205\350\277\207\351\231\220\345\210\266\347\232\204\346\234\200\351\225\277\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" index 685f56e6..6a80a4fd 100644 --- "a/LeetCode/1431-1440/1438. \347\273\235\345\257\271\345\267\256\344\270\215\350\266\205\350\277\207\351\231\220\345\210\266\347\232\204\346\234\200\351\225\277\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1431-1440/1438. \347\273\235\345\257\271\345\267\256\344\270\215\350\266\205\350\277\207\351\231\220\345\210\266\347\232\204\346\234\200\351\225\277\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,16 +6,16 @@ Tag : 「滑动窗口」、「单调队列」、「二分」 -给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。 +给你一个整数数组 `nums`,和一个表示限制的整数 `limit`,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 `limit`。 -如果不存在满足条件的子数组,则返回 0 。 - -  +如果不存在满足条件的子数组,则返回 0。 示例 1: ``` 输入:nums = [8,2,4,7], limit = 4 + 输出:2 + 解释:所有子数组如下: [8] 最大绝对差 |8-8| = 0 <= 4. [8,2] 最大绝对差 |8-2| = 6 > 4. @@ -32,27 +32,28 @@ Tag : 「滑动窗口」、「单调队列」、「二分」 示例 2: ``` 输入:nums = [10,1,2,4,7,2], limit = 5 + 输出:4 + 解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。 ``` 示例 3: ``` 输入:nums = [4,2,2,2,4,4,2,2], limit = 0 + 输出:3 ``` 提示: -* 1 <= nums.length <= $10^5$ -* 1 <= nums[i] <= $10^9$ -* 0 <= limit <= $10^9$ +* $1 <= nums.length <= 10^5$ +* $1 <= nums[i] <= 10^9$ +* $0 <= limit <= 10^9$ --- ### 二分 + 滑动窗口 -![image.png](https://pic.leetcode-cn.com/1613879228-rXrTqd-image.png) - 数据范围是 $10^5$,因此只能考虑「对数解法」和「线性解法」。 对数解法很容易想到「二分」。 @@ -65,18 +66,16 @@ Tag : 「滑动窗口」、「单调队列」、「二分」 我们可以枚举区间的右端点 `r`,那么对应的左端点为 `r - len + 1`,然后使用「单调队列」来保存区间的最大值和最小值。 -```java +Java 代码: +```Java class Solution { public int longestSubarray(int[] nums, int limit) { int n = nums.length; int l = 1, r = n; while (l < r) { int mid = l + r + 1 >> 1; - if (check(nums, mid, limit)) { - l = mid; - } else { - r = mid - 1; - } + if (check(nums, mid, limit)) l = mid; + else r = mid - 1; } return r; } @@ -96,26 +95,54 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int longestSubarray(vector& nums, int limit) { + int n = nums.size(); + int l = 1, r = n; + while (l < r) { + int mid = l + r + 1 >> 1; + if (check(nums, mid, limit)) l = mid; + else r = mid - 1; + } + return r; + } + bool check(const vector& nums, int len, int limit) { + int n = nums.size(); + deque max, min; + for (int r = 0, l = r - len + 1; r < n; r++, l = r - len + 1) { + if (!max.empty() && max.front() < l) max.pop_front(); + while (!max.empty() && nums[r] >= nums[max.back()]) max.pop_back(); + max.push_back(r); + if (!min.empty() && min.front() < l) min.pop_front(); + while (!min.empty() && nums[r] <= nums[min.back()]) min.pop_back(); + min.push_back(r); + if (l >= 0 && abs(nums[max.front()] - nums[min.front()]) <= limit) return true; + } + return false; + } +}; +``` * 时间复杂度:枚举长度的复杂度为 $O(\log{n})$,对于每次 `check` 而言,每个元素最多入队和出队常数次,复杂度为 $O(n)$。整体复杂度为 $O(n\log{n})$ * 空间复杂度:$O(n)$ -*** +--- ### 双指针 -![image.png](https://pic.leetcode-cn.com/1613878890-AHTVcy-image.png) - 上述解法我们是在对 `len` 进行二分,而事实上我们可以直接使用「双指针」解法找到最大值。 始终让右端点 `r` 右移,当不满足条件时让 `l` 进行右移。 同时,还是使用「单调队列」保存我们的区间最值,这样我们只需要对数组进行一次扫描即可得到答案。 +Java 代码: ```Java class Solution { public int longestSubarray(int[] nums, int limit) { - int n = nums.length; - int ans = 0; + int n = nums.length, ans = 0; Deque max = new ArrayDeque<>(), min = new ArrayDeque<>(); for (int r = 0, l = 0; r < n; r++) { while (!max.isEmpty() && nums[r] >= nums[max.peekLast()]) max.pollLast(); @@ -133,6 +160,29 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int longestSubarray(vector& nums, int limit) { + int n = nums.size(), ans = 0; + deque maxDeque, minDeque; + for (int r = 0, l = 0; r < n; r++) { + while (!maxDeque.empty() && nums[r] >= nums[maxDeque.back()]) maxDeque.pop_back(); + while (!minDeque.empty() && nums[r] <= nums[minDeque.back()]) minDeque.pop_back(); + maxDeque.push_back(r); + minDeque.push_back(r); + while (abs(nums[maxDeque.front()] - nums[minDeque.front()]) > limit) { + l++; + if (maxDeque.front() < l) maxDeque.pop_front(); + if (minDeque.front() < l) minDeque.pop_front(); + } + ans = max(ans, r - l + 1); + } + return ans; + } +}; +``` * 时间复杂度:每个元素最多入队和出队常数次,复杂度为 $O(n)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/1441-1450/1442. \345\275\242\346\210\220\344\270\244\344\270\252\345\274\202\346\210\226\347\233\270\347\255\211\346\225\260\347\273\204\347\232\204\344\270\211\345\205\203\347\273\204\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1441-1450/1442. \345\275\242\346\210\220\344\270\244\344\270\252\345\274\202\346\210\226\347\233\270\347\255\211\346\225\260\347\273\204\347\232\204\344\270\211\345\205\203\347\273\204\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" index b9de6fc2..fe657d2f 100644 --- "a/LeetCode/1441-1450/1442. \345\275\242\346\210\220\344\270\244\344\270\252\345\274\202\346\210\226\347\233\270\347\255\211\346\225\260\347\273\204\347\232\204\344\270\211\345\205\203\347\273\204\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1441-1450/1442. \345\275\242\346\210\220\344\270\244\344\270\252\345\274\202\346\210\226\347\233\270\347\255\211\346\225\260\347\273\204\347\232\204\344\270\211\345\205\203\347\273\204\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -7,16 +7,18 @@ Tag : 「数学」、「前缀和」 -给你一个整数数组 arr 。 +给你一个整数数组 `arr`。 -现需要从数组中取三个下标 i、j 和 k ,其中 (0 <= i < j <= k < arr.length) 。 +现需要从数组中取三个下标 `i`、`j` 和 `k`,其中 (`0 <= i < j <= k < arr.length`) 。 -a 和 b 定义如下: -* a = arr[i] ^ arr[i + 1] ^ ... ^ arr[j - 1] -* b = arr[j] ^ arr[j + 1] ^ ... ^ arr[k] -注意:^ 表示 按位异或 操作。 +`a` 和 `b` 定义如下: -请返回能够令 a == b 成立的三元组 (i, j , k) 的数目。 +* `a = arr[i] ^ arr[i + 1] ^ ... ^ arr[j - 1]` +* `b = arr[j] ^ arr[j + 1] ^ ... ^ arr[k]` + +注意:`^` 表示「按位异或」操作。 + +请返回能够令 `a == b` 成立的三元组 `(i, j , k)` 的数目。 示例 1: @@ -53,8 +55,8 @@ a 和 b 定义如下: ``` 提示: -* 1 <= arr.length <= 300 -* 1 <= arr[i] <= $10^8$ +* $1 <= arr.length <= 300$ +* $1 <= arr[i] <= 10^8$ --- diff --git "a/LeetCode/1481-1490/1482. \345\210\266\344\275\234 m \346\235\237\350\212\261\346\211\200\351\234\200\347\232\204\346\234\200\345\260\221\345\244\251\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1481-1490/1482. \345\210\266\344\275\234 m \346\235\237\350\212\261\346\211\200\351\234\200\347\232\204\346\234\200\345\260\221\345\244\251\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" index 1986807a..81c60fdf 100644 --- "a/LeetCode/1481-1490/1482. \345\210\266\344\275\234 m \346\235\237\350\212\261\346\211\200\351\234\200\347\232\204\346\234\200\345\260\221\345\244\251\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1481-1490/1482. \345\210\266\344\275\234 m \346\235\237\350\212\261\346\211\200\351\234\200\347\232\204\346\234\200\345\260\221\345\244\251\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,13 +6,13 @@ Tag : 「二分」 -给你一个整数数组 bloomDay,以及两个整数 m 和 k 。 +给你一个整数数组 `bloomDay`,以及两个整数 `m` 和 `k`。 -现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。 +现需要制作 `m` 束花。制作花束时,需要使用花园中相邻的 `k` 朵花 。 -花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。 +花园中有 `n` 朵花,第 `i` 朵花会在 `bloomDay[i]` 时盛开,恰好可以用于一束花中。 -请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。 +请你返回从花园中摘 `m` 束花需要等待的最少的天数。如果不能摘到 `m` 束花则返回 `-1`。   @@ -65,11 +65,11 @@ Tag : 「二分」 ``` 提示: -* bloomDay.length == n -* 1 <= n <= $10^5$ -* 1 <= bloomDay[i] <= $10^9$ -* 1 <= m <= $10^6$ -* 1 <= k <= n +* $bloomDay.length = n$ +* $1 <= n <= 10^5$ +* $1 <= bloomDay[i] <= 10^9$ +* $1 <= m <= 10^6$ +* $1 <= k <= n$ --- @@ -92,7 +92,7 @@ Tag : 「二分」 由于二分查找本身具有“折半”效率,因此两者不会有太大效率差距,我这里采用「粗略右边界」的方式。 -代码: +Java 代码: ```Java class Solution { int n, m, k; @@ -100,33 +100,24 @@ class Solution { public int minDays(int[] nums, int _m, int _k) { n = nums.length; m = _m; k = _k; - if (n < m * k) return -1; - fl = new boolean[n]; - int l = 0, r = (int)1e9; while (l < r) { int mid = l + r >> 1; - if (check(nums, mid)) { - r = mid; - } else { - l = mid + 1; - } + if (check(nums, mid)) r = mid; + else l = mid + 1; } return check(nums, r) ? r : -1; } boolean check(int[] nums, int mid) { - for (int i = 0; i < n; i++) { - fl[i] = nums[i] <= mid; - } + for (int i = 0; i < n; i++) fl[i] = nums[i] <= mid; int cnt = 0; for (int i = 0; i < n && cnt < m; ) { if (fl[i]) { int cur = 1, j = i; while (cur < k && j + 1 < n && fl[j + 1]) { - j++; - cur++; + j++; cur++; } if (cur == k) cnt++; i = j + 1; @@ -138,6 +129,75 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int n, m, k; + vector fl; + int minDays(vector& nums, int _m, int _k) { + n = nums.size(); + m = _m; k = _k; + if (n < m * 1L * k ) return -1; + fl.resize(n); + int l = 0, r = 1e9; + while (l < r) { + int mid = l + r >> 1; + if (check(nums, mid)) r = mid; + else l = mid + 1; + } + return check(nums, l) ? l : -1; + } + bool check(const vector& nums, int mid) { + for (int i = 0; i < n; i++) fl[i] = nums[i] <= mid; + int cnt = 0; + for (int i = 0; i < n && cnt < m; ) { + if (fl[i]) { + int cur = 1, j = i; + while (cur < k && j + 1 < n && fl[j + 1]) { + j++; cur++; + } + if (cur == k) cnt++; + i = j + 1; + } else { + i++; + } + } + return cnt >= m; + } +}; +``` +Python 代码: +```Python +class Solution: + def minDays(self, nums: List[int], m: int, k: int) -> int: + n = len(nums) + if n < m * k: return -1 + fl = [0] * n + def check(mid): + fl = [num <= mid for num in nums] + cnt, i = 0, 0 + while i < n and cnt < m: + if fl[i]: + cur, j = 1, i + while cur < k and j + 1 < n and fl[j + 1]: + j, cur = j + 1, cur + 1 + if cur == k: + cnt += 1 + i = j + 1 + else: + i += 1 + return cnt >= m + + l, r = 0, 10**9 + while l < r: + mid = l + r >> 1 + if check(mid): + r = mid + else: + l = mid + 1 + return check(l) and l or -1 +``` * 时间复杂度:`check` 函数的复杂度为 $O(n)$。整体复杂度为 $O(n\log{1e9})$ * 空间复杂度:$O(n)$ @@ -149,24 +209,19 @@ class Solution { 其实这个过程也能下放到统计逻辑去做,这样能够让 `check` 函数的复杂度从严格 $O(n)$ 变为最坏情况 $O(n)$,同时省去 $fl[]$ 数组,将空间优化至 $O(1)$。 -代码: +Java 代码: ```Java class Solution { int n, m, k; public int minDays(int[] nums, int _m, int _k) { n = nums.length; m = _m; k = _k; - if (n < m * k) return -1; - int l = 0, r = (int)1e9; while (l < r) { int mid = l + r >> 1; - if (check(nums, mid)) { - r = mid; - } else { - l = mid + 1; - } + if (check(nums, mid)) r = mid; + else l = mid + 1; } return check(nums, r) ? r : -1; } @@ -176,8 +231,7 @@ class Solution { int cur = nums[i] <= mid ? 1 : 0, j = i; if (cur > 0) { while (cur < k && j + 1 < n && nums[j + 1] <= mid) { - j++; - cur++; + j++; cur++; } if (cur == k) cnt++; i = j + 1; @@ -189,6 +243,71 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int n, m, k; + int minDays(vector& nums, int _m, int _k) { + n = nums.size(); + m = _m; k = _k; + if (n < m * 1L * k) return -1; + int l = 0, r = 1e9; + while (l < r) { + int mid = l + r >> 1; + if (check(nums, mid)) r = mid; + else l = mid + 1; + } + return check(nums, l) ? l : -1; + } + bool check(const vector& nums, int mid) { + int cnt = 0; + for (int i = 0; i < n && cnt < m; ) { + int cur = nums[i] <= mid ? 1 : 0, j = i; + if (cur > 0) { + while (cur < k && j + 1 < n && nums[j + 1] <= mid) { + j++; cur++; + } + if (cur == k) cnt++; + i = j + 1; + } else { + i++; + } + } + return cnt >= m; + } +}; +``` +Python 代码: +```Python +class Solution: + def minDays(self, nums: List[int], m: int, k: int) -> int: + n = len(nums) + if n < m * k: return -1 + def check(mid): + cnt, i = 0, 0 + while i < n and cnt < m: + cur = 1 if nums[i] <= mid else 0 + j = i + if cur > 0: + while cur < k and j + 1 < n and nums[j + 1] <= mid: + j, cur = j + 1, cur + 1 + if cur == k: + cnt += 1 + i = j + 1 + else: + i += 1 + return cnt >= m + + l, r = 0, 10**9 + while l < r: + mid = l + r >> 1 + if check(mid): + r = mid + else: + l = mid + 1 + return check(l) and l or -1 +``` * 时间复杂度:`check` 函数的复杂度为 $O(n)$。整体复杂度为 $O(n\log{1e9})$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/1481-1490/1486. \346\225\260\347\273\204\345\274\202\346\210\226\346\223\215\344\275\234\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1481-1490/1486. \346\225\260\347\273\204\345\274\202\346\210\226\346\223\215\344\275\234\357\274\210\347\256\200\345\215\225\357\274\211.md" index dac7c99a..8fcf839f 100644 --- "a/LeetCode/1481-1490/1486. \346\225\260\347\273\204\345\274\202\346\210\226\346\223\215\344\275\234\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1481-1490/1486. \346\225\260\347\273\204\345\274\202\346\210\226\346\223\215\344\275\234\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,11 +7,11 @@ Tag : 「数学」、「模拟」 -给你两个整数,n 和 start 。 +给你两个整数,`n` 和 `start`。 -数组 nums 定义为:nums[i] = start + 2*i(下标从 0 开始)且 n == nums.length 。 +数组 `nums` 定义为:`nums[i] = start + 2 * i`(下标从 `0` 开始)且 `n = nums.length`。 -请返回 nums 中所有元素按位异或(XOR)后得到的结果。 +请返回 `nums` 中所有元素按位异或(`XOR`)后得到的结果。 示例 1: @@ -45,9 +45,9 @@ Tag : 「数学」、「模拟」 ``` 提示: -* 1 <= n <= 1000 -* 0 <= start <= 1000 -* n == nums.length +* $1 <= n <= 1000$ +* $0 <= start <= 1000$ +* $n = nums.length$ --- @@ -55,7 +55,7 @@ Tag : 「数学」、「模拟」 数据范围只有 $10^3$,按照题目要求从头模拟一遍即可。 -代码: +Java 代码: ```Java class Solution { public int xorOperation(int n, int start) { @@ -68,6 +68,41 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int xorOperation(int n, int start) { + int ans = start; + for (int i = 1; i < n; ++i) { + int x = start + 2 * i; + ans ^= x; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def xorOperation(self, n: int, start: int) -> int: + ans = start + for i in range(1, n): + x = start + 2 * i + ans ^= x + return ans +``` +TypeScript 代码: +```TypeScript +function xorOperation(n: number, start: number): number { + let ans = start; + for (let i = 1; i < n; i++) { + let x = start + 2 * i; + ans ^= x; + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ @@ -111,8 +146,7 @@ class Solution { **总结一下,假设我们最终的答案为 `ans`。整个处理过程其实就是把原式中的每个 $item$ 右移一位(除以 $2$),计算 `ans` 中除了最低一位以外的结果;然后再将 `ans` 进行一位左移(重新乘以 $2$),将原本丢失的最后一位结果重新补上。补上则是利用了 `n` 和 `start` 的「奇偶性」的讨论。** - -代码: +Java 代码: ```Java class Solution { int calc(int x) { @@ -132,6 +166,60 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int calc(int x) { + if (x % 4 == 0) return x; + else if (x % 4 == 1) return 1; + else if (x % 4 == 2) return x + 1; + else return 0; + } + int xorOperation(int n, int start) { + int s = start >> 1; + int prefix = calc(s - 1) ^ calc(s + n - 1); + int last = n & start & 1; + int ans = (prefix << 1) | last; + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def calc(self, x): + if x % 4 == 0: + return x + elif x % 4 == 1: + return 1 + elif x % 4 == 2: + return x + 1 + else: + return 0 + def xorOperation(self, n: int, start: int) -> int: + s = start >> 1 + prefix = self.calc(s - 1) ^ self.calc(s + n - 1) + last = n & start & 1 + ans = (prefix << 1) | last + return ans +``` +TypeScript 代码: +```TypeScript +function calc(x: number): number { + if (x % 4 === 0) return x; + else if (x % 4 === 1) return 1; + else if (x % 4 === 2) return x + 1; + else return 0; +} +function xorOperation(n: number, start: number): number { + let s = start >> 1; + let prefix = calc(s - 1) ^ calc(s + n - 1); + let last = n & start & 1; + let ans = (prefix << 1) | last; + return ans; +}; +``` * 时间复杂度:$O(1)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/1501-1510/1503. \346\211\200\346\234\211\350\232\202\350\232\201\346\216\211\344\270\213\346\235\245\345\211\215\347\232\204\346\234\200\345\220\216\344\270\200\345\210\273\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1501-1510/1503. \346\211\200\346\234\211\350\232\202\350\232\201\346\216\211\344\270\213\346\235\245\345\211\215\347\232\204\346\234\200\345\220\216\344\270\200\345\210\273\357\274\210\344\270\255\347\255\211\357\274\211.md" index 96039d34..78ef2293 100644 --- "a/LeetCode/1501-1510/1503. \346\211\200\346\234\211\350\232\202\350\232\201\346\216\211\344\270\213\346\235\245\345\211\215\347\232\204\346\234\200\345\220\216\344\270\200\345\210\273\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1501-1510/1503. \346\211\200\346\234\211\350\232\202\350\232\201\346\216\211\344\270\213\346\235\245\345\211\215\347\232\204\346\234\200\345\220\216\344\270\200\345\210\273\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,15 +6,24 @@ Tag : 「模拟」、「脑筋急转弯」 -有一块木板,长度为 `n` 个 单位 。一些蚂蚁在木板上移动,每只蚂蚁都以每秒一个单位的速度移动。其中,一部分蚂蚁向左移动,其他蚂蚁向右移动。 +有一块木板,长度为 `n` 个单位。 -当两只向 不同 方向移动的蚂蚁在某个点相遇时,它们会同时改变移动方向并继续移动。假设更改方向不会花费任何额外时间。 +一些蚂蚁在木板上移动,每只蚂蚁都以每秒一个单位的速度移动。 + +其中,一部分蚂蚁向左移动,其他蚂蚁向右移动。 + +当两只向不同方向移动的蚂蚁在某个点相遇时,它们会同时改变移动方向并继续移动。 + +假设更改方向不会花费任何额外时间。 而当蚂蚁在某一时刻 `t` 到达木板的一端时,它立即从木板上掉下来。 -给你一个整数 `n` 和两个整数数组 `left` 以及 `right`。两个数组分别标识向左或者向右移动的蚂蚁在 `t = 0` 时的位置。请你返回最后一只蚂蚁从木板上掉下来的时刻。 +给你一个整数 `n` 和两个整数数组 `left` 以及 `right`,两个数组分别标识向左或者向右移动的蚂蚁在 `t = 0` 时的位置。 + +请你返回最后一只蚂蚁从木板上掉下来的时刻。 示例 1: + ![](https://assets.leetcode.com/uploads/2020/06/17/ants.jpg) ``` @@ -30,7 +39,9 @@ Tag : 「模拟」、「脑筋急转弯」 请注意,蚂蚁在木板上的最后时刻是 t = 4 秒,之后蚂蚁立即从木板上掉下来。(也就是说在 t = 4.0000000001 时,木板上没有蚂蚁)。 ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2020/06/17/ants2.jpg) + ``` 输入:n = 7, left = [], right = [0,1,2,3,4,5,6,7] @@ -39,7 +50,9 @@ Tag : 「模拟」、「脑筋急转弯」 解释:所有蚂蚁都向右移动,下标为 0 的蚂蚁需要 7 秒才能从木板上掉落。 ``` 示例 3: + ![](https://assets.leetcode.com/uploads/2020/06/17/ants3.jpg) + ``` 输入:n = 7, left = [0,1,2,3,4,5,6,7], right = [] @@ -65,7 +78,7 @@ Tag : 「模拟」、「脑筋急转弯」 蚂蚁之间的相互影响实际上并不会影响它们到达木板边缘的时间。 -两只相遇的蚂蚁只是简单地交换了彼此的移动方向,并没有影响到它们的移动步数,即两只蚂蚁相遇可视为身份互换。 +**两只相遇的蚂蚁只是简单地交换了彼此的移动方向,并没有影响到它们的移动步数,即两只蚂蚁相遇可视为身份互换。** 因此,我们只需要分别计算每只蚂蚁单独掉落到木板边缘所需的时间,然后取最大值即可得到所有蚂蚁掉落的最后时刻。 diff --git "a/LeetCode/151-160/153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/151-160/153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" index 54ce087a..14ddf58e 100644 --- "a/LeetCode/151-160/153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/151-160/153. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,18 +6,18 @@ Tag : 「二分」 -已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。 +已知一个长度为 `n` 的数组,预先按照升序排列,经由 `1` 到 `n` 次 旋转 后,得到输入数组。 -例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到: +例如,原数组 `nums = [0,1,2,4,5,6,7]` 在变化后可能得到: -* 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2] -* 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7] +* 若旋转 `4` 次,则可以得到 `[4,5,6,7,0,1,2]` +* 若旋转 `7` 次,则可以得到 `[0,1,2,4,5,6,7]` -注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。 - -给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。 +注意,数组 $[a[0], a[1], a[2], ..., a[n-1]]$ 旋转一次 的结果为数组 $[a[n-1], a[0], a[1], a[2], ..., a[n-2]]$。 +给你一个元素值 互不相同 的数组 `nums`,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。 +请你找出并返回数组中的最小元素。 示例 1: ``` @@ -45,48 +45,86 @@ Tag : 「二分」 ``` 提示: -* n == nums.length -* 1 <= n <= 5000 -* -5000 <= nums[i] <= 5000 -* nums 中的所有整数 互不相同 -* nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转 +* $n = nums.length$ +* $1 <= n <= 5000$ +* $-5000 <= nums[i] <= 5000$ +* `nums` 中的所有整数互不相同 +* `nums` 原来是一个升序排序的数组,并进行了 `1` 至 `n` 次旋转 --- ### 二分查找 -今天这道题和昨天的 [81. 搜索旋转排序数组 II](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) 相比,有了限制条件「所有整数互不相同」。 +今天这道题和昨天的 [81. 搜索旋转排序数组 II](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247508097&idx=1&sn=d92413b1342bed164f14af9965504b4c&chksm=fd9f0b9ecae88288a07a3a13a68a22bfc952052ce59d7bbce6395a0923f308ca6b72dc22c901#rd) 相比,有了限制条件「所有整数互不相同」。 -因此我们不需要进行「恢复二段性」的预处理,是可以做到严格 $O(log{n})$ 的复杂度。 +因此我们不需要进行「恢复二段性」的预处理,是可以做到严格 $O(\log{n})$ 的复杂度。 我们仍然从「二分」的本质「二段性」进行出发分析: -![image.png](https://pic.leetcode-cn.com/1617853107-AQzbEE-image.png) +![](https://pic.leetcode-cn.com/1617853107-AQzbEE-image.png) 经过旋转的数组,显然前半段满足 `>= nums[0]`,而后半段不满足 `>= nums[0]`。我们可以以此作为依据,通过「二分」找到旋转点。然后通过旋转点找到全局最小值即可。 -代码: -```java [] +Java 代码: +```Java class Solution { public int findMin(int[] nums) { int n = nums.length; int l = 0, r = n - 1; while (l < r) { int mid = l + r + 1 >> 1; - if (nums[mid] >= nums[0]) { - l = mid; - } else { - r = mid - 1; - } + if (nums[mid] >= nums[0]) l = mid; + else r = mid - 1; } return r + 1 < n ? nums[r + 1] : nums[0]; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int findMin(vector& nums) { + int n = nums.size(); + int l = 0, r = n - 1; + while (l < r) { + int mid = l + r + 1 >> 1; + if (nums[mid] >= nums[0]) l = mid; + else r = mid - 1; + } + return r + 1 < n ? nums[r + 1] : nums[0]; + } +}; +``` +Python 代码: +```Python +class Solution: + def findMin(self, nums: List[int]) -> int: + n = len(nums) + l, r = 0, n - 1 + while l < r: + mid = l + r + 1 >> 1 + if nums[mid] >= nums[0]: l = mid + else: r = mid - 1 + return nums[r + 1] if r + 1 < n else nums[0] +``` +TypeScript 代码: +```TypeScript +function findMin(nums: number[]): number { + const n: number = nums.length; + let l: number = 0, r: number = n - 1; + while (l < r) { + const mid: number = l + r + 1 >> 1; + if (nums[mid] >= nums[0]) l = mid; + else r = mid - 1; + } + return r + 1 < n ? nums[r + 1] : nums[0]; +}; +``` * 时间复杂度:$O(\log{n})$ * 空间复杂度:$O(1)$ -*** +--- ### 其他「二分」相关题解 diff --git "a/LeetCode/151-160/154. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274 II\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/151-160/154. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274 II\357\274\210\345\233\260\351\232\276\357\274\211.md" index b454e195..e96279a0 100644 --- "a/LeetCode/151-160/154. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274 II\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/151-160/154. \345\257\273\346\211\276\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274 II\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -11,7 +11,7 @@ Tag : 「二分」 例如,原数组 `nums = [0,1,4,4,5,6,7]` 在变化后可能得到: * 若旋转 $4$ 次,则可以得到 `[4,5,6,7,0,1,4]` -* 若旋转 $74 次,则可以得到 `[0,1,4,4,5,6,7]` +* 若旋转 $7$ 次,则可以得到 `[0,1,4,4,5,6,7]` 注意,数组 `[a[0], a[1], a[2], ..., a[n-1]]` 旋转一次 的结果为数组 `[a[n-1], a[0], a[1], a[2], ..., a[n-2]]` 。 @@ -33,14 +33,14 @@ Tag : 「二分」 ``` 提示: -* $n == nums.length$ +* $n = nums.length$ * $1 <= n <= 5000$ * $-5000 <= nums[i] <= 5000$ * `nums` 原来是一个升序排序的数组,并进行了 $1$ 至 $n$ 次旋转 进阶: -* 这道题是 寻找旋转排序数组中的最小值 的延伸题目。 +* 这道题是「寻找旋转排序数组中的最小值」的延伸题目。 * 允许重复会影响算法的时间复杂度吗?会如何影响,为什么? --- @@ -59,12 +59,12 @@ Tag : 「二分」 举个🌰,我们使用数据 [0,1,2,2,2,3,4,5] 来理解为什么不同的旋转点会导致「二段性丢失」: -![image.png](https://pic.leetcode-cn.com/1617852745-LoBNPK-image.png) +![](https://pic.leetcode-cn.com/1617852745-LoBNPK-image.png) Java 代码: ```java class Solution { - public int minArray(int[] nums) { + public int findMin(int[] nums) { int n = nums.length; int l = 0, r = n - 1; while (l < r && nums[0] == nums[r]) r--; @@ -77,9 +77,42 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int findMin(vector& nums) { + int n = nums.size(); + int l = 0, r = n - 1; + while (l < r && nums[0] == nums[r]) r--; + while (l < r) { + int mid = l + r + 1 >> 1; + if (nums[mid] >= nums[0]) l = mid; + else r = mid - 1; + } + return r + 1 < n ? nums[r + 1] : nums[0]; + } +}; +``` +Python 代码: +```Python +class Solution: + def findMin(self, nums: List[int]) -> int: + n = len(nums) + l, r = 0, n - 1 + while l < r and nums[0] == nums[r]: + r -= 1 + while l < r: + mid = l + r + 1 >> 1 + if nums[mid] >= nums[0]: + l = mid + else: + r = mid - 1 + return nums[r + 1] if r + 1 < n else nums[0] +``` TypeScript 代码: ```TypeScript -function minArray(nums: number[]): number { +function findMin(nums: number[]): number { const n = nums.length let l = 0, r = n - 1 while (l < r && nums[0] == nums[r]) r-- diff --git "a/LeetCode/151-160/155. \346\234\200\345\260\217\346\240\210\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/151-160/155. \346\234\200\345\260\217\346\240\210\357\274\210\347\256\200\345\215\225\357\274\211.md" index 595faac7..27914343 100644 --- "a/LeetCode/151-160/155. \346\234\200\345\260\217\346\240\210\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/151-160/155. \346\234\200\345\260\217\346\240\210\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,11 +7,11 @@ Tag : 「栈」 -设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 -* push(x) —— 将元素 x 推入栈中。 -* pop() —— 删除栈顶的元素。 -* top() —— 获取栈顶元素。 -* getMin() —— 检索栈中的最小元素。 +设计一个支持 `push`,`pop`,`top` 操作,并能在常数时间内检索到最小元素的栈。 +* `push(x)` —— 将元素 `x` 推入栈中。 +* `pop()` —— 删除栈顶的元素。 +* `top()` —— 获取栈顶元素。 +* `getMin()` —— 检索栈中的最小元素。 示例: @@ -36,47 +36,117 @@ minStack.getMin(); --> 返回 -2. ``` 提示: -* pop、top 和 getMin 操作总是在 非空栈 上调用。 +* `pop`、`top` 和 `getMin` 操作总是在非空栈上调用。 --- -### 双栈解法 +### 双栈 -为了快速找到栈中最小的元素,我们可以使用一个辅助栈 help。 +为了快速找到栈中最小的元素,我们可以使用一个辅助栈 `help`。 -通过控制 help 的压栈逻辑来实现:*help 栈顶中始终存放着栈内元素的最小值。* +通过控制 `help` 的压栈逻辑来实现:**help 栈顶中始终存放着栈内元素的最小值。** -![image.png](https://pic.leetcode-cn.com/1616918115-ubygKn-image.png) +![](https://pic.leetcode-cn.com/1616918115-ubygKn-image.png) -代码: -```java +Java 代码: +```Java class MinStack { Deque data = new ArrayDeque<>(); Deque help = new ArrayDeque<>(); - public void push(int val) { data.addLast(val); - if (help.isEmpty() || help.peekLast() >= val) { - help.addLast(val); - } else { - help.addLast(help.peekLast()); - } + if (help.isEmpty() || help.peekLast() >= val) help.addLast(val); + else help.addLast(help.peekLast()); } - public void pop() { data.pollLast(); help.pollLast(); } - public int top() { return data.peekLast(); } - public int getMin() { return help.peekLast(); } } ``` +C++ 代码: +```C++ +class MinStack { +public: + stack data; + deque help; + void push(int val) { + data.push(val); + if (help.empty() || help.back() >= val) help.push_back(val); + else help.push_back(help.back()); + } + void pop() { + data.pop(); + help.pop_back(); + } + int top() { + return data.top(); + } + int getMin() { + return help.back(); + } +}; +``` +Python 代码: +```Python +class MinStack: + def __init__(self): + self.data = [] + self.help = [] + + def push(self, val: int) -> None: + self.data.append(val) + if not self.help or self.help[-1] >= val: + self.help.append(val) + else: + self.help.append(self.help[-1]) + + def pop(self) -> None: + self.data.pop() + self.help.pop() + + def top(self) -> int: + return self.data[-1] + + def getMin(self) -> int: + return self.help[-1] +``` +TypeScript 代码: +```TypeScript +class MinStack { + private data: number[]; + private help: number[]; + + constructor() { + this.data = []; + this.help = []; + } + push(val: number): void { + this.data.push(val); + if (this.help.length === 0 || this.help[this.help.length - 1] >= val) { + this.help.push(val); + } else { + this.help.push(this.help[this.help.length - 1]); + } + } + pop(): void { + this.data.pop(); + this.help.pop(); + } + top(): number { + return this.data[this.data.length - 1]; + } + getMin(): number { + return this.help[this.help.length - 1]; + } +} +``` * 时间复杂度:所有的操作均为 $O(1)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/1601-1610/1603. \350\256\276\350\256\241\345\201\234\350\275\246\347\263\273\347\273\237\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1601-1610/1603. \350\256\276\350\256\241\345\201\234\350\275\246\347\263\273\347\273\237\357\274\210\347\256\200\345\215\225\357\274\211.md" index 0d5c5bd7..30e51dc4 100644 --- "a/LeetCode/1601-1610/1603. \350\256\276\350\256\241\345\201\234\350\275\246\347\263\273\347\273\237\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1601-1610/1603. \350\256\276\350\256\241\345\201\234\350\275\246\347\263\273\347\273\237\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -9,10 +9,15 @@ Tag : 「位运算」、「哈希表」 请你给一个停车场设计一个停车系统。停车场总共有三种不同大小的车位:大,中和小,每种尺寸分别有固定数目的车位。 -请你实现 ParkingSystem 类: +请你实现 `ParkingSystem` 类: -* ParkingSystem(int big, int medium, int small) 初始化 ParkingSystem 类,三个参数分别对应每种停车位的数目。 -* bool addCar(int carType) 检查是否有 carType 对应的停车位。 carType 有三种类型:大,中,小,分别用数字 1, 2 和 3 表示。一辆车只能停在  carType 对应尺寸的停车位中。如果没有空车位,请返回 false ,否则将该车停入车位并返回 true 。 +* `ParkingSystem(int big, int medium, int small)` 初始化 `ParkingSystem` 类,三个参数分别对应每种停车位的数目。 + +* `bool addCar(int carType)` 检查是否有 `carType` 对应的停车位。 + + `carType` 有三种类型:大,中,小,分别用数字 1, 2 和 3 表示。一辆车只能停在  `carType` 对应尺寸的停车位中。 + + 如果没有空车位,请返回 `false`,否则将该车停入车位并返回 `true`。 示例 1: ``` @@ -31,9 +36,9 @@ parkingSystem.addCar(1); // 返回 false ,因为没有空的大车位,唯一 ``` 提示: -* 0 <= big, medium, small <= 1000 -* carType 取值为 1, 2 或 3 -* 最多会调用 addCar 函数 1000 次 +* $0 <= big, medium, small <= 1000$ +* `carType` 取值为 `1`,`2` 或 `3` +* 最多会调用 `addCar` 函数 1000 次 --- @@ -41,6 +46,7 @@ parkingSystem.addCar(1); // 返回 false ,因为没有空的大车位,唯一 一个简单的做法是,直接使用几个成员变量来记录。 +Java 代码: ```Java class ParkingSystem { int big, medium, small; @@ -49,7 +55,6 @@ class ParkingSystem { medium = _medium; small = _small; } - public boolean addCar(int ct) { if (ct == 1 && big > 0) return big-- > 0; else if (ct == 2 && medium > 0) return medium-- > 0; @@ -58,10 +63,44 @@ class ParkingSystem { } } ``` +C++ 代码: +```C++ +class ParkingSystem { +public: + int big, medium, small; + ParkingSystem(int _big, int _medium, int _small) : big(_big), medium(_medium), small(_small) {} + bool addCar(int type) { + if (type == 1 && big > 0) { --big; return true; } + else if (type == 2 && medium > 0) { --medium; return true; } + else if (type == 3 && small > 0) { --small; return true; } + return false; + } +}; +``` +Python 代码: +```Python +class ParkingSystem: + def __init__(self, big: int, medium: int, small: int): + self.big = big + self.medium = medium + self.small = small + + def addCar(self, type_: int) -> bool: + if type_ == 1 and self.big > 0: + self.big -= 1 + return True + elif type_ == 2 and self.medium > 0: + self.medium -= 1 + return True + elif type_ == 3 and self.small > 0: + self.small -= 1 + return True + return False +``` * 时间复杂度:$O(1)$ * 空间复杂度:$O(1)$ -*** +--- ### 哈希表 @@ -69,6 +108,7 @@ class ParkingSystem { 这样做的好处是,当增加车类型,只需要重载一个构造方法即可。 +Java 代码: ```Java class ParkingSystem { Map map = new HashMap<>(); @@ -77,7 +117,6 @@ class ParkingSystem { map.put(2, _medium); map.put(3, _small); } - public boolean addCar(int ct) { if (map.get(ct) > 0) { map.put(ct, map.get(ct) - 1); @@ -87,33 +126,65 @@ class ParkingSystem { } } ``` +C++ 代码: +```C++ +class ParkingSystem { +public: + unordered_map map; + ParkingSystem(int _big, int _medium, int _small) { + map[1] = _big; + map[2] = _medium; + map[3] = _small; + } + bool addCar(int type) { + if (map[type] > 0) { + map[type] -= 1; + return true; + } + return false; + } +}; +``` +Python 代码: +```Python +class ParkingSystem: + def __init__(self, big, medium, small): + self.map = {1: big, 2: medium, 3: small} + + def addCar(self, type_): + if self.map.get(type_, 0) > 0: + self.map[type_] -= 1 + return True + return False +``` * 时间复杂度:$O(1)$ * 空间复杂度:$O(1)$ -*** +--- ### 二进制分段 -事实上,由于 $1000$ 的二进制表示只有 $10$ 位,而 $int$ 有 $32$ 位。 +事实上,由于 1000 的二进制表示只有 10 位,而 `int` 有 32 位。 -我们可以使用一个 $int$ 配合「位运算」来分段做。 +我们可以使用一个 `int` 配合「位运算」来分段做。 使用 $[0,10)$ 代表 big,$[10,20)$ 表示 medium,$[20,30)$ 表示 small *PS. 这样 $int$ 分段的做法,在工程源码上也有体现:`JDK` 中的 `ThreadPoolExecutor` 使用了一个 $ctl$ 变量 ($int$ 类型) 的前 $3$ 位记录线程池的状态,后 $29$ 位记录程池中线程个数。* -**这样的「二进制分段压缩存储」的主要目的,不是为了减少使用一个 $int$,而是为了让「非原子性操作」变为「原子性操作」。** +**这样的「二进制分段压缩存储」的主要目的,不是为了减少使用一个 `int`,而是为了让「非原子性操作」变为「原子性操作」。** 我们可以分析下为什么 `ThreadPoolExecutor` 要这么做。 当线程数量变化为某个特定值时,要修改的就不仅仅是「线程数量」,还需要修改「线程池的状态」。 -由于并发环境下,如果要做到「原子性」地同时需要修改两个 $int$ 的话。只能上「重量级锁」,「重量级锁」就会涉及到「内核态」的系统调用,通常是耗时是「用户态」的上百倍。 +由于并发环境下,如果要做到「原子性」地同时需要修改两个 `int` 的话。只能上「重量级锁」,「重量级锁」就会涉及到「内核态」的系统调用,通常是耗时是「用户态」的上百倍。 -但是如果我们将「线程数量」和「线程池的状态」合二为一之后,我们只需要修改一个 $int$,这时候只需要使用 CAS 做法(用户态)即可保证线程安全与原子性。 +但是如果我们将「线程数量」和「线程池的状态」合二为一之后,我们只需要修改一个 `int`,这时候只需要使用 CAS 做法(用户态)即可保证线程安全与原子性。 -那么对应到该题,如果我们允许同时停入不同类型的车,在不引入重量级锁的前提下,想要真正做到「同时」修改两种类型的车的车位的话,只能采用这样的「二进制分段」做法 ~ +那么对应到该题,如果我们允许同时停入不同类型的车,在不引入重量级锁的前提下,想要真正做到「同时」修改两种类型的车的车位的话,只能采用这样的「二进制分段」做法. +Java 代码: ```Java class ParkingSystem { int cnt; // [small medium big] @@ -130,7 +201,6 @@ class ParkingSystem { cnt += cur == 1 ? (1 << i) : 0; } } - public boolean addCar(int ct) { int cur = countOfType(ct); if (cur > 0) { @@ -139,7 +209,6 @@ class ParkingSystem { } return false; } - int countOfType(int ct) { int ans = 0; int start = --ct * 10, end = start + 10; @@ -150,7 +219,6 @@ class ParkingSystem { } return ans; } - void setCount(int ct, int pc) { int start = --ct * 10, end = start + 10; for (int i = start; i < end; i++) { @@ -163,6 +231,93 @@ class ParkingSystem { } } ``` +C++ 代码: +```C++ +class ParkingSystem { +public: + int cnt = 0; // [small medium big] + ParkingSystem(int _big, int _medium, int _small) { + for (int i = 0; i < 30; i++) { + int cur = 0; + if (i < 10) { + cur = (_big >> i) & 1; + } else if (i < 20) { + cur = (_medium >> (i - 10)) & 1; + } else if (i < 30) { + cur = (_small >> (i - 20)) & 1; + } + cnt += cur == 1 ? (1 << i) : 0; + } + } + bool addCar(int ct) { + int cur = countOfType(ct); + if (cur > 0) { + setCount(ct, cur - 1); + return true; + } + return false; + } + int countOfType(int ct) { + int ans = 0; + int start = --ct * 10, end = start + 10; + for (int i = start; i < end; i++) { + if (((cnt >> i) & 1) == 1) { + ans += (1 << (i - start)); + } + } + return ans; + } + void setCount(int ct, int pc) { + int start = --ct * 10, end = start + 10; + for (int i = start; i < end; i++) { + if (((pc >> (i - start)) & 1) == 1) { + cnt |= (1 << i); + } else { + cnt &= ~(1 << i); + } + } + } +}; +``` +Python 代码: +```Python +class ParkingSystem: + def __init__(self, big: int, medium: int, small: int): + self.cnt = 0 + for i in range(30): + cur = 0 + if i < 10: + cur = (big >> i) & 1 + elif i < 20: + cur = (medium >> (i - 10)) & 1 + else: + cur = (small >> (i - 20)) & 1 + if cur == 1: + self.cnt |= (1 << i) + + def addCar(self, ct: int) -> bool: + cur = self.countOfType(ct) + if cur > 0: + self.setCount(ct, cur - 1) + return True + return False + + def countOfType(self, ct): + start = (ct - 1) * 10 + ans = 0 + for i in range(start, start + 10): + if (self.cnt >> i) & 1: + ans |= (1 << (i - start)) + return ans + + def setCount(self, ct, pc): + start = (ct - 1) * 10 + for i in range(start, start + 10): + if (pc >> (i - start)) & 1: + self.cnt |= (1 << i) + else: + self.cnt &= ~(1 << i) +``` * 时间复杂度:$O(1)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/1631-1640/1631. \346\234\200\345\260\217\344\275\223\345\212\233\346\266\210\350\200\227\350\267\257\345\276\204\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1631-1640/1631. \346\234\200\345\260\217\344\275\223\345\212\233\346\266\210\350\200\227\350\267\257\345\276\204\357\274\210\344\270\255\347\255\211\357\274\211.md" index 7fa0ecc0..b8d5588e 100644 --- "a/LeetCode/1631-1640/1631. \346\234\200\345\260\217\344\275\223\345\212\233\346\266\210\350\200\227\350\267\257\345\276\204\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1631-1640/1631. \346\234\200\345\260\217\344\275\223\345\212\233\346\266\210\350\200\227\350\267\257\345\276\204\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -8,17 +8,17 @@ Tag : 「最小生成树」、「并查集」、「Kruskal」 你准备参加一场远足活动。 -给你一个二维 `rows x columns` 的地图 heights ,其中 `heights[row][col]` 表示格子 `(row, col)` 的高度。 +给你一个二维 `rows x columns` 的地图 `heights`,其中 `heights[row][col]` 表示格子 `(row, col)` 的高度。 一开始你在最左上角的格子 `(0, 0`) ,且你希望去最右下角的格子 `(rows-1, columns-1)` (注意下标从 0 开始编号)。 -你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。 +你每次可以往「上,下,左,右」四个方向之一移动,你想要找到耗费体力最小的一条路径。 一条路径耗费的「体力值」是路径上相邻格子之间「高度差绝对值」的「最大值」决定的。 -请你返回从左上角走到右下角的最小 体力消耗值 。 +请你返回从左上角走到右下角的最小体力消耗值。 + -  示例 1: @@ -50,10 +50,10 @@ Tag : 「最小生成树」、「并查集」、「Kruskal」 ``` 提示: -* rows == heights.length -* columns == heights[i].length -* 1 <= rows, columns <= 100 -* 1 <= heights[i][j] <= $10^6$ +* $rows = heights.length$ +* $columns = heights[i].length$ +* $1 <= rows, columns <= 100$ +* $1 <= heights[i][j] <= 10^6$ --- @@ -105,7 +105,7 @@ Tag : 「最小生成树」、「并查集」、「Kruskal」 **当第一次判断「起点」和「终点」联通时,说明我们「最短路径」的所有边都已经应用到并查集上了,而且由于我们的边是按照「从小到大」进行排序,因此最后一条添加的边就是「最短路径」上权重最大的边。** -代码: +Java 代码: ```Java class Solution { int N = 10009; @@ -168,11 +168,94 @@ class Solution { } } ``` - -令行数为 $r$,列数为 $c$,那么节点的数量为 $r * c$,无向边的数量严格为 $r * (c - 1) + c * (r - 1)$,数量级上为 $r * c$。 - -* 时间复杂度:获取所有的边复杂度为 $O(r * c)$,排序复杂度为 $O((r * c)\log{(r * c)})$,遍历得到最终解复杂度为 $O(r * c)$。整体复杂度为 $O((r * c)\log{(r * c)})$。 -* 空间复杂度:使用了并查集数组。复杂度为 $O(r * c)$。 +C++ 代码: +```C++ +class Solution { +public: + int N = 10009; + vector p; + int row, col; + void unions(int a, int b) { + p[find(a)] = find(b); + } + bool query(int a, int b) { + return find(a) == find(b); + } + int find(int x) { + if (p[x] != x) p[x] = find(p[x]); + return p[x]; + } + int minimumEffortPath(vector>& heights) { + row = heights.size(); + col = heights[0].size(); + p.resize(row * col); + for (int i = 0; i < row * col; ++i) p[i] = i; + vector>> edges; + for (int i = 0; i < row; ++i) { + for (int j = 0; j < col; ++j) { + int idx = i * col + j; + if (i + 1 < row) { + edges.emplace_back(abs(heights[i][j] - heights[i + 1][j]), make_pair(idx, (i + 1) * col + j)); + } + if (j + 1 < col) { + edges.emplace_back(abs(heights[i][j] - heights[i][j + 1]), make_pair(idx, idx + 1)); + } + } + } + sort(edges.begin(), edges.end()); + int start = 0, end = row * col - 1; + for (const auto& edge : edges) { + int w = edge.first, a = edge.second.first, b = edge.second.second; + unions(a, b); + if (query(start, end)) { + return w; + } + } + return 0; + } +}; +``` +Python 代码: +```Python +class Solution: + def union(self, a: int, b: int) -> None: + root_a = self.find(a) + root_b = self.find(b) + if root_a != root_b: + self.p[root_b] = root_a + + def query(self, a: int, b: int) -> bool: + return self.find(a) == self.find(b) + + def find(self, x: int) -> int: + if self.p[x] != x: + self.p[x] = self.find(self.p[x]) + return self.p[x] + + def minimumEffortPath(self, heights: List[List[int]]) -> int: + self.row = len(heights) + self.col = len(heights[0]) + self.N = 10009 + self.p = list(range(self.row * self.col)) + edges = [] + for i in range(self.row): + for j in range(self.col): + idx = i * self.col + j + if i + 1 < self.row: + edges.append((abs(heights[i][j] - heights[i + 1][j]), (idx, (i + 1) * self.col + j))) + if j + 1 < self.col: + edges.append((abs(heights[i][j] - heights[i][j + 1]), (idx, idx + 1))) + edges.sort(key=lambda x: x[0]) + start = 0 + end = self.row * self.col - 1 + for w, (a, b) in edges: + self.union(a, b) + if self.query(start, end): + return w + return 0 +``` +* 时间复杂度:令行数为 $r$,列数为 $c$,那么节点的数量为 $r \times c$,无向边的数量严格为 $r \times (c - 1) + c \times (r - 1)$,数量级上为 $r \times c$。获取所有的边复杂度为 $O(r \times c)$,排序复杂度为 $O((r \times c)\log{(r \times c)})$,遍历得到最终解复杂度为 $O(r \times c)$。整体复杂度为 $O((r \times c)\log{(r \times c)})$。 +* 空间复杂度:使用了并查集数组。复杂度为 $O(r \times c)$。 --- diff --git "a/LeetCode/1661-1670/1662. \346\243\200\346\237\245\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\346\225\260\347\273\204\346\230\257\345\220\246\347\233\270\347\255\211\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1661-1670/1662. \346\243\200\346\237\245\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\346\225\260\347\273\204\346\230\257\345\220\246\347\233\270\347\255\211\357\274\210\347\256\200\345\215\225\357\274\211.md" index 0944d3a3..6aab445e 100644 --- "a/LeetCode/1661-1670/1662. \346\243\200\346\237\245\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\346\225\260\347\273\204\346\230\257\345\220\246\347\233\270\347\255\211\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1661-1670/1662. \346\243\200\346\237\245\344\270\244\344\270\252\345\255\227\347\254\246\344\270\262\346\225\260\347\273\204\346\230\257\345\220\246\347\233\270\347\255\211\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,9 +6,11 @@ Tag : 「模拟」、「双指针」 -给你两个字符串数组 `word1` 和 `word2`。如果两个数组表示的字符串相同,返回 `true` ;否则,返回 `false` 。 +给你两个字符串数组 `word1` 和 `word2`。 -数组表示的字符串 是由数组中的所有元素 按顺序 连接形成的字符串。 +如果两个数组表示的字符串相同,返回 `true` ;否则,返回 `false` 。 + +数组表示的字符串是由数组中的所有元素按顺序连接形成的字符串。 示例 1: ``` diff --git "a/LeetCode/1681-1690/1684. \347\273\237\350\256\241\344\270\200\350\207\264\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1681-1690/1684. \347\273\237\350\256\241\344\270\200\350\207\264\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" index d6d0b1f3..6b0e9f51 100644 --- "a/LeetCode/1681-1690/1684. \347\273\237\350\256\241\344\270\200\350\207\264\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1681-1690/1684. \347\273\237\350\256\241\344\270\200\350\207\264\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\347\233\256\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,9 +6,11 @@ Tag : 「模拟」、「位运算」 -给你一个由不同字符组成的字符串 `allowed` 和一个字符串数组 `words`。如果一个字符串的每一个字符都在 `allowed` 中,就称这个字符串是 一致字符串 。 +给你一个由不同字符组成的字符串 `allowed` 和一个字符串数组 `words`。 -请你返回 `words` 数组中 一致字符串 的数目。 +如果一个字符串的每一个字符都在 `allowed` 中,就称这个字符串是一致字符串。 + +请你返回 `words` 数组中一致字符串的数目。 示例 1: ``` @@ -112,8 +114,8 @@ function countConsistentStrings(allowed: string, words: string[]): number { return ans } ``` -Python3 代码: -```Python3 +Python 代码: +```Python class Solution: def countConsistentStrings(self, allowed: str, words: List[str]) -> int: sset = set([c for c in allowed]) @@ -127,8 +129,8 @@ class Solution: ans += 1 if ok else 0 return ans ``` -Python3 代码: -```Python3 +Python 代码: +```Python class Solution: def countConsistentStrings(self, allowed: str, words: List[str]) -> int: num, ans = 0, 0 diff --git "a/LeetCode/171-180/173. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/171-180/173. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" index 231ef9f0..6e78d657 100644 --- "a/LeetCode/171-180/173. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/171-180/173. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -63,7 +63,7 @@ bSTIterator.hasNext(); // 返回 False 2. 将最后一个压入的节点弹出(栈顶元素),加入答案 3. 将当前弹出的节点作为当前节点,重复步骤一 -相应的裸题在这里:[94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) +相应的裸题在这里:[94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/)。 中序遍历的迭代代码: ```java @@ -92,7 +92,7 @@ class Solution { 总的来说是这么一个迭代过程:步骤 1 -> 步骤 2 -> 步骤 3 -> 步骤 1 ... -*** +--- ### 「中序遍历」代码的「等价拆分」 @@ -104,15 +104,14 @@ class Solution { 综上,我们应该在初始化时,走一遍「步骤 1」,然后在 `next()` 方法中走「步骤 2」、「步骤 3」和「步骤 1」。 -代码: -```java [] +Java 代码: +```Java class BSTIterator { Deque d = new ArrayDeque<>(); public BSTIterator(TreeNode root) { // 步骤 1 dfsLeft(root); } - public int next() { // 步骤 2 TreeNode root = d.pollLast(); @@ -123,28 +122,99 @@ class BSTIterator { dfsLeft(root); return ans; } - + public boolean hasNext() { + return !d.isEmpty(); + } void dfsLeft(TreeNode root) { while (root != null) { d.addLast(root); root = root.left; } } - - public boolean hasNext() { - return !d.isEmpty(); +} +``` +C++ 代码: +```C++ +class BSTIterator { +public: + stack d; + BSTIterator(TreeNode* root) { + dfsLeft(root); + } + int next() { + TreeNode* root = d.top(); + d.pop(); + int ans = root->val; + root = root->right; + dfsLeft(root); + return ans; + } + bool hasNext() { + return !d.empty(); + } + void dfsLeft(TreeNode* root) { + while (root != nullptr) { + d.push(root); + root = root->left; + } + } +}; +``` +Python 代码: +```Python +class BSTIterator: + def __init__(self, root: TreeNode): + self.d = deque() + self.dfsLeft(root) + + def next(self) -> int: + root = self.d.pop() + ans = root.val + root = root.right + self.dfsLeft(root) + return ans + + def hasNext(self) -> bool: + return len(self.d) > 0 + + def dfsLeft(self, root): + while root: + self.d.append(root) + root = root.left +``` +TypeScript 代码: +```TypeScript +class BSTIterator { + d: TreeNode[]; + constructor(root: TreeNode | null) { + this.d = []; + this.dfsLeft(root); + } + next(): number { + const root = this.d.pop()!; + const ans = root.val; + root.right && this.dfsLeft(root.right); + return ans; + } + hasNext(): boolean { + return this.d.length > 0; + } + dfsLeft(root: TreeNode | null): void { + while (root) { + this.d.push(root); + root = root.left; + } } } ``` * 时间复杂度:由于每个元素都是严格「进栈」和「出栈」一次,复杂度为均摊 $O(1)$ * 空间复杂度:栈内最多保存与深度一致的节点数量,复杂度为 $O(h)$ - -*** +--- ### 进阶 -事实上,我们空间复杂度也能做到 $O(1)$,该如何做呢? +若要求空间复杂度也能做到 $O(1)$,该如何做呢? --- diff --git "a/LeetCode/171-180/179. \346\234\200\345\244\247\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/171-180/179. \346\234\200\345\244\247\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" index 82dc54ba..9035825f 100644 --- "a/LeetCode/171-180/179. \346\234\200\345\244\247\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/171-180/179. \346\234\200\345\244\247\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -148,7 +148,7 @@ $ans$ 和 $max$ 都是由同样一批数字凑成的,如果有 $ans < max$。 那么在排序逻辑中 $x$ 所在的整体(可能不只有 $x$ 一个数)应该被排在 $y$ 所在的整体(可能不只有 $y$ 一个数)前面。 -![image.png](https://pic.leetcode-cn.com/1618191664-aUaIFS-image.png) +![](https://pic.leetcode-cn.com/1618191664-aUaIFS-image.png) #### 2. 全序关系 @@ -187,7 +187,7 @@ $b@a$ 说明字符串 $ab$ 的字典序大小数值要比字符串 $ba$ 字典 接下来,让我们从「自定义排序逻辑」出发,换个思路来证明 $a@c$: -![image.png](https://pic.leetcode-cn.com/1618207470-nFVtbm-image.png) +![](https://pic.leetcode-cn.com/1618207470-nFVtbm-image.png) **然后我们只需要证明在不同的 $i$ $j$ 关系之间(共三种情况),$a@c$ 恒成立即可:** @@ -195,17 +195,17 @@ $b@a$ 说明字符串 $ab$ 的字典序大小数值要比字符串 $ba$ 字典 1. 当 $i == j$ 的时候: -![image.png](https://pic.leetcode-cn.com/1618209987-kPJqkw-image.png) +![](https://pic.leetcode-cn.com/1618209987-kPJqkw-image.png) 2. 当 $i > j$ 的时候: -![image.png](https://pic.leetcode-cn.com/1618210019-pYydoU-image.png) +![](https://pic.leetcode-cn.com/1618210019-pYydoU-image.png) 3. 当 $i < j$ 的时候: -![image.png](https://pic.leetcode-cn.com/1618210522-mJgnzX-image.png) +![](https://pic.leetcode-cn.com/1618210522-mJgnzX-image.png) **综上,我们证明了无论在何种情况下,只要有 $a@b$ 和 $b@c$ 的话,那么 $a@c$ 恒成立。** diff --git "a/LeetCode/1741-1750/1748. \345\224\257\344\270\200\345\205\203\347\264\240\347\232\204\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1741-1750/1748. \345\224\257\344\270\200\345\205\203\347\264\240\347\232\204\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" index 9510e06e..cfae1745 100644 --- "a/LeetCode/1741-1750/1748. \345\224\257\344\270\200\345\205\203\347\264\240\347\232\204\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1741-1750/1748. \345\224\257\344\270\200\345\205\203\347\264\240\347\232\204\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,9 +6,9 @@ Tag : 「模拟」、「哈希表」、「双指针」、「排序」 -给你一个整数数组 nums 。数组中唯一元素是那些只出现「恰好一次」的元素。 +给你一个整数数组 `nums`,数组中唯一元素是那些只出现「恰好一次」的元素。 -请你返回 nums 中唯一元素的和 。 +请你返回 `nums` 中唯一元素的和。 示例 1: @@ -37,8 +37,8 @@ Tag : 「模拟」、「哈希表」、「双指针」、「排序」 ``` 提示: -* 1 <= nums.length <= 100 -* 1 <= nums[i] <= 100 +* $1 <= nums.length <= 100$ +* $1 <= nums[i] <= 100$ --- @@ -46,7 +46,7 @@ Tag : 「模拟」、「哈希表」、「双指针」、「排序」 根据题意,其中一个做法是先对 `nums` 进行排序,使用双指针找到值相同的连续段 $[i, j)$,若连续段长度为 $1$,则将该值累加到答案。 -代码: +Java 代码: ```Java class Solution { public int sumOfUnique(int[] nums) { @@ -62,6 +62,51 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int sumOfUnique(vector& nums) { + sort(nums.begin(), nums.end()); + int n = nums.size(), 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; + } +}; +``` +Python 代码: +```Python +class Solution: + def sumOfUnique(self, nums): + # count = Counter(nums) + # return sum(x for x, c in count.items() if c == 1) + + nums.sort() + n, ans = len(nums), 0 + for i in range(n): + if (i == 0 or nums[i] != nums[i - 1]) and (i == n - 1 or nums[i] != nums[i + 1]): + ans += nums[i] + return ans +``` +TypeScript 代码: +```TypeScript +function sumOfUnique(nums: number[]): number { + nums.sort((a, b) => a - b); + let n: number = nums.length, ans: number = 0; + for (let i: number = 0; i < n; ) { + let j: number = 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})$ @@ -71,7 +116,7 @@ class Solution { 另外容易想到使用「哈希表」统计某个数的出现次数,又根据 $nums[i]$ 的范围为 $[1, 100]$,可直接使用数组充当哈希表。 -代码: +Java 代码: ```Java class Solution { int[] cnt = new int[110]; @@ -85,6 +130,44 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int sumOfUnique(vector& nums) { + unordered_map cnt; + for (int i : nums) cnt[i]++; + int ans = 0; + for (auto& pair : cnt) { + if (pair.second == 1) ans += pair.first; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def sumOfUnique(self, nums): + cnt = [0] * 101 + for i in nums: cnt[i] += 1 + ans = 0 + for i in range(1, 101): + if cnt[i] == 1: ans += i + return ans +``` +TypeScript 代码: +```TypeScript +function sumOfUnique(nums: number[]): number { + const cnt: number[] = new Array(110).fill(0); + for (const i of nums) cnt[i]++; + let ans: number = 0; + for (let i: number = 1; i <= 100; i++) { + if (cnt[i] == 1) ans += i; + } + return ans; +}; +``` * 时间复杂度:令 $C$ 为 $nums[i]$ 的值域大小,本题固定为 $110$。整体复杂度为 $O(n + C)$ * 空间复杂度:$O(C)$ diff --git "a/LeetCode/1741-1750/1749. \344\273\273\346\204\217\345\255\220\346\225\260\347\273\204\345\222\214\347\232\204\347\273\235\345\257\271\345\200\274\347\232\204\346\234\200\345\244\247\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1741-1750/1749. \344\273\273\346\204\217\345\255\220\346\225\260\347\273\204\345\222\214\347\232\204\347\273\235\345\257\271\345\200\274\347\232\204\346\234\200\345\244\247\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" index 12171f68..92d02884 100644 --- "a/LeetCode/1741-1750/1749. \344\273\273\346\204\217\345\255\220\346\225\260\347\273\204\345\222\214\347\232\204\347\273\235\345\257\271\345\200\274\347\232\204\346\234\200\345\244\247\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1741-1750/1749. \344\273\273\346\204\217\345\255\220\346\225\260\347\273\204\345\222\214\347\232\204\347\273\235\345\257\271\345\200\274\347\232\204\346\234\200\345\244\247\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,17 +6,17 @@ Tag : 「前缀和」 -给你一个整数数组 nums 。 +给你一个整数数组 `nums`。 一个子数组 `[numsl, numsl+1, ..., numsr-1, numsr]` 的「和的绝对值」为 `abs(numsl + numsl+1 + ... + numsr-1 + numsr) `。 -请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),并返回该 最大值 。 +请你找出 `nums` 中和的绝对值最大的任意子数组(可能为空),并返回该最大值。 -abs(x) 定义如下: +`abs(x)` 定义如下: -* 如果 x 是负整数,那么 abs(x) = -x 。 +* 如果 `x` 是负整数,那么 `abs(x) = -x`。 -* 如果 x 是非负整数,那么 abs(x) = x 。 +* 如果 `x` 是非负整数,那么 `abs(x) = x`。 @@ -39,8 +39,8 @@ abs(x) 定义如下: ``` 提示: -* 1 <= nums.length <= $10^5$ -* -$10^4$ <= nums[i] <= $10^4$ +* $1 <= nums.length <= 10^5$ +* $-10^4 <= nums[i] <= 10^4$ --- @@ -59,14 +59,13 @@ abs(x) 定义如下: 因此我们可以边循环边做更新答案。 -代码: +Java 代码: ```Java class Solution { public int maxAbsoluteSum(int[] nums) { - int n = nums.length; + int n = nums.length, ans = 0; 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)); @@ -77,6 +76,55 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxAbsoluteSum(vector& nums) { + int n = nums.size(), ans = 0; + vector sumv(n + 1, 0); + for (int i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + nums[i - 1]; + for (int i = 1, minv = 0, maxv = 0; i <= n; i++) { + ans = max(ans, abs(sumv[i] - minv)); + ans = max(ans, abs(sumv[i] - maxv)); + maxv = max(maxv, sumv[i]); + minv = min(minv, sumv[i]); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxAbsoluteSum(self, nums: List[int]) -> int: + n, ans = len(nums), 0 + sumv = [0] * (n + 1) + for i in range(1, n + 1): + sumv[i] = sumv[i - 1] + nums[i - 1] + minv, maxv = 0, 0 + for i in range(1, n + 1): + ans = max(ans, abs(sumv[i] - minv)) + ans = max(ans, abs(sumv[i] - maxv)) + maxv = max(maxv, sumv[i]) + minv = min(minv, sumv[i]) + return ans +``` +TypeScript 代码: +```TypeScript +function maxAbsoluteSum(nums: number[]): number { + let n = nums.length, ans = 0; + const sum = new Array(n + 1).fill(0); + for (let i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1]; + for (let 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)$ diff --git "a/LeetCode/1761-1770/1763. \346\234\200\351\225\277\347\232\204\347\276\216\345\245\275\345\255\220\345\255\227\347\254\246\344\270\262\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/1761-1770/1763. \346\234\200\351\225\277\347\232\204\347\276\216\345\245\275\345\255\220\345\255\227\347\254\246\344\270\262\357\274\210\347\256\200\345\215\225\357\274\211.md" index 879fa93d..c8ebbbc6 100644 --- "a/LeetCode/1761-1770/1763. \346\234\200\351\225\277\347\232\204\347\276\216\345\245\275\345\255\220\345\255\227\347\254\246\344\270\262\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/1761-1770/1763. \346\234\200\351\225\277\347\232\204\347\276\216\345\245\275\345\255\220\345\255\227\347\254\246\344\270\262\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,11 +6,15 @@ Tag : 「模拟」、「前缀和」、「位运算」 -当一个字符串 `s` 包含的每一种字母的大写和小写形式 同时 出现在 `s` 中,就称这个字符串 `s` 是 美好 字符串。 +当一个字符串 `s` 包含的每一种字母的大写和小写形式同时出现在 `s` 中,就称这个字符串 `s` 是美好字符串。 -比方说,`"abABB"` 是美好字符串,因为 `'A'` 和 `'a'` 同时出现了,且 `'B'` 和 `'b'` 也同时出现了。然而,`"abA"` 不是美好字符串因为 `'b'` 出现了,而 `'B'` 没有出现。 +比方说,`"abABB"` 是美好字符串,因为 `'A'` 和 `'a'` 同时出现了,且 `'B'` 和 `'b'` 也同时出现了。 -给你一个字符串 `s` ,请你返回 `s` 最长的 美好子字符串 。如果有多个答案,请你返回 最早 出现的一个。如果不存在美好子字符串,请你返回一个空字符串。 +然而,`"abA"` 不是美好字符串因为 `'b'` 出现了,而 `'B'` 没有出现。 + +给你一个字符串 `s` ,请你返回 `s` 最长的美好子字符串。 + +如果有多个答案,请你返回最早出现的一个。如果不存在美好子字符串,请你返回一个空字符串。 示例 1: @@ -58,7 +62,7 @@ Tag : 「模拟」、「前缀和」、「位运算」 数据范围只有 $100$,最为简单的做法是枚举所有的子串( 复杂度为 $O(n^2)$ ),然后对子串进行合法性检查( 复杂度为 $O(n)$ ),整体复杂度为 $O(n^3)$,可以过。 -代码: +Java 代码: ```Java class Solution { public String longestNiceSubstring(String s) { @@ -82,6 +86,53 @@ class Solution { } } ``` +C++ 代码: +```C++ +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++) { + string sub = s.substr(i, j - i); + if (j - i > ans.length() && check(sub)) ans = sub; + } + } + return ans; + } + bool check(const string& s) { + unordered_set set; + for (char c : s) set.insert(c); + for (char c : s) { + char a = tolower(c), b = toupper(c); + if (set.find(a) == set.end() || set.find(b) == set.end()) return false; + } + return true; + } +}; +``` +Python 代码: +```Python +class Solution: + def longestNiceSubstring(self, s: str) -> str: + n = len(s) + ans = "" + for i in range(n): + for j in range(i + 1, n): + if j - i + 1 > len(ans) and self.check(s[i:j+1]): + ans = s[i:j+1] + return ans + def check(self, s): + set_ = set() + for c in s: + set_.add(c) + for c in s: + a, b = c.lower(), c.upper() + if a not in set_ or b not in set_: + return False + return True +``` * 时间复杂度:$O(n^3)$ * 空间复杂度:$O(n)$ @@ -99,9 +150,9 @@ $$ res[i] = cnt[r + 1][i] - cnt[l][i] $$ -这样我们在 `check` 实现中,只要检查 $26$ 个字母对应的大小写词频(ASCII 相差 $32$),是否同时为 $0$ 或者同时不为 $0$ 即可,复杂度为 $O(C)$。 +这样我们在 `check` 实现中,只要检查 $26$ 个字母对应的大小写词频(`ASCII` 相差 $32$),是否同时为 $0$ 或者同时不为 $0$ 即可,复杂度为 $O(C)$。 -代码: +Java 代码: ```Java class Solution { public String longestNiceSubstring(String s) { @@ -134,8 +185,67 @@ class Solution { } } ``` -* 时间复杂度:令 $C$ 为字符集大小,本题固定为 $26$,构建 $cnt$ 的复杂度为 $O(n * 128)$;枚举所有子串复杂度为 $O(n^2)$;`check` 的复杂度为 $O(C)$。整体复杂度为 $O(n^2 * C)$ -* 空间复杂度:$O(n * 128)$ +C++ 代码: +```C++ +class Solution { +public: + string longestNiceSubstring(string s) { + int n = s.length(); + vector> cnt(n + 1, vector(128)); + for (int i = 1; i <= n; i++) { + char c = s[i - 1]; + cnt[i] = cnt[i - 1]; + cnt[i][c - 'A'] += 1; + } + 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; + if (check(cnt[i], cnt[j + 1])) { + idx = i; len = j - i + 1; + } + } + } + return idx == -1 ? "" : s.substr(idx, len); + } + bool check(const vector& a, const vector& b) { + for (int i = 0; i < 26; i++) { + int low = b[i] - a[i], up = b[i + 32] - a[i + 32]; + if ((low != 0 && up == 0) || (low == 0 && up != 0)) return false; + } + return true; + } +}; +``` +Python 代码: +```Python +class Solution: + def longestNiceSubstring(self, s: str) -> str: + n = len(s) + cnt = [[0] * 128 for _ in range(n + 1)] + for i in range(1, n + 1): + c = s[i - 1] + cnt[i] = cnt[i - 1].copy() + cnt[i][ord(c) - ord('A')] += 1 + idx, lenv = -1, 0 + for i in range(n): + for j in range(i + 1, n): + if j - i + 1 <= lenv: + continue + if self.check(cnt[i], cnt[j + 1]): + idx, lenv = i, j - i + 1 + return "" if idx == -1 else s[idx: idx + lenv] + + def check(self, a, b): + for i in range(26): + low = b[i] - a[i] + up = b[i + 32] - a[i + 32] + if (low != 0 and up == 0) or (low == 0 and up != 0): + return False + return True +``` +* 时间复杂度:令 $C$ 为字符集大小,本题固定为 $26$,构建 `cnt` 的复杂度为 $O(n \times 128)$;枚举所有子串复杂度为 $O(n^2)$;`check` 的复杂度为 $O(C)$。整体复杂度为 $O(n^2 \times C)$ +* 空间复杂度:$O(n \times 128)$ --- @@ -145,12 +255,11 @@ class Solution { 因此我们无须使用二维数组来记录具体的词频,可以在枚举子串时,使用两个 `int` 的低 $26$ 位分别记录大小写字母的出现情况,利用枚举子串时右端点后移,维护两变量,当且仅当两变量相等时,满足 $26$ 个字母的大小写同时出现或同时不出现。 -代码: +Java 代码: ```Java class Solution { public String longestNiceSubstring(String s) { - int n = s.length(); - int idx = -1, len = 0; + int n = s.length(), idx = -1, len = 0; for (int i = 0; i < n; i++) { int a = 0, b = 0; for (int j = i; j < n; j++) { @@ -166,6 +275,44 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + string longestNiceSubstring(string s) { + int n = s.length(), 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[j]; + if (islower(c)) 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.substr(idx, len); + } +}; +``` +Python 代码: +```Python +class Solution: + def longestNiceSubstring(self, s: str) -> str: + n, idx, lenv = len(s), -1, 0 + for i in range(n): + a, b = 0, 0 + for j in range(i, n): + c = s[j] + if c.islower(): + a |= (1 << (ord(c) - ord('a'))) + else: + b |= (1 << (ord(c) - ord('A'))) + if a == b and j - i + 1 > lenv: + idx, lenv = i, j - i + 1 + return "" if idx == -1 else s[idx: idx + lenv] +``` * 时间复杂度:$O(n^2)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/1761-1770/1764. \351\200\232\350\277\207\350\277\236\346\216\245\345\217\246\344\270\200\344\270\252\346\225\260\347\273\204\347\232\204\345\255\220\346\225\260\347\273\204\345\276\227\345\210\260\344\270\200\344\270\252\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1761-1770/1764. \351\200\232\350\277\207\350\277\236\346\216\245\345\217\246\344\270\200\344\270\252\346\225\260\347\273\204\347\232\204\345\255\220\346\225\260\347\273\204\345\276\227\345\210\260\344\270\200\344\270\252\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" index 14d795bc..2aa56769 100644 --- "a/LeetCode/1761-1770/1764. \351\200\232\350\277\207\350\277\236\346\216\245\345\217\246\344\270\200\344\270\252\346\225\260\347\273\204\347\232\204\345\255\220\346\225\260\347\273\204\345\276\227\345\210\260\344\270\200\344\270\252\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1761-1770/1764. \351\200\232\350\277\207\350\277\236\346\216\245\345\217\246\344\270\200\344\270\252\346\225\260\347\273\204\347\232\204\345\255\220\346\225\260\347\273\204\345\276\227\345\210\260\344\270\200\344\270\252\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -8,7 +8,7 @@ Tag : 「双指针」 给你一个长度为 `n` 的二维整数数组 `groups` ,同时给你一个整数数组 `nums` 。 -你是否可以从 `nums` 中选出 `n` 个 不相交 的子数组,使得第 `i` 个子数组与 `groups[i]` (下标从 `0` 开始)完全相同,且如果 `i > 0` ,那么第 (`i-1`) 个子数组在 `nums` 中出现的位置在第 `i` 个子数组前面。(也就是说,这些子数组在 `nums` 中出现的顺序需要与 `groups` 顺序相同) +你是否可以从 `nums` 中选出 `n` 个 不相交 的子数组,使得第 `i` 个子数组与 `groups[i]`(下标从 `0` 开始)完全相同,且如果 `i > 0` ,那么第 (`i-1`) 个子数组在 `nums` 中出现的位置在第 `i` 个子数组前面。(也就是说,这些子数组在 `nums` 中出现的顺序需要与 `groups` 顺序相同) 如果你可以找出这样的 `n` 个子数组,请你返回 `true` ,否则返回 `false`。 @@ -59,7 +59,7 @@ Tag : 「双指针」 每次尝试从 `idx` 出发匹配 `gs[i]`,若能匹配成功,则整段更新 $idx = idx + gs[i].length$;否则将 `idx` 后移一位,继续尝试匹配 `gs[i]`。 -代码: +Java 代码: ```Java class Solution { public boolean canChoose(int[][] gs, int[] nums) { @@ -81,6 +81,67 @@ class Solution { } } ``` +C++ 代码(用 `ct` 模拟 `continue tag` 效果): +```C++ +class Solution { +public: + bool canChoose(vector>& gs, vector& nums) { + int m = nums.size(), idx = 0; + out:for (auto& info : gs) { + bool ct = false; + for (int j = idx; j + info.size() <= m; j++) { + bool ok = true; + for (int k = 0; k < info.size() && ok; k++) { + if (nums[j + k] != info[k]) ok = false; + } + if (ok) { + idx = j + info.size(); + ct = true; + break; + } + } + if (!ct) return false; + } + return true; + } +}; +``` +Python 代码: +```Python +class Solution: + def canChoose(self, gs: List[List[int]], nums: List[int]) -> bool: + m, idx = len(nums), 0 + for info in gs: + for j in range(idx, len(nums) - len(info) + 1): + if all(nums[j + k] == info[k] for k in range(len(info))): + idx = j + len(info) + break + else: + return False + return True +``` +TypeScript 代码: +```TypeScript +function canChoose(gs: number[][], nums: number[]): boolean { + let m = nums.length, idx = 0; + for (const info of gs) { + let ct = false; + for (let j = idx; j + info.length <= m; j++) { + let ok = true; + for (let k = 0; k < info.length && ok; k++) { + if (nums[j + k] !== info[k]) ok = false; + } + if (ok) { + idx = j + info.length; + ct = true; + break; + } + } + if (!ct) return false; + } + return true; +}; +``` * 时间复杂度:$O(n \times m)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/1761-1770/1766. \344\272\222\350\264\250\346\240\221\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/1761-1770/1766. \344\272\222\350\264\250\346\240\221\357\274\210\345\233\260\351\232\276\357\274\211.md" index b9614810..3b1b165d 100644 --- "a/LeetCode/1761-1770/1766. \344\272\222\350\264\250\346\240\221\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/1761-1770/1766. \344\272\222\350\264\250\346\240\221\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -20,6 +20,7 @@ Tag : 「DFS」 请你返回一个大小为 `n` 的数组 `ans`,其中 `ans[i]` 是离节点 `i` 最近的祖先节点且满足 `nums[i]` 和 `nums[ans[i]]` 是互质的,如果不存在这样的祖先节点,`ans[i]` 为 `-1`。 示例 1: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/02/20/untitled-diagram.png) ``` @@ -35,7 +36,9 @@ Tag : 「DFS」 ``` 示例 2: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/02/20/untitled-diagram1.png) + ``` 输入:nums = [5,6,10,2,3,6,15], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]] diff --git "a/LeetCode/1781-1790/1786. \344\273\216\347\254\254\344\270\200\344\270\252\350\212\202\347\202\271\345\207\272\345\217\221\345\210\260\346\234\200\345\220\216\344\270\200\344\270\252\350\212\202\347\202\271\347\232\204\345\217\227\351\231\220\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1781-1790/1786. \344\273\216\347\254\254\344\270\200\344\270\252\350\212\202\347\202\271\345\207\272\345\217\221\345\210\260\346\234\200\345\220\216\344\270\200\344\270\252\350\212\202\347\202\271\347\232\204\345\217\227\351\231\220\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" index 980dce7f..ce75807d 100644 --- "a/LeetCode/1781-1790/1786. \344\273\216\347\254\254\344\270\200\344\270\252\350\212\202\347\202\271\345\207\272\345\217\221\345\210\260\346\234\200\345\220\216\344\270\200\344\270\252\350\212\202\347\202\271\347\232\204\345\217\227\351\231\220\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1781-1790/1786. \344\273\216\347\254\254\344\270\200\344\270\252\350\212\202\347\202\271\345\207\272\345\217\221\345\210\260\346\234\200\345\220\216\344\270\200\344\270\252\350\212\202\347\202\271\347\232\204\345\217\227\351\231\220\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,37 +6,52 @@ Tag : 「最短路」、「线性 DP」 +现有一个加权无向连通图。 -现有一个加权无向连通图。给你一个正整数 `n` ,表示图中有 `n` 个节点,并按从 `1` 到 `n` 给节点编号;另给你一个数组 `edges`,其中每个 $edges[i] = [u_{i}, v_{i}, weight_{i}]$ 表示存在一条位于节点 $u_{i}$ 和 $v_{i}$ 之间的边,这条边的权重为 $weight_{i}$ 。 +给你一个正整数 `n` ,表示图中有 `n` 个节点,并按从 `1` 到 `n` 给节点编号。 + +另给你一个数组 `edges`,其中每个 $edges[i] = [u_{i}, v_{i}, weight_{i}]$ 表示存在一条位于节点 $u_{i}$ 和 $v_{i}$ 之间的边,这条边的权重为 $weight_{i}$ 。 从节点 start 出发到节点 `end` 的路径是一个形如 $[z_{0}, z_{1}, z_{2}, ..., z_{k}]$ 的节点序列,满足 $z_{0} = start$ 、$z_{k} = end$ 且在所有符合 $0 <= i <= k-1$ 的节点 $z_{i}$ 和 $z_{i}+1$ 之间存在一条边。 -路径的距离定义为这条路径上所有边的权重总和。用 `distanceToLastNode(x)` 表示节点 `n` 和 `x` 之间路径的最短距离。受限路径 为满足 $distanceToLastNode(z_{i}) > distanceToLastNode(z_{i}+1)$ 的一条路径,其中 $0 <= i <= k-1$ 。 +路径的距离定义为这条路径上所有边的权重总和。用 `distanceToLastNode(x)` 表示节点 `n` 和 `x` 之间路径的最短距离。 + +受限路径为满足 $distanceToLastNode(z_{i}) > distanceToLastNode(z_{i}+1)$ 的一条路径,其中 $0 <= i <= k-1$ 。 + +返回从节点 `1` 出发到节点 `n` 的 受限路径数 。 -返回从节点 `1` 出发到节点 `n` 的 受限路径数 。由于数字可能很大,请返回对 $10^9 + 7$ 取余 的结果。 +由于数字可能很大,请返回对 $10^9 + 7$ 取余的结果。 示例 1: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/03/07/restricted_paths_ex1.png) + ``` 输入:n = 5, edges = [[1,2,3],[1,3,3],[2,3,1],[1,4,2],[5,2,2],[3,5,1],[5,4,10]] + 输出:3 + 解释:每个圆包含黑色的节点编号和蓝色的 distanceToLastNode 值。三条受限路径分别是: 1) 1 --> 2 --> 5 2) 1 --> 2 --> 3 --> 5 3) 1 --> 3 --> 5 ``` 示例 2: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2021/03/07/restricted_paths_ex22.png) + ``` 输入:n = 7, edges = [[1,3,1],[4,1,2],[7,3,4],[2,5,3],[5,6,1],[6,7,2],[7,5,3],[2,6,4]] + 输出:1 + 解释:每个圆包含黑色的节点编号和蓝色的 distanceToLastNode 值。唯一一条受限路径是:1 --> 3 --> 7 。 ``` 提示: * $1 <= n <= 2 \times 10^4$ * $n - 1 <= edges.length <= 4 \times 10^4$ -* $edges[i].length == 3$ +* $edges[i].length = 3$ * $1 <= ui, vi <= n$ * $u_i != v_i$ * $1 <= weighti <= 10^5$ diff --git "a/LeetCode/1781-1790/1787. \344\275\277\346\211\200\346\234\211\345\214\272\351\227\264\347\232\204\345\274\202\346\210\226\347\273\223\346\236\234\344\270\272\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/1781-1790/1787. \344\275\277\346\211\200\346\234\211\345\214\272\351\227\264\347\232\204\345\274\202\346\210\226\347\273\223\346\236\234\344\270\272\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" index a65465c9..16015375 100644 --- "a/LeetCode/1781-1790/1787. \344\275\277\346\211\200\346\234\211\345\214\272\351\227\264\347\232\204\345\274\202\346\210\226\347\273\223\346\236\234\344\270\272\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/1781-1790/1787. \344\275\277\346\211\200\346\234\211\345\214\272\351\227\264\347\232\204\345\274\202\346\210\226\347\273\223\346\236\234\344\270\272\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -7,9 +7,9 @@ Tag : 「线性 DP」、「异或」、「数学」 给你一个整数数组 `nums` 和一个整数 `k` 。 -区间 `[left, right]``(left <= right)`的 异或结果 是对下标位于 `left` 和 `right`(包括 `left` 和 `right` )之间所有元素进行 XOR 运算的结果:`nums[left] XOR nums[left+1] XOR ... XOR nums[right]` 。 +区间 `[left, right]``(left <= right)`的异或结果是对下标位于 `left` 和 `right`(包括 `left` 和 `right` )之间所有元素进行 XOR 运算的结果:`nums[left] XOR nums[left+1] XOR ... XOR nums[right]` 。 -返回数组中**要更改的最小元素数** ,以使所有长度为 `k` 的区间异或结果等于零。 +返回数组中**要更改的最小元素数**,以使所有长度为 `k` 的区间异或结果等于零。   @@ -40,8 +40,8 @@ Tag : 「线性 DP」、「异或」、「数学」 提示: -* 1 <= k <= nums.length <= 2000 -* 0 <= nums[i] < $2^{10}$ +* $1 <= k <= nums.length <= 2000$ +* $0 <= nums[i] < 2^{10}$ --- @@ -63,14 +63,13 @@ Tag : 「线性 DP」、「异或」、「数学」 因此我们将这个一维的输入看成二维,从而将问题转化为:**使得最终每列相等,同时「首行」的异或值为 $0$ 的最小修改次数。** -![image.png](https://pic.leetcode-cn.com/1621904944-ApPozf-image.png) +![](https://pic.leetcode-cn.com/1621904944-ApPozf-image.png) 当然 $n$ 和 $k$ 未必成倍数关系,这时候最后一行可能为不足 $k$ 个。这也是为什么上面没有说「每行异或结果为 $0$」,而是说「首行异或结果为 $0$」的原因: -![image.png](https://pic.leetcode-cn.com/1621907349-YZYOCA-image.png) +![](https://pic.leetcode-cn.com/1621907349-YZYOCA-image.png) - -*** +--- ### 动态规划 @@ -91,27 +90,18 @@ $$ * 当前处于其他列:需要考虑与前面列的关系。 我们知道最终的 $f[i][xor]$ 由两部分组成:一部分是前 $i - 1$ 列的修改次数,一部分是当前列的修改次数。 - 这时候需要分情况讨论: - - * **仅修改当列的部分数**:这时候需要从哈希表中遍历已存在的数,在所有方案中取 $min$: - $$ - f[i][xor] = f[i - 1][xor ⊕ cur] + cnt - map.get(cur) - $$ - * **对整列进行修改替换**:此时当前列的修改成本固定为 $cnt$,只需要取前 $i - 1$ 列的最小修改次数过来即可: - $$ - f[i][xor] = f[i - 1][xor'] + cnt - $$ + * 仅修改当列的部分数:这时候需要从哈希表中遍历已存在的数,在所有方案中取 $min$:$f[i][xor] = f[i - 1][xor ⊕ cur] + cnt - map.get(cur)$ + * 对整列进行修改替换:此时当前列的修改成本固定为 $cnt$,只需要取前 $i - 1$ 列的最小修改次数过来即可:$f[i][xor] = f[i - 1][xor'] + cnt$ 最终 $f[i][xor]$ 在所有上述方案中取 $min$。为了加速「取前 $i - 1$ 列的最小修改次数」的过程,我们可以多开一个 $g[]$ 数组来记录前一列的最小状态值。 -代码: +Java 代码: ```Java class Solution { public int minChanges(int[] nums, int k) { - int n = nums.length; - int max = 1024; + int n = nums.length, max = 1024; int[][] f = new int[k][max]; int[] g = new int[k]; for (int i = 0; i < k; i++) { @@ -144,8 +134,97 @@ class Solution { } } ``` -* 时间复杂度:共有 $O(C * k)$ 个状态需要被转移(其中 $C$ 固定为 $2^{10}$),每个状态的转移需要遍历哈希表,最多有 $\frac{n}{k}$ 个数,复杂度为 $O(\frac{n}{k})$。整体复杂度为 $O(C * n)$ -* 空间复杂度:$O(C * k)$,其中 $C$ 固定为 $2^{10} + 1$ +C++ 代码: +```C++ +class Solution { +public: + int minChanges(vector& nums, int k) { + int n = nums.size(), maxv = 1024; + vector> f(k, vector(maxv, 0x3f3f3f3f)); + vector g(k, 0x3f3f3f3f); + for (int i = 0, cnt = 0; i < k; i++, cnt = 0) { + unordered_map map; + for (int j = i; j < n; j += k) { + map[nums[j]] += 1; + cnt++; + } + if (i == 0) { + for (int xorVal = 0; xorVal < maxv; xorVal++) { + f[0][xorVal] = min(f[0][xorVal], cnt - map[xorVal]); + g[0] = min(g[0], f[0][xorVal]); + } + } else { + for (int xorVal = 0; xorVal < maxv; xorVal++) { + f[i][xorVal] = g[i - 1] + cnt; + for (auto& entry : map) { + f[i][xorVal] = min(f[i][xorVal], f[i - 1][xorVal ^ entry.first] + cnt - entry.second); + } + g[i] = min(g[i], f[i][xorVal]); + } + } + } + return f[k - 1][0]; + } +}; +``` +Python 代码: +```Python +class Solution: + def minChanges(self, nums: List[int], k: int) -> int: + n, maxv = len(nums), 1024 + f = [[0x3f3f3f3f] * maxv for _ in range(k)] + g = [0x3f3f3f3f] * k + for i in range(k): + mapping = defaultdict(int) + cnt = 0 + for j in range(i, n, k): + mapping[nums[j]] += 1 + cnt += 1 + if i == 0: + for xorv in range(maxv): + f[0][xorv] = min(f[0][xorv], cnt - mapping[xorv]) + g[0] = min(g[0], f[0][xorv]) + else: + for xorv in range(maxv): + f[i][xorv] = g[i - 1] + cnt + for x, y in mapping.items(): + f[i][xorv] = min(f[i][xorv], f[i - 1][xorv ^ x] + cnt - y) + g[i] = min(g[i], f[i][xorv]) + return f[k - 1][0] +``` +TypeScript 代码: +```TypeScript +function minChanges(nums: number[], k: number): number { + const n = nums.length, max = 1024; + const f = new Array(k).fill(0).map(() => new Array(max).fill(0x3f3f3f3f)); + const g = new Array(k).fill(0).map(() => 0x3f3f3f3f); + for (let i = 0, cnt = 0; i < k; i++, cnt = 0) { + const map = new Map(); + for (let j = i; j < n; j += k) { + map.set(nums[j], (map.get(nums[j]) || 0) + 1); + cnt++; + } + if (i === 0) { + for (let xor = 0; xor < max; xor++) { + f[0][xor] = Math.min(f[0][xor], cnt - (map.get(xor) || 0)); + g[0] = Math.min(g[0], f[0][xor]); + } + } else { + for (let xor = 0; xor < max; xor++) { + f[i][xor] = g[i - 1] + cnt; + for (let cur of map.keys()) { + const curXor = Number(cur); + f[i][xor] = Math.min(f[i][xor], f[i - 1][xor ^ curXor] + cnt - (map.get(curXor) || 0)); + } + g[i] = Math.min(g[i], f[i][xor]); + } + } + } + return f[k - 1][0]; +}; +``` +* 时间复杂度:共有 $O(C \times k)$ 个状态需要被转移(其中 $C$ 固定为 $2^{10}$),每个状态的转移需要遍历哈希表,最多有 $\frac{n}{k}$ 个数,复杂度为 $O(\frac{n}{k})$。整体复杂度为 $O(C \times n)$ +* 空间复杂度:$O(C \times k)$,其中 $C$ 固定为 $2^{10} + 1$ --- diff --git "a/LeetCode/1791-1800/1798. \344\275\240\350\203\275\346\236\204\351\200\240\345\207\272\350\277\236\347\273\255\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/1791-1800/1798. \344\275\240\350\203\275\346\236\204\351\200\240\345\207\272\350\277\236\347\273\255\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" index a64cf407..72f12e6d 100644 --- "a/LeetCode/1791-1800/1798. \344\275\240\350\203\275\346\236\204\351\200\240\345\207\272\350\277\236\347\273\255\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/1791-1800/1798. \344\275\240\350\203\275\346\236\204\351\200\240\345\207\272\350\277\236\347\273\255\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\347\233\256\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,7 +6,9 @@ Tag : 「数学」、「脑筋急转弯」、「排序」、「构造」 -给你一个长度为 `n` 的整数数组 `coins`,它代表你拥有的 `n` 个硬币。第 `i` 个硬币的值为 `coins[i]`。如果你从这些硬币中选出一部分硬币,它们的和为 `x` ,那么称,你可以构造出 `x` 。 +给你一个长度为 `n` 的整数数组 `coins`,它代表你拥有的 `n` 个硬币。 + +第 `i` 个硬币的值为 `coins[i]`。如果你从这些硬币中选出一部分硬币,它们的和为 `x` ,那么称,你可以构造出 `x` 。 请返回从 `0` 开始(包括 `0` ),你最多能构造出多少个连续整数。 diff --git "a/LeetCode/181-190/190. \351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/181-190/190. \351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215\357\274\210\347\256\200\345\215\225\357\274\211.md" index 815d32b5..7252f459 100644 --- "a/LeetCode/181-190/190. \351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/181-190/190. \351\242\240\345\200\222\344\272\214\350\277\233\345\210\266\344\275\215\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -10,8 +10,8 @@ Tag : 「位运算」、「模拟」 提示: -* 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 -* 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。 +* 请注意,在某些语言(如 `Java`)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 +* 在 `Java` 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。 进阶: @@ -29,7 +29,9 @@ Tag : 「位运算」、「模拟」 示例 2: ``` 输入:11111111111111111111111111111101 + 输出:10111111111111111111111111111111 + 解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,   因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。 ``` @@ -42,46 +44,61 @@ Tag : 「位运算」、「模拟」 ### 「对称位」构造 -![image.png](https://pic.leetcode-cn.com/1616992045-iIZbTD-image.png) - 一个简单的做法是对输入的 $n$ 做诸位检查。 **如果某一位是 1 的话,则将答案相应的对称位置修改为 1。** -代码: +Java 代码: ```Java public class Solution { public int reverseBits(int n) { int ans = 0; for (int i = 0; i < 32; i++) { - int t = (n >> i) & 1; - if (t == 1) { - ans |= (1 << (31 - i)); - } + if (((n >> i) & 1) == 1) ans |= (1 << (31 - i)); } return ans; } } ``` -* 时间复杂度:$int$ 固定 32 位,循环次数不随输入样本发生改变。复杂度为 $O(1)$ +C++ 代码: +```C++ +class Solution { +public: + int reverseBits(uint32_t n) { + int ans = 0; + for (int i = 0; i < 32; i++) { + if ((n >> i) & 1) ans |= (1 << (31 - i)); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def reverseBits(self, n: int) -> int: + ans = 0 + for i in range(32): + if (n >> i) & 1: + ans |= (1 << (31 - i)) + return ans +``` +* 时间复杂度:`int` 固定 $C = 32$ 位,循环次数不随输入样本发生改变。复杂度为 $O(C)$ * 空间复杂度:$O(1)$ -*** +--- ### 「逐位分离」构造 -![image.png](https://pic.leetcode-cn.com/1616992076-abbLNX-image.png) - 另外一种做法是,每次都使用 $n$ 的最低一位,使用 $n$ 的最低一位去更新答案的最低一位,使用完将 $n$ 进行右移一位,将答案左移一位。 **相当于每次都用 $n$ 的最低一位更新成 $ans$ 的最低一位。** -代码: +Java 代码: ```Java public class Solution { public int reverseBits(int n) { - int ans = 0; - int cnt = 32; + int ans = 0, cnt = 32; while (cnt-- > 0) { ans <<= 1; ans += (n & 1); @@ -91,16 +108,40 @@ public class Solution { } } ``` -* 时间复杂度:$int$ 固定 32 位,循环次数不随输入样本发生改变。复杂度为 $O(1)$ +C++ 代码: +```C++ +class Solution { +public: + int reverseBits(uint32_t n) { + int ans = 0, cnt = 32; + while (cnt-- > 0) { + ans <<= 1; + ans += (n & 1); + n >>= 1; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def reverseBits(self, n: int) -> int: + ans = 0 + for _ in range(32): + ans <<= 1 + ans += (n & 1) + n >>= 1 + return ans +``` +* 时间复杂度:`int` 固定 32 位,循环次数不随输入样本发生改变。复杂度为 $O(1)$ * 空间复杂度:$O(1)$ -*** - -### 「分组互换」 +--- -![image.png](https://pic.leetcode-cn.com/1616992100-QmigBE-image.png) +### 分组互换 -事实上,可以对于长度固定的 $int$ 类型,我们可以使用「分组构造」的方式进行。 +事实上,可以对于长度固定的 `int` 类型,我们可以使用「分组构造」的方式进行。 **两位互换 -> 四位互换 -> 八位互换 -> 十六位互换。** @@ -117,12 +158,36 @@ public class Solution { } } ``` -* 时间复杂度:如何进行互换操作取决于 $int$ 长度。复杂度为 $O(1)$ +C++ 代码: +```C++ +class Solution { +public: + int reverseBits(uint32_t n) { + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); + n = ((n & 0xFFFF0000) >> 16) | ((n & 0x0000FFFF) << 16); + return n; + } +}; +``` +Python 代码: +```Python +class Solution: + def reverseBits(self, n: int) -> int: + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1) + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2) + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4) + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8) + n = ((n & 0xFFFF0000) >> 16) | ((n & 0x0000FFFF) << 16) + return n +``` +* 时间复杂度:如何进行互换操作取决于 `int` 长度。复杂度为 $O(1)$ * 空间复杂度:$O(1)$ **PS. 类似的做法我在 [191. 位1的个数](https://leetcode-cn.com/problems/number-of-1-bits/solution/yi-ti-san-jie-wei-shu-jian-cha-you-yi-to-av1r/) 也介绍过。如果大家学有余力的话,建议大家在纸上模拟一下这个过程。如果不想深入,也可以当成模板背过(写法非常固定)。** -**但请不要认为「方法三」一定就比「方法一」等直接采用循环的方式更快。此类做法的最大作用,不是处理 int,而是处理更大位数的情况,在长度只有 32 位的 int 的情况下,该做法不一定就比循环要快(该做法会产生多个的中间结果,导致赋值发生多次,而且由于指令之间存在对 n 数值依赖,可能不会被优化为并行指令),这个道理和对于排序元素少的情况下,我们会选择「冒泡排序」而不是「归并排序」是一样的,因为「冒泡排序」常数更小。** - +**但请不要认为「方法三」一定就比「方法一」等直接采用循环的方式更快。此类做法的最大作用,不是处理 `int`,而是处理更大位数的情况,在长度只有 32 位的 `int` 的情况下,该做法不一定就比循环要快(该做法会产生多个的中间结果,导致赋值发生多次,而且由于指令之间存在对 n 数值依赖,可能不会被优化为并行指令),这个道理和对于排序元素少的情况下,我们会选择「冒泡排序」而不是「归并排序」是一样的,因为「冒泡排序」常数更小。** --- @@ -135,8 +200,3 @@ public class Solution { 为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 - - -``` - -``` \ No newline at end of file diff --git "a/LeetCode/201-210/203. \347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/201-210/203. \347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" index 3404a2bc..52675f22 100644 --- "a/LeetCode/201-210/203. \347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/201-210/203. \347\247\273\351\231\244\351\223\276\350\241\250\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,30 +7,35 @@ Tag : 「链表」 -给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。 +给你一个链表的头节点 `head` 和一个整数 `val`,请你删除链表中所有满足 `Node.val = val` 的节点,并返回 新的头节点 。 示例 1: + ![](https://assets.leetcode.com/uploads/2021/03/06/removelinked-list.jpg) + ``` 输入:head = [1,2,6,3,4,5,6], val = 6 + 输出:[1,2,3,4,5] ``` 示例 2: ``` 输入:head = [], val = 1 + 输出:[] ``` 示例 3: ``` 输入:head = [7,7,7,7], val = 7 + 输出:[] ``` 提示: -* 列表中的节点在范围 [0, $10^4$] 内 -* 1 <= Node.val <= 50 -* 0 <= k <= 50 +* 列表中的节点在范围 $[0, 10^4]$ 内 +* $1 <= Node.val <= 50$ +* $0 <= k <= 50$ --- @@ -40,7 +45,7 @@ Tag : 「链表」 由于是单链表,无法通过某个节点直接找到「前一个节点」,因此为了方便,我们可以为递归函数多设置一个入参,代表「前一个节点」。 -代码: +Java 代码: ```Java class Solution { public ListNode removeElements(ListNode head, int val) { @@ -51,15 +56,47 @@ class Solution { } void dfs(ListNode prev, ListNode root, int val) { if (root == null) return ; - if (root.val == val) { - prev.next = root.next; - } else { - prev = root; - } + if (root.val == val) prev.next = root.next; + else prev = root; dfs(prev, prev.next, val); } } ``` +C++ 代码: +```C++ +class Solution { +public: + ListNode* removeElements(ListNode* head, int val) { + ListNode* dummy = new ListNode(-1); + dummy->next = head; + dfs(dummy, head, val); + return dummy->next; + } + void dfs(ListNode* prev, ListNode* root, int val) { + if (!root) return; + if (root->val == val) prev->next = root->next; + else prev = root; + dfs(prev, root->next, val); + } +}; +``` +Python 代码: +```Python +class Solution: + def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]: + dummy = ListNode(-1) + dummy.next = head + self.dfs(dummy, dummy.next, val) + return dummy.next + def dfs(self, prev, root, val): + if not root: + return + if root.val == val: + prev.next = root.next + else: + prev = root + self.dfs(prev, prev.next, val) +``` * 时间复杂度:$O(n)$ * 空间复杂度:忽略递归带来的额外空间开销。复杂度为 $O(1)$ @@ -69,23 +106,21 @@ class Solution { 同理,我们可以使用「迭代」方式来实现,而迭代有 `while` 和 `for` 两种写法。 -代码: +Java 代码(`for`): ```Java class Solution { public ListNode removeElements(ListNode head, int val) { ListNode dummy = new ListNode(-1); dummy.next = head; for (ListNode tmp = dummy.next, prev = dummy; tmp != null; tmp = tmp.next) { - if (tmp.val == val) { - prev.next = tmp.next; - } else { - prev = tmp; - } + if (tmp.val == val) prev.next = tmp.next; + else prev = tmp; } return dummy.next; } } ``` +Java 代码(`while`): ```Java class Solution { public ListNode removeElements(ListNode head, int val) { @@ -93,17 +128,47 @@ class Solution { dummy.next = head; ListNode tmp = dummy.next, prev = dummy; while (tmp != null) { - if (tmp.val == val) { - prev.next = tmp.next; - } else { - prev = tmp; - } + if (tmp.val == val) prev.next = tmp.next; + else prev = tmp; tmp = tmp.next; } return dummy.next; } } ``` +C++ 代码: +```C++ +class Solution { +public: + ListNode* removeElements(ListNode* head, int val) { + ListNode* dummy = new ListNode(-1); + dummy->next = head; + ListNode* tmp = dummy->next; + ListNode* prev = dummy; + while (tmp != nullptr) { + if (tmp->val == val) prev->next = tmp->next; + else prev = tmp; + tmp = tmp->next; + } + return dummy->next; + } +}; +``` +Python 代码: +```Python +class Solution: + def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]: + dummy = ListNode(-1) + dummy.next = head + tmp, prev = dummy.next, dummy + while tmp: + if tmp.val == val: + prev.next = tmp.next + else: + prev = tmp + tmp = tmp.next + return dummy.next +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/201-210/208. \345\256\236\347\216\260 Trie (\345\211\215\347\274\200\346\240\221)\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/201-210/208. \345\256\236\347\216\260 Trie (\345\211\215\347\274\200\346\240\221)\357\274\210\344\270\255\347\255\211\357\274\211.md" index eaa0f484..f50918c3 100644 --- "a/LeetCode/201-210/208. \345\256\236\347\216\260 Trie (\345\211\215\347\274\200\346\240\221)\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/201-210/208. \345\256\236\347\216\260 Trie (\345\211\215\347\274\200\346\240\221)\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,14 +6,16 @@ Tag :「字典树」 -Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。 +`Trie`(发音类似 `"try"`)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。 -请你实现 Trie 类: +这一数据结构有相当多的应用情景,例如自动补完和拼写检查。 -* Trie() 初始化前缀树对象。 -* void insert(String word) 向前缀树中插入字符串 word 。 -* boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。 -* boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。 +请你实现 `Trie` 类: + +* `Trie()` 初始化前缀树对象。 +* `void insert(String word)` 向前缀树中插入字符串 `word`。 +* `boolean search(String word)` 如果字符串 `word` 在前缀树中,返回 `true`(即,在检索之前已经插入);否则,返回 `false`。 +* `boolean startsWith(String prefix)` 如果之前已经插入的字符串 `word` 的前缀之一为 `prefix`,返回 `true`;否则,返回 `false`。 示例: @@ -36,19 +38,19 @@ trie.search("app"); // 返回 True ``` 提示: -* 1 <= word.length, prefix.length <= 2000 -* word 和 prefix 仅由小写英文字母组成 -* insert、search 和 startsWith 调用次数 总计 不超过 3 * $10^4$ 次 +* $1 <= word.length, prefix.length <= 2000$ +* `word` 和 `prefix` 仅由小写英文字母组成 +* `insert`、`search` 和 `startsWith` 调用次数总计不超过 $3 \times 10^4$ 次 --- ### Trie 树 -$Trie$ 树(又叫「前缀树」或「字典树」)是一种用于快速查询「某个字符串/字符前缀」是否存在的数据结构。 +`Trie` 树(又叫「前缀树」或「字典树」)是一种用于快速查询「某个字符串/字符前缀」是否存在的数据结构。 其核心是使用「边」来代表有无字符,使用「点」来记录是否为「单词结尾」以及「其后续字符串的字符是什么」。 -![IMG_1659.PNG](https://pic.leetcode-cn.com/1618369228-slAfrQ-IMG_1659.PNG) +![](https://pic.leetcode-cn.com/1618369228-slAfrQ-IMG_1659.PNG) --- @@ -61,7 +63,7 @@ $Trie$ 树(又叫「前缀树」或「字典树」)是一种用于快速查 * 使用 $count[]$ 数组记录某个格子被「被标记为结尾的次数」(当 $idx$ 编号的格子被标记了 $n$ 次,则有 $cnt[idx] = n$)。 代码 : -```java [] +```Java class Trie { int N = 100009; // 直接设置为十万级 int[][] trie; @@ -105,19 +107,19 @@ class Trie { } } ``` -* 时间复杂度:$Trie$ 树的每次调用时间复杂度取决于入参字符串的长度。复杂度为 $O(Len)$。 +* 时间复杂度:`Trie` 树的每次调用时间复杂度取决于入参字符串的长度。复杂度为 $O(Len)$。 * 空间复杂度:二维数组的高度为 $n$,字符集大小为 $k$。复杂度为 $O(nk)$。 --- ### TrieNode -相比二维数组,更加常规的做法是建立 $TrieNode$ 结构节点。 +相比二维数组,更加常规的做法是建立 `TrieNode` 结构节点。 -随着数据的不断插入,根据需要不断创建 $TrieNode$ 节点。 +随着数据的不断插入,根据需要不断创建 `TrieNode` 节点。 代码: -```java [] +```Java class Trie { class TrieNode { boolean end; @@ -169,13 +171,13 @@ class Trie { 使用「二维数组」的好处是写起来飞快,同时没有频繁 $new$ 对象的开销。但是需要根据数据结构范围估算我们的「二维数组」应该开多少行。 -坏处是使用的空间通常是「$TrieNode$」方式的数倍,而且由于通常对行的估算会很大,导致使用的二维数组开得很大,如果这时候每次创建 $Trie$ 对象时都去创建数组的话,会比较慢,而且当样例多的时候甚至会触发 $GC$(因为 $OJ$ 每测试一个样例会创建一个 $Trie$ 对象)。 +坏处是使用的空间通常是 `TrieNode` 方式的数倍,而且由于通常对行的估算会很大,导致使用的二维数组开得很大,如果这时候每次创建 `Trie` 对象时都去创建数组的话,会比较慢,而且当样例多的时候甚至会触发 $GC$(因为 $OJ$ 每测试一个样例会创建一个 `Trie` 对象)。 因此还有一个小技巧是将使用到的数组转为静态,然后利用 $index$ 自增的特性在初始化 $Trie$ 时执行清理工作 & 重置逻辑。 -这样的做法能够使评测时间降低一半,运气好的话可以得到一个与「$TrieNode$」方式差不多的时间。 +这样的做法能够使评测时间降低一半,运气好的话可以得到一个与 `TrieNode` 方式差不多的时间。 -```java [] +```Java class Trie { // 以下 static 成员独一份,被创建的多个 Trie 共用 static int N = 100009; // 直接设置为十万级 @@ -230,11 +232,11 @@ class Trie { ### 关于「二维数组」是如何工作 & 1e5 大小的估算 -要搞懂为什么行数估算是 1e5,首先要搞清楚「二维数组」是如何工作的。 +要搞懂为什么行数估算是 $1e5$,首先要搞清楚「二维数组」是如何工作的。 -在「二维数组」中,我们是通过 $index$ 自增来控制使用了多少行的。 +在「二维数组」中,我们是通过 `index` 自增来控制使用了多少行的。 -当我们有一个新的字符需要记录,我们会将 $index$ 自增(代表用到了新的一行),然后将这新行的下标记录到当前某个前缀的格子中。 +当我们有一个新的字符需要记录,我们会将 `index` 自增(代表用到了新的一行),然后将这新行的下标记录到当前某个前缀的格子中。 举个🌰,假设我们先插入字符串 `abc` 这时候,前面三行会被占掉。 @@ -248,11 +250,11 @@ class Trie { 但当插入 `abl` 的时候,则会定位到 `ab` 的前缀行(第 2 行),然后将 `l` 的下标更新为 5,代表 `abl` 被加入前缀树,并且前缀 `abl` 接下来会使用第 5 行进行记录。 -当搞清楚了「二维数组」是如何工作之后,我们就能开始估算会用到多少行了,调用次数为 $10^4$,传入的字符串长度为 $10^3$,假设每一次的调用都是 $insert$,并且每一次调用都会使用到新的 $10^3$ 行。那么我们的行数需要开到 $10^7$。 +当搞清楚了「二维数组」是如何工作之后,我们就能开始估算会用到多少行了,调用次数为 $10^4$,传入的字符串长度为 $10^3$,假设每一次的调用都是 `insert`,并且每一次调用都会使用到新的 $10^3$ 行。那么我们的行数需要开到 $10^7$。 **但由于我们的字符集大小只有 26,因此不太可能在 $10^4$ 次调用中都用到新的 $10^3$ 行。** -**而且正常的测试数据应该是 $search$ 和 $startsWith$ 调用次数大于 $insert$ 才有意义的,一个只有 $insert$ 调用的测试数据,任何实现方案都能 AC。** +**而且正常的测试数据应该是 `search` 和 `startsWith` 调用次数大于 `insert` 才有意义的,一个只有 `insert` 调用的测试数据,任何实现方案都能 AC。** **因此我设定了 $10^5$ 为行数估算,当然直接开到 $10^6$ 也没有问题。** @@ -262,23 +264,23 @@ class Trie { 首先,在纯算法领域,前缀树算是一种较为常用的数据结构。 -不过如果在工程中,不考虑前缀匹配的话,基本上使用 hash 就能满足。 +不过如果在工程中,不考虑前缀匹配的话,基本上使用 `hash` 就能满足。 -如果考虑前缀匹配的话,工程也不会使用 Trie 。 +如果考虑前缀匹配的话,工程也不会使用 `Trie`。 -一方面是字符集大小不好确定(题目只考虑 26 个字母,字符集大小限制在较小的 26 内)因此可以使用 Trie,但是工程一般兼容各种字符集,一旦字符集大小很大的话,Trie 将会带来很大的空间浪费。 +一方面是字符集大小不好确定(题目只考虑 26 个字母,字符集大小限制在较小的 26 内)因此可以使用 `Trie`,但是工程一般兼容各种字符集,一旦字符集大小很大的话,`Trie` 将会带来很大的空间浪费。 -另外,对于个别的超长字符 Trie 会进一步变深。 +另外,对于个别的超长字符 `Trie` 会进一步变深。 -这时候如果 Trie 是存储在硬盘中,Trie 结构过深带来的影响是多次随机 IO,随机 IO 是成本很高的操作。 +这时候如果 `Trie` 是存储在硬盘中,`Trie` 结构过深带来的影响是多次随机 IO,随机 IO 是成本很高的操作。 -同时 Trie 的特殊结构,也会为分布式存储将会带来困难。 +同时 `Trie` 的特殊结构,也会为分布式存储将会带来困难。 -因此在工程领域中 Trie 的应用面不广。 +因此在工程领域中 `Trie` 的应用面不广。 -至于一些诸如「联想输入」、「模糊匹配」、「全文检索」的典型场景在工程主要是通过 ES (ElasticSearch) 解决的。 +至于一些诸如「联想输入」、「模糊匹配」、「全文检索」的典型场景在工程主要是通过 `ES` (`ElasticSearch`) 解决的。 -而 ES 的实现则主要是依靠「倒排索引」 +而 `ES` 的实现则主要是依靠「倒排索引」 --- diff --git "a/LeetCode/21-30/23. \345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/21-30/23. \345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" index ff27613a..32ed2d07 100644 --- "a/LeetCode/21-30/23. \345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/21-30/23. \345\220\210\345\271\266K\344\270\252\345\215\207\345\272\217\351\223\276\350\241\250\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -59,7 +59,7 @@ Tag : 「优先队列(堆)」、「链表」、「多路归并」 优先队列(堆)则是满足这样要求的数据结构,而整个过程其实就是「多路归并」过程。 -代码: +Java 代码: ```Java class Solution { public ListNode mergeKLists(ListNode[] lists) { @@ -78,6 +78,28 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + ListNode* mergeKLists(vector& lists) { + priority_queue, vector>, greater>> q; + ListNode* dummy = new ListNode(-1); + ListNode* tail = dummy; + for (auto node : lists) { + if (node) q.push({node->val, node}); + } + while (!q.empty()) { + auto [val, node] = q.top(); + q.pop(); + tail->next = node; + tail = tail->next; + if (node->next) q.push({node->next->val, node->next}); + } + return dummy->next; + } +}; +``` * 时间复杂度:假设共有 $k$ 条建表,共 $n$ 个节点,复杂度为 $O(n\log{k})$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/21-30/26. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/21-30/26. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271\357\274\210\347\256\200\345\215\225\357\274\211.md" index c16b74b6..042c68d3 100644 --- "a/LeetCode/21-30/26. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/21-30/26. \345\210\240\351\231\244\346\234\211\345\272\217\346\225\260\347\273\204\344\270\255\347\232\204\351\207\215\345\244\215\351\241\271\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,9 +6,9 @@ Tag : 「数组」、「双指针」、「数组移除元素问题」 -给你一个有序数组 `nums` ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。 +给你一个有序数组 `nums` ,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。 -不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 $O(1)$ 额外空间的条件下完成。 +不要使用额外的数组空间,你必须在原地修改输入数组并在使用 $O(1)$ 额外空间的条件下完成。 说明: @@ -58,21 +58,52 @@ for (int i = 0; i < len; i++) { 只有当 `i` 所指向的值和 `j` 不一致(不重复),才将 `i` 的值添加到 `j` 的下一位置。 -代码: +Java 代码: ```Java class Solution { public int removeDuplicates(int[] nums) { - int n = nums.length; - int j = 0; + int n = nums.length, j = 0; for (int i = 0; i < n; i++) { - if (nums[i] != nums[j]) { - nums[++j] = nums[i]; - } + if (nums[i] != nums[j]) nums[++j] = nums[i]; } return j + 1; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int removeDuplicates(vector& nums) { + int n = nums.size(), j = 0; + for (int i = 0; i < n; i++) { + if (nums[i] != nums[j]) nums[++j] = nums[i]; + } + return j + 1; + } +}; +``` +Python 代码: +```Python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + n, j = len(nums), 0 + for i in range(n): + if nums[i] != nums[j]: + j += 1 + nums[j] = nums[i] + return j + 1 +``` +TypeScript 代码: +```TypeScript +function removeDuplicates(nums: number[]): number { + let n: number = nums.length, j: number = 0; + for (let i: number = 0; i < n; i++) { + if (nums[i] !== nums[j]) nums[++j] = nums[i]; + } + return j + 1; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ @@ -99,7 +130,7 @@ class Solution { 4. 当整个数组被扫描完,最终我们得到了目标数组 `[3,4,5]` 和 答案 `idx` 为 `3`。 -代码: +Java 代码: ```Java class Solution { public int removeDuplicates(int[] nums) { @@ -114,6 +145,50 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int removeDuplicates(vector& nums) { + return process(nums, 1); + } + int process(vector& nums, int k) { + int idx = 0; + for (int x : nums) { + if (idx < k || nums[idx - k] != x) nums[idx++] = x; + } + return idx; + } +}; +``` +Python 代码: +```Python +class Solution: + def removeDuplicates(self, nums): + return self.process(nums, 1) + + def process(self, nums, k): + idx = 0 + for x in nums: + if idx < k or nums[idx - k] != x: + nums[idx] = x + idx += 1 + return idx +``` +TypeScript 代码: +```TypeScript +function process(nums: number[], k: number): number { + let idx: number = 0; + nums.forEach((x) => { + if (idx < k || nums[idx - k] !== x) nums[idx++] = x; + }); + return idx; +} + +function removeDuplicates(nums: number[]): number { + return process(nums, 1) +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ @@ -121,7 +196,7 @@ class Solution { **但需要注意这种「剪枝」同时会让我们单次循环的常数变大,所以仅作为简单拓展。** -代码: +Java 代码: ```Java class Solution { public int removeDuplicates(int[] nums) { @@ -139,6 +214,59 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int removeDuplicates(vector& nums) { + int n = nums.size(); + if (n <= 1) return n; + return process(nums, 1, nums[n - 1]); + } + int process(vector& nums, int k, int max) { + int idx = 0; + for (int x : nums) { + if (idx < k || nums[idx - k] != x) nums[idx++] = x; + if (idx - k >= 0 && nums[idx - k] == max) break; + } + return idx; + } +}; +``` +Python 代码: +```Python +class Solution: + def removeDuplicates(self, nums): + n = len(nums) + if n <= 1: return n + return self.process(nums, 1, nums[-1]) + + def process(self, nums, k, max_val): + idx = 0 + for x in nums: + if idx < k or nums[idx - k] != x: + nums[idx] = x + idx += 1 + if idx - k >= 0 and nums[idx - k] == max_val: + break + return idx +``` +TypeScript 代码: +```TypeScript +function process(nums: number[], k: number, max: number): number { + let idx: number = 0; + for (const x of nums) { + if (idx < k || nums[idx - k] !== x) nums[idx++] = x; + if (idx - k >= 0 && nums[idx - k] === max) break; + } + return idx; +} +function removeDuplicates(nums: number[]): number { + const n: number = nums.length; + if (n <= 1) return n; + return process(nums, 1, nums[n - 1]); +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/21-30/30. \344\270\262\350\201\224\346\211\200\346\234\211\345\215\225\350\257\215\347\232\204\345\255\220\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/21-30/30. \344\270\262\350\201\224\346\211\200\346\234\211\345\215\225\350\257\215\347\232\204\345\255\220\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" index 3c636f9c..874918be 100644 --- "a/LeetCode/21-30/30. \344\270\262\350\201\224\346\211\200\346\234\211\345\215\225\350\257\215\347\232\204\345\255\220\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/21-30/30. \344\270\262\350\201\224\346\211\200\346\234\211\345\215\225\350\257\215\347\232\204\345\255\220\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -43,7 +43,7 @@ words = ["word","good","best","word"] --- -### 朴素哈希表 +### 朴素哈希表(TLE) 令 `n` 为字符串 `s` 的长度,`m` 为数组 `words` 的长度(单词的个数),`w` 为单个单词的长度。 @@ -60,7 +60,7 @@ words = ["word","good","best","word"] 剪枝处使用了带标签的 `continue` 语句直接回到外层循环进行。 -代码: +Java 代码: ```Java class Solution { public List findSubstring(String s, String[] words) { @@ -93,7 +93,7 @@ class Solution { 我们可以将起点根据 **当前下标与单词长度的取余结果** 进行分类,这样我们就不用频繁的建立新的哈希表和进行单词统计。 -代码: +Java 代码: ```Java class Solution { public List findSubstring(String s, String[] words) { @@ -125,6 +125,58 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector findSubstring(string s, vector& words) { + int n = s.size(), m = words.size(), w = words[0].size(); + unordered_map map; + for (string word : words) map[word]++; + vector ans; + for (int i = 0; i < w; i++) { + unordered_map curMap; + for (int j = i; j + w <= n; j += w) { + string cur = s.substr(j, w); + curMap[cur]++; + if (j >= i + (m * w)) { + int idx = j - m * w; + string prev = s.substr(idx, w); + if (curMap[prev] == 1) curMap.erase(prev); + else curMap[prev]--; + } + if (curMap == map) ans.push_back(j - (m - 1) * w); + } + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def findSubstring(self, s: str, words: List[str]) -> List[int]: + n, m, w = len(s), len(words), len(words[0]) + mapping = defaultdict(int) + for word in words: + mapping[word] += 1 + ans = [] + for i in range(w): + curMap = defaultdict(int) + for j in range(i, n - w + 1, w): + cur = s[j:j + w] + curMap[cur] += 1 + if j >= i + (m * w): + idx = j - m * w + prev = s[idx:idx + w] + if curMap[prev] == 1: + del curMap[prev] + else: + curMap[prev] -= 1 + if curMap == mapping: + ans.append(j - (m - 1) * w) + return ans +``` * 时间复杂度:将 `words` 中的单词存入哈希表,复杂度为 $O(m)$(由于字符串长度固定且不超过 $30$,假定所有哈希操作均为 $O(1)$ 的);然后枚举了取余的结果,复杂度为 $O(w)$;每次循环最多处理 `n` 长度的字符串,复杂度为 $O(n)$。整体复杂度为 $O(m + w \times n)$ * 空间复杂度:$O(m \times w)$ diff --git "a/LeetCode/211-220/213. \346\211\223\345\256\266\345\212\253\350\210\215 II\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/211-220/213. \346\211\223\345\256\266\345\212\253\350\210\215 II\357\274\210\344\270\255\347\255\211\357\274\211.md" index e8f300d6..28e9b6de 100644 --- "a/LeetCode/211-220/213. \346\211\223\345\256\266\345\212\253\350\210\215 II\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/211-220/213. \346\211\223\345\256\266\345\212\253\350\210\215 II\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -15,8 +15,6 @@ Tag : 「线性 DP」 给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。 - - 示例 1: ``` 输入:nums = [2,3,2] @@ -42,16 +40,16 @@ Tag : 「线性 DP」 ``` 提示: -* 1 <= nums.length <= 100 -* 0 <= nums[i] <= 1000 +* $1 <= nums.length <= 100$ +* $0 <= nums[i] <= 1000$ --- ### 动态规划 -在 [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/) 中,并没有「第一间」和「最后一间」不能同时选择的限制,因此我们从头到尾做一遍 DP 即可。 +在 [198. 打家劫舍](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247498534&idx=1&sn=e05558d6096806f6acd256ad15338217&chksm=fd9f5039cae8d92f57d55d74e729aa431a1a3e00536e6f3da456724289bebb8ba5d3a252f8d5#rd) 中,并没有「第一间」和「最后一间」不能同时选择的限制,因此我们从头到尾做一遍 DP 即可。 -在 [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/) 中,我们可以将状态定义为两维: +在 [198. 打家劫舍](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247498534&idx=1&sn=e05558d6096806f6acd256ad15338217&chksm=fd9f5039cae8d92f57d55d74e729aa431a1a3e00536e6f3da456724289bebb8ba5d3a252f8d5#rd) 中,我们可以将状态定义为两维: **$f[i][j]$ 代表考虑前 $i$ 个房间,当前 $i$ 房间的现在状态为 $j$ 的最大价值。** @@ -59,7 +57,7 @@ Tag : 「线性 DP」 * $f[i][1]$ 代表考虑前 $i$ 个房间,并且「选」第 $i$ 个房间的最大价值。由于已经明确了第 $i$ 个房间被选,因此 $f[i][1]$ 直接由 $f[i - 1][0] + nums[i]$ 转移过来。 -到这里,你已经解决了 [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/) 了。 +到这里,你已经解决了 [198. 打家劫舍](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247498534&idx=1&sn=e05558d6096806f6acd256ad15338217&chksm=fd9f5039cae8d92f57d55d74e729aa431a1a3e00536e6f3da456724289bebb8ba5d3a252f8d5#rd) 了。 对于本题,由于只是增加了「第一间」和「最后一间」不能同时选择的限制。 @@ -79,8 +77,8 @@ Tag : 「线性 DP」 走完两遍 DP 后,再从两种情况的最大价值中再取一个 $max$ 即是答案。 -代码: -```java [] +Java 代码: +```Java class Solution { public int rob(int[] nums) { int n = nums.length; @@ -107,6 +105,79 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int rob(vector& nums) { + int n = nums.size(); + if (n == 0) return 0; + if (n == 1) return nums[0]; + + vector> f(n, vector(2, 0)); + for (int i = 1; i < n - 1; i++) { + f[i][0] = max(f[i - 1][0], f[i - 1][1]); + f[i][1] = f[i - 1][0] + nums[i]; + } + int a = max(f[n - 2][1], f[n - 2][0] + nums[n - 1]); + + f[0][0] = 0; f[0][1] = nums[0]; + for (int i = 1; i < n - 1; i++) { + f[i][0] = max(f[i - 1][0], f[i - 1][1]); + f[i][1] = f[i - 1][0] + nums[i]; + } + int b = max(f[n - 2][0], f[n - 2][1]); + + return max(a, b); + } +}; +``` +Python 代码: +```Python +class Solution: + def rob(self, nums: List[int]) -> int: + n = len(nums) + if n == 0: return 0 + if n == 1: return nums[0] + + f = [[0, 0] for _ in range(n)] + for i in range(1, n - 1): + f[i][0] = max(f[i - 1][0], f[i - 1][1]) + f[i][1] = f[i - 1][0] + nums[i] + a = max(f[n - 2][1], f[n - 2][0] + nums[n - 1]) + + f[0][0] = 0; f[0][1] = nums[0] + for i in range(1, n - 1): + f[i][0] = max(f[i - 1][0], f[i - 1][1]) + f[i][1] = f[i - 1][0] + nums[i] + b = max(f[n - 2][0], f[n - 2][1]) + + return max(a, b) +``` +TypeScript 代码: +```TypeScript +function rob(nums: number[]): number { + const n: number = nums.length; + if (n === 0) return 0; + if (n === 1) return nums[0]; + + const f: number[][] = new Array(n).fill(0).map(() => [0, 0]); + for (let i: number = 1; i < n - 1; i++) { + f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]); + f[i][1] = f[i - 1][0] + nums[i]; + } + let a: number = Math.max(f[n - 2][1], f[n - 2][0] + nums[n - 1]); + + f[0][0] = 0; f[0][1] = nums[0]; + for (let i: number = 1; i < n - 1; i++) { + f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]); + f[i][1] = f[i - 1][0] + nums[i]; + } + let b: number = Math.max(f[n - 2][0], f[n - 2][1]); + + return Math.max(a, b); +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ @@ -116,8 +187,8 @@ class Solution { 不难发现,我们状态转移最多依赖到前面的 1 行,因此可以通过很机械的「滚动数组」方式将空间修改到 $O(1)$。 -代码: -```java [] +Java 代码: +```Java class Solution { public int rob(int[] nums) { int n = nums.length; @@ -144,6 +215,79 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int rob(vector& nums) { + int n = nums.size(); + if (n == 0) return 0; + if (n == 1) return nums[0]; + + vector> f(2, vector(2, 0)); + for (int i = 1; i < n - 1; i++) { + f[i % 2][0] = max(f[(i - 1) % 2][0], f[(i - 1) % 2][1]); + f[i % 2][1] = f[(i - 1) % 2][0] + nums[i]; + } + int a = max(f[(n - 2) % 2][1], f[(n - 2) % 2][0] + nums[n - 1]); + + f[0][0] = 0; f[0][1] = nums[0]; + for (int i = 1; i < n - 1; i++) { + f[i % 2][0] = max(f[(i - 1) % 2][0], f[(i - 1) % 2][1]); + f[i % 2][1] = f[(i - 1) % 2][0] + nums[i]; + } + int b = max(f[(n - 2) % 2][0], f[(n - 2) % 2][1]); + + return max(a, b); + } +}; +``` +Python 代码: +```Python +class Solution: + def rob(self, nums: List[int]) -> int: + n = len(nums) + if n == 0: return 0 + if n == 1: return nums[0] + + f = [[0, 0] for _ in range(2)] + for i in range(1, n - 1): + f[i % 2][0] = max(f[(i - 1) % 2][0], f[(i - 1) % 2][1]) + f[i % 2][1] = f[(i - 1) % 2][0] + nums[i] + a = max(f[(n - 2) % 2][1], f[(n - 2) % 2][0] + nums[n - 1]) + + f[0][0] = 0; f[0][1] = nums[0] + for i in range(1, n - 1): + f[i % 2][0] = max(f[(i - 1) % 2][0], f[(i - 1) % 2][1]) + f[i % 2][1] = f[(i - 1) % 2][0] + nums[i] + b = max(f[(n - 2) % 2][0], f[(n - 2) % 2][1]) + + return max(a, b) +``` +TypeScript 代码: +```TypeScript +function rob(nums: number[]): number { + const n: number = nums.length; + if (n === 0) return 0; + if (n === 1) return nums[0]; + + const f: number[][] = [[0, 0], [0, 0]]; + for (let i: number = 1; i < n - 1; i++) { + f[i % 2][0] = Math.max(f[(i - 1) % 2][0], f[(i - 1) % 2][1]); + f[i % 2][1] = f[(i - 1) % 2][0] + nums[i]; + } + let a: number = Math.max(f[(n - 2) % 2][1], f[(n - 2) % 2][0] + nums[n - 1]); + + f[0][0] = 0; f[0][1] = nums[0]; + for (let i: number = 1; i < n - 1; i++) { + f[i % 2][0] = Math.max(f[(i - 1) % 2][0], f[(i - 1) % 2][1]); + f[i % 2][1] = f[(i - 1) % 2][0] + nums[i]; + } + let b: number = Math.max(f[(n - 2) % 2][0], f[(n - 2) % 2][1]); + + return Math.max(a, b); +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/211-220/220. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 III\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/211-220/220. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 III\357\274\210\344\270\255\347\255\211\357\274\211.md" index d71813e3..0b301d08 100644 --- "a/LeetCode/211-220/220. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 III\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/211-220/220. \345\255\230\345\234\250\351\207\215\345\244\215\345\205\203\347\264\240 III\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,13 +6,11 @@ Tag : 「滑动窗口」、「二分」、「桶排序」 -给你一个整数数组 nums 和两个整数 k 和 t 。 - -请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。 - -如果存在则返回 true,不存在返回 false。 +给你一个整数数组 `nums` 和两个整数 `k` 和 `t`。 +请你判断是否存在两个不同下标 `i` 和 `j`,使得 `abs(nums[i] - nums[j]) <= t`,同时又满足 `abs(i - j) <= k` 。 +如果存在则返回 `true`,不存在返回 `false`。 示例 1: @@ -35,11 +33,10 @@ Tag : 「滑动窗口」、「二分」、「桶排序」 ``` 提示: -* 0 <= nums.length <= 2 * $10^4$ -* -$2^{31}$ <= nums[i] <= $2^{31}$ - 1 -* 0 <= k <= $10^4$ -* 0 <= t <= $2^{31}$ - 1 - +* $0 <= nums.length <= 2 \times 10^4$ +* $-2^{31} <= nums[i] <= 2^{31} - 1$ +* $0 <= k <= 10^4$ +* $0 <= t <= 2^{31} - 1$ --- @@ -70,12 +67,11 @@ Tag : 「滑动窗口」、「二分」、「桶排序」 也就是对应到 Java 中的 `TreeSet` 数据结构(基于红黑树,查找和插入都具有折半的效率)。 -![IMG_1693.PNG](https://pic.leetcode-cn.com/1618627911-oWYBGq-IMG_1693.PNG) - +![](https://pic.leetcode-cn.com/1618627911-oWYBGq-IMG_1693.PNG) 其他细节:由于 `nums` 中的数较大,会存在 `int` 溢出问题,我们需要使用 `long` 来存储。 -代码: +Java 代码: ```Java class Solution { public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) { @@ -97,6 +93,45 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool containsNearbyAlmostDuplicate(vector& nums, int k, int t) { + int n = nums.size(); + set ts; + for (int i = 0; i < n; i++) { + long long u = static_cast(nums[i]); + auto l = ts.lower_bound(u), r = ts.upper_bound(u); + if (l != ts.end() && *l - u <= t) return true; + if (r != ts.begin() && u - *(--r) <= t) return true; + ts.insert(u); + if (i >= k) ts.erase(static_cast(nums[i - k])); + } + return false; + } +}; +``` +Python 代码: +```Python +from sortedcontainers import SortedList + +class Solution: + def containsNearbyAlmostDuplicate(self, nums, k, t): + n = len(nums) + ts = SortedList() + for i in range(n): + u = nums[i] + idx = ts.bisect_left(u) + if idx < len(ts) and ts[idx] - u <= t: + return True + if idx > 0 and u - ts[idx - 1] <= t: + return True + ts.add(u) + if i >= k: + ts.remove(nums[i - k]) + return False +``` * 时间复杂度:`TreeSet` 基于红黑树,查找和插入都是 $O(\log{k})$ 复杂度。整体复杂度为 $O(n\log{k})$ * 空间复杂度:$O(k)$ @@ -114,7 +149,7 @@ class Solution { * 如果不存在该桶,则检查相邻两个桶的元素是有 $[u - t, u + t]$ 范围的数字,如有 返回 `true` * 建立目标桶,并删除下标范围不在 $[max(0, i - k), i)$ 内的桶 -代码: +Java 代码: ```Java class Solution { long size; @@ -143,6 +178,53 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + long long size; + bool containsNearbyAlmostDuplicate(vector& nums, int k, int t) { + size = t + 1; + unordered_map map; + int n = nums.size(); + for (int i = 0; i < n; i++) { + long long u = static_cast(nums[i]); + long long idx = getIdx(u); + if (map.count(idx)) return true; + if (map.count(idx - 1) && u - map[idx - 1] <= t) return true; + if (map.count(idx + 1) && map[idx + 1] - u <= t) return true; + map[idx] = u; + if (i >= k) map.erase(getIdx(static_cast(nums[i - k]))); + } + return false; + } + long long getIdx(long long u) { + return u >= 0 ? u / size : ((u + 1) / size) - 1; + } +}; +``` +Python 代码: +```Python +class Solution: + def containsNearbyAlmostDuplicate(self, nums, k, t): + size = t + 1 + mapping = {} + for i, u in enumerate(nums): + idx = self.getIdx(u, size) + if idx in mapping: + return True + if (idx - 1) in mapping and u - mapping[idx - 1] <= t: + return True + if (idx + 1) in mapping and mapping[idx + 1] - u <= t: + return True + mapping[idx] = u + if i >= k: + del mapping[self.getIdx(nums[i - k], size)] + return False + + def getIdx(self, u, size): + return u // size if u >= 0 else ((u + 1) // size) - 1; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(k)$ diff --git "a/LeetCode/2211-2220/2216. \347\276\216\345\214\226\346\225\260\347\273\204\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/2211-2220/2216. \347\276\216\345\214\226\346\225\260\347\273\204\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" index 0683fde5..0138d44b 100644 --- "a/LeetCode/2211-2220/2216. \347\276\216\345\214\226\346\225\260\347\273\204\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/2211-2220/2216. \347\276\216\345\214\226\346\225\260\347\273\204\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -13,7 +13,7 @@ Tag : 「模拟」 注意,空数组同样认为是美丽数组。 -你可以从 `nums` 中删除任意数量的元素。当你删除一个元素时,被删除元素右侧的所有元素将会向左移动一个单位以填补空缺,而左侧的元素将会保持 不变 。 +你可以从 `nums` 中删除任意数量的元素。当你删除一个元素时,被删除元素右侧的所有元素将会向左移动一个单位以填补空缺,而左侧的元素将会保持不变。 返回使 `nums` 变为美丽数组所需删除的最少元素数目。 diff --git "a/LeetCode/2301-2310/2304. \347\275\221\346\240\274\344\270\255\347\232\204\346\234\200\345\260\217\350\267\257\345\276\204\344\273\243\344\273\267\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/2301-2310/2304. \347\275\221\346\240\274\344\270\255\347\232\204\346\234\200\345\260\217\350\267\257\345\276\204\344\273\243\344\273\267\357\274\210\344\270\255\347\255\211\357\274\211.md" index 5024696b..f92c3b18 100644 --- "a/LeetCode/2301-2310/2304. \347\275\221\346\240\274\344\270\255\347\232\204\346\234\200\345\260\217\350\267\257\345\276\204\344\273\243\344\273\267\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/2301-2310/2304. \347\275\221\346\240\274\344\270\255\347\232\204\346\234\200\345\260\217\350\267\257\345\276\204\344\273\243\344\273\267\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -10,13 +10,18 @@ Tag : 「最短路」、「图」、「模拟」、「序列 DP」、「动态 你可以在此矩阵中,从一个单元格移动到下一行的任何其他单元格。 -如果你位于单元格 $(x, y)$ ,且满足 $x < m - 1$,你可以移动到 $(x + 1, 0)$, $(x + 1, 1)$, ..., $(x + 1, n - 1)$ 中的任何一个单元格。注意: 在最后一行中的单元格不能触发移动。 +如果你位于单元格 $(x, y)$ ,且满足 $x < m - 1$,你可以移动到 $(x + 1, 0)$, $(x + 1, 1)$, ..., $(x + 1, n - 1)$ 中的任何一个单元格。 -每次可能的移动都需要付出对应的代价,代价用一个下标从 `0` 开始的二维数组 `moveCost` 表示,该数组大小为 $(m \times n) \times n$ ,其中 `moveCost[i][j]` 是从值为 `i` 的单元格移动到下一行第 `j` 列单元格的代价。从 `grid` 最后一行的单元格移动的代价可以忽略。 +注意: 在最后一行中的单元格不能触发移动。 + +每次可能的移动都需要付出对应的代价,代价用一个下标从 `0` 开始的二维数组 `moveCost` 表示,该数组大小为 $(m \times n) \times n$ ,其中 `moveCost[i][j]` 是从值为 `i` 的单元格移动到下一行第 `j` 列单元格的代价。 + +从 `grid` 最后一行的单元格移动的代价可以忽略。 `grid` 一条路径的代价是:所有路径经过的单元格的值之和加上所有移动的代价之和 。从第一行任意单元格出发,返回到达最后一行任意单元格的最小路径代价。 示例 1: + ![](https://assets.leetcode.com/uploads/2022/04/28/griddrawio-2.png) ``` diff --git "a/LeetCode/231-240/240. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265 II\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/231-240/240. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265 II\357\274\210\344\270\255\347\255\211\357\274\211.md" index e5af7962..5d5337c1 100644 --- "a/LeetCode/231-240/240. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265 II\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/231-240/240. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265 II\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -9,20 +9,22 @@ Tag : 「二分」、「二叉搜索树」、「模拟」 编写一个高效的算法来搜索 `m x n` 矩阵 `matrix` 中的一个目标值 `target` 。 该矩阵具有以下特性: - * 每行的元素从左到右升序排列。 * 每列的元素从上到下升序排列。 - 示例 1: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/searchgrid2.jpg) + ``` 输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5 输出:true ``` 示例 2: + ![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/11/25/searchgrid.jpg) + ``` 输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20 @@ -30,9 +32,9 @@ Tag : 「二分」、「二叉搜索树」、「模拟」 ``` 提示: -* m == matrix.length -* n == matrix[i].length -* 1 <= n, m <= 300 +* $m = matrix.length$ +* $n = matrix[i].length$ +* $1 <= n, m <= 300$ * $-10^9 <= matrix[i][j] <= 10^9$ * 每行的所有元素从左到右升序排列 * 每列的所有元素从上到下升序排列 @@ -100,7 +102,7 @@ class Solution { 我们可以将二维矩阵抽象成「以右上角为根的 BST」: -![image.png](https://pic.leetcode-cn.com/1617066993-AyRIiF-image.png) +![](https://pic.leetcode-cn.com/1617066993-AyRIiF-image.png) 那么我们可以从根(右上角)开始搜索,如果当前的节点不等于目标值,可以按照树的搜索顺序进行: diff --git "a/LeetCode/2471-2480/2477. \345\210\260\350\276\276\351\246\226\351\203\275\347\232\204\346\234\200\345\260\221\346\262\271\350\200\227\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/2471-2480/2477. \345\210\260\350\276\276\351\246\226\351\203\275\347\232\204\346\234\200\345\260\221\346\262\271\350\200\227\357\274\210\344\270\255\347\255\211\357\274\211.md" index 10d80e90..8edebbb1 100644 --- "a/LeetCode/2471-2480/2477. \345\210\260\350\276\276\351\246\226\351\203\275\347\232\204\346\234\200\345\260\221\346\262\271\350\200\227\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/2471-2480/2477. \345\210\260\350\276\276\351\246\226\351\203\275\347\232\204\346\234\200\345\260\221\346\262\271\350\200\227\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -8,18 +8,20 @@ Tag : 「DFS」 给你一棵 `n` 个节点的树(一个无向、连通、无环图),每个节点表示一个城市,编号从 `0` 到 `n - 1`,且恰好有 `n - 1` 条路。 -`0` 是首都。给你一个二维整数数组 `roads`,其中 $roads[i] = [a_{i}, b_{i}]$ ,表示城市 $a_{i}$ 和 $b_{i}$ 之间有一条 双向路 。 +`0` 是首都。给你一个二维整数数组 `roads`,其中 $roads[i] = [a_{i}, b_{i}]$ ,表示城市 $a_{i}$ 和 $b_{i}$ 之间有一条双向路。 每个城市里有一个代表,他们都要去首都参加一个会议。 -每座城市里有一辆车。给你一个整数 `seats` 表示每辆车里面座位的数目。 +每座城市里有一辆车,给你一个整数 `seats` 表示每辆车里面座位的数目。 城市里的代表可以选择乘坐所在城市的车,或者乘坐其他城市的车。相邻城市之间一辆车的油耗是一升汽油。 请你返回到达首都最少需要多少升汽油。 示例 1: + ![](https://assets.leetcode.com/uploads/2022/09/22/a4c380025e3ff0c379525e96a7d63a3.png) + ``` 输入:roads = [[0,1],[0,2],[0,3]], seats = 5 @@ -32,7 +34,9 @@ Tag : 「DFS」 最少消耗 3 升汽油。 ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2022/11/16/2.png) + ``` 输入:roads = [[3,1],[3,2],[1,0],[0,4],[0,5],[4,6]], seats = 2 @@ -49,7 +53,9 @@ Tag : 「DFS」 最少消耗 7 升汽油。 ``` 示例 3: + ![](https://assets.leetcode.com/uploads/2022/09/27/efcf7f7be6830b8763639cfd01b690a.png) + ``` 输入:roads = [], seats = 1 @@ -73,7 +79,7 @@ Tag : 「DFS」 将双向图看作是以节点 `0` 为根的有向树,从每个节点出发往 `0` 前行,可看作是自底向上的移动过程。 -![image.png](https://pic.leetcode.cn/1701743655-EZFMCm-image.png) +![](https://pic.leetcode.cn/1701743655-EZFMCm-image.png) 当 `seats = 1` 时,每个节点前往 `0` 的过程相互独立,总油耗为每节点到 `0` 的最短距离之和。 @@ -87,7 +93,7 @@ Tag : 「DFS」 因此我们可统计每条边会被多少个节点经过,通过 `DFS` 统计「以每个节点为根时,子树的节点数量」即是经过该节点往上的边。 -![image.png](https://pic.leetcode.cn/1701743638-UXsdes-image.png) +![](https://pic.leetcode.cn/1701743638-UXsdes-image.png) 知道经过某条边的节点数量 `cnt` 后,$\left \lceil \frac{cnt}{seats} \right \rceil$ 即是该边对答案的贡献。 diff --git "a/LeetCode/2551-2560/2558. \344\273\216\346\225\260\351\207\217\346\234\200\345\244\232\347\232\204\345\240\206\345\217\226\350\265\260\347\244\274\347\211\251\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/2551-2560/2558. \344\273\216\346\225\260\351\207\217\346\234\200\345\244\232\347\232\204\345\240\206\345\217\226\350\265\260\347\244\274\347\211\251\357\274\210\347\256\200\345\215\225\357\274\211.md" index 93e18bda..9eb54786 100644 --- "a/LeetCode/2551-2560/2558. \344\273\216\346\225\260\351\207\217\346\234\200\345\244\232\347\232\204\345\240\206\345\217\226\350\265\260\347\244\274\347\211\251\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/2551-2560/2558. \344\273\216\346\225\260\351\207\217\346\234\200\345\244\232\347\232\204\345\240\206\345\217\226\350\265\260\347\244\274\347\211\251\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -93,7 +93,7 @@ Tag : 「优先队列(堆)」 因此将小根堆可视化后,也十分形象:小根堆的堆顶元素最小,然后从上往下,元素“依次”增大。 -![image.png](https://pic.leetcode.cn/1698461866-lJXmLB-image.png){:width=400} +![](https://pic.leetcode.cn/1698461866-lJXmLB-image.png) 即对于小根堆而言,每个子树的根节点,都是该子树的最小值。 @@ -105,7 +105,7 @@ Tag : 「优先队列(堆)」 注意:基于此规则,我们需要调整数组下标从 $1$ 开始进行存储。 -![image.png](https://pic.leetcode.cn/1698461818-WzLUTy-image.png){:width=400} +![](https://pic.leetcode.cn/1698461818-WzLUTy-image.png) #### 3.基本操作如何实现? diff --git "a/LeetCode/261-270/263. \344\270\221\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/261-270/263. \344\270\221\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" index 41b1fac8..7a371b1f 100644 --- "a/LeetCode/261-270/263. \344\270\221\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/261-270/263. \344\270\221\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,9 +6,9 @@ Tag : 「数学」、「模拟」 -给你一个整数 `n` ,请你判断 n 是否为 丑数 。如果是,返回 `true` ;否则,返回 `false` 。 +给你一个整数 `n` ,请你判断 n 是否为丑数。如果是,返回 `true` ,否则,返回 `false` 。 -丑数 就是只包含质因数 `2`、`3` 和 `5` 的正整数。 +丑数就是只包含质因数 `2`、`3` 和 `5` 的正整数。 示例 1: ``` @@ -48,7 +48,7 @@ Tag : 「数学」、「模拟」 --- -### 朴素解法 +### 分情况讨论 输入范围是 $-2^{31} <= n <= 2^{31} - 1$,我们只需要对输入进行分情况讨论即可: @@ -57,8 +57,8 @@ Tag : 「数学」、「模拟」 注意,`2` `3` `5` 先除哪一个都是可以的,因为乘法本身具有交换律。 -代码: -```java +Java 代码: +```Java class Solution { public boolean isUgly(int n) { if (n <= 0) return false; @@ -69,6 +69,43 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool isUgly(int n) { + if (n <= 0) return false; + while (n % 2 == 0) n /= 2; + while (n % 3 == 0) n /= 3; + while (n % 5 == 0) n /= 5; + return n == 1; + } +}; +``` +Python 代码: +```Python +class Solution: + def isUgly(self, n: int) -> bool: + if n <= 0: + return False + while n % 2 == 0: + n //= 2 + while n % 3 == 0: + n //= 3 + while n % 5 == 0: + n //= 5 + return n == 1 +``` +TypeScript 代码: +```TypeScript +function isUgly(n: number): boolean { + if (n <= 0) return false; + while (n % 2 === 0) n /= 2; + while (n % 3 === 0) n /= 3; + while (n % 5 === 0) n /= 5; + return n === 1; +}; +``` * 时间复杂度:当 $n$ 是以 $2$ 为底的对数时,需要除以 $\log{n}$ 次。复杂度为 $O(\log{n})$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/271-280/274. H \346\214\207\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/271-280/274. H \346\214\207\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" index ac343daa..fd4083fc 100644 --- "a/LeetCode/271-280/274. H \346\214\207\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/271-280/274. H \346\214\207\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,7 +6,9 @@ Tag : 「二分」、「数学」 -给你一个整数数组 `citations`,其中 `citations[i]` 表示研究者的第 `i` 篇论文被引用的次数。计算并返回该研究者的 h 指数。 +给你一个整数数组 `citations`,其中 `citations[i]` 表示研究者的第 `i` 篇论文被引用的次数。 + +计算并返回该研究者的 h 指数。 根据维基百科上 `h` 指数的定义:`h` 代表“高引用次数”,一名科研人员的 h指数是指他(她)的 (`n` 篇论文中)总共有 `h` 篇论文分别被引用了至少 `h` 次。且其余的 `n - h` 篇论文每篇被引用次数 不超过 `h` 次。 diff --git "a/LeetCode/31-40/34. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/31-40/34. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256\357\274\210\344\270\255\347\255\211\357\274\211.md" index bc613234..9b7c96c6 100644 --- "a/LeetCode/31-40/34. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/31-40/34. \345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\346\237\245\346\211\276\345\205\203\347\264\240\347\232\204\347\254\254\344\270\200\344\270\252\345\222\214\346\234\200\345\220\216\344\270\200\344\270\252\344\275\215\347\275\256\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -12,10 +12,6 @@ Tag : 「二分」 如果数组中不存在目标值 `target`,返回 $[-1, -1]$。 -**进阶:** - -* 你可以设计并实现时间复杂度为 $O(\log{n})$ 的算法解决此问题吗? - 示例 1: ``` 输入:nums = [5,7,7,8,8,10], target = 8 @@ -41,9 +37,13 @@ Tag : 「二分」 * `nums` 是一个非递减数组 * $-10^9 <= target <= 10^9$ +进阶: + +* 你可以设计并实现时间复杂度为 O(\log{n}) 的算法解决此问题吗? + --- -### 二分解法 +### 二分 这是一道「二分查找」的裸题。 @@ -55,9 +55,9 @@ Tag : 「二分」 文字不好理解,我们结合图片来看: -![640.png](https://pic.leetcode-cn.com/1611730934-iKurnj-640.png) +![](https://pic.leetcode-cn.com/1611730934-iKurnj-640.png) -代码: +Java 代码: ```Java class Solution { public int[] searchRange(int[] nums, int t) { @@ -83,6 +83,79 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector searchRange(vector& nums, int t) { + vector ans = {-1, -1}; + int n = nums.size(); + if (n == 0) return ans; + int l = 0, r = n - 1; + while (l < r) { + int mid = l + r >> 1; + if (nums[mid] >= t) r = mid; + else l = mid + 1; + } + if (nums[r] != t) return ans; + ans[0] = r; + l = 0; r = n - 1; + while (l < r) { + int mid = l + r + 1 >> 1; + if (nums[mid] <= t) l = mid; + else r = mid - 1; + } + ans[1] = r; + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def searchRange(self, nums: List[int], t: int) -> List[int]: + ans = [-1, -1] + n = len(nums) + if n == 0: return ans + l, r = 0, n - 1 + while l < r: + mid = l + r >> 1 + if nums[mid] >= t: r = mid + else: l = mid + 1 + if nums[r] != t: return ans + ans[0] = r + l, r = 0, n - 1 + while l < r: + mid = l + r + 1 >> 1 + if nums[mid] <= t: l = mid + else: r = mid - 1 + ans[1] = r + return ans +``` +TypeScript 代码: +```TypeScript +function searchRange(nums: number[], t: number): number[] { + const ans= [-1, -1]; + const n = nums.length; + if (n == 0) return ans; + let l = 0, r = n - 1; + while (l < r) { + const mid = l + r >> 1; + if (nums[mid] >= t) r = mid; + else l = mid + 1; + } + if (nums[r] !== t) return ans; + ans[0] = r; + l = 0; r = n - 1; + while (l < r) { + const mid = l + r + 1 >> 1; + if (nums[mid] <= t) l = mid; + else r = mid - 1; + } + ans[1] = r; + return ans; +}; +``` * 时间复杂度:$O(\log{n})$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/31-40/38. \345\244\226\350\247\202\346\225\260\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/31-40/38. \345\244\226\350\247\202\346\225\260\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" index 093402dc..c1f33cd8 100644 --- "a/LeetCode/31-40/38. \345\244\226\350\247\202\346\225\260\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/31-40/38. \345\244\226\350\247\202\346\225\260\345\210\227\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,13 +6,13 @@ Tag : 「模拟」 -给定一个正整数 n ,输出外观数列的第 n 项。 +给定一个正整数 `n` ,输出外观数列的第 `n` 项。 「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。 你可以将其视作是由递归公式定义的数字字符串序列: -* countAndSay(1) = "1" -* countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。 +* `countAndSay(1) = "1"` +* `countAndSay(n)` 是对 `countAndSay(n-1)` 的描述,然后转换成另一个数字字符串。 前五项如下: ``` @@ -27,9 +27,9 @@ Tag : 「模拟」 描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211" 描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221" ``` -要**描述**一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。 +要描述一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。 -例如,数字字符串 "3322251" 的描述如下图: +例如,数字字符串 `"3322251"` 的描述如下图: ![](https://assets.leetcode.com/uploads/2020/10/23/countandsay.jpg) @@ -56,7 +56,7 @@ countAndSay(4) = 读 "21" = 一 个 2 + 一 个 1 = "12" + "11" = "1211" ``` 提示: -* 1 <= n <= 30 +* $1 <= n <= 30$ --- @@ -64,7 +64,7 @@ countAndSay(4) = 读 "21" = 一 个 2 + 一 个 1 = "12" + "11" = "1211" 一个朴素的想法是:根据题意进行模拟,从起始条件 $k = 1$ 时 `ans = "1"` 出发,逐步递推到 $k = n$ 的情况,对于第 $k$ 项而言,其实就是对第 $k - 1$ 项的「连续段」的描述,而求「连续段」长度,可以使用双指针实现。 -代码: +Java 代码: ```Java class Solution { public String countAndSay(int n) { @@ -85,6 +85,67 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + string countAndSay(int n) { + string ans = "1"; + for (int i = 2; i <= n; i++) { + string cur = ""; + int m = ans.length(); + for (int j = 0; j < m; ) { + int k = j + 1; + while (k < m && ans[j] == ans[k]) k++; + int cnt = k - j; + cur += to_string(cnt) + ans[j]; + j = k; + } + ans = cur; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def countAndSay(self, n: int) -> str: + ans = "1" + for i in range(2, n + 1): + cur = "" + m, j = len(ans), 0 + while j < m: + k = j + 1 + while k < m and ans[j] == ans[k]: + k += 1 + cnt = k - j + cur += str(cnt) + ans[j] + j = k + ans = cur + return ans +``` +TypeScript 代码: +```TypeScript +function countAndSay(n: number): string { + let ans: string = "1"; + for (let i: number = 2; i <= n; i++) { + let cur: string = ""; + let m: number = ans.length; + for (let j: number = 0; j < m; ) { + let k: number = j + 1; + while (k < m && ans[j] === ans[k]) { + k++; + } + const cnt: number = k - j; + cur += cnt.toString() + ans[j]; + j = k; + } + ans = cur; + } + return ans; +}; +``` * 时间复杂度:$O(n^2)$ * 空间复杂度:$O(n)$ @@ -96,7 +157,7 @@ class Solution { 例如对于 $n = 5$ 和 $n = 6$ 都存在先计算前五项的公共部分,打表可以确保这部分只会被计算一次,同时能够应用到后面项中。 -代码: +Java 代码: ```Java class Solution { static String[] f = new String[35]; diff --git "a/LeetCode/331-340/338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/331-340/338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" index 4a777782..9293ade5 100644 --- "a/LeetCode/331-340/338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/331-340/338. \346\257\224\347\211\271\344\275\215\350\256\241\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,7 +6,9 @@ Tag : 「位运算」、「数学」、「线性 DP」 -给定一个非负整数 `num`。对于 `0 ≤ i ≤ num` 范围中的每个数字 `i`,计算其二进制数中的 $1$ 的数目并将它们作为数组返回。 +给定一个非负整数 `num`。 + +对于 `0 ≤ i ≤ num` 范围中的每个数字 `i`,计算其二进制数中的 $1$ 的数目并将它们作为数组返回。 示例 1: ``` @@ -31,9 +33,9 @@ Tag : 「位运算」、「数学」、「线性 DP」 这道题要对每个数进行统计,因此不会有比 $O(n)$ 更低的做法。 -而很容易想到的朴素做法是对每个数进行「位运算」计数,每个数都是 $32$ 位的,因此是一个 $O(32n)$ 的做法。 +而很容易想到的朴素做法是对每个数进行「位运算」计数,每个数都是 $C = 32$ 位的,因此是一个 $O(C \times n)$ 的做法。 -代码: +Java 代码: ```Java class Solution { public int[] countBits(int n) { @@ -48,6 +50,50 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector countBits(int n) { + vector ans(n + 1); + for (int i = 0; i <= n; i++) ans[i] = getCnt(i); + return ans; + } + int getCnt(int u) { + int ans = 0; + for (int i = 0; i < 32; i++) ans += (u >> i) & 1; + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def countBits(self, n: int) -> List[int]: + ans = [0] * (n + 1) + for i in range(n + 1): + ans[i] = self.getCnt(i) + return ans + + def getCnt(self, u): + ans = 0 + for i in range(32): + ans += (u >> i) & 1 + return ans +``` +TypeScript 代码: +```TypeScript +function countBits(n: number): number[] { + const ans: number[] = new Array(n + 1).fill(0); + for (let i = 0; i <= n; i++) ans[i] = getCnt(i); + return ans; +} +function getCnt(u: number): number { + let ans = 0; + for (let i = 0; i < 32; i++) ans += (u >> i) & 1; + return ans; +} +``` * 时间复杂度:$O(n)$ * 空间复杂度:使用与输入同等规模的空间存储答案。复杂度为 $O(n)$ @@ -73,20 +119,8 @@ class Solution { * 当**从大到小遍历** :$f(i) = f(i << 1) + ((i >>31 ) \& 1)$ * 当**从小到大遍历** :$f(i) = f(i >> 1) + ( i \& 1 )$ -代码: +Java 代码(P1): ```Java -class Solution { - // 从小到大遍历 - public int[] countBits(int n) { - int[] ans = new int[n + 1]; - // ans[i] = 「i >> 1 所包含的 1 的个数」+「i 的最低位是否为 1」 - for (int i = 1; i <= n; i++) ans[i] = ans[i >> 1] + (i & 1); - return ans; - } -} -``` -- -```Java class Solution { // 从大到小遍历 public int[] countBits(int n) { @@ -106,6 +140,50 @@ class Solution { } } ``` +Java 代码(P2): +```Java +class Solution { + // 从小到大遍历 + public int[] countBits(int n) { + int[] ans = new int[n + 1]; + // ans[i] = 「i >> 1 所包含的 1 的个数」+「i 的最低位是否为 1」 + for (int i = 1; i <= n; i++) ans[i] = ans[i >> 1] + (i & 1); + return ans; + } +} +``` +C++ 代码(P2): +```C++ +class Solution { +public: + vector countBits(int n) { + vector ans(n + 1); + for (int i = 1; i <= n; i++) { + ans[i] = ans[i >> 1] + (i & 1); + } + return ans; + } +}; +``` +Python 代码(P2): +```Python +class Solution: + def countBits(self, n: int) -> List[int]: + ans = [0] * (n + 1) + for i in range(1, n + 1): + ans[i] = ans[i >> 1] + (i & 1) + return ans +``` +TypeScript 代码(P2): +```TypeScript +function countBits(n: number): number[] { + const ans: number[] = new Array(n + 1).fill(0); + for (let i = 1; i <= n; i++) { + ans[i] = ans[Math.floor(i >> 1)] + (i & 1); + } + return ans; +} +``` * 时间复杂度:$O(n)$ * 空间复杂度:使用与输入同等规模的空间存储答案。复杂度为 $O(n)$ diff --git "a/LeetCode/341-350/341. \346\211\201\345\271\263\345\214\226\345\265\214\345\245\227\345\210\227\350\241\250\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/341-350/341. \346\211\201\345\271\263\345\214\226\345\265\214\345\245\227\345\210\227\350\241\250\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" index 64b79f40..822e15db 100644 --- "a/LeetCode/341-350/341. \346\211\201\345\271\263\345\214\226\345\265\214\345\245\227\345\210\227\350\241\250\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/341-350/341. \346\211\201\345\271\263\345\214\226\345\265\214\345\245\227\345\210\227\350\241\250\350\277\255\344\273\243\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,25 +6,31 @@ Tag : 「DFS」、「队列」、「栈」 +给你一个嵌套的整型列表。 -给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。 +请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。 -列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。 - -  +列表中的每一项或者为一个整数,或者是另一个列表,其中列表的元素也可能是整数或是其他列表。  示例 1: ``` 输入: [[1,1],2,[1,1]] + 输出: [1,1,2,1,1] + 解释: 通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,1,2,1,1]。 ``` 示例 2: ``` 输入: [1,[4,[6]]] + 输出: [1,4,6] + 解释: 通过重复调用 next 直到 hasNext 返回 false,next 返回的元素的顺序应该是: [1,4,6]。 ``` +提示: +* $1 <= nestedList.length <= 500$ +* 嵌套列表中的整数值在范围 $[-10^6, 10^6]$ 内 --- @@ -32,11 +38,11 @@ Tag : 「DFS」、「队列」、「栈」 由于所有的元素都是在初始化时提供的,因此一个朴素的做法是在初始化的时候进行处理。 -由于存在嵌套,比较简单的做法是通过 DFS 进行处理,将元素都放至队列。 +由于存在嵌套,比较简单的做法是通过 `DFS` 进行处理,将元素都放至队列。 -代码: -```java [] +Java 代码: +```Java public class NestedIterator implements Iterator { Deque queue = new ArrayDeque<>(); @@ -44,7 +50,7 @@ public class NestedIterator implements Iterator { public NestedIterator(List nestedList) { dfs(nestedList); } - + @Override public Integer next() { return hasNext() ? queue.pollFirst() : -1; @@ -66,10 +72,43 @@ public class NestedIterator implements Iterator { } } ``` -* 时间复杂度:构建迭代器的复杂度为 $O(n)$,调用 $next()$ 与 $hasNext()$ 的复杂度为 $O(1)$ +C++ 代码: +```C++ +class NestedIterator { +public: + deque queue; + NestedIterator(vector &nestedList) { + dfs(nestedList); + } + + int next() { + if (hasNext()) { + int result = queue.front(); + queue.pop_front(); + return result; + } + return -1; + } + + bool hasNext() { + return !queue.empty(); + } + + void dfs(const vector &list) { + for (const NestedInteger &item : list) { + if (item.isInteger()) { + queue.push_back(item.getInteger()); + } else { + dfs(item.getList()); + } + } + } +}; +``` +* 时间复杂度:构建迭代器的复杂度为 $O(n)$,调用 `next` 与 `hasNext` 的复杂度为 $O(1)$ * 空间复杂度:$O(n)$ -*** +--- ### 递归 + 栈 @@ -77,8 +116,8 @@ public class NestedIterator implements Iterator { 而是先将所有的 `NestedInteger` 逆序放到栈中,当需要展开的时候才进行展开。 -代码: -```java +Java 代码: +```Java public class NestedIterator implements Iterator { Deque stack = new ArrayDeque<>(); @@ -115,7 +154,46 @@ public class NestedIterator implements Iterator { } } ``` -* 时间复杂度:构建迭代器的复杂度为 $O(n)$,$hasNext()$ 的复杂度为均摊 $O(1)$,$next()$ 严格按照迭代器的访问顺序( 先 $hasNext()$ 再 $next()$ )的话为 $O(1)$,防御性编程生效的情况下为均摊 $O(1)$ +C++ 代码: +```C++ +class NestedIterator { +public: + stack stk; + NestedIterator(vector &nestedList) { + for (int i = nestedList.size() - 1; i >= 0; i--) { + stk.push(nestedList[i]); + } + } + + int next() { + if (hasNext()) { + NestedInteger ni = stk.top(); + stk.pop(); + return ni.getInteger(); + } + return -1; + } + + bool hasNext() { + if (stk.empty()) { + return false; + } else { + NestedInteger item = stk.top(); + if (item.isInteger()) { + return true; + } else { + stk.pop(); + const vector &list = item.getList(); + for (int i = list.size() - 1; i >= 0; i--) { + stk.push(list[i]); + } + return hasNext(); + } + } + } +}; +``` +* 时间复杂度:构建迭代器的复杂度为 $O(n)$,`hasNext` 的复杂度为均摊 $O(1)$,`next` 严格按照迭代器的访问顺序( 先 `hasNext` 再 `next()`)的话为 $O(1)$,防御性编程生效的情况下为均摊 $O(1)$ * 空间复杂度:$O(n)$ --- diff --git "a/LeetCode/351-360/354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/351-360/354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230\357\274\210\345\233\260\351\232\276\357\274\211.md" index 372f4da3..99c69e1b 100644 --- "a/LeetCode/351-360/354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/351-360/354. \344\277\204\347\275\227\346\226\257\345\245\227\345\250\203\344\277\241\345\260\201\351\227\256\351\242\230\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -7,7 +7,7 @@ Tag : 「二分」、「序列 DP」 -给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。 +给你一个二维整数数组 `envelopes`,其中 $envelopes[i] = [w_{i}, h_{i}]$,表示第 `i` 个信封的宽度和高度。 当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。 @@ -19,26 +19,32 @@ Tag : 「二分」、「序列 DP」 示例 1: ``` 输入:envelopes = [[5,4],[6,4],[6,7],[2,3]] + 输出:3 + 解释:最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。 ``` 示例 2: ``` 输入:envelopes = [[1,1],[1,1],[1,1]] + 输出:1 ``` -提示: -* 1 <= envelopes.length <= 5000 -* envelopes[i].length == 2 -* 1 <= wi, hi <= $10^4$ +【原数据结构范围】提示: + +* 无 + +【题目更新后数据范围】提示: + +* $1 <= envelopes.length <= 10^5$ +* $envelopes[i].length = 2$ +* $1 <= w_{i}, h_{i} <= 10^5$ --- ### 动态规划 -![image.png](https://pic.leetcode-cn.com/1614822923-BaePjD-image.png) - 这是一道经典的 DP 模型题目:最长上升子序列(LIS)。 首先我们先对 `envelopes` 进行排序,确保信封是从小到大进行排序。 @@ -55,26 +61,25 @@ Tag : 「二分」、「序列 DP」 然后在所有方案中取一个 `max` 即是答案。 -代码: +> 由于 LC 补充了数据范围 & 样例,该做法现已 TLE。 + +Java 代码: ```Java class Solution { public int maxEnvelopes(int[][] es) { - int n = es.length; + int n = es.length, ans = 1; if (n == 0) return n; // 因为我们在找第 i 件物品的前一件物品时,会对前面的 i - 1 件物品都遍历一遍,因此第二维(高度)排序与否都不影响 Arrays.sort(es, (a, b)->a[0]-b[0]); int[] f = new int[n]; // f(i) 为考虑前 i 个物品,并以第 i 个物品为结尾的最大值 - int ans = 1; for (int i = 0; i < n; i++) { // 对于每个 f[i] 都满足最小值为 1 f[i] = 1; // 枚举第 i 件物品的前一件物品, for (int j = i - 1; j >= 0; j--) { // 只要有满足条件的前一件物品,我们就尝试使用 f[j] + 1 更新 f[i] - if (check(es, j, i)) { - f[i] = Math.max(f[i], f[j] + 1); - } + if (check(es, j, i)) f[i] = Math.max(f[i], f[j] + 1); } // 在所有的 f[i] 中取 max 作为 ans ans = Math.max(ans, f[i]); @@ -89,12 +94,10 @@ class Solution { * 时间复杂度:$O(n^2)$ * 空间复杂度:$O(n)$ -*** +--- ### 二分 + 动态规划 -![image.png](https://pic.leetcode-cn.com/1614826949-dhqXEs-image.png) - 上述方案其实算是一个朴素方案,复杂度是 $O(n^2)$ 的,也是我最先想到思路,但是题目没有给出数据范围,也不知道能不能过。 唯唯诺诺交了一个居然过了。 @@ -119,12 +122,12 @@ class Solution { 还是不理解?没关系,我们可以直接看看代码,我把基本逻辑写在了注释当中(你的重点应该落在对 $g[]$ 数组的理解上)。 -代码: +Java 代码: ```Java class Solution { public int maxEnvelopes(int[][] es) { - int n = es.length; + int n = es.length, ans = 1; if (n == 0) return n; // 由于我们使用了 g 记录高度,因此这里只需将 w 从小到达排序即可 Arrays.sort(es, (a, b)->a[0] - b[0]); @@ -135,7 +138,6 @@ class Solution { // 因为要取 min,用一个足够大(不可能)的高度初始化 Arrays.fill(g, Integer.MAX_VALUE); g[0] = 0; - int ans = 1; for (int i = 0, j = 0, len = 1; i < n; i++) { // 对于 w 相同的数据,不更新 g 数组 if (es[i][0] != es[j][0]) { @@ -162,11 +164,8 @@ class Solution { // 令 check 条件为 es[i][1] <= g[mid](代表 w 和 h 都严格小于当前信封) // 这样我们找到的就是满足条件,最靠近数组中心点的数据(也就是满足 check 条件的最大下标) // 对应回 g[] 数组的含义,其实就是找到 w 和 h 都满足条件的最大上升长度 - if (es[i][1] <= g[mid]) { - r = mid; - } else { - l = mid + 1; - } + if (es[i][1] <= g[mid]) r = mid; + else l = mid + 1; } // 更新 f[i] 与答案 f[i] = r; @@ -176,10 +175,48 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxEnvelopes(vector>& es) { + int n = es.size(), ans = 1; + if (n == 0) return n; + sort(es.begin(), es.end(), [](const vector& a, const vector& b) { + return a[0] < b[0]; + }); + vector f(n, 0); + vector g(n, INT_MAX); + g[0] = 0; + for (int i = 0, j = 0, len = 1; i < n; i++) { + if (es[i][0] != es[j][0]) { + while (j < i) { + int prev = f[j], cur = es[j][1]; + if (prev == len) { + g[len++] = cur; + } else { + g[prev] = min(g[prev], cur); + } + j++; + } + } + int l = 0, r = len; + while (l < r) { + int mid = l + r >> 1; + if (es[i][1] <= g[mid]) r = mid; + else l = mid + 1; + } + f[i] = r; + ans = max(ans, f[i]); + } + return ans; + } +}; +``` * 时间复杂度:对于每件物品都是通过「二分」找到其前一件物品。复杂度为 $O(n\log{n})$ * 空间复杂度:$O(n)$ -*** +--- ### 证明 @@ -211,12 +248,10 @@ class Solution { 既然 $g[]$ 具有单调性,我们可以通过「二分」找到恰满足 check 条件的最大下标(最大下标达标表示最长上升序列长度)。 -*** +--- ### 树状数组 + 动态规划 -![image.png](https://pic.leetcode-cn.com/1614860986-mpTgpr-image.png) - 在「二分 + 动态规划」的解法中,我们通过「二分」来优化找第 $i$ 个文件的前一个文件过程。 这个过程同样能通过「树状数组」来实现。 @@ -227,7 +262,7 @@ class Solution { 通常使用「树状数组」都需要进行离散化,尤其是这里我们本身就要使用 $O(n)$ 的空间来存储 dp 值。 -代码: +Java 代码: ```java class Solution { diff --git "a/LeetCode/361-370/363. \347\237\251\345\275\242\345\214\272\345\237\237\344\270\215\350\266\205\350\277\207 K \347\232\204\346\234\200\345\244\247\346\225\260\345\200\274\345\222\214\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/361-370/363. \347\237\251\345\275\242\345\214\272\345\237\237\344\270\215\350\266\205\350\277\207 K \347\232\204\346\234\200\345\244\247\346\225\260\345\200\274\345\222\214\357\274\210\345\233\260\351\232\276\357\274\211.md" index 5a2fa2b8..3b9e2b4c 100644 --- "a/LeetCode/361-370/363. \347\237\251\345\275\242\345\214\272\345\237\237\344\270\215\350\266\205\350\277\207 K \347\232\204\346\234\200\345\244\247\346\225\260\345\200\274\345\222\214\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/361-370/363. \347\237\251\345\275\242\345\214\272\345\237\237\344\270\215\350\266\205\350\277\207 K \347\232\204\346\234\200\345\244\247\346\225\260\345\200\274\345\222\214\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -11,6 +11,7 @@ Tag : 「二分」、「前缀和」 题目数据保证总会存在一个数值和不超过 $k$ 的矩形区域。 示例 1: + ![](https://assets.leetcode.com/uploads/2021/03/18/sum-grid.jpg) ``` @@ -28,8 +29,8 @@ Tag : 「二分」、「前缀和」 ``` 提示: -* $m == matrix.length$ -* $n == matrix[i].length$ +* $m = matrix.length$ +* $n = matrix[i].length$ * $1 <= m, n <= 100$ * $-100 <= matrix[i][j] <= 100$ * -$10^5 <= k <= 10^5$ @@ -38,11 +39,11 @@ Tag : 「二分」、「前缀和」 ### 朴素二维前缀和 -从题面来看显然是一道「二维前缀和」的题目,如果你还不了解「二维前缀和」,可以看看 [(题解)304. 二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/)。本题预处理前缀和的复杂度为 $O(m * n)$。 +从题面来看显然是一道「二维前缀和」的题目,如果你还不了解「二维前缀和」,可以看看 [(题解)304. 二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/)。本题预处理前缀和的复杂度为 $O(m \times n)$。 -搜索所有子矩阵需要枚举「矩形左上角」和「矩形右下角」,复杂度是 $O(m^2 * n^2)$。 +搜索所有子矩阵需要枚举「矩形左上角」和「矩形右下角」,复杂度是 $O(m^2 \times n^2)$。 -因此,如果把本题当做二维前缀和模板题来做的话,整体复杂度是 $O(m^2 * n^2)$。 +因此,如果把本题当做二维前缀和模板题来做的话,整体复杂度是 $O(m^2 \times n^2)$。 数据范围是 $10^2$,对应的计算量是 $10^8$,理论上会超时,但当我们枚举「矩形左上角」$(i,j)$ 的时候,我们只需要搜索位于 $(i,j)$ 的右下方的点 $(p,q)$ 作为「矩形右下角」,所以其实我们是取不满 $m^2 * n^2$ 的,但仍然具有超时风险(2021/04/20 Java 测试可通过,C++ 使用 `vector` 会 TLE)。 @@ -103,8 +104,8 @@ public: } }; ``` -* 时间复杂度:预处理前缀和数组复杂度为 $O(m * n)$,查找答案的复杂度为 $O(m^2 * n^2)$。整体复杂度为 $O(m^2 * n^2)$。 -* 空间复杂度:$O(m * n)$ +* 时间复杂度:预处理前缀和数组复杂度为 $O(m \times n)$,查找答案的复杂度为 $O(m^2 \times n^2)$。整体复杂度为 $O(m^2 \times n^2)$。 +* 空间复杂度:$O(m \times n)$ --- @@ -124,13 +125,13 @@ public: 当我们确定了三条边(红色)之后,形成的子矩阵就单纯取决于第四条边的位置(黄色): -![image.png](https://pic.leetcode-cn.com/1618906986-JMALHO-image.png) +![](https://pic.leetcode-cn.com/1618906986-JMALHO-image.png) 于是问题转化为「**如何快速求得第四条边(黄色)的位置在哪**」。 我们可以进一步将问题缩小,考虑矩阵只有一行(一维)的情况: -![image.png](https://pic.leetcode-cn.com/1618907001-HvoRxP-image.png) +![](https://pic.leetcode-cn.com/1618907001-HvoRxP-image.png) 这时候问题进一步转化为「**在一维数组中,求解和不超过 K 的最大连续子数组之和**」。 @@ -163,7 +164,7 @@ $$sum[j] \leqslant k + sum[i - 1]$$ 我们先不考虑「最大化二分收益」问题,先假设我们是固定枚举「上下行」和「右边列」,这时候唯一能够确定一个子矩阵则是取决于「左边列」: -![439B50D739F1D963EB2460394C5689B5.png](https://pic.leetcode-cn.com/1618975243-AnNcYI-439B50D739F1D963EB2460394C5689B5.png) +![](https://pic.leetcode-cn.com/1618975243-AnNcYI-439B50D739F1D963EB2460394C5689B5.png) **重点是如何与「一维」问题进行关联:显然「目标子矩阵的和」等于「子矩阵的右边列 与 原矩阵的左边列 形成的子矩阵和」-「子矩阵左边列 与 原矩阵左边列 形成的子矩阵和」** @@ -251,8 +252,8 @@ public: } }; ``` -* 时间复杂度:枚举上下边界复杂度为 $O(m^2)$;枚举右边界为 $O(n)$,使用 `TreeSet`(基于红黑树)存储和查找左边界复杂度为 $O(\log{n})$。整体复杂度为 $O(m^2 * n\log{n})$ -* 空间复杂度:$O(m * n)$ +* 时间复杂度:枚举上下边界复杂度为 $O(m^2)$;枚举右边界为 $O(n)$,使用 `TreeSet`(基于红黑树)存储和查找左边界复杂度为 $O(\log{n})$。整体复杂度为 $O(m^2n\log{n})$ +* 空间复杂度:$O(m \times n)$ --- @@ -328,8 +329,8 @@ public: } }; ``` -* 时间复杂度:预处理「每行」或「每列」的前缀和,复杂度为 $O(m * n)$;枚举子矩阵的「上下行」或「左右行」,复杂度为 $O(\min(m, n)^2)$;结合二维前缀和套用一维最大连续子数组解决方案,复杂度为$\max(m, n)\log{\max(m, n)}$。整体复杂度为 $O(\min(m, n)^2 * \max(m, n)\log{\max(m, n)})$ -* 空间复杂度:$O(m * n)$ +* 时间复杂度:预处理「每行」或「每列」的前缀和,复杂度为 $O(m \times n)$;枚举子矩阵的「上下行」或「左右行」,复杂度为 $O(\min(m, n)^2)$;结合二维前缀和套用一维最大连续子数组解决方案,复杂度为$\max(m, n)\log{\max(m, n)}$。整体复杂度为 $O(\min(m, n)^2 \times \max(m, n)\log{\max(m, n)})$ +* 空间复杂度:$O(m \times n)$ --- @@ -400,7 +401,7 @@ public: } }; ``` -* 时间复杂度:预处理「每行」或「每列」的前缀和,复杂度为 $O(m * n)$;枚举子矩阵的「上下行」或「左右行」,复杂度为 $O(\min(m, n)^2)$;结合二维前缀和套用一维最大连续子数组解决方案,复杂度为$\max(m, n)\log{max(m, n)}$。整体复杂度为 $O(\min(m, n)^2 * \max(m, n)\log{\max(m, n)})$ +* 时间复杂度:预处理「每行」或「每列」的前缀和,复杂度为 $O(m \times n)$;枚举子矩阵的「上下行」或「左右行」,复杂度为 $O(\min(m, n)^2)$;结合二维前缀和套用一维最大连续子数组解决方案,复杂度为$\max(m, n)\log{max(m, n)}$。整体复杂度为 $O(\min(m, n)^2 \times \max(m, n)\log{\max(m, n)})$ * 空间复杂度:$O(\max(m, n))$ --- diff --git "a/LeetCode/361-370/368. \346\234\200\345\244\247\346\225\264\351\231\244\345\255\220\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/361-370/368. \346\234\200\345\244\247\346\225\264\351\231\244\345\255\220\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" index 5017f2ae..d14a4b84 100644 --- "a/LeetCode/361-370/368. \346\234\200\345\244\247\346\225\264\351\231\244\345\255\220\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/361-370/368. \346\234\200\345\244\247\346\225\264\351\231\244\345\255\220\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -7,14 +7,10 @@ Tag : 「序列 DP」 -给你一个由 无重复 正整数组成的集合 nums ,请你找出并返回其中最大的整除子集 answer ,子集中每一元素对 (answer[i], answer[j]) 都应当满足: -* answer[i] % answer[j] == 0 ,或 -* answer[j] % answer[i] == 0 +给你一个由无重复正整数组成的集合 `nums`,请你找出并返回其中最大的整除子集 `answer`,子集中每一元素对 $(answer[i], answer[j])$ 都应当满足:`answer[i] % answer[j] = 0` 或 `answer[j] % answer[i] = 0`。 如果存在多个有效解子集,返回其中任何一个均可。 - - 示例 1: ``` 输入:nums = [1,2,3] @@ -31,9 +27,9 @@ Tag : 「序列 DP」 ``` 提示: -* 1 <= nums.length <= 1000 -* 1 <= nums[i] <= 2 * $10^9$ -* nums 中的所有整数 互不相同 +* $1 <= nums.length <= 1000$ +* $1 <= nums[i] <= 2 \times 10^9$ +* `nums` 中的所有整数互不相同 --- @@ -94,14 +90,13 @@ Tag : 「序列 DP」 当我们求得所有的状态值之后,可以对 `f[]` 数组进行遍历,取得具体的最长「整除子集」长度和对应下标,然后使用 `g[]` 数组进行回溯,取得答案。 -代码: +Java 代码: ```Java class Solution { public List largestDivisibleSubset(int[] nums) { Arrays.sort(nums); int n = nums.length; - int[] f = new int[n]; - int[] g = new int[n]; + int[] f = new int[n], g = new int[n]; for (int i = 0; i < n; i++) { // 至少包含自身一个数,因此起始长度为 1,由自身转移而来 int len = 1, prev = i; @@ -115,10 +110,8 @@ class Solution { } } // 记录「最终长度」&「从何转移而来」 - f[i] = len; - g[i] = prev; + f[i] = len; g[i] = prev; } - // 遍历所有的 f[i],取得「最大长度」和「对应下标」 int max = -1, idx = -1; for (int i = 0; i < n; i++) { @@ -127,7 +120,6 @@ class Solution { max = f[i]; } } - // 使用 g[] 数组回溯出具体方案 List ans = new ArrayList<>(); while (ans.size() != max) { @@ -138,6 +130,66 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector largestDivisibleSubset(vector& nums) { + sort(nums.begin(), nums.end()); + int n = nums.size(); + vector f(n, 0), g(n, 0); + for (int i = 0; i < n; i++) { + int len = 1, prev = i; + for (int j = 0; j < i; j++) { + if (nums[i] % nums[j] == 0) { + if (f[j] + 1 > len) { + len = f[j] + 1; + prev = j; + } + } + } + f[i] = len; g[i] = prev; + } + int max = -1, idx = -1; + for (int i = 0; i < n; i++) { + if (f[i] > max) { + max = f[i]; + idx = i; + } + } + vector ans; + while (ans.size() != max) { + ans.push_back(nums[idx]); + idx = g[idx]; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def largestDivisibleSubset(self, nums: List[int]) -> List[int]: + nums.sort() + n = len(nums) + f, g = [0] * n, [0] * n + for i in range(n): + lenv, prev = 1, i + for j in range(i): + if nums[i] % nums[j] == 0: + if f[j] + 1 > lenv: + lenv, prev = f[j] + 1, j + f[i], g[i] = lenv, prev + maxv, idx = -1, -1 + for i in range(n): + if f[i] > maxv: + maxv, idx = f[i], i + ans = [] + while len(ans) != maxv: + ans.append(nums[idx]) + idx = g[idx] + return ans +``` * 时间复杂度:$O(n^2)$ * 空间复杂度:$O(n)$ @@ -158,10 +210,10 @@ class Solution { 因此需要证得由 $a | b$ 和 $b | c$,可推导出 $a | c$ 的传递性: -由 $a | b$ 可得 $b = x * a$ -由 $b | c$ 可得 $c = y * b$ +由 $a | b$ 可得 $b = x \times a$ +由 $b | c$ 可得 $c = y \times b$ -最终有 $c = y * b = y * x * a$,由于 $x$ 和 $y$ 都是整数,因此可得 $a | c$。 +最终有 $c = y \times b = y \times x \times a$,由于 $x$ 和 $y$ 都是整数,因此可得 $a | c$。 得证「倍数/约数关系」具有传递性。 diff --git "a/LeetCode/391-400/395. \350\207\263\345\260\221\346\234\211 K \344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/391-400/395. \350\207\263\345\260\221\346\234\211 K \344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" index 67529033..11bc4b61 100644 --- "a/LeetCode/391-400/395. \350\207\263\345\260\221\346\234\211 K \344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/391-400/395. \350\207\263\345\260\221\346\234\211 K \344\270\252\351\207\215\345\244\215\345\255\227\347\254\246\347\232\204\346\234\200\351\225\277\345\255\220\344\270\262\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,26 +6,30 @@ Tag : 「双指针」、「枚举」 -给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。 +给你一个字符串 `s` 和一个整数 `k`,请你找出 `s` 中的最长子串,要求该子串中的每一字符出现次数都不少于 `k`,返回这一子串的长度。 示例 1: ``` 输入:s = "aaabb", k = 3 + 输出:3 + 解释:最长子串为 "aaa" ,其中 'a' 重复了 3 次。 ``` 示例 2: ``` 输入:s = "ababbc", k = 2 + 输出:5 + 解释:最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。 ``` 提示: -* 1 <= s.length <= $10^4$ -* s 仅由小写英文字母组成 -* 1 <= k <= $10^5$ +* $1 <= s.length <= 10^4$ +* `s` 仅由小写英文字母组成 +* $1 <= k <= 10^5$ --- @@ -58,9 +62,9 @@ Tag : 「双指针」、「枚举」 1. 右端点往右移动必然会导致字符类型数量增加(或不变) 2. 左端点往右移动必然会导致字符类型数量减少(或不变) -当然,我们还需要记录有多少字符符合要求(出现次数不少于 k),当区间内所有字符都符合时更新答案。 +当然,我们还需要记录有多少字符符合要求(出现次数不少于 `k`),当区间内所有字符都符合时更新答案。 -代码: +Java 代码: ```Java class Solution { public int longestSubstring(String s, int k) { @@ -95,18 +99,48 @@ class Solution { } } ``` -* 时间复杂度:枚举 26 种可能性,每种可能性会扫描一遍数组。复杂度为 $O(n)$ -* 空间复杂度:$O(n)$ +C++ 代码: +```C++ +class Solution { +public: + int longestSubstring(string s, int k) { + int ans = 0; + int n = s.length(); + vector cnt(26, 0); + for (int p = 1; p <= 26; p++) { + fill(cnt.begin(), cnt.end(), 0); + for (int i = 0, j = 0, tot = 0, sum = 0; i < n; i++) { + int u = s[i] - 'a'; + cnt[u]++; + if (cnt[u] == 1) tot++; + if (cnt[u] == k) sum++; + while (tot > p) { + int t = s[j++] - 'a'; + cnt[t]--; + if (cnt[t] == 0) tot--; + if (cnt[t] == k - 1) sum--; + } + if (tot == sum) ans = max(ans, i - j + 1); + } + } + return ans; + } +}; +``` +* 时间复杂度:枚举 $C = 26$ 种可能性,每种可能性会扫描一遍数组。复杂度为 $O(C \times n)$ +* 空间复杂度:$O(C)$ -*** +--- ### 总结 & 补充 -【总结】: +##### 总结 「当确定了窗口内所包含的字符数量时,区间重新具有了二段性质」。这是本题的滑动窗口解法和迄今为止做的滑动窗口题目的最大不同,本题需要手动增加限制,即限制窗口内字符种类。 -【补充】这里解释一下「为什么需要先枚举 26 种可能性」: +##### 补充 + +这里解释一下「为什么需要先枚举 26 种可能性」。 首先我们知道「**答案子串的左边界左侧的字符以及右边界右侧的字符一定不会出现在子串中,否则就不会是最优解**」。 @@ -120,11 +154,11 @@ class Solution { 然后遍历 26 种可能性(答案所包含的字符种类数量),**对每种可能性应用滑动窗口(由上述性质确保正确),可以得到每种可能性的最大值(局部最优),由所有可能性的最大值可以得出答案(全局最优)**。 -*** +--- ### 点评 -这道题的突破口分析其实和 [1178. 猜字谜](https://leetcode-cn.com/problems/number-of-valid-words-for-each-puzzle/solution/xiang-jin-zhu-shi-xiang-jie-po-su-wei-yu-3cr2/) 类似。 +这道题的突破口分析其实和 [1178. 猜字谜](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247506176&idx=1&sn=2cff84a5e92cede569abf569607fb7b6&chksm=fd9f721fcae8fb098dd56d53505f5bee5f8ec6d30b20f0d6ac90637b938ca6e86a530931a026#rd) 类似。 解决思路:当我们采用常规的分析思路发现无法进行时,**要去关注一下数据范围中「数值小」的值。因为数值小其实是代表了「可枚举」,往往是解题或者降低复杂度的一个重要(甚至是唯一)的突破口。** diff --git "a/LeetCode/41-50/41. \347\274\272\345\244\261\347\232\204\347\254\254\344\270\200\344\270\252\346\255\243\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/41-50/41. \347\274\272\345\244\261\347\232\204\347\254\254\344\270\200\344\270\252\346\255\243\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" index 1471317b..a4f2cde0 100644 --- "a/LeetCode/41-50/41. \347\274\272\345\244\261\347\232\204\347\254\254\344\270\200\344\270\252\346\255\243\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/41-50/41. \347\274\272\345\244\261\347\232\204\347\254\254\344\270\200\344\270\252\346\255\243\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -51,7 +51,7 @@ Tag : 「桶排序」、「原地哈希」 例如样例预处理后的数组 `[1,-1,3,4]` 中第一个 `nums[i] != i + 1` 的是数字 2(i = 1)。 -代码: +Java 代码: ```Java class Solution { public int firstMissingPositive(int[] nums) { @@ -73,6 +73,57 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int firstMissingPositive(vector& nums) { + int n = nums.size(); + for (int i = 0; i < n; i++) { + while (nums[i] >= 1 && nums[i] <= n && nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) { + swap(nums[i], nums[nums[i] - 1]); + } + } + for (int i = 0; i < n; i++) { + if (nums[i] != i + 1) return i + 1; + } + return n + 1; + } +}; +``` +Python 代码: +```Python +class Solution: + def firstMissingPositive(self, nums: List[int]) -> int: + n = len(nums) + for i in range(n): + while 1 <= nums[i] <= n and nums[i] != i + 1 and nums[i] != nums[nums[i] - 1]: + nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1] + for i in range(n): + if nums[i] != i + 1: + return i + 1 + return n + 1 +``` +TypeScript 代码: +```TypeScript +function swap(nums, a, b) { + const c = nums[a]; + nums[a] = nums[b]; + nums[b] = c; +} +function firstMissingPositive(nums: number[]): number { + const n: number = nums.length; + for (let i: number = 0; i < n; i++) { + while (nums[i] >= 1 && nums[i] <= n && nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) { + swap(nums, i, nums[i] - 1); + } + } + for (let i: number = 0; i < n; i++) { + if (nums[i] != i + 1) return i + 1; + } + return n + 1; +}; +``` * 时间复杂度:每个数字应该被挪动的数都会被一次性移动到目标位置。复杂度为 $O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/41-50/45. \350\267\263\350\267\203\346\270\270\346\210\217 II\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/41-50/45. \350\267\263\350\267\203\346\270\270\346\210\217 II\357\274\210\344\270\255\347\255\211\357\274\211.md" index 5ca08bb0..e67a7809 100644 --- "a/LeetCode/41-50/45. \350\267\263\350\267\203\346\270\270\346\210\217 II\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/41-50/45. \350\267\263\350\267\203\346\270\270\346\210\217 II\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -16,7 +16,6 @@ Tag : 「贪心」、「线性 DP」、「双指针」 - 示例 1: ``` 输入: [2,3,1,1,4] @@ -34,8 +33,8 @@ Tag : 「贪心」、「线性 DP」、「双指针」 ``` 提示: -* 1 <= nums.length <= 1000 -* 0 <= nums[i] <= $10^5$ +* $1 <= nums.length <= 1000$ +* $0 <= nums[i] <= 10^5$ --- @@ -45,12 +44,11 @@ Tag : 「贪心」、「线性 DP」、「双指针」 本题的 BFS 解法的复杂度是 $O(n^2)$,数据范围为 $10^3$,可以过。 -代码: +Java 代码: ```Java class Solution { public int jump(int[] nums) { - int n = nums.length; - int ans = 0; + int n = nums.length, ans = 0; boolean[] st = new boolean[n]; Deque d = new ArrayDeque<>(); st[0] = true; @@ -73,6 +71,58 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int jump(vector& nums) { + int n = nums.size(), ans = 0; + bool st[n]; + memset(st, false, sizeof(st)); + deque d; + st[0] = true; + d.push_back(0); + while (!d.empty()) { + int size = d.size(); + while (size-- > 0) { + int idx = d.front(); + d.pop_front(); + if (idx == n - 1) return ans; + for (int i = idx + 1; i <= idx + nums[idx] && i < n; i++) { + if (!st[i]) { + st[i] = true; + d.push_back(i); + } + } + } + ans++; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def jump(self, nums: List[int]) -> int: + n, ans = len(nums), 0 + st = [False] * n + d = deque([0]) + st[0] = True + while d: + size = len(d) + while size > 0: + idx = d.popleft() + if idx == n - 1: + return ans + for i in range(idx + 1, min(idx + nums[idx] + 1, n)): + if not st[i]: + st[i] = True + d.append(i) + size -= 1 + ans += 1 + return ans +``` * 时间复杂度:如果每个点跳跃的距离足够长的话,每次都会将当前点「后面的所有点」进行循环入队操作(由于 st 的存在,不一定都能入队,但是每个点都需要被循环一下)。复杂度为 $O(n^2)$ * 空间复杂度:队列中最多有 $n - 1$ 个元素。复杂度为 $O(n)$ @@ -104,8 +154,7 @@ class Solution { 我们知道最后一个点前面可能会有很多个点能够一步到达最后一个点。 -![image.png](https://pic.leetcode-cn.com/1621327340-bRhges-image.png) - +![](https://pic.leetcode-cn.com/1621327340-bRhges-image.png) 也就是有 $f[n - 1] = min(f[n - k],...,f[n - 3],f[n - 2]) + 1$。 @@ -129,7 +178,7 @@ class Solution { 因此这个思路其实是一个「双指针 + 贪心 + 动态规划」的一个解法。 -代码: +Java 代码: ```Java class Solution { public int jump(int[] nums) { @@ -143,6 +192,34 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int jump(vector& nums) { + int n = nums.size(); + vector f(n, 0); + for (int i = 1, j = 0; i < n; i++) { + while (j + nums[j] < i) j++; + f[i] = f[j] + 1; + } + return f[n - 1]; + } +}; +``` +Python 代码: +```Python +class Solution: + def jump(self, nums: List[int]) -> int: + n = len(nums) + f = [0] * n + for i in range(1, n): + j = 0 + while j + nums[j] < i: + j += 1 + f[i] = f[j] + 1 + return f[-1] +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/421-430/421. \346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\345\274\202\346\210\226\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/421-430/421. \346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\345\274\202\346\210\226\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" index 016b8b57..931ef4af 100644 --- "a/LeetCode/421-430/421. \346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\345\274\202\346\210\226\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/421-430/421. \346\225\260\347\273\204\344\270\255\344\270\244\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\345\274\202\346\210\226\345\200\274\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,7 +6,7 @@ Tag : 「字典树」、「贪心」 -给你一个整数数组 $nums$ ,返回 `nums[i] XOR nums[j]` 的最大运算结果,其中 $0 ≤ i ≤ j < n$ 。 +给你一个整数数组 $nums$,返回 `nums[i] XOR nums[j]` 的最大运算结果,其中 $0 ≤ i ≤ j < n$ 。 **进阶**:你可以在 $O(n)$ 的时间解决这个问题吗? @@ -133,40 +133,6 @@ class Solution { } } ``` -Python 代码: -```Python -class Solution: - def findMaximumXOR(self, nums: List[int]) -> int: - N, idx = (len(nums) + 1) * 31, 0 - tr = [[0, 0] for _ in range(N)] - def add(x): - nonlocal idx - p = 0 - for i in range(31, -1, -1): - u = (x >> i) & 1 - if tr[p][u] == 0: - idx += 1 - tr[p][u] = idx - p = tr[p][u] - def getVal(x): - p, ans = 0, 0 - for i in range(31, -1, -1): - a = (x >> i) & 1 - b = 1 - a - if tr[p][b] != 0: - ans |= (b << i) - p = tr[p][b] - else: - ans |= (a << i) - p = tr[p][a] - return ans - ans = 0 - for i in nums: - add(i) - j = getVal(i) - ans = max(ans, i ^ j) - return ans -``` C++ 代码: ```C++ class Solution { @@ -214,6 +180,40 @@ public: } }; ``` +Python 代码: +```Python +class Solution: + def findMaximumXOR(self, nums: List[int]) -> int: + N, idx = (len(nums) + 1) * 31, 0 + tr = [[0, 0] for _ in range(N)] + def add(x): + nonlocal idx + p = 0 + for i in range(31, -1, -1): + u = (x >> i) & 1 + if tr[p][u] == 0: + idx += 1 + tr[p][u] = idx + p = tr[p][u] + def getVal(x): + p, ans = 0, 0 + for i in range(31, -1, -1): + a = (x >> i) & 1 + b = 1 - a + if tr[p][b] != 0: + ans |= (b << i) + p = tr[p][b] + else: + ans |= (a << i) + p = tr[p][a] + return ans + ans = 0 + for i in nums: + add(i) + j = getVal(i) + ans = max(ans, i ^ j) + return ans +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(C)$ diff --git "a/LeetCode/421-430/424. \346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/421-430/424. \346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246\357\274\210\344\270\255\347\255\211\357\274\211.md" index be8143a2..ffa23ff0 100644 --- "a/LeetCode/421-430/424. \346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/421-430/424. \346\233\277\346\215\242\345\220\216\347\232\204\346\234\200\351\225\277\351\207\215\345\244\215\345\255\227\347\254\246\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,12 +6,10 @@ Tag : 「双指针」、「滑动窗口」 -给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 $k$ 次。 +给你一个仅由大写英文字母组成的字符串,你可以将任意位置上的字符替换成另外的字符,总共可最多替换 `k` 次。 在执行上述操作后,找到包含重复字母的最长子串的长度。 -注意:字符串长度 和 $k$ 不会超过 $10^4$。 - 示例 1: ``` 输入:s = "ABAB", k = 2 @@ -30,6 +28,10 @@ Tag : 「双指针」、「滑动窗口」 将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。 子串 "BBBB" 有最长重复字母, 答案为 4。 ``` +提示: +* $1 <= s.length <= 10^5$ +* `s` 仅由大写英文字母组成 +* $0 <= k <= s.length$ --- @@ -43,35 +45,136 @@ Tag : 「双指针」、「滑动窗口」 当找到这样的性质之后,我们可以对 `s` 进行遍历,每次让 `r` 右移并计数,如果符合条件,更新最大值;如果不符合条件,让 `l` 右移,更新计数,直到符合条件。 -代码: +Java 代码: ```Java class Solution { public int characterReplacement(String s, int k) { - char[] cs = s.toCharArray(); + int n = s.length(), ans = 0; int[] cnt = new int[26]; - int ans = 0; - for (int l = 0, r = 0; r < s.length(); r++) { - cnt[cs[r] - 'A']++; - while (!check(cnt, k)) cnt[cs[l++] - 'A']--; + for (int l = 0, r = 0; r < n; r++) { + cnt[s.charAt(r) - 'A']++; + while (!check(cnt, k)) cnt[s.charAt(l++) - 'A']--; ans = Math.max(ans, r - l + 1); } return ans; } boolean check(int[] cnt, int k) { - int max = 0, sum = 0; - for (int i = 0; i < 26; i++) { - max = Math.max(max, cnt[i]); - sum += cnt[i]; + int maxv = 0, sumv = 0; + for (int x : cnt) { + maxv = Math.max(maxv, x); + sumv += x; } - return sum - max <= k; + return sumv - maxv <= k; } } ``` -* 时间复杂度:使用 `l` 和 `r` 指针对 `s` 进行单次扫描,复杂度为 $O(n)$;令 $C = 26$ 为字符集大小,`check` 方法复杂度为 $O(C)$。整体复杂度为 $O(n * C)$。 +C++ 代码: +```C++ +class Solution { +public: + int characterReplacement(string s, int k) { + int n = s.length(), ans = 0; + vector cnt(26, 0); + for (int l = 0, r = 0; r < n; r++) { + cnt[s[r] - 'A']++; + while (!check(cnt, k)) cnt[s[l++] - 'A']--; + ans = max(ans, r - l + 1); + } + return ans; + } + bool check(vector& cnt, int k) { + int maxv = 0, sumv = 0; + for (int i = 0; i < 26; i++) { + maxv = max(maxv, cnt[i]); + sumv += cnt[i]; + } + return sumv - maxv <= k; + } +}; +``` +Python 代码: +```Python +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + n, ans = len(s), 0 + l = 0 + cnt = [0] * 26 + for r in range(n): + cnt[ord(s[r]) - ord('A')] += 1 + while not self.check(cnt, k): + cnt[ord(s[l]) - ord('A')] -= 1 + l += 1 + ans = max(ans, r - l + 1) + return ans + def check(self, cnt: List[int], k: int) -> bool: + maxv, sumv = 0, 0 + for i in range(26): + maxv = max(maxv, cnt[i]) + sumv += cnt[i] + return sumv - maxv <= k +``` +* 时间复杂度:使用 `l` 和 `r` 指针对 `s` 进行单次扫描,复杂度为 $O(n)$;令 $C = 26$ 为字符集大小,`check` 方法复杂度为 $O(C)$。整体复杂度为 $O(n \times C)$。 * 空间复杂度:$O(C)$ --- +### 滑动窗口(优化) + +方法一是直观且正确的,但我们有更好的做法。 + + +Java 代码: +```Java +class Solution { + public int characterReplacement(String s, int k) { + int n = s.length(), ans = 0; + int[] cnt = new int[26]; + for (int l = 0, r = 0, maxv = 0; r < n; r++) { + maxv = Math.max(maxv, ++cnt[s.charAt(r) - 'A']); + if (r - l + 1 - maxv > k) cnt[s.charAt(l++) - 'A']--; + ans = Math.max(ans, r - l + 1); + } + return ans; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + int characterReplacement(string s, int k) { + int n = s.length(), ans = 0; + vector cnt(26, 0); + for (int l = 0, r = 0, maxv = 0; r < n; r++) { + maxv = max(maxv, ++cnt[s[r] - 'A']); + if (r - l + 1 - maxv > k) cnt[s[l++] - 'A']--; + ans = max(ans, r - l + 1); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + n, maxv, ans = len(s), 0, 0 + l = 0 + cnt = [0] * 26 + for r in range(n): + cnt[ord(s[r]) - ord('A')] += 1 + maxv = max(maxv, cnt[ord(s[r]) - ord('A')]) + if r - l + 1 - maxv > k: + cnt[ord(s[l]) - ord('A')] -= 1 + l += 1 + ans = max(ans, r - l + 1) + return ans +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(C)$,其中 $C = 26$ 为字符集大小 + +--- + ### 最后 这是我们「刷穿 LeetCode」系列文章的第 `No.424` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 diff --git "a/LeetCode/421-430/427. \345\273\272\347\253\213\345\233\233\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/421-430/427. \345\273\272\347\253\213\345\233\233\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" index 70aef11b..07f7ee8c 100644 --- "a/LeetCode/421-430/427. \345\273\272\347\253\213\345\233\233\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/421-430/427. \345\273\272\347\253\213\345\233\233\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,13 +6,17 @@ Tag : 「递归」、「前缀和」 -给你一个 $n \times n$ 矩阵 `grid` ,矩阵由若干 $0$ 和 $1$ 组成。请你用四叉树表示该矩阵 `grid` 。 +给你一个 $n \times n$ 矩阵 `grid` ,矩阵由若干 $0$ 和 $1$ 组成。 -你需要返回能表示矩阵的 四叉树 的根结点。 +请你用四叉树表示该矩阵 `grid` 。 -注意,当 `isLeaf` 为 `False` 时,你可以把 `True` 或者 `False` 赋值给节点,两种值都会被判题机制 接受 。 +你需要返回能表示矩阵的四叉树的根结点。 -四叉树数据结构中,每个内部节点只有四个子节点。此外,每个节点都有两个属性: +注意,当 `isLeaf` 为 `False` 时,你可以把 `True` 或者 `False` 赋值给节点,两种值都会被判题机制接受。 + +四叉树数据结构中,每个内部节点只有四个子节点。 + +此外,每个节点都有两个属性: * `val`:储存叶子结点所代表的区域的值。$1$ 对应 `True`,$0$ 对应 `False`; * `isLeaf`: 当这个节点是一个叶子结点时为 `True`,如果它有 $4$ 个子节点则为 `False` 。 @@ -28,7 +32,7 @@ class Node { ``` 我们可以按以下步骤为二维区域构建四叉树: -1. 如果当前网格的值相同(即,全为 $0$ 或者全为 $1$),将 `isLeaf` 设为 `True` ,将 `val` 设为网格相应的值,并将四个子节点都设为 `Null` 然后停止。 +1. 如果当前网格的值相同(即全为 $0$ 或者全为 $1$),将 `isLeaf` 设为 `True` ,将 `val` 设为网格相应的值,并将四个子节点都设为 `Null` 然后停止。 2. 如果当前网格的值不同,将 `isLeaf` 设为 `False`, 将 `val` 设为任意值,然后如下图所示,将当前网格划分为四个子网格。 3. 使用适当的子网格递归每个子节点。 @@ -38,7 +42,7 @@ class Node { 输出为使用层序遍历后四叉树的序列化形式,其中 `null` 表示路径终止符,其下面不存在节点。 -它与二叉树的序列化非常相似。唯一的区别是节点以列表形式表示 $[isLeaf, val]$ 。 +它与二叉树的序列化非常相似,唯一的区别是节点以列表形式表示 $[isLeaf, val]$ 。 如果 `isLeaf` 或者 `val` 的值为 `True` ,则表示它在列表 $[isLeaf, val]$ 中的值为 $1$ ;如果 `isLeaf` 或者 `val` 的值为 `False` ,则表示值为 $0$ 。 @@ -90,8 +94,8 @@ topRight 具有不同的值,因此我们将其再分为 4 个子网格,这 ``` 提示: -* $n == grid.length == grid[i].length$ -* $n == 2^x$ 其中 $0 <= x <= 6$ +* $n = grid.length = grid[i].length$ +* $n = 2^x$ 其中 $0 <= x <= 6$ --- @@ -107,7 +111,7 @@ topRight 具有不同的值,因此我们将其再分为 4 个子网格,这 由于矩阵大小最多为 $2^6 = 64$ ,因此判断某个子矩阵是否为全 $0$ 或全 $1$ 的操作用「前缀和」或者是「暴力」来做都可以。 -代码: +Java 代码: ```Java class Solution { int[][] g; @@ -134,6 +138,83 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector> g; + Node* construct(vector>& grid) { + g = grid; + return dfs(0, 0, g.size() - 1, g.size() - 1); + } + Node* dfs(int a, int b, int c, int d) { + bool ok = true; + int t = g[a][b]; + for (int i = a; i <= c && ok; ++i) { + for (int j = b; j <= d && ok; ++j) { + if (g[i][j] != t) ok = false; + } + } + if (ok) return new Node(t == 1, true); + Node* root = new Node(t == 1, false); + int dx = c - a + 1, dy = d - b + 1; + root->topLeft = dfs(a, b, a + dx / 2 - 1, b + dy / 2 - 1); + root->topRight = dfs(a, b + dy / 2, a + dx / 2 - 1, d); + root->bottomLeft = dfs(a + dx / 2, b, c, b + dy / 2 - 1); + root->bottomRight = dfs(a + dx / 2, b + dy / 2, c, d); + return root; + } +}; +``` +Python 代码: +```Python +class Solution: + def construct(self, grid: List[List[int]]) -> 'Node': + g = grid + def dfs(a, b, c, d): + ok = True + t = g[a][b] + for i in range(a, c + 1): + for j in range(b, d + 1): + if g[i][j] != t: + ok = False + break + if ok: + return Node(t == 1, True) + root = Node(t == 1, False) + dx = c - a + 1 + dy = d - b + 1 + root.topLeft = dfs(a, b, a + dx // 2 - 1, b + dy // 2 - 1) + root.topRight = dfs(a, b + dy // 2, a + dx // 2 - 1, d) + root.bottomLeft = dfs(a + dx // 2, b, c, b + dy // 2 - 1) + root.bottomRight = dfs(a + dx // 2, b + dy // 2, c, d) + return root + return dfs(0, 0, len(grid) - 1, len(grid) - 1) +``` +TypeScript 代码: +```TypeScript +function construct(grid: number[][]): _Node | null { + const g = grid; + const dfs = function(a: number, b: number, c: number, d: number): Node { + let ok = true; + let t = g[a][b]; + for (let i = a; i <= c && ok; i++) { + for (let j = b; j <= d && ok; j++) { + if (g[i][j] !== t) ok = false; + } + } + if (ok) return new Node(t === 1, true); + let root = new Node(t === 1, false); + let dx = c - a + 1, dy = d - b + 1; + root.topLeft = dfs(a, b, Math.floor(a + dx / 2) - 1, Math.floor(b + dy / 2) - 1); + root.topRight = dfs(a, Math.ceil(b + dy / 2), Math.floor(a + dx / 2) - 1, d); + root.bottomLeft = dfs(Math.ceil(a + dx / 2), b, c, Math.floor(b + dy / 2) - 1); + root.bottomRight = dfs(Math.ceil(a + dx / 2), Math.ceil(b + dy / 2), c, d); + return root; + }; + return dfs(0, 0, g.length - 1, g.length - 1); +}; +``` * 时间复杂度:递归的复杂度分析要根据主定理,假设矩阵大小为 $n \times n$,根据主定理 $T(n) = aT(\frac{n}{b}) + f(n)$,单次递归最多会产生 $4$ 个子问题(由大矩阵递归 $4$ 个小矩阵),因此问题递归子问题数量 $a = 4$,而子问题规模缩减系数 $b$ 为原本的一半(子矩阵的大小为 $\frac{n}{2} \times \frac{n}{2}$),剩余的 $f(n)$ 为判断全 $0$ 和 全 $1$ 的时间开销,不考虑标识位 $ok$ 带来的剪枝效果,每次判断全 $0$ 或全 $1$ 的复杂度与当前问题规模相等,即 $f(n) = O(n^2)$,但整个大小为 $n \times n$ 矩阵每次进行长宽减半的子矩阵拆分,最多会被拆分为 $\log{n}$ 次,因此这部分总的计算量为 $\log{n} \times n^2$ 。整体复杂度为 $O(n^2 + \log{n} \times n^2)$ * 空间复杂度:忽略递归带来的额外空间开销,复杂度为 $O(1)$ @@ -143,7 +224,7 @@ class Solution { 使用前缀和优化「判断全 $0$ 和全 $1$」的操作:对矩阵 `grid` 求前缀和数组 `sum`,对于一个「以左上角为 $(a, b)$,右下角为 $(c, d)$ 」的子矩阵而言,其所包含的格子总数为 $tot = (c - a + 1) * (d - b + 1)$ 个,当且仅当矩阵和为 $0$ 或 $tot$ 时,矩阵全 $0$ 或 $1$。 -代码: +Java 代码: ```Java class Solution { static int[][] sum = new int[70][70]; @@ -171,6 +252,88 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector> g; + vector> sum; + Node* construct(vector>& grid) { + g = grid; + int n = grid.size(); + sum.assign(n + 1, vector(n + 1, 0)); + for (int i = 1; i <= n; ++i) { + for (int j = 1; j <= n; ++j) { + sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + g[i - 1][j - 1]; + } + } + return dfs(0, 0, n - 1, n - 1); + } + Node* dfs(int a, int b, int c, int d) { + int cur = sum[c + 1][d + 1] - sum[a][d + 1] - sum[c + 1][b] + sum[a][b]; + int dx = c - a + 1, dy = d - b + 1, tot = dx * dy; + if (cur == 0 || cur == tot) return new Node(g[a][b] == 1, true); + Node* root = new Node(g[a][b] == 1, false); + root->topLeft = dfs(a, b, a + dx / 2 - 1, b + dy / 2 - 1); + root->topRight = dfs(a, b + dy / 2, a + dx / 2 - 1, d); + root->bottomLeft = dfs(a + dx / 2, b, c, b + dy / 2 - 1); + root->bottomRight = dfs(a + dx / 2, b + dy / 2, c, d); + return root; + } +}; +``` +Python 代码: +```Python +class Solution: + def construct(self, grid: List[List[int]]) -> 'Node': + n = len(grid) + sumv = [[0] * (n + 1) for _ in range(n + 1)] + for i in range(1, n + 1): + for j in range(1, n + 1): + sumv[i][j] = sumv[i - 1][j] + sumv[i][j - 1] - sumv[i - 1][j - 1] + grid[i - 1][j - 1] + def dfs(a, b, c, d): + cur = sumv[c + 1][d + 1] - sumv[a][d + 1] - sumv[c + 1][b] + sumv[a][b] + dx, dy = c - a + 1, d - b + 1 + tot = dx * dy + if cur == 0 or cur == tot: + return Node(grid[a][b] == 1, True) + root = Node(grid[a][b] == 1, False) + root.topLeft = dfs(a, b, a + dx // 2 - 1, b + dy // 2 - 1) + root.topRight = dfs(a, b + dy // 2, a + dx // 2 - 1, d) + root.bottomLeft = dfs(a + dx // 2, b, c, b + dy // 2 - 1) + root.bottomRight = dfs(a + dx // 2, b + dy // 2, c, d) + return root + return dfs(0, 0, len(grid) - 1, len(grid) - 1) +``` +TypeScript 代码: +```TypeScript +function construct(grid: number[][]): _Node | null { + const g = grid; + const n = g.length; + const sum = Array(n + 1).fill(0).map(() => Array(n + 1).fill(0)); + for (let i = 1; i <= n; i++) { + for (let j = 1; j <= n; j++) { + sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + g[i - 1][j - 1]; + } + } + const dfs = function(a: number, b: number, c: number, d: number): Node { + const cur = sum[c + 1][d + 1] - sum[a][d + 1] - sum[c + 1][b] + sum[a][b]; + const dx = c - a + 1; + const dy = d - b + 1; + const tot = dx * dy; + if (cur === 0 || cur === tot) { + return new Node(g[a][b] === 1, true); + } + const root = new Node(g[a][b] === 1, false); + root.topLeft = dfs(a, b, Math.floor(a + dx / 2) - 1, Math.floor(b + dy / 2) - 1); + root.topRight = dfs(a, Math.ceil(b + dy / 2), Math.floor(a + dx / 2) - 1, d); + root.bottomLeft = dfs(Math.ceil(a + dx / 2), b, c, Math.floor(b + dy / 2) - 1); + root.bottomRight = dfs(Math.ceil(a + dx / 2), Math.ceil(b + dy / 2), c, d); + return root; + }; + return dfs(0, 0, n - 1, n - 1); +}; +``` * 时间复杂度:分析同理,但判断全 $0$ 和全 $1$ 的复杂度下降为 $O(1)$,整体复杂度为 $O(n^2 + \log{n})$ * 空间复杂度:忽略递归带来的额外空间开销,复杂度为 $O(n^2)$ diff --git "a/LeetCode/441-450/448. \346\211\276\345\210\260\346\211\200\346\234\211\346\225\260\347\273\204\344\270\255\346\266\210\345\244\261\347\232\204\346\225\260\345\255\227\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/441-450/448. \346\211\276\345\210\260\346\211\200\346\234\211\346\225\260\347\273\204\344\270\255\346\266\210\345\244\261\347\232\204\346\225\260\345\255\227\357\274\210\347\256\200\345\215\225\357\274\211.md" index 42a252e5..3fdbd606 100644 --- "a/LeetCode/441-450/448. \346\211\276\345\210\260\346\211\200\346\234\211\346\225\260\347\273\204\344\270\255\346\266\210\345\244\261\347\232\204\346\225\260\345\255\227\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/441-450/448. \346\211\276\345\210\260\346\211\200\346\234\211\346\225\260\347\273\204\344\270\255\346\266\210\345\244\261\347\232\204\346\225\260\345\255\227\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,20 +6,30 @@ Tag : 「排序」、「原地哈希」 -给定一个范围在  $1 ≤ a[i] ≤ n$ ( $n$ = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。 +给你一个含 `n` 个整数的数组 `nums` ,其中 `nums[i]` 在区间 `[1, n]` 内。 -找到所有在 $[1, n]$ 范围之间没有出现在数组中的数字。 +请你找出所有在 `[1, n]` 范围内但没有出现在 `nums` 中的数字,并以数组的形式返回结果。 -您能在不使用额外空间且时间复杂度为 $O(n)$ 的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。 +示例 1: -示例: ``` -输入: -[4,3,2,7,8,2,3,1] +输入:nums = [4,3,2,7,8,2,3,1] -输出: -[5,6] +输出:[5,6] ``` +示例 2: +``` +输入:nums = [1,1] + +输出:[2] +``` + +提示: +* $n = nums.length$ +* $1 <= n <= 10^5$ +* $1 <= nums[i] <= n$ + +进阶:你能在不使用额外空间且时间复杂度为 $O(n)$ 的情况下解决这个问题吗? 你可以假定返回的数组不算在额外空间内。 --- @@ -27,15 +37,13 @@ Tag : 「排序」、「原地哈希」 题目规定了 $1 ≤ a[i] ≤ n$,因此我们可以使用「桶排序」的思路,将每个数放在其应该出现的位置上。 -基本思路为: - -按照桶排序思路进行预处理:保证 $1$ 出现在 $nums[0]$ 的位置上,$2$ 出现在 $nums[1]$ 的位置上,…,$n$ 出现在 $nums[n - 1]$ 的位置上。不在 $[1, n]$ 范围内的数不用动。 +基本思路为:按照桶排序思路进行预处理,保证 $1$ 出现在 $nums[0]$ 的位置上,$2$ 出现在 $nums[1]$ 的位置上,…,$n$ 出现在 $nums[n - 1]$ 的位置上。不在 $[1, n]$ 范围内的数不用动。 例如样例中 $[4,3,2,7,8,2,3,1]$ 将会被预处理成 $[1,2,3,4,3,2,7,8]$。 -遍历 $nums$,将不符合 `nums[i] != i + 1` 的数字加入结果集 ~ +遍历 $nums$,将不符合 `nums[i] != i + 1` 的数字加入结果集。 -代码: +Java 代码: ```Java class Solution { public List findDisappearedNumbers(int[] nums) { @@ -56,6 +64,59 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector findDisappearedNumbers(vector& nums) { + int n = nums.size(); + for (int i = 0; i < n; i++) { + while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) swap(nums[i], nums[nums[i] - 1]); + } + vector ans; + for (int i = 0; i < n; i++) { + if (nums[i] != i + 1) ans.push_back(i + 1); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def findDisappearedNumbers(self, nums): + n = len(nums) + for i in range(n): + while nums[i] != i + 1 and nums[nums[i] - 1] != nums[i]: + self.swap(nums, i, nums[i] - 1) + ans = [] + for i in range(n): + if nums[i] != i + 1: + ans.append(i + 1) + return ans + + def swap(self, nums, a, b): + nums[a], nums[b] = nums[b], nums[a] +``` +TypeScript 代码: +```TypeScript +function swap(nums: number[], a: number, b: number): void { + [nums[a], nums[b]] = [nums[b], nums[a]]; +} +function findDisappearedNumbers(nums: number[]): number[] { + const n = nums.length; + for (let i = 0; i < n; i++) { + while (nums[i] !== i + 1 && nums[nums[i] - 1] !== nums[i]) { + swap(nums, i, nums[i] - 1); + } + } + const ans = []; + for (let i = 0; i < n; i++) { + if (nums[i] !== i + 1) ans.push(i + 1); + } + return ans; +}; +``` * 时间复杂度:每个数字最多挪动一次。复杂度为 $O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/481-490/485. \346\234\200\345\244\247\350\277\236\347\273\255 1 \347\232\204\344\270\252\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/481-490/485. \346\234\200\345\244\247\350\277\236\347\273\255 1 \347\232\204\344\270\252\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" index 9ea265f9..04018e7b 100644 --- "a/LeetCode/481-490/485. \346\234\200\345\244\247\350\277\236\347\273\255 1 \347\232\204\344\270\252\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/481-490/485. \346\234\200\345\244\247\350\277\236\347\273\255 1 \347\232\204\344\270\252\346\225\260\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -16,39 +16,81 @@ Tag : 「双指针」 ``` 提示: -* 输入的数组只包含 0 和 1 。 -* 输入数组的长度是正整数,且不超过 10,000。 +* 输入的数组只包含 `0` 和 `1` +* 输入数组的长度是正整数,且不超过 $10^4$ --- -### 双指针解法 +### 双指针 -![image.png](https://pic.leetcode-cn.com/1613355918-fZcGGx-image.png) +使用 `l` 和 `r` 作为左右端点,找最长连续 1 的连续段。 -使用 `i` 和 `j` 分别代表连续 1 的左右边界。 - -起始状态 `i == j`,当 `i` 到达第一个 1 的位置时,让 `j` 不断右移直到右边界。 - -更新 `ans` +起始让 `l = 0`,若当前值 `nums[l]` 为 0,跳过,直接让 `l` 右移到下一位;若当前值 `num[l]` 为 1,此时让 `r = l` ,然后往后找最远的符合条件的右边界(当 `r` 越过数组边界或 `nums[r] = 0` 时停止),此时 $[l, r)$ 均为 1,长度为 $r - l$,用此更新 `ans`,然后让 `l` 移到 `r + 1` 位置继续处理(其中 `l` 以 $[l + 1, r]$ 作为左端点的无须检查,因为寻找右边界的过程,必然会被 `r` 位置中断,长度必然不大于 $r - l$)。 +Java 代码: ```java class Solution { public int findMaxConsecutiveOnes(int[] nums) { - int n = nums.length; - int ans = 0; - for (int i = 0, j = 0; i < n; j = i) { - if (nums[i] == 1) { - while (j + 1 < n && nums[j + 1] == 1) j++; - ans = Math.max(ans, j - i + 1); - i = j + 1; - } else { - i++; - } + int n = nums.length, ans = 0; + for (int l = 0; l < n; ) { + if (nums[l] == 0 && ++l >= 0) continue; + int r = l; + while (r < n && nums[r] == 1) r++; + ans = Math.max(ans, r - l); + l = r + 1; } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int findMaxConsecutiveOnes(vector& nums) { + int n = nums.size(), ans = 0; + for (int l = 0; l < n;) { + if (nums[l] == 0 && ++l < n) continue; + int r = l; + while (r < n && nums[r] == 1) ++r; + ans = max(ans, r - l); + l = r + 1; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def findMaxConsecutiveOnes(self, nums: List[int]) -> int: + n, ans = len(nums), 0 + l, r = 0, 0 + while l < n: + if nums[l] == 0: + l += 1 + continue + r = l + while r < n and nums[r] == 1: + r += 1 + ans = max(ans, r - l) + l = r + 1 + return ans +``` +TypeScript 代码: +```TypeScript +function findMaxConsecutiveOnes(nums: number[]): number { + let n = nums.length, ans = 0; + for (let l = 0, r = 0; l < n; ) { + if (nums[l] == 0 && ++l >= 0) continue; + r = l; + while (r < n && nums[r] == 1) r++; + ans = Math.max(ans, r - l); + l = r + 1; + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/501-510/503. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 II\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/501-510/503. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 II\357\274\210\344\270\255\347\255\211\357\274\211.md" index c2d07529..74351ce7 100644 --- "a/LeetCode/501-510/503. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 II\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/501-510/503. \344\270\213\344\270\200\344\270\252\346\233\264\345\244\247\345\205\203\347\264\240 II\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,23 +6,28 @@ Tag : 「单调栈」 +给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。 -给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。 +数字 `x` 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 `-1`。 示例 1: ``` 输入: [1,2,1] + 输出: [2,-1,2] + 解释: 第一个 1 的下一个更大的数是 2; 数字 2 找不到下一个更大的数; 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。 ``` ---- +提示: +* $1 <= nums.length <= 10^4$ +* $-10^9 <= nums[i] <= 10^9$ -### 单调栈解法 +--- -![image.png](https://pic.leetcode-cn.com/1615000491-xXibsr-image.png) +### 单调栈 对于「找最近一个比当前值大/小」的问题,都可以使用单调栈来解决。 @@ -48,8 +53,8 @@ Tag : 「单调栈」 以及因为栈内存放的是还没更新答案的下标,可能会有位置会一直留在栈内(最大值的位置),因此我们要在处理前预设答案为 -1。而从实现那些没有下一个更大元素(不出栈)的位置的答案是 -1。 -代码: -```java +Java 代码: +```Java class Solution { public int[] nextGreaterElements(int[] nums) { int n = nums.length; @@ -67,20 +72,53 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector nextGreaterElements(vector& nums) { + int n = nums.size(); + vector ans(n, -1); + deque d; + for (int i = 0; i < n * 2; i++) { + while (!d.empty() && nums[i % n] > nums[d.back()]) { + int u = d.back(); + d.pop_back(); + ans[u] = nums[i % n]; + } + d.push_back(i % n); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def nextGreaterElements(self, nums: List[int]) -> List[int]: + n = len(nums) + ans = [-1] * n + d = deque() + for i in range(n * 2): + while d and nums[i % n] > nums[d[-1]]: + u = d.pop() + ans[u] = nums[i % n] + d.append(i % n) + return ans +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ -*** - -### 卡常小技巧 +--- -![image.png](https://pic.leetcode-cn.com/1615000960-kAlYtj-image.png) +### 数组模拟栈 本题不需要用到这个技巧,但是还是介绍一下,可作为拓展。 我们可以使用静态数组来模拟栈,这样我们的代码将会更快一点: -```java +Java 代码: +```Java class Solution { public int[] nextGreaterElements(int[] nums) { int n = nums.length; @@ -100,8 +138,46 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector nextGreaterElements(vector& nums) { + int n = nums.size(); + vector ans(n, -1); + vector d(n * 2); + int hh = 0, tt = -1; + for (int i = 0; i < n * 2; i++) { + while (hh <= tt && nums[i % n] > nums[d[tt]]) { + int u = d[tt--]; + ans[u] = nums[i % n]; + } + d[++tt] = i % n; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def nextGreaterElements(self, nums: List[int]) -> List[int]: + n = len(nums) + ans = [-1] * n + d = [0] * (n * 2) + hh, tt = 0, -1 + for i in range(n * 2): + while hh <= tt and nums[i % n] > nums[d[tt]]: + ans[d[tt]] = nums[i % n] + tt -= 1 + tt += 1 + d[tt] = i % n + return ans +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(n)$ -*** +--- ### 总结 diff --git "a/LeetCode/551-560/554. \347\240\226\345\242\231\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/551-560/554. \347\240\226\345\242\231\357\274\210\344\270\255\347\255\211\357\274\211.md" index 37de240f..047b6a41 100644 --- "a/LeetCode/551-560/554. \347\240\226\345\242\231\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/551-560/554. \347\240\226\345\242\231\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,21 +6,21 @@ Tag : 「哈希表」 -你的面前有一堵矩形的、由 n 行砖块组成的砖墙。 +你的面前有一堵矩形的、由 `n` 行砖块组成的砖墙。 -这些砖块高度相同(也就是一个单位高)但是宽度不同。每一行砖块的宽度之和相等。 +这些砖块高度相同(也就是一个单位高)但是宽度不同,每一行砖块的宽度之和相等。 -你现在要画一条 自顶向下 的、穿过 最少 砖块的垂线。 +你现在要画一条自顶向下的、穿过最少砖块的垂线。 如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。 你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。 -给你一个二维数组 wall ,该数组包含这堵墙的相关信息。 +给你一个二维数组 `wall`,该数组包含这堵墙的相关信息。 -其中,wall[i] 是一个代表从左至右每块砖的宽度的数组。 +其中,`wall[i]` 是一个代表从左至右每块砖的宽度的数组。 -你需要找出怎样画才能使这条线 穿过的砖块数量最少 ,并且返回 穿过的砖块数量 。 +你需要找出怎样画才能使这条线穿过的砖块数量最少,并且返回穿过的砖块数量。   @@ -40,12 +40,12 @@ Tag : 「哈希表」 输出:3 ``` 提示: -* n == wall.length -* 1 <= n <= $10^4$ -* 1 <= wall[i].length <= $10^4$ -* 1 <= sum(wall[i].length) <= 2 * $10^4$ -* 对于每一行 i ,sum(wall[i]) 是相同的 -* 1 <= `wall[i][j]` <= $2^{31}$ - 1 +* $n = wall.length$ +* $1 <= n <= 10^4$ +* $1 <= wall[i].length <= 10^4$ +* $1 <= sum(wall[i].length) <= 2 \times 10^4$ +* 对于每一行 `i`,`sum(wall[i])` 是相同的 +* $1 <= wall[i][j] <= 2^{31} - 1$ --- @@ -59,7 +59,7 @@ Tag : 「哈希表」 就用示例数据来举 🌰 : -![image.png](https://pic.leetcode-cn.com/1619762681-rvgTEO-image.png) +![](https://pic.leetcode-cn.com/1619762681-rvgTEO-image.png) * 第 1 行的间隙有 `[1,3,5]` * 第 2 行的间隙有 `[3,4]` @@ -70,11 +70,11 @@ Tag : 「哈希表」 对间隙计数完成后,遍历「哈希表」找出出现次数最多间隙 `4`,根据同一个间隙编号只会在单行内被统计一次,用总行数减去出现次数,即得到「最少穿过的砖块数」。 -代码: +Java 代码: ```Java class Solution { public int leastBricks(List> wall) { - int n = wall.size(); + int n = wall.size(), ans = n; Map map = new HashMap<>(); for (int i = 0, sum = 0; i < n; i++, sum = 0) { for (int cur : wall.get(i)) { @@ -83,15 +83,66 @@ class Solution { } map.remove(sum); // 不能从两边穿过,需要 remove 掉最后一个 } - int ans = n; - for (int u : map.keySet()) { - int cnt = map.get(u); - ans = Math.min(ans, n - cnt); - } + for (int u : map.keySet()) ans = Math.min(ans, n - map.get(u)); return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int leastBricks(vector>& wall) { + int n = wall.size(), ans = n; + unordered_map map; + for (int i = 0, sum = 0; i < n; i++, sum = 0) { + for (int cur : wall[i]) { + sum += cur; + map[sum]++; + } + map[sum]--; + } + for (auto& u : map) ans = min(ans, n - u.second); + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def leastBricks(self, wall: List[List[int]]) -> int: + n, ans = len(wall), len(wall) + mapping = defaultdict(int) + sumv = 0 + for i in range(n): + for cur in wall[i]: + sumv += cur + mapping[sumv] += 1 + mapping[sumv] -= 1 + sumv = 0 + for u in mapping: + ans = min(ans, n - mapping[u]) + return ans +``` +TypeScript 代码: +```TypeScript +function leastBricks(wall: number[][]): number { + let n = wall.length, ans = n; + const map: Map = new Map(); + for (let i = 0, sum = 0; i < n; i++, sum = 0) { + for (const cur of wall[i]) { + sum += cur; + if (map.has(sum)) map.set(sum, map.get(sum) + 1); + else map.set(sum, 1); + } + map.set(sum, map.get(sum) - 1); + } + map.forEach((value: number) => { + ans = Math.min(ans, n - value); + }); + return ans; +}; +``` * 时间复杂度:记所有砖块数量为 `n`,所有砖块都会被扫描。复杂度为 $O(n)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/551-560/558. \345\233\233\345\217\211\346\240\221\344\272\244\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/551-560/558. \345\233\233\345\217\211\346\240\221\344\272\244\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" index 4940bb64..8de54d00 100644 --- "a/LeetCode/551-560/558. \345\233\233\345\217\211\346\240\221\344\272\244\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/551-560/558. \345\233\233\345\217\211\346\240\221\344\272\244\351\233\206\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -14,14 +14,16 @@ Tag : 「递归」 请你返回一个表示 $n \times n$ 二进制矩阵的四叉树,它是 `quadTree1` 和 `quadTree2` 所表示的两个二进制矩阵进行 按位逻辑或运算 的结果。 -注意,当 `isLeaf` 为 `False` 时,你可以把 `True` 或者 `False` 赋值给节点,两种值都会被判题机制 接受 。 +注意,当 `isLeaf` 为 `False` 时,你可以把 `True` 或者 `False` 赋值给节点,两种值都会被判题机制接受。 -四叉树数据结构中,每个内部节点只有四个子节点。此外,每个节点都有两个属性: +四叉树数据结构中,每个内部节点只有四个子节点。 + +此外,每个节点都有两个属性: * `val`:储存叶子结点所代表的区域的值。$1$ 对应 `True`,$0$ 对应 `False`; * `isLeaf`: 当这个节点是一个叶子结点时为 `True`,如果它有 $4$ 个子节点则为 `False` 。 -``` +```Java class Node { public boolean val;     public boolean isLeaf; @@ -33,7 +35,7 @@ class Node { ``` 我们可以按以下步骤为二维区域构建四叉树: -1. 如果当前网格的值相同(即,全为 $0$ 或者全为 $1$),将 `isLeaf` 设为 `True` ,将 `val` 设为网格相应的值,并将四个子节点都设为 `Null` 然后停止。 +1. 如果当前网格的值相同(即全为 $0$ 或者全为 $1$),将 `isLeaf` 设为 `True` ,将 `val` 设为网格相应的值,并将四个子节点都设为 `Null` 然后停止。 2. 如果当前网格的值不同,将 `isLeaf` 设为 `False`, 将 `val` 设为任意值,然后如下图所示,将当前网格划分为四个子网格。 3. 使用适当的子网格递归每个子节点。 @@ -47,6 +49,7 @@ class Node { 示例 1: ![](https://assets.leetcode.com/uploads/2020/02/11/qt2.png) + ``` 输入:quadTree1 = [[0,1],[1,1],[1,1],[1,0],[1,0]] , quadTree2 = [[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]] @@ -57,6 +60,7 @@ class Node { 如果我们对这两个矩阵进行按位逻辑或运算,则可以得到下面的二进制矩阵,由一个作为结果的四叉树表示。 注意,我们展示的二进制矩阵仅仅是为了更好地说明题意,你无需构造二进制矩阵来获得结果四叉树。 ``` + 示例 2: ``` 输入:quadTree1 = [[1,0]] @@ -67,6 +71,7 @@ class Node { 解释:两个数所表示的矩阵大小都为 1*1,值全为 0 结果矩阵大小为 1*1,值全为 0 。 ``` + 示例 3: ``` 输入:quadTree1 = [[0,0],[1,0],[1,0],[1,1],[1,1]] @@ -74,6 +79,7 @@ class Node { 输出:[[1,1]] ``` + 示例 4: ``` 输入:quadTree1 = [[0,0],[1,1],[1,0],[1,1],[1,1]] @@ -81,6 +87,7 @@ class Node { 输出:[[0,0],[1,1],[0,1],[1,1],[1,1],null,null,null,null,[1,1],[1,0],[1,0],[1,1]] ``` + 示例 5: ``` 输入:quadTree1 = [[0,1],[1,0],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]] @@ -91,7 +98,7 @@ class Node { 提示: * `quadTree1` 和 `quadTree2` 都是符合题目要求的四叉树,每个都代表一个 $n \times n$ 的矩阵。 -* $n == 2^x$ ,其中 $0 <= x <= 9$ +* $n = 2^x$ ,其中 $0 <= x <= 9$ --- @@ -99,7 +106,7 @@ class Node { 为了方便,我们令 `quadTree1` 为 `t1`,令 `quadTree2` 为 `t2`。 -根据题意,并利用给定函数作为递归函数,当 `t1` 和 `t2` 均为叶子节点数时,执行「与逻辑」,即若 `t1` 和 `t2` 任一值为 $1$ 时,返回该节点,否则(两者均为 $0$),返回任一节点。 +根据题意,并利用给定函数作为递归函数,当 `t1` 和 `t2` 均为叶子节点时,根据 `t1` 和 `t2` 的值情况分别处理,若 `t1` 和 `t2` 中存在值为 $1$ 的节点,返回该节点,否则(两者均为 $0$),返回任一节点。 然后考虑其他情况下,如何使用 `t1` 和 `t2` 构造新节点 `ans`,分别使用对应位置的进行「递归」构造即可(即使用 `t1.topLeft` 和 `t2.topLeft` 来赋值给 `ans.topLeft`,其余位置同理),要注意可能存在 `t1` 或 `t2` 其中一节点为叶子节点的情况,此时应当使用当前叶子节点和另一节点的子节点进行构造。 diff --git "a/LeetCode/561-570/561. \346\225\260\347\273\204\346\213\206\345\210\206 I\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/561-570/561. \346\225\260\347\273\204\346\213\206\345\210\206 I\357\274\210\347\256\200\345\215\225\357\274\211.md" index 0cb9d099..11287ce3 100644 --- "a/LeetCode/561-570/561. \346\225\260\347\273\204\346\213\206\345\210\206 I\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/561-570/561. \346\225\260\347\273\204\346\213\206\345\210\206 I\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,7 +7,7 @@ Tag : 「贪心」 -给定长度为 $2 \times n$ 的整数数组 `nums `,你的任务是将这些数分成 `n` 对, 例如 $(a_1, b_1), (a_2, b_2), ..., (a_n, b_n)$ ,使得从 `1` 到 `n` 的 $min(a_i, b_i)$ 总和最大。 +给定长度为 $2 \times n$ 的整数数组 `nums `,你的任务是将这些数分成 `n` 对, 例如 $(a_1, b_1), (a_2, b_2), ..., (a_n, b_n)$ ,使得从 `1` 到 `n` 的 $\min(a_i, b_i)$ 总和最大。 返回该 最大总和 。 @@ -36,7 +36,7 @@ Tag : 「贪心」 --- -### 贪心解法 +### 贪心 我们先对数组进行排序。 @@ -44,33 +44,66 @@ Tag : 「贪心」 因此我们猜想应该从第一个位置进行选择,然后隔一步选择下一个数。这样形成的序列的求和值最大。 -```java +Java 代码: +```Java class Solution { public int arrayPairSum(int[] nums) { - int n = nums.length; + int n = nums.length, ans = 0; Arrays.sort(nums); - int ans = 0; for (int i = 0; i < n; i += 2) ans += nums[i]; return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int arrayPairSum(vector& nums) { + int n = nums.size(), ans = 0; + sort(nums.begin(), nums.end()); + for (int i = 0; i < n; i += 2) ans += nums[i]; + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def arrayPairSum(self, nums: List[int]) -> int: + nums.sort() + ans = 0 + for i in range(0, len(nums), 2): + ans += nums[i] + return ans +``` +TypeScript 代码: +```TypeScript +function arrayPairSum(nums: number[]): number { + let n = nums.length, ans = 0; + nums.sort((a, b) => a - b); + for (let i = 0; i < nums.length; i += 2) ans += nums[i]; + return ans; +}; +``` * 时间复杂度:$O(n\log{n})$ * 空间复杂度:$O(\log{n})$ -*** +--- ### 证明 +解法不难,重点是要证明该做法的正确性,这才是"贪心"类算法题的意义。 + 我们用反证法来证明下,为什么这样选择的序列的求和值一定是最大的: 猜想:对数组进行排序,从第一个位置进行选择,然后隔一步选择下一个数。这样形成的序列的求和值最大(下图黑标,代表当前被选择的数字)。 -![image.png](https://pic.leetcode-cn.com/1613440254-hwKbAR-image.png) +![](https://pic.leetcode-cn.com/1613440254-hwKbAR-image.png) 之所以我们能这么选择,是因为每一个被选择的数的「下一位位置」都对应着一个「大于等于」当前数的值(假设位置为 `k` ),使得当前数在 `min(a,b)` 关系中能被选择(下图红标,代表保证前面一个黑标能够被选择的辅助数)。 -![image.png](https://pic.leetcode-cn.com/1613440579-WdNtDP-image.png) +![](https://pic.leetcode-cn.com/1613440579-WdNtDP-image.png) 假如我们这样选择的序列求和不是最大值,那么说明至少我们有一个值选错了,应该选择更大的数才对。 @@ -82,7 +115,7 @@ class Solution { 最终会导致我们最后一个黑标出现在最后一位,这时候最后一位黑标不得不与我们第 `k` 个位置的数形成一对。 -![image.png](https://pic.leetcode-cn.com/1613441184-cvdARk-image.png) +![](https://pic.leetcode-cn.com/1613441184-cvdARk-image.png) 我们看看这是求和序列的变化( `k` 位置前的求和项没有发生变化,我们从 `k` 位置开始分析): @@ -93,7 +126,7 @@ class Solution { 显然从原答案的每一项都「大于等于」调整后答案的每一项,因此**不可能在「假想序列」中通过选择别的更大的数得到更优解,假想得证。** -*** +--- ### 为什么要「证明」或「理解证明」? @@ -105,7 +138,7 @@ class Solution { 2. 在「面试」阶段,**你可以很清晰讲解你的思路**。让面试官从你的「思维方式」上喜欢上你( emmm 当然从颜值上也可以 :) ... -*** +--- ### 更多与证明/分析相关的题解: @@ -116,7 +149,6 @@ class Solution { [11. 盛最多水的容器](https://leetcode-cn.com/problems/container-with-most-water/) : [双指针+贪心解法【含证明】](https://leetcode-cn.com/problems/container-with-most-water/solution/shua-chuan-lc-shuang-zhi-zhen-tan-xin-ji-52gf/) - --- ### 最后 diff --git "a/LeetCode/561-570/566. \351\207\215\345\241\221\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/561-570/566. \351\207\215\345\241\221\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" index 8e05459f..5ee21c22 100644 --- "a/LeetCode/561-570/566. \351\207\215\345\241\221\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/561-570/566. \351\207\215\345\241\221\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -13,7 +13,7 @@ Tag : 「模拟」 重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。 -如果具有给定参数的 `reshape` 操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。 +如果具有给定参数的 `reshape` 操作是可行且合理的,则输出新的重塑矩阵,否则,输出原始矩阵。 示例 1: ``` @@ -72,7 +72,7 @@ class Solution { } } ``` -* 时间复杂度:$O(r * c)$ +* 时间复杂度:$O(r \times c)$ * 空间复杂度:$O(1)$ --- diff --git "a/LeetCode/561-570/567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/561-570/567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" index dd3b14cb..8277308c 100644 --- "a/LeetCode/561-570/567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/561-570/567. \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,44 +6,44 @@ Tag : 「滑动窗口」 -给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。 +给定两个字符串 `s1` 和 `s2`,写一个函数来判断 `s2` 是否包含 `s1` 的排列。 -换句话说,第一个字符串的排列之一是第二个字符串的 子串 。 +换句话说,第一个字符串的排列之一是第二个字符串的子串。 示例 1: ``` 输入: s1 = "ab" s2 = "eidbaooo" + 输出: True + 解释: s2 包含 s1 的排列之一 ("ba"). ``` 示例 2: ``` 输入: s1= "ab" s2 = "eidboaoo" + 输出: False ``` 提示: * 输入的字符串只包含小写字母 -* 两个字符串的长度都在 [1, 10,000] 之间 +* 两个字符串的长度都在 $[1, 10000]$ 之间 --- ### 滑动窗口 -由于是 `s2` 中判断是否包含 `s1` 的排列,而且 `s1` 和 `s2` 均为小数。 - -可以使用数组先对 `s1` 进行统计,之后使用滑动窗口进行扫描,每滑动一次检查窗口内的字符频率和 `s1` 是否相等 ~ +由于是 `s2` 中判断是否包含 `s1` 的排列,而且 `s1` 和 `s2` 均为小写字母。 -以下代码,可以作为滑动窗口模板使用: +可以使用数组先对 `s1` 进行统计,之后使用滑动窗口进行扫描,每滑动一次检查窗口内的字符频率和 `s1` 是否相等即可。 -**PS. 你会发现以下代码和 [643. 子数组最大平均数 I](https://leetcode-cn.com/problems/maximum-average-subarray-i/) 和 [1423. 可获得的最大点数](https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/) 代码很相似,因为是一套模板。** +滑动窗口的基本思路: 1. 初始化将滑动窗口压满,取得第一个滑动窗口的目标值 - 2. 继续滑动窗口,每往前滑动一次,需要删除一个和添加一个元素 -代码: -```java +Java 代码: +```Java class Solution { public boolean checkInclusion(String s1, String s2) { int m = s1.length(), n = s2.length(); @@ -68,25 +68,71 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool checkInclusion(string s1, string s2) { + int m = s1.length(), n = s2.length(); + if (m > n) return false; + vector cnt(26, 0); + for (char c : s1) cnt[c - 'a']++; + vector cur(26, 0); + for (int i = 0; i < m; i++) cur[s2[i] - 'a']++; + if (check(cnt, cur)) return true; + for (int i = m; i < n; i++) { + cur[s2[i] - 'a']++; + cur[s2[i - m] - 'a']--; + if (check(cnt, cur)) return true; + } + return false; + } + bool check(vector& cnt1, vector& cnt2) { + for (int i = 0; i < 26; i++) { + if (cnt1[i] != cnt2[i]) return false; + } + return true; + } +}; +``` +Python 代码: +```Python +class Solution: + def checkInclusion(self, s1: str, s2: str) -> bool: + m, n = len(s1), len(s2) + if m > n: + return False + cnt = [0] * 26 + for c in s1: + cnt[ord(c) - ord('a')] += 1 + cur = [0] * 26 + for i in range(m): + cur[ord(s2[i]) - ord('a')] += 1 + if cnt == cur: + return True + for i in range(m, n): + cur[ord(s2[i]) - ord('a')] += 1 + cur[ord(s2[i - m]) - ord('a')] -= 1 + if cnt == cur: + return True + return False +``` * 时间复杂度:$O(n)$ -* 空间复杂度:$O(n)$ - -经过了前面几天的「滑动窗口」,相信你做这题已经很有感觉了。 +* 空间复杂度:$O(C)$,所用数组大小只与字符集大小相关,与输入无关。 -但其实这是某道困难题的简化版,本题根据「字符」滑动,而 [30. 串联所有单词的子串](https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/) 则是根据「单词」来。但基本思路都是一样的,强烈建议你来试试 ~ - -*** +--- -### 相关链接 +### 总结 -[30. 串联所有单词的子串:【刷穿LC】朴素哈希表解法 + 滑动窗口解法](https://leetcode-cn.com/problems/substring-with-concatenation-of-all-words/solution/shua-chuan-lc-po-su-ha-xi-biao-jie-fa-hu-ml3x/) +这其实是某道困难题的简化版,本题根据「字符」滑动,而 [30. 串联所有单词的子串](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247507931&idx=1&sn=b11c5a4c9321a6b27719cb34f2f6d5ea&chksm=fd9f74c4cae8fdd2af1a4eecdf9001d065c0325ad896946aa61a204ed864d065ee0390611d17#rd) 则是根据「单词」来。 -[643. 子数组最大平均数 I:滑动窗口裸题 (含模板)~](https://leetcode-cn.com/problems/maximum-average-subarray-i/solution/hua-dong-chuang-kou-luo-ti-han-mo-ban-by-buo3/) +换句话说,如果在面试过程中,面试官稍微修改一下条件,这道题会有很多玩法: -[1423. 可获得的最大点数:详解滑动窗口基本思路(含模板)~](https://leetcode-cn.com/problems/maximum-points-you-can-obtain-from-cards/solution/jian-dan-de-hua-dong-chuang-kou-he-kuai-1go5h/) +如果不再是匹配不考虑顺序的「排列」,而是匹配考虑顺序的「子串」,那么问题会变成 [28. 实现 strStr()](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486317&idx=1&sn=9c2ff2fa5db427133cce9c875064e7a4&chksm=fd9ca072caeb29642bf1f5c151e4d5aaff4dc10ba408b23222ea1672cfc41204a584fede5c05#rd) 的子串匹配问题。考察的内容可以是 BM、KMP 或者字符串哈希。 -*** +如果不再是匹配单个子串,而是要求同时匹配多个子串,则变成 [30. 串联所有单词的子串](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247507931&idx=1&sn=b11c5a4c9321a6b27719cb34f2f6d5ea&chksm=fd9f74c4cae8fdd2af1a4eecdf9001d065c0325ad896946aa61a204ed864d065ee0390611d17#rd) 的单词串联问题,考虑的内容是滑动窗口 & 哈希表。 +--- ### 最后 diff --git "a/LeetCode/571-580/576. \345\207\272\347\225\214\347\232\204\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/571-580/576. \345\207\272\347\225\214\347\232\204\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" index 0b1b0fd5..547ab6bd 100644 --- "a/LeetCode/571-580/576. \345\207\272\347\225\214\347\232\204\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/571-580/576. \345\207\272\347\225\214\347\232\204\350\267\257\345\276\204\346\225\260\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,26 +6,30 @@ Tag : 「路径 DP」、「动态规划」、「记忆化搜索」 -给你一个大小为 m x n 的网格和一个球。球的起始坐标为 [startRow, startColumn] 。 +给你一个大小为 `m x n` 的网格和一个球。球的起始坐标为 `[startRow, startColumn]`。 你可以将球移到在四个方向上相邻的单元格内(可以穿过网格边界到达网格之外)。 -你**最多**可以移动 maxMove 次球。 +你**最多**可以移动 `maxMove` 次球。 -给你五个整数 m、n、maxMove、startRow 以及 startColumn ,找出并返回可以将球移出边界的路径数量。 +给你五个整数 `m`、`n`、`maxMove`、`startRow` 以及 `startColumn`,找出并返回可以将球移出边界的路径数量。 因为答案可能非常大,返回对 $10^9 + 7$​ 取余 后的结果。 示例 1: + ![](https://assets.leetcode.com/uploads/2021/04/28/out_of_boundary_paths_1.png) + ``` 输入:m = 2, n = 2, maxMove = 2, startRow = 0, startColumn = 0 输出:6 ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2021/04/28/out_of_boundary_paths_2.png) + ``` 输入:m = 1, n = 3, maxMove = 3, startRow = 0, startColumn = 1 @@ -33,10 +37,10 @@ Tag : 「路径 DP」、「动态规划」、「记忆化搜索」 ``` 提示: -* 1 <= m, n <= 50 -* 0 <= maxMove <= 50 -* 0 <= startRow < m -* 0 <= startColumn < n +* $1 <= m, n <= 50$ +* $0 <= maxMove <= 50$ +* $0 <= startRow < m$ +* $0 <= startColumn < n$ --- @@ -77,7 +81,7 @@ int dfs(int x, int y, int k) {} 主逻辑则是根据四联通规则进行移动即可,最终答案为 `dfs(startRow, startColumn, maxMove)`。 -代码: +Java 代码: ```Java class Solution { int MOD = (int)1e9+7; @@ -111,6 +115,34 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int MOD = 1e9 + 7; + int m, n, max; + vector>> cache; + vector> dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + int findPaths(int _m, int _n, int _max, int r, int c) { + m = _m; n = _n; max = _max; + cache = vector>>(m, vector>(n, vector(max + 1, -1))); + return dfs(r, c, max); + } + int dfs(int x, int y, int k) { + if (x < 0 || x >= m || y < 0 || y >= n) return 1; + if (k == 0) return 0; + if (cache[x][y][k] != -1) return cache[x][y][k]; + int ans = 0; + for (auto& d : dirs) { + int nx = x + d[0], ny = y + d[1]; + ans += dfs(nx, ny, k - 1); + ans %= MOD; + } + cache[x][y][k] = ans; + return ans; + } +}; +``` --- @@ -157,7 +189,7 @@ $$ 同时,由于我们能够往四个方向进行移动,因此不同的边缘格子会有不同数量的路径。 -![image.png](https://pic.leetcode-cn.com/1616507893-KjPAvs-image.png) +![](https://pic.leetcode-cn.com/1616507893-KjPAvs-image.png) 换句话说,我们需要先对边缘格子进行初始化操作,预处理每个边缘格子直接走出矩阵的路径数量。 @@ -165,7 +197,7 @@ $$ **可以发现,动态规划的实现,本质是将问题进行反向:原问题是让我们求从棋盘的特定位置出发,出界的路径数量。实现时,我们则是从边缘在状态出发,逐步推导回起点的出界路径数量为多少。** -代码: +Java 代码: ```Java class Solution { int MOD = (int)1e9+7; @@ -213,8 +245,58 @@ class Solution { } } ``` -* 时间复杂度:共有 $m * n * max$ 个状态需要转移,复杂度为 $O(m * n * max)$ -* 空间复杂度:$O(m * n * max)$ +C++ 代码: +```C++ +class Solution { +public: + int MOD = 1e9 + 7; + int m, n, max; + vector> dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + int findPaths(int _m, int _n, int _max, int r, int c) { + m = _m; n = _n; max = _max; + vector> f(m * n, vector(max + 1, 0)); + // 初始化边缘格子的路径数量 + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (i == 0) add(i, j, f); + if (j == 0) add(i, j, f); + if (i == m - 1) add(i, j, f); + if (j == n - 1) add(i, j, f); + } + } + // 从小到大枚举「可移动步数」 + for (int k = 1; k <= max; k++) { + // 枚举所有的「位置」 + for (int idx = 0; idx < m * n; idx++) { + pair info = parseIdx(idx); + int x = info.first, y = info.second; + for (auto& d : dirs) { + int nx = x + d[0], ny = y + d[1]; + if (nx >= 0 && nx < m && ny >= 0 && ny < n) { + int nidx = getIdx(nx, ny); + f[idx][k] += f[nidx][k - 1]; + f[idx][k] %= MOD; + } + } + } + } + return f[getIdx(r, c)][max]; + } + void add(int x, int y, vector>& f) { + for (int k = 1; k <= max; k++) { + f[getIdx(x, y)][k]++; + } + } + int getIdx(int x, int y) { + return x * n + y; + } + pair parseIdx(int idx) { + return {idx / n, idx % n}; + } +}; +``` +* 时间复杂度:共有 $m \times n \times max$ 个状态需要转移,复杂度为 $O(m \times n \times max)$ +* 空间复杂度:$O(m \times n \times max)$ --- diff --git "a/LeetCode/61-70/61. \346\227\213\350\275\254\351\223\276\350\241\250\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/61-70/61. \346\227\213\350\275\254\351\223\276\350\241\250\357\274\210\344\270\255\347\255\211\357\274\211.md" index 8e5bb700..8f0c5902 100644 --- "a/LeetCode/61-70/61. \346\227\213\350\275\254\351\223\276\350\241\250\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/61-70/61. \346\227\213\350\275\254\351\223\276\350\241\250\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -9,6 +9,7 @@ Tag : 「链表」、[快慢指针] 给你一个链表的头节点 `head`,旋转链表,将链表每个节点向右移动 `k` 个位置。 示例 1: + ![](https://assets.leetcode.com/uploads/2020/11/13/rotate1.jpg) ``` @@ -17,7 +18,9 @@ Tag : 「链表」、[快慢指针] 输出:[4,5,1,2,3] ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2020/11/13/roate2.jpg) + ``` 输入:head = [0,1,2], k = 4 @@ -27,7 +30,7 @@ Tag : 「链表」、[快慢指针] 提示: * 链表中节点的数目在范围 $[0, 500]$ 内 * $-100 <= Node.val <= 100$ -* $0 <= k <= 2 * 10^9$ +* $0 <= k <= 2 \times 10^9$ --- diff --git "a/LeetCode/621-630/623. \345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/621-630/623. \345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214\357\274\210\344\270\255\347\255\211\357\274\211.md" index b0975dc3..ec94fa20 100644 --- "a/LeetCode/621-630/623. \345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/621-630/623. \345\234\250\344\272\214\345\217\211\346\240\221\344\270\255\345\242\236\345\212\240\344\270\200\350\241\214\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -18,14 +18,18 @@ Tag : 「二叉树」、「BFS」、「DFS」 * 如果 `depth == 1` 意味着 `depth - 1` 根本没有深度,那么创建一个树节点,值 `val` 作为整个原始树的新根,而原始树就是新根的左子树。 示例 1: + ![](https://assets.leetcode.com/uploads/2021/03/15/addrow-tree.jpg) + ``` 输入: root = [4,2,6,3,1,5], val = 1, depth = 2 输出: [4,1,1,2,null,null,6,3,1,5] ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2021/03/11/add2-tree.jpg) + ``` 输入: root = [4,2,null,3,1], val = 1, depth = 3 @@ -72,6 +76,61 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + TreeNode* addOneRow(TreeNode* root, int val, int depth) { + if (depth == 1) return new TreeNode(val, root, nullptr); + queue q; + q.push(root); + int cur = 1; + while (!q.empty()) { + int sz = q.size(); + while (sz--) { + TreeNode* t = q.front(); + q.pop(); + if (cur == depth - 1) { + TreeNode* a = new TreeNode(val); + TreeNode* b = new TreeNode(val); + a->left = t->left; b->right = t->right; + t->left = a; t->right = b; + } else { + if (t->left) q.push(t->left); + if (t->right) q.push(t->right); + } + } + cur++; + } + return root; + } +}; +``` +Python 代码: +```Python +class Solution: + def addOneRow(self, root: Optional[TreeNode], val: int, depth: int) -> Optional[TreeNode]: + if depth == 1: + return TreeNode(val, root, None) + d = deque([root]) + cur = 1 + while d: + sz = len(d) + while sz > 0: + t = d.popleft() + if cur == depth - 1: + a, b = TreeNode(val), TreeNode(val) + a.left, b.right = t.left, t.right + t.left, t.right = a, b + else: + if t.left: + d.append(t.left) + if t.right: + d.append(t.right) + sz -= 1 + cur += 1 + return root +``` TypeScript 代码: ```TypeScript function addOneRow(root: TreeNode | null, val: number, depth: number): TreeNode | null { @@ -129,6 +188,53 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int d, v; + TreeNode* addOneRow(TreeNode* root, int val, int depth) { + d = depth; v = val; + if (d == 1) return new TreeNode(val, root, nullptr); + dfs(root, 1); + return root; + } + void dfs(TreeNode* root, int cur) { + if (root == nullptr) return; + if (cur == d - 1) { + TreeNode* a = new TreeNode(v); + TreeNode* b = new TreeNode(v); + a->left = root->left; b->right = root->right; + root->left = a; root->right = b; + } else { + dfs(root->left, cur + 1); + dfs(root->right, cur + 1); + } + } +}; +``` +Python 代码: +```Python +class Solution: + def addOneRow(self, root: Optional[TreeNode], val: int, depth: int) -> Optional[TreeNode]: + self.d = depth + self.v = val + if depth == 1: + return TreeNode(val, root, None) + self.dfs(root, 1) + return root + + def dfs(self, root, cur): + if not root: + return + if cur == self.d - 1: + a, b = TreeNode(self.v), TreeNode(self.v) + a.left, b.right = root.left, root.right + root.left, root.right = a, b + else: + self.dfs(root.left, cur + 1) + self.dfs(root.right, cur + 1) +``` TypeScript 代码: ```TypeScript let d = 0, v = 0 diff --git "a/LeetCode/641-650/643. \345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\271\263\345\235\207\346\225\260 I\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/641-650/643. \345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\271\263\345\235\207\346\225\260 I\357\274\210\347\256\200\345\215\225\357\274\211.md" index aeb208be..b1f8a19d 100644 --- "a/LeetCode/641-650/643. \345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\271\263\345\235\207\346\225\260 I\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/641-650/643. \345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\271\263\345\235\207\346\225\260 I\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,7 +7,7 @@ Tag : 「滑动窗口」 -给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。 +给定 `n` 个整数,找出平均数最大且长度为 `k` 的连续子数组,并输出该最大平均数。 示例: @@ -20,8 +20,8 @@ Tag : 「滑动窗口」 ``` 提示: -* 1 <= k <= n <= 30,000。 -* 所给数据范围 [-10,000,10,000]。 +* $1 <= k <= n <= 30000$ +* 所给数据范围 $[-10000, 10000]$ --- @@ -35,7 +35,7 @@ Tag : 「滑动窗口」 2. 继续滑动窗口,每往前滑动一次,需要删除一个和添加一个元素 -代码: +Java 代码: ```Java class Solution { public double findMaxAverage(int[] nums, int k) { @@ -50,6 +50,47 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + double findMaxAverage(vector& nums, int k) { + double ans = 0, sum = 0; + for (int i = 0; i < k; i++) sum += nums[i]; + ans = sum / k; + for (int i = k; i < nums.size(); i++) { + sum = sum + nums[i] - nums[i - k]; + ans = max(ans, sum / k); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def findMaxAverage(self, nums: List[int], k: int) -> float: + ans = 0 + sumv = sum(nums[:k]) + ans = sumv / k + for i in range(k, len(nums)): + sumv += nums[i] - nums[i - k] + ans = max(ans, sumv / k) + return ans +``` +TypeScript 代码: +```TypeScript +function findMaxAverage(nums: number[], k: number): number { + let ans: number = 0, sum: number = 0; + for (let i: number = 0; i < k; i++) sum += nums[i]; + ans = sum / k; + for (let i: number = k; i < nums.length; i++) { + sum = sum + nums[i] - nums[i - k]; + ans = Math.max(ans, sum / k); + } + return ans; +}; +``` * 时间复杂度:每个元素最多滑入和滑出窗口一次。复杂度为 $O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/641-650/646. \346\234\200\351\225\277\346\225\260\345\257\271\351\223\276\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/641-650/646. \346\234\200\351\225\277\346\225\260\345\257\271\351\223\276\357\274\210\344\270\255\347\255\211\357\274\211.md" index 582bc164..0c4d2827 100644 --- "a/LeetCode/641-650/646. \346\234\200\351\225\277\346\225\260\345\257\271\351\223\276\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/641-650/646. \346\234\200\351\225\277\346\225\260\345\257\271\351\223\276\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,7 +6,9 @@ Tag : 「贪心」、「排序」、「二分」、「序列 DP」、「LIS」 -给出 `n` 个数对。 在每一个数对中,第一个数字总是比第二个数字小。 +给出 `n` 个数对。 + +在每一个数对中,第一个数字总是比第二个数字小。 现在,我们定义一种跟随关系,当且仅当 `b < c` 时,数对 $(c, d)$ 才可以跟在 $(a, b)$ 后面。我们用这种形式来构造一个数对链。 @@ -38,7 +40,7 @@ Tag : 「贪心」、「排序」、「二分」、「序列 DP」、「LIS」 容易证明该做法的正确性:**假设贪心解(该做法)找到的位置 $j$ 不是最优位置,即存在比 $j$ 更小的合法下标 $j'$ 满足 $f[j'] > f[j]$。根据我们的排序规则必然有 $pairs[j'][0] <= pairs[j][0]$ 的性质,则可知 $pairs[j]$ 必然可以代替 $pairs[j']$ 接在原本以 $pairs[j']$ 为结尾的最优数链上(最优数链长度不变,结果不会变差),则至少有 $f[j'] = f[j]$。** -代码: +Java 代码: ```Java class Solution { public int findLongestChain(int[][] pairs) { @@ -56,6 +58,57 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int findLongestChain(vector>& pairs) { + sort(pairs.begin(), pairs.end()); + int n = pairs.size(), ans = 1; + vector f(n, 1); + for (int i = 0; i < n; i++) { + f[i] = 1; + for (int j = i - 1; j >= 0 && f[i] == 1; j--) { + if (pairs[j][1] < pairs[i][0]) f[i] = f[j] + 1; + } + ans = max(ans, f[i]); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def findLongestChain(self, pairs: List[List[int]]) -> int: + pairs.sort() + n, ans = len(pairs), 1 + f = [1] * n + for i in range(n): + j = i - 1 + while j >= 0 and f[i] == 1: + if pairs[j][1] < pairs[i][0]: + f[i] = f[j] + 1 + j -= 1 + ans = max(ans, f[i]) + return ans +``` +TypeScript 代码: +```TypeScript +function findLongestChain(pairs: number[][]): number { + pairs.sort((a, b) => a[0] - b[0]); + let n = pairs.length, ans = 1; + const f: number[] = new Array(n).fill(1); + for (let i = 0; i < n; i++) { + f[i] = 1; + for (let j = i - 1; j >= 0 && f[i] == 1; j--) { + if (pairs[j][1] < pairs[i][0]) f[i] = f[j] + 1; + } + ans = Math.max(ans, f[i]); + } + return ans; +}; +``` * 时间复杂度:排序的复杂度为 $O(n\log{n})$;不考虑剪枝效果 `DP` 复杂度为 $O(n^2)$。整体复杂度为 $O(n^2)$ * 空间复杂度:$O(n)$ @@ -71,9 +124,9 @@ class Solution { 如此一来,当我们要找 $f[i]$ 的前驱状态时,等价于在 $g$ 数组中找满足「小于 $pairs[i][0]$」的最大下标。同时,我们不再需要显式维护 $f$ 数组,只需要边转移变更新答案即可。 -> **不了解 `LIS` 问题的同学可以看前置 🧀 : [LCS 问题与 LIS 问题的相互关系,以及 LIS 问题的最优解证明](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247487814&idx=1&sn=e33023c2d474ff75af83eda1c4d01892) 🎉🎉🎉** +> **不了解 `LIS` 问题的同学可以看前置 🧀 : [LCS 问题与 LIS 问题的相互关系,以及 LIS 问题的最优解证明](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247505761&idx=1&sn=a84744d9670c3276e41ccea043dc798d&chksm=fd9f7c7ecae8f568e2c4203dcccacabe880fa3ccd09dcd050dfcc0cc98c1ccc9ebcf39babded#rd) 🎉🎉🎉** -代码: +Java 代码: ```Java class Solution { public int findLongestChain(int[][] pairs) { @@ -95,6 +148,66 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int findLongestChain(vector>& pairs) { + sort(pairs.begin(), pairs.end()); + int n = pairs.size(), ans = 1; + vector g(n + 10, 0x3f3f3f3f); + for (int i = 0; i < n; i++) { + int l = 1, r = i + 1; + while (l < r) { + int mid = l + r >> 1; + if (g[mid] >= pairs[i][0]) r = mid; + else l = mid + 1; + } + g[r] = min(g[r], pairs[i][1]); + ans = max(ans, r); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def findLongestChain(self, pairs: List[List[int]]) -> int: + pairs.sort() + n, ans = len(pairs), 1 + g = [0x3f3f3f3f] * (n + 10) + for i in range(n): + l, r = 1, i + 1 + while l < r: + mid = l + r >> 1 + if g[mid] >= pairs[i][0]: + r = mid + else: + l = mid + 1 + g[r] = min(g[r], pairs[i][1]) + ans = max(ans, r) + return ans +``` +TypeScript 代码: +```TypeScript +function findLongestChain(pairs: number[][]): number { + pairs.sort((a, b) => a[0] - b[0]); + let n = pairs.length, ans = 1; + const g: number[] = new Array(n + 10).fill(0x3f3f3f3f); + for (let i = 0; i < n; i++) { + let l = 1, r = i + 1; + while (l < r) { + const mid = l + r >> 1; + if (g[mid] >= pairs[i][0]) r = mid; + else l = mid + 1; + } + g[r] = Math.min(g[r], pairs[i][1]); + ans = Math.max(ans, r); + } + return ans; +}; +``` * 时间复杂度:排序的复杂度为 $O(n\log{n})$;`DP` 复杂度为 $O(n\log{n})$。整体复杂度为 $O(n\log{n})$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/651-660/655. \350\276\223\345\207\272\344\272\214\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/651-660/655. \350\276\223\345\207\272\344\272\214\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" index f7e4e9e0..f98b9e35 100644 --- "a/LeetCode/651-660/655. \350\276\223\345\207\272\344\272\214\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/651-660/655. \350\276\223\345\207\272\344\272\214\345\217\211\346\240\221\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,11 +6,13 @@ Tag : 「二叉树」、「递归」 -给你一棵二叉树的根节点 root ,请你构造一个下标从 0 开始、大小为 m x n 的字符串矩阵 res ,用以表示树的 格式化布局 。构造此格式化布局矩阵需要遵循以下规则: +给你一棵二叉树的根节点 `root`,请你构造一个下标从 `0` 开始、大小为 $m \times n$ 的字符串矩阵 `res`,用以表示树的格式化布局。 -* 树的 高度 为 $height$,矩阵的行数 $m$ 应该等于 $height + 1$。 -* 矩阵的列数 $n$ 应该等于 $2^{height+1 - 1}$ 。 -* 根节点 需要放置在 顶行 的 正中间 ,对应位置为 $res[0][(n-1)/2]$ 。 +构造此格式化布局矩阵需要遵循以下规则: + +* 树的高度为 `height`,矩阵的行数 `m` 应该等于 $height + 1$。 +* 矩阵的列数 `n` 应该等于 $2^{height+1 - 1}$ 。 +* 根节点需要放置在顶行的正中间,对应位置为 $res[0][(n-1)/2]$ 。 * 对于放置在矩阵中的每个节点,设对应位置为 $res[r][c]$ ,将其左子节点放置在 $res[r+1][c-2^{height-r-1}]$ ,右子节点放置在 $res[r+1][c+2^{height-r-1}]$ 。 * 继续这一过程,直到树中的所有节点都妥善放置。 * 任意空单元格都应该包含空字符串 `""` 。 @@ -18,6 +20,7 @@ Tag : 「二叉树」、「递归」 返回构造得到的矩阵 `res` 。 示例 1: + ![](https://assets.leetcode.com/uploads/2021/05/03/print1-tree.jpg) ``` @@ -28,7 +31,9 @@ Tag : 「二叉树」、「递归」  ["2","",""]] ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2021/05/03/print2-tree.jpg) + ``` 输入:root = [1,2,3,null,4] @@ -51,6 +56,8 @@ Tag : 「二叉树」、「递归」 随后根据填充规则,设计 `dfs2` 递归函数往矩阵进行填充。 +注意:在位移操作中,`C++` 和 `Python` 不允许负值移位量,需要额外处理一下。 + Java 代码: ```Java class Solution { @@ -82,6 +89,66 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { + int h = 0, m, n; + vector> ans; +public: + vector> printTree(TreeNode* root) { + dfs1(root, 0); + m = h + 1; + n = (1 << (h + 1)) - 1; + ans = vector>(m, vector(n, "")); + dfs2(root, 0, (n - 1) / 2); + return ans; + } + void dfs1(TreeNode* root, int depth) { + if (!root) return; + h = max(h, depth); + dfs1(root->left, depth + 1); + dfs1(root->right, depth + 1); + } + void dfs2(TreeNode* root, int x, int y) { + if (!root) return; + ans[x][y] = to_string(root->val); + if (h - x - 1 >= 0) { + dfs2(root->left, x + 1, y - (1 << (h - x - 1))); + dfs2(root->right, x + 1, y + (1 << (h - x - 1))); + } + } +}; +``` +Python 代码: +```Python +class Solution: + def printTree(self, root: Optional[TreeNode]) -> List[List[str]]: + self.h = 0 + self.dfs1(root, 0) + m = self.h + 1 + n = (1 << (self.h + 1)) - 1 + self.ans = [["" for _ in range(n)] for _ in range(m)] + self.dfs2(root, 0, (n - 1) // 2) + return self.ans + + def dfs1(self, root: TreeNode, depth: int) -> None: + if not root: + return + self.h = max(self.h, depth) + self.dfs1(root.left, depth + 1) + self.dfs1(root.right, depth + 1) + + def dfs2(self, root: TreeNode, x: int, y: int) -> None: + if not root: + return + self.ans[x][y] = str(root.val) + if self.h - x - 1 >= 0: + shift = 1 << (self.h - x - 1) + if y - shift >= 0: + self.dfs2(root.left, x + 1, y - shift) + if y + shift < len(self.ans[0]): + self.dfs2(root.right, x + 1, y + shift) +``` Typescript 代码: ```Typescript let h: number, m: number, n: number; diff --git "a/LeetCode/661-670/664. \345\245\207\346\200\252\347\232\204\346\211\223\345\215\260\346\234\272\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/661-670/664. \345\245\207\346\200\252\347\232\204\346\211\223\345\215\260\346\234\272\357\274\210\345\233\260\351\232\276\357\274\211.md" index 80bead0a..d3ba0147 100644 --- "a/LeetCode/661-670/664. \345\245\207\346\200\252\347\232\204\346\211\223\345\215\260\346\234\272\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/661-670/664. \345\245\207\346\200\252\347\232\204\346\211\223\345\215\260\346\234\272\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -74,18 +74,18 @@ Tag : 「区间 DP」 ### 动态规划 -定义 $f[l][r]$ 为将 $[l, r]$ 这一段打印成目标结果所消耗的最小打印次数。 +定义 `f[l][r]` 为将 $[l, r]$ 这一段打印成目标结果所消耗的最小打印次数。 -不失一般性考虑 $f[l][r]$ 该如何转移: +不失一般性考虑 `f[l][r]` 该如何转移: -* 只染 $l$ 这个位置,此时 $f[l][r] = f[l + 1][r] + 1$ -* 不只染 $l$ 这个位置,而是从 $l$ 染到 $k$(需要确保首位相同 $s[l] = s[k]$):$f[l][r] = f[l][k - 1] + f[k + 1][r], l < k <= r$ +* 只染 `l` 这个位置,此时 $f[l][r] = f[l + 1][r] + 1$ +* 不只染 `l` 这个位置,而是从 `l` 染到 `k`(需要确保首位相同 `s[l] = s[k]`):$f[l][r] = f[l][k - 1] + f[k + 1][r], l < k <= r$ -其中状态转移方程中的情况 $2$ 需要说明一下:由于我们只确保 $s[l] = s[k]$,并不确保 $[l, k]$ 之间的字符相同,根据我们基本分析可知,$s[k]$ 这个点可由打印 $s[l]$ 的时候一同打印,因此本身 $s[k]$ 并不独立消耗打印次数,所以这时候 $[l, k]$ 这一段的最小打印次数应该取 $f[l][k - 1]$,而不是 $f[l][k]$。 +其中状态转移方程中的情况 2 需要说明一下:由于我们只确保 `s[l] = s[k]`,并不确保 $[l, k]$ 之间的字符相同,根据我们基本分析可知,`s[k]` 这个点可由打印 `s[l]` 的时候一同打印,因此本身 `s[k]` 并不独立消耗打印次数,所以这时候 $[l, k]$ 这一段的最小打印次数应该取 `f[l][k-1]`,而不是 `f[l][k]`。 -最终的 $f[l][r]$ 为上述所有方案中取 $min$。 +最终的 `f[l][r]` 为上述所有方案中取 $min$。 -代码: +Java 代码: ```Java class Solution { public int strangePrinter(String s) { @@ -106,6 +106,64 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int strangePrinter(string s) { + int n = s.length(); + vector> f(n + 1, vector(n + 1, 0)); + for (int len = 1; len <= n; ++len) { + for (int l = 0; l + len - 1 < n; ++l) { + int r = l + len - 1; + f[l][r] = f[l + 1][r] + 1; + for (int k = l + 1; k <= r; ++k) { + if (s[l] == s[k]) { + f[l][r] = min(f[l][r], f[l][k - 1] + f[k + 1][r]); + } + } + } + } + return f[0][n - 1]; + } +}; +``` +Python 代码: +```Python +class Solution: + def strangePrinter(self, s: str) -> int: + n = len(s) + f = [[0] * (n + 1) for _ in range(n + 1)] + for lenv in range(1, n + 1): + l = 0 + while l + lenv - 1 < n: + r = l + lenv - 1 + f[l][r] = f[l + 1][r] + 1 + for k in range(l + 1, r + 1): + if s[l] == s[k]: + f[l][r] = min(f[l][r], f[l][k - 1] + f[k + 1][r]) + l += 1 + return f[0][n - 1] +``` +TypeScript 代码: +```TypeScript +function strangePrinter(s: string): number { + const n = s.length; + const f = Array.from({ length: n + 1 }, () => new Array(n + 1).fill(0)); + for (let len = 1; len <= n; len++) { + for (let l = 0; l + len - 1 < n; l++) { + const r = l + len - 1; + f[l][r] = f[l + 1][r] + 1; + for (let k = l + 1; k <= r; k++) { + if (s.charAt(l) === s.charAt(k)) { + f[l][r] = Math.min(f[l][r], f[l][k - 1] + f[k + 1][r]); + } + } + } + } + return f[0][n - 1]; +}; +``` * 时间复杂度:$O(n^3)$ * 空间复杂度:$O(n^2)$ diff --git "a/LeetCode/661-670/667. \344\274\230\347\276\216\347\232\204\346\216\222\345\210\227 II\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/661-670/667. \344\274\230\347\276\216\347\232\204\346\216\222\345\210\227 II\357\274\210\344\270\255\347\255\211\357\274\211.md" index f51f2cc3..52fcaeae 100644 --- "a/LeetCode/661-670/667. \344\274\230\347\276\216\347\232\204\346\216\222\345\210\227 II\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/661-670/667. \344\274\230\347\276\216\347\232\204\346\216\222\345\210\227 II\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -8,9 +8,11 @@ Tag : 「构造」、「脑筋急转弯」 给你两个整数 `n` 和 `k` ,请你构造一个答案列表 `answer`,该列表应当包含从 `1` 到 `n` 的 `n` 个不同正整数,并同时满足下述条件: -假设该列表是 $answer = [a_1, a_2, a_3, ... , a_n]$ ,那么列表 $[|a_1 - a_2|, |a_2 - a_3|, |a_3 - a_4|, ... , |a_{n-1} - a_n|]$ 中应该有且仅有 k 个不同整数。 +假设该列表是 $answer = [a_1, a_2, a_3, ... , a_n]$ ,那么列表 $[|a_1 - a_2|, |a_2 - a_3|, |a_3 - a_4|, ... , |a_{n-1} - a_n|]$ 中应该有且仅有 `k` 个不同整数。 -返回列表 `answer`。如果存在多种答案,只需返回其中 任意一种 。 +返回列表 `answer`。 + +如果存在多种答案,只需返回其中 任意一种 。 示例 1: ``` @@ -57,6 +59,39 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector constructArray(int n, int k) { + vector ans(n); + int t = n - k - 1; + for (int i = 0; i < t; ++i) ans[i] = i + 1; + for (int i = t, a = n - k, b = n; i < n; ) { + ans[i++] = a++; + if (i < n) ans[i++] = b--; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def constructArray(self, n: int, k: int) -> List[int]: + ans = [0] * n + t = n - k - 1 + for i in range(t): + ans[i] = i + 1 + i, a, b = t, n - k, n + while i < n: + ans[i] = a + i, a = i + 1, a + 1 + if i < n: + ans[i] = b + i, b = i + 1, b - 1 + return ans +``` TypeScript 代码: ```TypeScript function constructArray(n: number, k: number): number[] { diff --git "a/LeetCode/681-690/690. \345\221\230\345\267\245\347\232\204\351\207\215\350\246\201\346\200\247\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/681-690/690. \345\221\230\345\267\245\347\232\204\351\207\215\350\246\201\346\200\247\357\274\210\347\256\200\345\215\225\357\274\211.md" index 3e50983a..6a37ae87 100644 --- "a/LeetCode/681-690/690. \345\221\230\345\267\245\347\232\204\351\207\215\350\246\201\346\200\247\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/681-690/690. \345\221\230\345\267\245\347\232\204\351\207\215\350\246\201\346\200\247\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,11 +7,15 @@ Tag : 「BFS」、「DFS」、「队列」 -给定一个保存员工信息的数据结构,它包含了员工 唯一的 id ,重要度 和 直系下属的 id 。 +给定一个保存员工信息的数据结构,它包含了员工唯一的 id ,重要度和直系下属的 id 。 -比如,员工 1 是员工 2 的领导,员工 2 是员工 3 的领导。他们相应的重要度为 15 , 10 , 5 。那么员工 1 的数据结构是 [1, 15, [2]] ,员工 2的 数据结构是 [2, 10, [3]] ,员工 3 的数据结构是 [3, 5, []] 。注意虽然员工 3 也是员工 1 的一个下属,但是由于 并不是直系 下属,因此没有体现在员工 1 的数据结构中。 +比如,员工 1 是员工 2 的领导,员工 2 是员工 3 的领导。他们相应的重要度为 15 , 10 , 5 。 -现在输入一个公司的所有员工信息,以及单个员工 id ,返回这个员工和他所有下属的重要度之和。 +那么员工 1 的数据结构是 `[1, 15, [2]]`,员工 2的 数据结构是 `[2, 10, [3]]`,员工 3 的数据结构是 `[3, 5, []]`。 + +注意虽然员工 3 也是员工 1 的一个下属,但是由于并不是直系下属,因此没有体现在员工 1 的数据结构中。 + +现在输入一个公司的所有员工信息,以及单个员工 id,返回这个员工和他所有下属的重要度之和。 示例: ``` @@ -24,8 +28,8 @@ Tag : 「BFS」、「DFS」、「队列」 ``` 提示: -* 一个员工最多有一个 直系 领导,但是可以有多个 直系 下属 -* 员工数量不超过 2000 。 +* 一个员工最多有一个直系领导,但是可以有多个直系下属 +* 员工数量不超过 2000 --- @@ -33,15 +37,15 @@ Tag : 「BFS」、「DFS」、「队列」 一个直观的做法是,写一个递归函数来统计某个员工的总和。 -统计自身的 $importance$ 值和直系下属的 $importance$ 值。同时如果某个下属还有下属的话,则递归这个过程。 +统计自身的 `importance` 值和直系下属的 `importance` 值。同时如果某个下属还有下属的话,则递归这个过程。 -代码: +Java 代码: ```Java class Solution { Map map = new HashMap<>(); - public int getImportance(List es, int id) { - int n = es.size(); - for (int i = 0; i < n; i++) map.put(es.get(i).id, es.get(i)); + public int getImportance(List employees, int id) { + int n = employees.size(); + for (int i = 0; i < n; i++) map.put(employees.get(i).id, employees.get(i)); return getVal(id); } int getVal(int id) { @@ -56,6 +60,67 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + unordered_map map; + int getImportance(vector& employees, int id) { + for (auto employee : employees) map[employee->id] = employee; + return getVal(id); + } + int getVal(int id) { + Employee* master = map[id]; + int ans = master->importance; + for (int oid : master->subordinates) { + Employee* other = map[oid]; + ans += other->importance; + for (int sub : other->subordinates) ans += getVal(sub); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def __init__(self): + self.mapping = {} + + def getImportance(self, employees, id): + for emp in employees: + self.mapping[emp.id] = emp + return self.getVal(id) + + def getVal(self, id): + master = self.mapping[id] + ans = master.importance + for oid in master.subordinates: + other = self.mapping[oid] + ans += other.importance + for sub in other.subordinates: + ans += self.getVal(sub) + return ans +``` +TypeScript 代码: +```TypeScript +map: Map; +function getImportance(employees: Employee[], id: number): number { + this.map = new Map(); + employees.forEach(emp => this.map.set(emp.id, emp)); + return getVal(id); +}; +function getVal(id: number): number { + const master = this.map.get(id); + let ans = master.importance; + for (const oid of master.subordinates) { + const other = this.map.get(oid); + ans += other.importance; + for (const sub of other.subordinates) ans += getVal(sub); + } + return ans; +} +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ @@ -63,29 +128,75 @@ class Solution { ### 迭代 / BFS -另外一个做法是使用「队列」来存储所有将要计算的 $Employee$ 对象,每次弹出时进行统计,并将其「下属」添加到队列尾部。 +另外一个做法是使用「队列」来存储所有将要计算的 `Employee` 对象,每次弹出时进行统计,并将其「下属」添加到队列尾部。 -代码: ```Java class Solution { - public int getImportance(List es, int id) { - int n = es.size(); + public int getImportance(List employees, int id) { + int n = employees.size(), ans = 0; Map map = new HashMap<>(); - for (int i = 0; i < n; i++) map.put(es.get(i).id, es.get(i)); - int ans = 0; + for (int i = 0; i < n; i++) map.put(employees.get(i).id, employees.get(i)); Deque d = new ArrayDeque<>(); d.addLast(map.get(id)); while (!d.isEmpty()) { Employee poll = d.pollFirst(); ans += poll.importance; - for (int oid : poll.subordinates) { - d.addLast(map.get(oid)); - } + for (int oid : poll.subordinates) d.addLast(map.get(oid)); } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int getImportance(vector employees, int id) { + int n = employees.size(), ans = 0; + unordered_map map; + for (int i = 0; i < n; i++) map[employees[i]->id] = employees[i]; + deque d; + d.push_back(map[id]); + while (!d.empty()) { + Employee* poll = d.front(); + d.pop_front(); + ans += poll->importance; + for (int oid : poll->subordinates) d.push_back(map[oid]); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def getImportance(self, employees: List['Employee'], id: int) -> int: + n, ans = len(employees), 0 + mapping = {emp.id: emp for emp in employees} + d = [] + d.append(mapping[id]) + while d: + poll = d.pop(0) + ans += poll.importance + for oid in poll.subordinates: + d.append(mapping[oid]) + return ans +``` +TypeScript 代码: +```TypeScript +function getImportance(employees: Employee[], id: number): number { + const map = new Map(employees.map(emp => [emp.id, emp])); + let ans = 0; + const d: Employee[] = []; + d.push(map.get(id)); + while (d.length) { + const poll = d.shift(); + ans += poll.importance; + for (const oid of poll.subordinates) d.push(map.get(oid)); + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/691-700/692. \345\211\215K\344\270\252\351\253\230\351\242\221\345\215\225\350\257\215\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/691-700/692. \345\211\215K\344\270\252\351\253\230\351\242\221\345\215\225\350\257\215\357\274\210\344\270\255\347\255\211\357\274\211.md" index 9dfafdd3..879f135f 100644 --- "a/LeetCode/691-700/692. \345\211\215K\344\270\252\351\253\230\351\242\221\345\215\225\350\257\215\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/691-700/692. \345\211\215K\344\270\252\351\253\230\351\242\221\345\215\225\350\257\215\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -65,9 +65,9 @@ Tag : 「哈希表」、「优先队列(堆)」 代码: ```Java class Solution { - public List topKFrequent(String[] ws, int k) { + public List topKFrequent(String[] words, int k) { Map map = new HashMap<>(); - for (String w : ws) map.put(w, map.getOrDefault(w, 0) + 1); + for (String w : words) map.put(w, map.getOrDefault(w, 0) + 1); PriorityQueue q = new PriorityQueue<>(k, (a, b)->{ // 如果词频不同,根据词频升序 int c1 = (Integer)a[0], c2 = (Integer)b[0]; @@ -101,6 +101,44 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector topKFrequent(vector& words, int k) { + unordered_map map; + for (auto& w : words) map[w]++; + auto comp = [](const pair& a, const pair& b) { + if (a.first != b.first) return a.first > b.first; + return a.second < b.second; + }; + priority_queue, vector>, decltype(comp)> q(comp); + for (auto& s : map) { + int cnt = s.second; + if (q.size() < k) { + q.push({cnt, s.first}); + } else { + if (cnt > q.top().first) { + q.pop(); + q.push({cnt, s.first}); + } else if (cnt == q.top().first) { + if (s.first.compare(q.top().second) < 0) { + q.pop(); + q.push({cnt, s.first}); + } + } + } + } + vector ans; + while (!q.empty()) { + ans.push_back(q.top().second); + q.pop(); + } + reverse(ans.begin(), ans.end()); + return ans; + } +}; +``` * 时间复杂度:使用哈希表统计词频,复杂度为 $O(n)$;使用最多 $n$ 个元素维护一个大小为 $k$ 的堆,复杂度为 $O(n\log{k})$;输出答案复杂度为 $O(k)$(同时 $k \leq n$)。整体复杂度为 $O(n\log{k})$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/691-700/697. \346\225\260\347\273\204\347\232\204\345\272\246\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/691-700/697. \346\225\260\347\273\204\347\232\204\345\272\246\357\274\210\347\256\200\345\215\225\357\274\211.md" index d11c2d09..ea708673 100644 --- "a/LeetCode/691-700/697. \346\225\260\347\273\204\347\232\204\345\272\246\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/691-700/697. \346\225\260\347\273\204\347\232\204\345\272\246\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,15 +6,17 @@ Tag : 「哈希表」 -给定一个非空且只包含非负数的整数数组 nums,数组的度的定义是指数组里任一元素出现频数的最大值。 +给定一个非空且只包含非负数的整数数组 `nums`,数组的度的定义是指数组里任一元素出现频数的最大值。 -你的任务是在 nums 中找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。 +你的任务是在 `nums` 中找到与 `nums` 拥有相同大小的度的最短连续子数组,返回其长度。 示例 1: ``` 输入:[1, 2, 2, 3, 1] + 输出:2 + 解释: 输入数组的度是2,因为元素1和2的出现频数最大,均为2. 连续子数组里面拥有相同度的有如下所示: @@ -24,20 +26,19 @@ Tag : 「哈希表」 示例 2: ``` 输入:[1,2,2,3,1,4,2] + 输出:6 ``` 提示: -* nums.length 在1到 50,000 区间范围内。 -* nums[i] 是一个在 0 到 49,999 范围内的整数。 +* `nums.length` 在 `1` 到 `50000` 区间范围内。 +* `nums[i]` 是一个在 `0` 到 `49999` 范围内的整数。 --- ### 数组计数 -![image.png](https://pic.leetcode-cn.com/1613799894-ZftKMC-image.png) - -由于已知值的范围是 `[0, 49999]`。 +由于已知值的范围是 $[0, 49999]$。 我们可以使用数组 `cnt` 来统计每个值出现的次数,数组 `first` 和 `last` 记录每个值「首次出现」和「最后出现」的下标。 @@ -45,49 +46,88 @@ Tag : 「哈希表」 然后再遍历一次数组,对于那些满足词频为 `max` 的数值进行长度计算。 +Java 代码: ```Java class Solution { - int N = 50009; + int N = 50010; public int findShortestSubArray(int[] nums) { - int n = nums.length; + int n = nums.length, max = 0, ans = 0x3f3f3f3f; int[] cnt = new int[N]; int[] first = new int[N], last = new int[N]; Arrays.fill(first, -1); - int max = 0; for (int i = 0; i < n; i++) { int t = nums[i]; max = Math.max(max, ++cnt[t]); if (first[t] == -1) first[t] = i; last[t] = i; } - int ans = Integer.MAX_VALUE; - for (int i = 0; i < n; i++) { - int t = nums[i]; + for (int t : nums) { if (cnt[t] == max) ans = Math.min(ans, last[t] - first[t] + 1); } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int N = 50010; + int findShortestSubArray(vector& nums) { + int n = nums.size(), maxv = 0, ans = 0x3f3f3f3f; + vector cnt(N, 0); + vector first(N, -1), last(N, -1); + for (int i = 0; i < n; i++) { + int t = nums[i]; + maxv = max(maxv, ++cnt[t]); + if (first[t] == -1) first[t] = i; + last[t] = i; + } + for (auto& t : nums) { + if (cnt[t] == maxv) ans = min(ans, last[t] - first[t] + 1); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def __init__(self): + self.N = 50010 + + def findShortestSubArray(self, nums): + n, maxv, ans = len(nums), 0, 0x3f3f3f3f + cnt = [0] * self.N + first, last = [-1] * self.N, [-1] * self.N + for i, t in enumerate(nums): + cnt[t] += 1 + maxv = max(maxv, cnt[t]) + if first[t] == -1: + first[t] = i + last[t] = i + for t in nums: + if cnt[t] == maxv: + ans = min(ans, last[t] - first[t] + 1) + return ans +``` * 时间复杂度:对数组进行常数次扫描。复杂度为 $O(n)$ * 空间复杂度:$O(n)$ -*** - -### 哈希表计数 +--- -![image.png](https://pic.leetcode-cn.com/1613799953-HZKoaL-image.png) +### 哈希表 同样的,除了使用静态数组,我们还可以使用哈希表进行计数。 -```java +Java 代码: +```Java class Solution { public int findShortestSubArray(int[] nums) { - int n = nums.length; + int n = nums.length, max = 0, ans = 0x3f3f3f3f; Map cnt = new HashMap<>(); Map first = new HashMap<>(), last = new HashMap<>(); - int max = 0; for (int i = 0; i < n; i++) { int t = nums[i]; cnt.put(t, cnt.getOrDefault(t, 0) + 1); @@ -95,22 +135,57 @@ class Solution { if (!first.containsKey(t)) first.put(t, i); last.put(t, i); } - int ans = Integer.MAX_VALUE; - for (int i = 0; i < n; i++) { - int t = nums[i]; - if (cnt.get(t) == max) { - ans = Math.min(ans, last.get(t) - first.get(t) + 1); - } + for (int t : nums) { + if (cnt.get(t) == max) ans = Math.min(ans, last.get(t) - first.get(t) + 1); } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int findShortestSubArray(vector& nums) { + int n = nums.size(), maxv = 0, ans = 0x3f3f3f3f; + unordered_map cnt; + unordered_map first, last; + for (int i = 0; i < n; ++i) { + int num = nums[i]; + cnt[num] += 1; + maxv = max(maxv, cnt[num]); + if (first.find(num) == first.end()) first[num] = i; + last[num] = i; + } + for (auto& t : nums) { + if (cnt[t] == maxv) ans = min(ans, last[t] - first[t] + 1); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def findShortestSubArray(self, nums: List[int]) -> int: + n, maxv, ans = len(nums), 0, 0x3f3f3f3f + count = {} + first, last = {}, {} + for i, num in enumerate(nums): + count[num] = count.get(num, 0) + 1 + maxv = max(maxv, count[num]) + if num not in first: + first[num] = i + last[num] = i + for t in nums: + if count[t] == maxv: + ans = min(ans, last[t] - first[t] + 1) + return ans +``` * 时间复杂度:对数组进行常数次扫描。复杂度为 $O(n)$ * 空间复杂度:$O(n)$ - -*** +--- ### 总结 @@ -120,7 +195,6 @@ class Solution { 因此我建议,对于那些 **数值范围确定且不太大($10^6$ 以内都可以使用,本题数量级在 $10^4$)** 的计算场景,使用数组进行计数,而不是使用哈希表。 - --- ### 最后 diff --git "a/LeetCode/701-710/703. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\254\254 K \345\244\247\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/701-710/703. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\254\254 K \345\244\247\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" index 884f4082..8b156b9c 100644 --- "a/LeetCode/701-710/703. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\254\254 K \345\244\247\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/701-710/703. \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\347\254\254 K \345\244\247\345\205\203\347\264\240\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,7 +6,9 @@ Tag : 「Top K」、「排序」、「优先队列(堆)」 -设计一个找到数据流中第 `k` 大元素的类(class)。注意是排序后的第 `k` 大元素,不是第 `k` 个不同的元素。 +设计一个找到数据流中第 `k` 大元素的类(class)。 + +注意是排序后的第 `k` 大元素,不是第 `k` 个不同的元素。 请实现 `KthLargest` 类: * `KthLargest(int k, int[] nums)` 使用整数 `k` 和整数流 `nums` 初始化对象。 @@ -18,6 +20,7 @@ Tag : 「Top K」、「排序」、「优先队列(堆)」 输入: ["KthLargest", "add", "add", "add", "add", "add"] [[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]] + 输出: [null, 4, 5, 5, 8, 8] diff --git "a/LeetCode/71-80/73. \347\237\251\351\230\265\347\275\256\351\233\266\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/71-80/73. \347\237\251\351\230\265\347\275\256\351\233\266\357\274\210\344\270\255\347\255\211\357\274\211.md" index 94472bc9..dbf4485e 100644 --- "a/LeetCode/71-80/73. \347\237\251\351\230\265\347\275\256\351\233\266\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/71-80/73. \347\237\251\351\230\265\347\275\256\351\233\266\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -16,14 +16,18 @@ Tag : 「模拟」 * 你能想出一个仅使用常量空间的解决方案吗? 示例 1: + ![](https://assets.leetcode.com/uploads/2020/08/17/mat1.jpg) + ``` 输入:matrix = [[1,1,1],[1,0,1],[1,1,1]] 输出:[[1,0,1],[0,0,0],[1,0,1]] ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2020/08/17/mat2.jpg) + ``` 输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]] @@ -31,8 +35,8 @@ Tag : 「模拟」 ``` 提示: -* $m == matrix.length$ -* $n == matrix[0].length$ +* $m = matrix.length$ +* $n = matrix[0].length$ * $1 <= m, n <= 200$ * $-2^{31} <= matrix[i][j] <= 2^{31} - 1$ @@ -50,19 +54,19 @@ Tag : 「模拟」 **1. 使用两个变量(r0 & c0),记录「首行 & 首列」是否该被置零** -![image.png](https://pic.leetcode-cn.com/1616292004-JWVOyl-image.png) +![](https://pic.leetcode-cn.com/1616292004-JWVOyl-image.png) **2.「非首行首列」的位置** * 将置零信息存储到原矩阵 * 根据置零信息,置零「非首行首列」的位置 -![image.png](https://pic.leetcode-cn.com/1616291987-UnBQcI-image.png) +![](https://pic.leetcode-cn.com/1616291987-UnBQcI-image.png) **3. 使用 r0 & c0 ,置零「首行 & 首列」** -![image.png](https://pic.leetcode-cn.com/1616292108-mznYHo-image.png) +![](https://pic.leetcode-cn.com/1616292108-mznYHo-image.png) -代码: +Java 代码: ```Java class Solution { public void setZeroes(int[][] mat) { @@ -102,6 +106,113 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + void setZeroes(vector>& matrix) { + int m = matrix.size(), n = matrix[0].size(); + bool r0 = false, c0 = false; + for (int i = 0; i < m; i++) { + if (matrix[i][0] == 0) { + r0 = true; + break; + } + } + for (int j = 0; j < n; j++) { + if (matrix[0][j] == 0) { + c0 = true; + break; + } + } + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + if (matrix[i][j] == 0) matrix[i][0] = matrix[0][j] = 0; + } + } + for (int j = 1; j < n; j++) { + if (matrix[0][j] == 0) { + for (int i = 1; i < m; i++) matrix[i][j] = 0; + } + } + for (int i = 1; i < m; i++) { + if (matrix[i][0] == 0) fill(matrix[i].begin(), matrix[i].end(), 0); + } + if (r0) for (int i = 0; i < m; i++) matrix[i][0] = 0; + if (c0) fill(matrix[0].begin(), matrix[0].end(), 0); + } +}; +``` +Python 代码: +```Python +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + m, n = len(matrix), len(matrix[0]) + r0, c0 = False, False + for i in range(m): + if matrix[i][0] == 0: + r0 = True + break + for j in range(n): + if matrix[0][j] == 0: + c0 = True + break + for i in range(1, m): + for j in range(1, n): + if matrix[i][j] == 0: + matrix[i][0] = matrix[0][j] = 0 + for j in range(1, n): + if matrix[0][j] == 0: + for i in range(1, m): + matrix[i][j] = 0 + for i in range(1, m): + if matrix[i][0] == 0: + matrix[i] = [0] * n + if r0: + for i in range(m): + matrix[i][0] = 0 + if c0: + matrix[0] = [0] * n +``` +TypeScript 代码: +```TypeScript +/** + Do not return anything, modify matrix in-place instead. + */ +function setZeroes(matrix: number[][]): void { + let m = matrix.length, n = matrix[0].length; + let r0 = false, c0 = false; + for (let i = 0; i < m; i++) { + if (matrix[i][0] === 0) { + r0 = true; + break; + } + } + for (let j = 0; j < n; j++) { + if (matrix[0][j] === 0) { + c0 = true; + break; + } + } + for (let i = 1; i < m; i++) { + for (let j = 1; j < n; j++) { + if (matrix[i][j] === 0) matrix[i][0] = matrix[0][j] = 0; + } + } + for (let j = 1; j < n; j++) { + if (matrix[0][j] === 0) { + for (let i = 1; i < m; i++) matrix[i][j] = 0; + } + } + for (let i = 1; i < m; i++) { + if (matrix[i][0] === 0) { + for (let j = 0; j < n; j++) matrix[i][j] = 0; + } + } + if (r0) for (let i = 0; i < m; i++) matrix[i][0] = 0; + if (c0) for (let i = 0; i < n; i++) matrix[0][i] = 0; +}; +``` * 时间复杂度:$O(n \times m)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/71-80/74. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/71-80/74. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265\357\274\210\344\270\255\347\255\211\357\274\211.md" index 4f85afda..e31c3230 100644 --- "a/LeetCode/71-80/74. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/71-80/74. \346\220\234\347\264\242\344\272\214\347\273\264\347\237\251\351\230\265\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -7,10 +7,10 @@ Tag : 「二叉搜索树」、「二分」 -编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: +编写一个高效的算法来判断 `m x n` 矩阵中,是否存在一个目标值。该矩阵具有如下特性: -每行中的整数从左到右按升序排列。 -每行的第一个整数大于前一行的最后一个整数。 +* 每行中的整数从左到右按升序排列。 +* 每行的第一个整数大于前一行的最后一个整数。 示例 1: @@ -32,14 +32,14 @@ Tag : 「二叉搜索树」、「二分」 ``` 提示: -* m == matrix.length -* n == matrix[i].length -* 1 <= m, n <= 100 -* -$10^4$ <= matrix[i][j], target <= $10^4$ +* $m = matrix.length$ +* $n = matrix[i].length$ +* $1 <= m, n <= 100$ +* $-10^4 <= matrix[i][j], target <= 10^4$ --- -### 二分解法(一) +### 二分(一) 由于二维矩阵固定列的「从上到下」或者固定行的「从左到右」都是升序的。 @@ -49,55 +49,127 @@ Tag : 「二叉搜索树」、「二分」 2. 第二次二分:从 `row` 中「所有列」开始找,找到合适的列 `col` -代码: +Java 代码: ```Java class Solution { public boolean searchMatrix(int[][] mat, int t) { int m = mat.length, n = mat[0].length; - // 第一次二分:定位到所在行(从上往下,找到最后一个满足 mat[x]][0] <= t 的行号) int l = 0, r = m - 1; while (l < r) { int mid = l + r + 1 >> 1; - if (mat[mid][0] <= t) { - l = mid; - } else { - r = mid - 1; - } + if (mat[mid][0] <= t) l = mid; + else r = mid - 1; } - int row = r; if (mat[row][0] == t) return true; if (mat[row][0] > t) return false; - // 第二次二分:从所在行中定位到列(从左到右,找到最后一个满足 mat[row][x] <= t 的列号) l = 0; r = n - 1; while (l < r) { int mid = l + r + 1 >> 1; - if (mat[row][mid] <= t) { - l = mid; - } else { - r = mid - 1; - } + if (mat[row][mid] <= t) l = mid; + else r = mid - 1; } int col = r; - - return mat[row][col] == t; + return mat[row][r] == t; } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool searchMatrix(vector>& mat, int t) { + int m = mat.size(), n = mat[0].size(); + // 第一次二分:定位到所在行 + int l = 0, r = m - 1; + while (l < r) { + int mid = l + r + 1 >> 1; + if (mat[mid][0] <= t) l = mid; + else r = mid - 1; + } + int row = r; + if (mat[row][0] == t) return true; + if (mat[row][0] > t) return false; + // 第二次二分:从所在行中定位到列 + l = 0; r = n - 1; + while (l < r) { + int mid = l + r + 1 >> 1; + if (mat[row][mid] <= t) l = mid; + else r = mid - 1; + } + int col = r; + return mat[row][l] == t; + } +}; +``` +Python 代码: +```Python +class Solution: + def searchMatrix(self, mat, t): + m, n = len(mat), len(mat[0]) + # 第一次二分:定位到所在行 + l, r = 0, m - 1 + while l < r: + mid = l + r + 1 >> 1 + if mat[mid][0] <= t: + l = mid + else: + r = mid - 1 + row = r + if mat[row][0] == t: + return True + if mat[row][0] > t: + return False + # 第二次二分:从所在行中定位到列 + l, r = 0, n - 1 + while l < r: + mid = l + r + 1 >> 1 + if mat[row][mid] <= t: + l = mid + else: + r = mid - 1 + col = r + return mat[row][l] == t +``` +TypeScript 代码: +```Java +function searchMatrix(mat: number[][], t: number): boolean { + const m = mat.length, n = mat[0].length; + // 第一次二分:定位到所在行 + let l = 0, r = m - 1; + while (l < r) { + const mid = l + r + 1 >> 1; + if (mat[mid][0] <= t) l = mid; + else r = mid - 1; + } + const row = r; + if (mat[row][0] == t) return true; + if (mat[row][0] > t) return false; + // 第二次二分:从所在行中定位到列 + l = 0; r = n - 1; + while (l < r) { + const mid = l + r + 1 >> 1; + if (mat[row][mid] <= t) l = mid; + else r = mid - 1; + } + const col = r; + return mat[row][l] == t; +}; +``` * 时间复杂度:$O(\log{m} + \log{n})$ * 空间复杂度:$O(1)$ -*** +--- -### 二分解法(二) +### 二分(二) 当然,因为将二维矩阵的行尾和行首连接,也具有单调性。 我们可以将「二维矩阵」当做「一维矩阵」来做。 -代码: +Java 代码: ```Java class Solution { public boolean searchMatrix(int[][] mat, int t) { @@ -105,33 +177,73 @@ class Solution { int l = 0, r = m * n - 1; while (l < r) { int mid = l + r + 1 >> 1; - if (mat[mid / n][mid % n] <= t) { - l = mid; - } else { - r = mid - 1; - } + if (mat[mid / n][mid % n] <= t) l = mid; + else r = mid - 1; } return mat[r / n][r % n] == t; } } ``` -* 时间复杂度:$O(\log{(m * n)})$ +C++ 代码: +```C++ +class Solution { +public: + bool searchMatrix(vector>& mat, int t) { + int m = mat.size(), n = mat[0].size(); + int l = 0, r = m * n - 1; + while (l < r) { + int mid = l + r + 1 >> 1; + if (mat[mid / n][mid % n] <= t) l = mid; + else r = mid - 1; + } + return mat[l / n][l % n] == t; + } +}; +``` +Python 代码: +```Python +class Solution: + def searchMatrix(self, mat, t): + m, n = len(mat), len(mat[0]) + l, r = 0, m * n - 1 + while l < r: + mid = l + r + 1 >> 1 + if mat[mid // n][mid % n] <= t: + l = mid + else: + r = mid - 1 + return mat[l // n][l % n] == t +``` +TypeScript 代码: +```Java +function searchMatrix(mat: number[][], t: number): boolean { + const m = mat.length, n = mat[0].length; + let l = 0, r = m * n - 1; + while (l < r) { + const mid = l + r + 1 >> 1; + if (mat[Math.floor(mid / n)][mid % n] <= t) l = mid; + else r = mid - 1; + } + return mat[Math.floor(l / n)][l % n] === t; +}; +``` +* 时间复杂度:$O(\log{(m \times n)})$ * 空间复杂度:$O(1)$ -*** +--- -### 抽象 BST 解法 +### 抽象 BST 我们可以将二维矩阵抽象成「以右上角为根的 BST」: -![image.png](https://pic.leetcode-cn.com/1617066993-AyRIiF-image.png) +![](https://pic.leetcode-cn.com/1617066993-AyRIiF-image.png) 那么我们可以从根(右上角)开始搜索,如果当前的节点不等于目标值,可以按照树的搜索顺序进行: 1. 当前节点「大于」目标值,搜索当前节点的「左子树」,也就是当前矩阵位置的「左方格子」,即 y-- 2. 当前节点「小于」目标值,搜索当前节点的「右子树」,也就是当前矩阵位置的「下方格子」,即 x++ -代码: +Java 代码: ```Java class Solution { int m, n; @@ -139,11 +251,8 @@ class Solution { m = mat.length; n = mat[0].length; int x = 0, y = n - 1; while (check(x, y) && mat[x][y] != t) { - if (mat[x][y] > t) { - y--; - } else { - x++; - } + if (mat[x][y] > t) y--; + else x++; } return check(x, y) && mat[x][y] == t; } @@ -152,10 +261,59 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int m, n; + bool searchMatrix(vector>& mat, int t) { + m = mat.size(); n = mat[0].size(); + int x = 0, y = n - 1; + while (check(x, y) && mat[x][y] != t) { + if (mat[x][y] > t) y--; + else x++; + } + return check(x, y) && mat[x][y] == t; + } + bool check(int x, int y) { + return x >= 0 && x < m && y >= 0 && y < n; + } +}; +``` +Python 代码: +```Python +class Solution: + def searchMatrix(self, mat, t): + m, n = len(mat), len(mat[0]) + x, y = 0, n - 1 + while self.check(m, n, x, y) and mat[x][y] != t: + if mat[x][y] > t: + y -= 1 + else: + x += 1 + return self.check(m, n, x, y) and mat[x][y] == t + def check(self, m, n, x, y): + return 0 <= x < m and 0 <= y < n +``` +TypeScript 代码: +```Java +function check(m: number, n: number, x: number, y: number): boolean { + return x >= 0 && x < m && y >= 0 && y < n; +} +function searchMatrix(mat: number[][], t: number): boolean { + const m = mat.length, n = mat[0].length; + let x = 0, y = n - 1; + while (check(m, n, x, y) && mat[x][y] !== t) { + if (mat[x][y] > t) y--; + else x++; + } + return check(m, n, x, y) && mat[x][y] === t; +}; +``` * 时间复杂度:$O(m+n)$ * 空间复杂度:$O(1)$ -*** +--- ### 拓展 @@ -163,7 +321,6 @@ class Solution { [240. 搜索二维矩阵 II](https://leetcode-cn.com/problems/search-a-2d-matrix-ii/) - --- ### 最后 diff --git "a/LeetCode/721-730/724. \345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\344\270\213\346\240\207\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/721-730/724. \345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\344\270\213\346\240\207\357\274\210\347\256\200\345\215\225\357\274\211.md" index 2f582dfa..48d9e817 100644 --- "a/LeetCode/721-730/724. \345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\344\270\213\346\240\207\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/721-730/724. \345\257\273\346\211\276\346\225\260\347\273\204\347\232\204\344\270\255\345\277\203\344\270\213\346\240\207\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,11 +7,11 @@ Tag : 「前缀和」 -给你一个整数数组 nums,请编写一个能够返回数组 “中心下标” 的方法。 +给你一个整数数组 `nums`,请编写一个能够返回数组 “中心下标” 的方法。 -数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。 +数组中心下标是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。 -如果数组不存在中心下标,返回 -1 。 +如果数组不存在中心下标,返回 `-1`。 如果数组有多个中心下标,应该返回最靠近左边的那一个。 @@ -51,8 +51,8 @@ Tag : 「前缀和」 ``` 提示: -* nums 的长度范围为 [0, 10000]。 -* 任何一个 nums[i] 将会是一个范围在 [-1000, 1000]的整数。 +* `nums` 的长度范围为 $[0, 10000]$。 +* 任何一个 `nums[i]` 将会是一个范围在 $[-1000, 1000]$ 的整数。 --- @@ -66,7 +66,7 @@ Tag : 「前缀和」 这里由于要求前后前缀和。所以我们直接多开两位。 -代码: +Java 代码: ```Java class Solution { public int pivotIndex(int[] nums) { @@ -81,6 +81,37 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int pivotIndex(vector& nums) { + int n = nums.size(); + vector s1(n + 2, 0), s2(n + 2, 0); + for (int i = 1; i <= n; i++) s1[i] = s1[i - 1] + nums[i - 1]; + for (int i = n; i >= 1; i--) s2[i] = s2[i + 1] + nums[i - 1]; + for (int i = 1; i <= n; i++) { + if (s1[i] == s2[i]) return i - 1; + } + return -1; + } +}; +``` +Python 代码: +```Python +class Solution: + def pivotIndex(self, nums: List[int]) -> int: + n = len(nums) + s1, s2 = [0] * (n + 2), [0] * (n + 2) + for i in range(1, n + 1): + s1[i] = s1[i - 1] + nums[i - 1] + for i in range(n, 0, -1): + s2[i] = s2[i + 1] + nums[i - 1] + for i in range(1, n + 1): + if s1[i] == s2[i]: + return i - 1 + return -1 +``` * 时间复杂度:对数组进行线性扫描。复杂度为 $O(n)$ * 空间复杂度:使用了前缀和数组。复杂度为$O(n)$ @@ -94,7 +125,7 @@ class Solution { 但这只是常数级别的优化,并不影响其时空复杂度。 -代码: +Java 代码: ```Java class Solution { public int pivotIndex(int[] nums) { @@ -109,6 +140,36 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int pivotIndex(vector& nums) { + int n = nums.size(); + vector sumv(n + 1, 0); + for (int i = 1; i <= n; i++) sumv[i] = sumv[i - 1] + nums[i - 1]; + for (int i = 1; i <= n; i++) { + int left = sumv[i - 1], right = sumv[n] - sumv[i]; + if (left == right) return i - 1; + } + return -1; + } +}; +``` +Python 代码: +```Python +class Solution: + def pivotIndex(self, nums: List[int]) -> int: + n = len(nums) + sumv = [0] * (n + 1) + for i in range(1, n + 1): + sumv[i] = sumv[i - 1] + nums[i - 1] + for i in range(1, n + 1): + left, right = sumv[i - 1], sumv[n] - sumv[i] + if left == right: + return i - 1 + return -1 +``` * 时间复杂度:对数组进行线性扫描。复杂度为 $O(n)$ * 空间复杂度:使用了前缀和数组。复杂度为$O(n)$ @@ -122,12 +183,11 @@ class Solution { 对于中心索引必然有:`sum = total - sum - nums[i]` (左边值 = 右边值) -代码: +Java 代码: ```Java class Solution { public int pivotIndex(int[] nums) { - int n = nums.length; - int total = 0, sum = 0; + int n = nums.length, total = 0, sum = 0; // 我们的 nums 处理不涉及并行操作,使用循环要比 Arrays.stream 快 // total = Arrays.stream(nums).sum(); for (int i = 0; i < n; i++) total += nums[i]; @@ -139,24 +199,33 @@ class Solution { } } ``` -* 时间复杂度:对数组进行线性扫描。复杂度为 $O(n)$ -* 空间复杂度:$O(1)$ - ---- - -### 总结 - -这是我使用到的前缀和模板(高频): - -```Java +C++ 代码: +```C++ class Solution { - public void func(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]; +public: + int pivotIndex(vector& nums) { + int n = nums.size(), total = accumulate(nums.begin(), nums.end(), 0), sumv = 0; + for (int i = 0; i < n; i++) { + if (sumv == total - sumv - nums[i]) return i; + sumv += nums[i]; + } + return -1; } -} +}; +``` +Python 代码: +```Python +class Solution: + def pivotIndex(self, nums: List[int]) -> int: + n, total, sumv = len(nums), sum(nums), 0 + for i in range(n): + if sumv == total - sumv - nums[i]: + return i + sumv += nums[i] + return -1 ``` +* 时间复杂度:对数组进行线性扫描。复杂度为 $O(n)$ +* 空间复杂度:$O(1)$ --- diff --git "a/LeetCode/741-750/743. \347\275\221\347\273\234\345\273\266\350\277\237\346\227\266\351\227\264\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/741-750/743. \347\275\221\347\273\234\345\273\266\350\277\237\346\227\266\351\227\264\357\274\210\344\270\255\347\255\211\357\274\211.md" index a9630ab4..d78e50b0 100644 --- "a/LeetCode/741-750/743. \347\275\221\347\273\234\345\273\266\350\277\237\346\227\266\351\227\264\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/741-750/743. \347\275\221\347\273\234\345\273\266\350\277\237\346\227\266\351\227\264\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -8,11 +8,16 @@ Tag : 「最短路」、「图」、「优先队列(堆)」 有 $n$ 个网络节点,标记为 $1$ 到 $n$。 -给你一个列表 times,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi),其中 ui 是源节点,vi 是目标节点, wi 是一个信号从源节点传递到目标节点的时间。 +给你一个列表 `times`,表示信号经过有向边的传递时间。  -现在,从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1 。 +`times[i] = (ui, vi, wi)`,其中 `ui` 是源节点,`vi` 是目标节点,`wi` 是一个信号从源节点传递到目标节点的时间 + +现在,从某个节点 `K` 发出一个信号。需要多久才能使所有节点都收到信号? + +如果不能使所有节点收到信号,返回 `-1`。 示例 1: + ![](https://assets.leetcode.com/uploads/2019/05/23/931_example_1.png) ``` @@ -21,12 +26,14 @@ Tag : 「最短路」、「图」、「优先队列(堆)」 输出:2 ``` 示例 2: + ``` 输入:times = [[1,2,1]], n = 2, k = 1 输出:1 ``` 示例 3: + ``` 输入:times = [[1,2,1]], n = 2, k = 2 @@ -148,7 +155,7 @@ for (Edge e : es) { 跑一遍 Floyd,可以得到「**从任意起点出发,到达任意起点的最短距离**」。然后从所有 $w[k][x]$ 中取 $max$ 即是「**从 $k$ 点出发,到其他点 $x$ 的最短距离的最大值**」。 -![image.png](https://pic.leetcode-cn.com/1628304636-QXNdHd-image.png) +![](https://pic.leetcode-cn.com/1628304636-QXNdHd-image.png) 代码: ```Java @@ -206,7 +213,7 @@ class Solution { 朴素 Dijkstra 复杂度为 $O(n^2)$,可以过。 -![image.png](https://pic.leetcode-cn.com/1628304649-fZWlTG-image.png) +![](https://pic.leetcode-cn.com/1628304649-fZWlTG-image.png) 代码: ```Java @@ -280,7 +287,7 @@ class Solution { 此时算法复杂度为 $O(m\log{n})$,可以过。 -![image.png](https://pic.leetcode-cn.com/1628304662-JgsyVc-image.png) +![](https://pic.leetcode-cn.com/1628304662-JgsyVc-image.png) 代码: ```Java @@ -355,13 +362,13 @@ class Solution { ### Bellman Ford(类 & 邻接表) -虽然题目规定了不存在「负权边」,但我们仍然可以使用可以在「负权图中求最短路」的 Bellman Ford 进行求解,该算法也是「单源最短路」算法,复杂度为 $O(n * m)$。 +虽然题目规定了不存在「负权边」,但我们仍然可以使用可以在「负权图中求最短路」的 Bellman Ford 进行求解,该算法也是「单源最短路」算法,复杂度为 $O(n \times m)$。 -通常为了确保 $O(n * m)$,可以单独建一个类代表边,将所有边存入集合中,在 $n$ 次松弛操作中直接对边集合进行遍历(代码见 $P1$)。 +通常为了确保 $O(n \times m)$,可以单独建一个类代表边,将所有边存入集合中,在 $n$ 次松弛操作中直接对边集合进行遍历(代码见 $P1$)。 由于本题边的数量级大于点的数量级,因此也能够继续使用「邻接表」的方式进行边的遍历,遍历所有边的复杂度的下界为 $O(n)$,上界可以确保不超过 $O(m)$(代码见 $P2$)。 -![image.png](https://pic.leetcode-cn.com/1628304674-vZRSkx-image.png) +![](https://pic.leetcode-cn.com/1628304674-vZRSkx-image.png) 代码: ```Java @@ -476,9 +483,9 @@ class Solution { SPFA 是对 Bellman Ford 的优化实现,可以使用队列进行优化,也可以使用栈进行优化。 -通常情况下复杂度为 $O(k*m)$,$k$ 一般为 $4$ 到 $5$,最坏情况下仍为 $O(n * m)$,当数据为网格图时,复杂度会从 $O(k*m)$ 退化为 $O(n*m)$。 +通常情况下复杂度为 $O(k \times m)$,$k$ 一般为 $4$ 到 $5$,最坏情况下仍为 $O(n \times m)$,当数据为网格图时,复杂度会从 $O(k \times m)$ 退化为 $O(n \times m)$。 -![image.png](https://pic.leetcode-cn.com/1628304686-hfZCXS-image.png) +![](https://pic.leetcode-cn.com/1628304686-hfZCXS-image.png) 代码: ```Java diff --git "a/LeetCode/761-770/765. \346\203\205\344\276\243\347\211\265\346\211\213\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/761-770/765. \346\203\205\344\276\243\347\211\265\346\211\213\357\274\210\345\233\260\351\232\276\357\274\211.md" index 90bec49e..9fe7d8c7 100644 --- "a/LeetCode/761-770/765. \346\203\205\344\276\243\347\211\265\346\211\213\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/761-770/765. \346\203\205\344\276\243\347\211\265\346\211\213\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -6,41 +6,45 @@ Tag : 「并查集」、「贪心」 -N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。 +`N` 对情侣坐在连续排列的 `2N` 个座位上,想要牵到对方的手。 -人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。 +计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。 -这些情侣的初始座位  row[i] 是由最初始坐在第 i 个座位上的人决定的。 +人和座位用 `0` 到 `2N-1` 的整数表示,情侣们按顺序编号,第一对是 $(0, 1)$,第二对是 $(2, 3)$,以此类推,最后一对是 $(2N-2, 2N-1)$。 + +这些情侣的初始座位  `row[i]` 是由最初始坐在第 i 个座位上的人决定的。 示例 1: ``` 输入: row = [0, 2, 1, 3] + 输出: 1 + 解释: 我们只需要交换row[1]和row[2]的位置即可。 ``` 示例 2: ``` 输入: row = [3, 2, 0, 1] + 输出: 0 + 解释: 无需交换座位,所有的情侣都已经可以手牵手了。 ``` 说明: -* len(row) 是偶数且数值在 [4, 60]范围内。 -* 可以保证row 是序列 `0...len(row)-1` 的一个全排列。 +* `len(row)` 是偶数且数值在 $[4, 60]$范围内。 +* 可以保证 `row` 是序列 `0...len(row)-1` 的一个全排列。 --- ### 并查集 -![image.png](https://pic.leetcode-cn.com/1613291147-hfyqtT-image.png) - 首先,我们总是以「情侣对」为单位进行设想: -1. 当有两对情侣相互坐错了位置,ta们两对之间形成了一个环。需要进行一次交换,使得每队情侣独立(相互牵手) +1. 当有两对情侣相互坐错了位置,ta 们两对之间形成了一个环。需要进行一次交换,使得每队情侣独立(相互牵手) -2. 如果三对情侣相互坐错了位置,ta们三对之间形成了一个环,需要进行两次交换,使得每队情侣独立(相互牵手) +2. 如果三对情侣相互坐错了位置,ta 们三对之间形成了一个环,需要进行两次交换,使得每队情侣独立(相互牵手) -3. 如果四对情侣相互坐错了位置,ta们四对之间形成了一个环,需要进行三次交换,使得每队情侣独立(相互牵手) +3. 如果四对情侣相互坐错了位置,ta 们四对之间形成了一个环,需要进行三次交换,使得每队情侣独立(相互牵手) 也就是说,如果我们有 `k` 对情侣形成了错误环,需要交换 `k - 1` 次才能让情侣牵手。 @@ -50,6 +54,7 @@ N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 由于 0和1配对、2和3配对 ... 因此互为情侣的两个编号除以 2 对应同一个数字,可直接作为它们的「情侣组」编号: +Java 代码: ```Java class Solution { int[] p = new int[70]; @@ -72,15 +77,65 @@ N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector p; + Solution() : p(70) { + for (int i = 0; i < 70; ++i) p[i] = i; + } + void unionSet(int a, int b) { + p[find(a)] = p[find(b)]; + } + int find(int x) { + if (p[x] != x) p[x] = find(p[x]); + return p[x]; + } + int minSwapsCouples(vector& row) { + int n = row.size(), m = n / 2; + for (int i = 0; i < m; i++) p[i] = i; + for (int i = 0; i < n; i += 2) unionSet(row[i] / 2, row[i + 1] / 2); + int cnt = 0; + for (int i = 0; i < m; i++) { + if (i == find(i)) ++cnt; + } + return m - cnt; + } +}; +``` +Python 代码: +```Python +class Solution: + def __init__(self): + self.p = [i for i in range(70)] + + def union(self, a, b): + self.p[self.find(a)] = self.p[self.find(b)] + + def find(self, x): + if self.p[x] != x: + self.p[x] = self.find(self.p[x]) + return self.p[x] + + def minSwapsCouples(self, row): + n = len(row) + m = n // 2 + for i in range(0, n, 2): + self.union(row[i] // 2, row[i + 1] // 2) + cnt = 0 + for i in range(m): + if i == self.find(i): + cnt += 1 + return m - cnt +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ -*** +--- ### 贪心 -![image.png](https://pic.leetcode-cn.com/1613291131-wiYksH-image.png) - 还是以「情侣对」为单位进行分析: 由于题目保证有解,我们也可以从前往后(每两格作为一步)处理,对于某一个位置而言,如果下一个位置不是应该出现的情侣的话。 @@ -89,11 +144,11 @@ N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 同时为了方便我们找到某个值的下标,需要先对 `row` 进行预处理(可以使用哈希表或数组)。 +Java 代码: ```Java class Solution { public int minSwapsCouples(int[] row) { - int n = row.length; - int ans = 0; + int n = row.length, ans = 0; int[] cache = new int[n]; for (int i = 0; i < n; i++) cache[row[i]] = i; for (int i = 0; i < n - 1; i += 2) { @@ -115,10 +170,52 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int minSwapsCouples(vector& row) { + int n = row.size(), ans = 0; + vector cache(n); + for (int i = 0; i < n; i++) cache[row[i]] = i; + for (int i = 0; i < n - 1; i += 2) { + int a = row[i], b = a ^ 1; + if (row[i + 1] != b) { + int src = i + 1, tar = cache[b]; + cache[row[tar]] = src; + cache[row[src]] = tar; + swap(row[src], row[tar]); + ans++; + } + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def minSwapsCouples(self, row): + n, ans = len(row), 0 + cache = [0] * (max(row) + 1) + for i in range(n): + cache[row[i]] = i + for i in range(0, n, 2): + a = row[i] + b = a ^ 1 + if row[i + 1] != b: + src = i + 1 + tar = cache[b] + cache[row[tar]] = src + cache[row[src]] = tar + row[src], row[tar] = row[tar], row[src] + ans += 1 + return ans +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ -*** +--- ### 证明/分析 @@ -139,11 +236,12 @@ a. 现在处理第 `k` 个位置,使其牵手成功: 分两种情况来讨论: 1. 与第 `k` 个位置的匹配的两个情侣不在同一个位置上:这时候无论交换左边还是右边,后面需要调整的「情侣对数量」都是一样。假设处理第 `k` 个位置前需要调整的数量为 `n` 的话,处理完第 `k` 个位置(交换左边或是右边),需要调整的「情侣对数量」都为 `n - 1` 。 -![image.png](https://pic.leetcode-cn.com/1613294210-JIMqBl-image.png) +![](https://pic.leetcode-cn.com/1613294210-JIMqBl-image.png) 2. 与第 `k` 个位置的匹配的两个情侣在同一个位置上:这时候无论交换左边还是右边,后面需要调整的「情侣对数量」都是一样。假设处理第 `k` 个位置前需要调整的数量为 `n` 的话,处理完第 `k` 个位置(交换左边或是右边),需要调整的「情侣对数量」都为 `n - 2` 。 -![image.png](https://pic.leetcode-cn.com/1613294249-TkefQo-image.png) + +![](https://pic.leetcode-cn.com/1613294249-TkefQo-image.png) 因此对于第 `k` 个位置而言,交换左边还是右边,并不会影响后续需要调整的「情侣对数量」。 @@ -155,8 +253,6 @@ b. 现在先不处理第 `k` 个位置,等到后面的情侣处理的时候「 由于被处理都是同一批的联通位置,因此和「a. 现在处理第 `k` 个位置」的分析结果是一样的。 ---- - 不失一般性的,我们可以将这个分析推广到第一个位置,其实就已经是符合「当我处理到第 `k` 个位置的时候,前面的 `k - 1` 个位置的情侣已经牵手成功了」的定义了。 **综上所述,我们只需要确保从前往后处理,并且每次处理都保留第 `k` 个位置的其中一位,无论保留的左边还是右边都能得到最优解。** diff --git "a/LeetCode/761-770/766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/761-770/766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" index a5443b6b..250e56d3 100644 --- "a/LeetCode/761-770/766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/761-770/766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,14 +6,16 @@ Tag : 「模拟」 -给你一个 m x n 的矩阵 matrix 。如果这个矩阵是托普利茨矩阵,返回 true ;否则,返回 false 。 - -如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是 托普利茨矩阵 。 +给你一个 `m x n` 的矩阵 `matrix`。 +如果这个矩阵是托普利茨矩阵,返回 `true`;否则,返回 `false`。 +如果矩阵上每一条由左上到右下的对角线上的元素都相同,那么这个矩阵是托普利茨矩阵。 示例 1: + ![](https://assets.leetcode.com/uploads/2020/11/04/ex1.jpg) + ``` 输入:matrix = [[1,2,3,4],[5,1,2,3],[9,5,1,2]] 输出:true @@ -23,7 +25,9 @@ Tag : 「模拟」 各条对角线上的所有元素均相同, 因此答案是 True 。 ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2020/11/04/ex2.jpg) + ``` 输入:matrix = [[1,2],[2,2]] 输出:false @@ -32,10 +36,10 @@ Tag : 「模拟」 ``` 提示: -* m == matrix.length -* n == matrix[i].length -* 1 <= m, n <= 20 -* 0 <= matrix[i][j] <= 99 +* $m = matrix.length$ +* $n == matrix[i].length$ +* $1 <= m, n <= 20$ +* $0 <= matrix[i][j] <= 99$ 进阶: @@ -46,14 +50,13 @@ Tag : 「模拟」 ### 按「格子」遍历 -![image.png](https://pic.leetcode-cn.com/1613957825-VPQgjG-image.png) - 对于一个合格的「托普利茨矩阵」而言,每个格子总是与其左上角格子相等(如果有的话)。 我们以「格子」为单位进行遍历,每次与左上角的格子进行检查即可。 这样我们每对一个格子进行判断,都要读 `matrix` 上的两个格子的值(即**非边缘格子其实会被读取两次**)。 +Java 代码: ```Java class Solution { public boolean isToeplitzMatrix(int[][] matrix) { @@ -67,15 +70,51 @@ class Solution { } } ``` -* 时间复杂度:$O(n * m)$ +C++ 代码: +```C++ +class Solution { +public: + bool isToeplitzMatrix(vector>& matrix) { + int m = matrix.size(), n = matrix[0].size(); + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + if (matrix[i][j] != matrix[i - 1][j - 1]) return false; + } + } + return true; + } +}; +``` +Python 代码: +```Python +class Solution: + def isToeplitzMatrix(self, matrix: List[List[int]]) -> bool: + m, n = len(matrix), len(matrix[0]) + for i in range(1, m): + for j in range(1, n): + if matrix[i][j] != matrix[i - 1][j - 1]: + return False + return True +``` +TypeScript 代码: +```TypeScript +function isToeplitzMatrix(matrix: number[][]): boolean { + const m: number = matrix.length, n: number = matrix[0].length; + for (let i: number = 1; i < m; i++) { + for (let j: number = 1; j < n; j++) { + if (matrix[i][j] !== matrix[i - 1][j - 1]) return false; + } + } + return true; +}; +``` +* 时间复杂度:$O(n \times m)$ * 空间复杂度:$O(1)$ -*** +--- ### 按「线」遍历 -![image.png](https://pic.leetcode-cn.com/1613957207-jQmjZi-image.png) - 如果稍微增加一点点难度:限制每个格子只能被读取一次呢? 这时候我们也可以按照「线」为单位进行检查。 @@ -84,6 +123,7 @@ class Solution { 这样对于每个格子,我们都是**严格只读取一次**(如果整个矩阵是存在磁盘中,且不考虑操作系统的按页读取等机制,那么 IO 成本将下降为原来的一半)。 +Java 代码: ```Java class Solution { public boolean isToeplitzMatrix(int[][] matrix) { @@ -103,24 +143,87 @@ class Solution { } } ``` -* 时间复杂度:$O(n * m)$ +C++ 代码: +```C++ +class Solution { +public: + bool isToeplitzMatrix(vector>& matrix) { + int m = matrix.size(), n = matrix[0].size(); + int row = m, col = n; + while (col-- > 0) { + for (int i = 0, j = col, val = matrix[i++][j++]; i < m && j < n; i++, j++) { + if (matrix[i][j] != val) return false; + } + } + while (row-- > 0) { + for (int i = row, j = 0, val = matrix[i++][j++]; i < m && j < n; i++, j++) { + if (matrix[i][j] != val) return false; + } + } + return true; + } +}; +``` +Python 代码: +```Python +class Solution: + def isToeplitzMatrix(self, matrix: List[List[int]]) -> bool: + m, n = len(matrix), len(matrix[0]) + row, col = m, n + while col > 0: + col -= 1 + i, j, val = 1, col + 1, matrix[0][col] + while i < m and j < n: + if matrix[i][j] != val: + return False + i, j = i + 1, j + 1 + while row > 0: + row -= 1 + i, j, val = row + 1, 1, matrix[row][0] + while i < m and j < n: + if matrix[i][j] != val: + return False + i, j = i + 1, j + 1 + return True +``` +TypeScript 代码: +```TypeScript +function isToeplitzMatrix(matrix: number[][]): boolean { + const m = matrix.length, n = matrix[0].length; + let row = m, col = n; + while (col-- > 0) { + for (let i: number = 0, j: number = col, val: number = matrix[i++][j++]; i < m && j < n; i++, j++) { + if (matrix[i][j] !== val) return false; + } + } + while (row-- > 0) { + for (let i: number = row, j: number = 0, val: number = matrix[i++][j++]; i < m && j < n; i++, j++) { + if (matrix[i][j] !== val) return false; + } + } + return true; +}; +``` +* 时间复杂度:$O(n \times m)$ * 空间复杂度:$O(1)$ -*** +--- ### 进阶 1. 如果矩阵存储在磁盘上,并且内存有限,以至于一次最多只能将矩阵的一行加载到内存中,该怎么办? + 使用「背包问题」的一维优化思路:假设我们有装载一行数据大小的内存,每次读取新的行时都进行「从右往左」的覆盖,每次覆盖都与前一个位置的进行比较(其实就是和上一行的左上角位置进行比较)。 如图: -![image.png](https://pic.leetcode-cn.com/1613965997-pGJUMv-image.png) -如果你对更多「背包问题」相关内容感兴趣,可以看我这篇总结:[深度讲解背包问题](https://leetcode-cn.com/circle/discuss/GWpXCM/) +![](https://pic.leetcode-cn.com/1613965997-pGJUMv-image.png) + +如果你对更多「背包问题」相关内容感兴趣,可以看我这个系列:[背包问题](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&__biz=MzU4NDE3MTEyMA==&scene=1&album_id=1751702161341628417&count=3&uin=&key=&devicetype=iMac+MacBookPro18%2C3+OSX+OSX+15.0+build(24A335)&version=13080810&lang=en&nettype=WIFI&ascene=0&fontScale=100) 2. 如果矩阵太大,以至于一次只能将不完整的一行加载到内存中,该怎么办? -亲,这边建议升级内存,啊呸 ... + 将问题看做子问题进行解决:调整读取矩阵的方式(按照垂直方向进行读取);或使用额外数据记录当前当前片段的行/列数。 更为合理的解决方案是:存储的时候按照「数组」的形式进行存储(行式存储),然后读取的时候计算偏移量直接读取其「左上角」或者「右下角」的值。 diff --git "a/LeetCode/771-780/778. \346\260\264\344\275\215\344\270\212\345\215\207\347\232\204\346\263\263\346\261\240\344\270\255\346\270\270\346\263\263\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/771-780/778. \346\260\264\344\275\215\344\270\212\345\215\207\347\232\204\346\263\263\346\261\240\344\270\255\346\270\270\346\263\263\357\274\210\345\233\260\351\232\276\357\274\211.md" index 3fade241..fef2ec62 100644 --- "a/LeetCode/771-780/778. \346\260\264\344\275\215\344\270\212\345\215\207\347\232\204\346\263\263\346\261\240\344\270\255\346\270\270\346\263\263\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/771-780/778. \346\260\264\344\275\215\344\270\212\345\215\207\347\232\204\346\263\263\346\261\240\344\270\255\346\270\270\346\263\263\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -8,7 +8,7 @@ Tag : 「最小生成树」、「并查集」、「Kruskal」、「二分」、 在一个 `N x N` 的坐标方格 `grid` 中,每一个方格的值 $grid[i][j]$ 表示在位置 $(i,j)$ 的平台高度。 -现在开始下雨了。当时间为 $t$ 时,此时雨水导致水池中任意位置的水位为 $t$ 。 +现在开始下雨了,当时间为 $t$ 时,此时雨水导致水池中任意位置的水位为 $t$ 。 你可以从一个平台游向四周相邻的任意一个平台,但是前提是此时水位必须同时淹没这两个平台。 @@ -43,8 +43,8 @@ Tag : 「最小生成树」、「并查集」、「Kruskal」、「二分」、 ``` 提示: -* 2 <= N <= 50. -* grid[i][j] 是 `[0, ..., N*N - 1]` 的排列。 +* $2 <= N <= 50$ +* $grid[i][j]$ 是 `[0, ..., N*N - 1]` 的排列。 --- @@ -64,11 +64,11 @@ Tag : 「最小生成树」、「并查集」、「Kruskal」、「二分」、 当我们的合并了某条边之后,判定左上角和右下角的点联通,那么该边的权重即是答案。 -这道题和前天的 [1631. 最小体力消耗路径](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486139&idx=1&sn=37083315de3c6a7e1b675e0363ee7d98&chksm=fd9ca1a4caeb28b220eba888ebc773b460d41123a1042e3b01cfd2001ddfb487ff2a3959f3b9&token=990510480&lang=zh_CN#rd) 几乎是完全一样的思路。 +这道题和前天的 [1631. 最小体力消耗路径](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247507800&idx=1&sn=9b968642bc8cc313e65616a583501df9&chksm=fd9f7447cae8fd51bf34d4aa6f9692c19522768a4533774810ff3c2517f3cd059777fadc7661#rd) 几乎是完全一样的思路。 你甚至可以将那题的代码拷贝过来,改一下对于 $w$ 的定义即可。 -代码: +Java 代码: ```Java class Solution { int n; @@ -83,7 +83,6 @@ class Solution { if (p[x] != x) p[x] = find(p[x]); return p[x]; } - public int swimInWater(int[][] grid) { n = grid.length; @@ -132,18 +131,121 @@ class Solution { } } ``` -节点的数量为 $n * n$,无向边的数量严格为 $2 * n * (n - 1)$,数量级上为 $n^2$。 +C++ 代码: +```C++ +class Solution { +public: + int n; + vector p; + void unionSets(int x, int y) { + p[find(x)] = find(y); + } + bool query(int x, int y) { + return find(x) == find(y); + } + int find(int x) { + if (p[x] != x) p[x] = find(p[x]); + return p[x]; + } + int swimInWater(vector>& grid) { + n = grid.size(); + p.resize(n * n); + for (int i = 0; i < n * n; ++i) p[i] = i; + + vector> edges; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + int idx = i * n + j; + if (i + 1 < n) { + int a = idx, b = (i + 1) * n + j; + int w = max(grid[i][j], grid[i + 1][j]); + edges.push_back({a, b, w}); + } + if (j + 1 < n) { + int a = idx, b = idx + 1; + int w = max(grid[i][j], grid[i][j + 1]); + edges.push_back({a, b, w}); + } + } + } + + sort(edges.begin(), edges.end(), [](const vector& a, const vector& b) { + return a[2] < b[2]; + }); + + int start = getIndex(0, 0), end = getIndex(n - 1, n - 1); + for (const auto& edge : edges) { + unionSets(edge[0], edge[1]); + if (query(start, end)) { + return edge[2]; + } + } + return 0; + } + int getIndex(int i, int j) { + return i * n + j; + } +}; +``` +Python 代码: +```Python +class Solution: + def __init__(self): + self.n = 0 + self.p = [] + + def union(self, x, y): + self.p[self.find(x)] = self.find(y) + + def query(self, x, y): + return self.find(x) == self.find(y) + + def find(self, x): + if self.p[x] != x: + self.p[x] = self.find(self.p[x]) + return self.p[x] + + def swimInWater(self, grid): + self.n = len(grid) + self.p = list(range(self.n * self.n)) + + edges = [] + for i in range(self.n): + for j in range(self.n): + idx = i * self.n + j + if i + 1 < self.n: + a, b = idx, (i + 1) * self.n + j + w = max(grid[i][j], grid[i + 1][j]) + edges.append([a, b, w]) + if j + 1 < self.n: + a, b = idx, idx + 1 + w = max(grid[i][j], grid[i][j + 1]) + edges.append([a, b, w]) + + edges.sort(key=lambda x: x[2]) + + start, end = self.getIdx(0, 0), self.getIdx(self.n - 1, self.n - 1) + for edge in edges: + self.union(edge[0], edge[1]) + if self.query(start, end): + return edge[2] + return 0 + + def getIdx(self, i, j): + return i * self.n + j +``` +节点的数量为 $n \times n$,无向边的数量严格为 $2 \times n \times (n - 1)$,数量级上为 $n^2$。 * 时间复杂度:获取所有的边复杂度为 $O(n^2)$,排序复杂度为 $O(n^2\log{n})$,遍历得到最终解复杂度为 $O(n^2)$。整体复杂度为 $O(n^2\log{n})$。 * 空间复杂度:使用了并查集数组。复杂度为 $O(n^2)$。 -注意:假定 Collections.sort() 使用 Arrays.sort() 中的双轴快排实现。 +注意:假定 `Java` 的 `Collections.sort()` 使用 `Arrays.sort()` 中的双轴快排实现。 --- ### 二分 + BFS/DFS -在与本题类型的 [1631. 最小体力消耗路径](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486139&idx=1&sn=37083315de3c6a7e1b675e0363ee7d98&chksm=fd9ca1a4caeb28b220eba888ebc773b460d41123a1042e3b01cfd2001ddfb487ff2a3959f3b9&token=990510480&lang=zh_CN#rd)中,有同学问到是否可以用「二分」。 +在与本题类型的 [1631. 最小体力消耗路径](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247507800&idx=1&sn=9b968642bc8cc313e65616a583501df9&chksm=fd9f7447cae8fd51bf34d4aa6f9692c19522768a4533774810ff3c2517f3cd059777fadc7661#rd)中,有同学问到是否可以用「二分」。 答案是可以的。 @@ -153,7 +255,7 @@ class Solution { *因此在以最优解 $min$ 为分割点的数轴上具有两段性,可以通过「二分」来找到分割点 $min$。* -> 注意:「二分」的本质是两段性,并非单调性。只要一段满足某个性质,另外一段不满足某个性质,就可以用「二分」。其中 [33. 搜索旋转排序数组](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486020&idx=2&sn=9ada4b6e7eccecddbd8faa18f14c4eeb&chksm=fd9ca15bcaeb284d727905c174624e32b39b196e2337a2bfc791b22fcce35eb543b552031530&token=990510480&lang=zh_CN#rd) 是一个很好的说明例子。 +> 注意:「二分」的本质是两段性,并非单调性。只要一段满足某个性质,另外一段不满足某个性质,就可以用「二分」。其中 [33. 搜索旋转排序数组](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247499436&idx=1&sn=97aab321ccaf96a78fd5270cc29d75bf&chksm=fd9f55b3cae8dca5e5aa2e8c48d5a74cca297c77d677fc6ce90f5954a0ecb70d81c49732aa5d#rd) 是一个很好的说明例子。 接着分析,假设最优解为 $min$,我们在 $[l, r]$ 范围内进行二分,当前二分到的时间为 $mid$ 时: @@ -169,7 +271,7 @@ class Solution { 实现 $check$ 既可以使用 DFS 也可以使用 BFS。两者思路类似,这里就只以 BFS 为例。 -代码: +Java 代码: ```Java class Solution { @@ -179,11 +281,8 @@ class Solution { int l = 0, r = n * n; while (l < r) { int mid = l + r >> 1; - if (check(grid, mid)) { - r = mid; - } else { - l = mid + 1; - } + if (check(grid, mid)) r = mid; + else l = mid + 1; } return r; } @@ -217,8 +316,88 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector> dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; + int swimInWater(vector>& grid) { + int n = grid.size(); + int l = 0, r = n * n; + while (l < r) { + int mid = l + r >> 1; + if (check(grid, mid)) r = mid; + else l = mid + 1; + } + return r; + } + bool check(vector>& grid, int time) { + int n = grid.size(); + vector> visited(n, vector(n, false)); + queue> q; + q.push({0, 0}); + visited[0][0] = true; + while (!q.empty()) { + auto pos = q.front(); q.pop(); + int x = pos.first, y = pos.second; + if (x == n - 1 && y == n - 1) return true; + for (auto& dir : dirs) { + int nx = x + dir[0], ny = y + dir[1]; + if (inArea(n, nx, ny) && !visited[nx][ny] && canMove(grid, {x, y}, {nx, ny}, time)) { + visited[nx][ny] = true; + q.push({nx, ny}); + } + } + } + return false; + } + bool inArea(int n, int x, int y) { + return x >= 0 && x < n && y >= 0 && y < n; + } + bool canMove(vector>& grid, pair from, pair to, int time) { + return time >= max(grid[from.first][from.second], grid[to.first][to.second]); + } +}; +``` +Python 代码: +```Python +class Solution: + def __init__(self): + self.dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]] + + def swimInWater(self, grid): + n = len(grid) + l, r = 0, n * n + while l < r: + mid = l + r >> 1 + if self.check(grid, mid): r = mid + else: l = mid + 1 + return r + + def check(self, grid, time): + n = len(grid) + visited = [[False] * n for _ in range(n)] + queue = deque([(0, 0)]) + visited[0][0] = True + while queue: + x, y = queue.popleft() + if x == n - 1 and y == n - 1: + return True + for dx, dy in self.dirs: + nx, ny = x + dx, y + dy + if self.inArea(n, nx, ny) and not visited[nx][ny] and self.canMove(grid, (x, y), (nx, ny), time): + visited[nx][ny] = True + queue.append((nx, ny)) + return False + + def inArea(self, n, x, y): + return 0 <= x < n and 0 <= y < n + + def canMove(self, grid, from_pos, to_pos, time): + return time >= max(grid[from_pos[0]][from_pos[1]], grid[to_pos[0]][to_pos[1]]) +``` * 时间复杂度:在 $[0, n^2]$ 范围内进行二分,复杂度为 $O(\log{n})$;每一次 BFS 最多有 $n^2$ 个节点入队,复杂度为 $O(n^2)$。整体复杂度为 $O({n^2}\log{n})$ -* 空间复杂度:使用了 visited 数组。复杂度为 $O(n^2)$ +* 空间复杂度:使用了 `visited` 数组。复杂度为 $O(n^2)$ --- diff --git "a/LeetCode/781-790/781. \346\243\256\346\236\227\344\270\255\347\232\204\345\205\224\345\255\220\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/781-790/781. \346\243\256\346\236\227\344\270\255\347\232\204\345\205\224\345\255\220\357\274\210\344\270\255\347\255\211\357\274\211.md" index 7735f3f5..4daaed46 100644 --- "a/LeetCode/781-790/781. \346\243\256\346\236\227\344\270\255\347\232\204\345\205\224\345\255\220\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/781-790/781. \346\243\256\346\236\227\344\270\255\347\232\204\345\205\224\345\255\220\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,7 +6,11 @@ Tag : 「贪心」 -森林中,每个兔子都有颜色。其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。我们将这些回答放在 `answers` 数组里。 +森林中,每个兔子都有颜色。 + +其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。 + +我们将这些回答放在 `answers` 数组里。 返回森林中兔子的最少数量。 @@ -58,7 +62,7 @@ Tag : 「贪心」 **换句话说,我们应该让「同一颜色的兔子数量」尽量多,从而实现「总的兔子数量」最少。** -*** +--- ### 证明 @@ -76,52 +80,100 @@ Tag : 「贪心」 **相反,如果不执行这样的操作的话,得到的 $answers$ 数值数量必然会更多,「总的兔子数量」也必然会更多,也必然不会比这样做法更优。** -*** +--- -### 模拟解法 +### 模拟 按照上述思路,我们可以先对 $answers$ 进行排序,然后根据遍历到某个 $cnt$ 时,将其对答案的影响应用到 $ans$ 中(`ans += cnt + 1`),并将后面的 $cnt$ 个 $cnt$ 进行忽略。 -代码: +Java 代码: ```Java class Solution { - public int numRabbits(int[] cs) { - Arrays.sort(cs); - int n = cs.length; - int ans = 0; + public int numRabbits(int[] answers) { + Arrays.sort(answers); + int n = answers.length, ans = 0; for (int i = 0; i < n; i++) { - int cnt = cs[i]; + int cnt = answers[i]; ans += cnt + 1; // 跳过「数值 cnt」后面的 cnt 个「数值 cnt」 int k = cnt; - while (k-- > 0 && i + 1 < n && cs[i] == cs[i + 1]) i++; + while (k-- > 0 && i + 1 < n && answers[i] == answers[i + 1]) i++; } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int numRabbits(vector& answers) { + sort(answers.begin(), answers.end()); + int n = answers.size(), ans = 0; + for (int i = 0; i < n; i++) { + int cnt = answers[i]; + ans += cnt + 1; + // 跳过「数值 cnt」后面的 cnt 个「数值 cnt」 + int k = cnt; + while (k-- > 0 && i + 1 < n && answers[i] == answers[i + 1]) i++; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def numRabbits(self, answers: List[int]) -> int: + answers.sort() + n, ans = len(answers), 0 + i = 0 + while i < n: + cnt = answers[i] + ans += cnt + 1 + # 跳过「数值 cnt」后面的 cnt 个「数值 cnt」 + k = cnt + while k > 0 and i + 1 < n and answers[i] == answers[i + 1]: + k -= 1 + i += 1 + i += 1 + return ans +``` +TypeScript 代码: +```TypeScript +function numRabbits(answers: number[]): number { + answers.sort((a, b) => a - b); + let n = answers.length, ans = 0; + for (let i = 0; i < n; i++) { + const cnt = answers[i]; + ans += cnt + 1; + // 跳过「数值 cnt」后面的 cnt 个「数值 cnt」 + let k = cnt; + while (k-- > 0 && i + 1 < n && answers[i] === answers[i + 1]) i++; + } + return ans; +}; +``` * 时间复杂度:$O(n\log{n})$ * 空间复杂度:$O(1)$ -*** +--- ### 统计分配 我们也可以先对所有出现过的数字进行统计,然后再对数值进行(颜色)分配。 -代码: +Java 代码: ```Java class Solution { int N = 1009; int[] counts = new int[N]; - public int numRabbits(int[] cs) { - // counts[x] = cnt 代表在 cs 中数值 x 的数量为 cnt - for (int i : cs) counts[i]++; + public int numRabbits(int[] answers) { + // counts[x] = cnt 代表在 answers 中数值 x 的数量为 cnt + for (int i : answers) counts[i]++; int ans = counts[0]; for (int i = 1; i < N; i++) { - int per = i + 1; - int cnt = counts[i]; - int k = cnt / per; + int per = i + 1, cnt = counts[i], k = cnt / per; if (k * per < cnt) k++; ans += k * per; } @@ -129,16 +181,65 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int numRabbits(vector& answers) { + int N = 1009; + vector counts(N, 0); + for (int i : answers) counts[i]++; + int ans = counts[0]; + for (int i = 1; i < N; i++) { + int per = i + 1, cnt = counts[i], k = cnt / per; + if (k * per < cnt) k++; + ans += k * per; + } + return ans; + } +}; +``` +Python 代码: +```Java +class Solution: + def numRabbits(self, answers: List[int]) -> int: + N = 1009 + counts = [0] * N + for i in answers: + counts[i] += 1 + ans = counts[0] + for i in range(1, N): + per, cnt = i + 1, counts[i] + k = cnt // per + if k * per < cnt: + k += 1 + ans += k * per + return ans +``` +TypeScript 代码: +```TypeScript +function numRabbits(answers: number[]): number { + const N = 1009; + const counts = new Array(N).fill(0); + for (const i of answers) counts[i]++; + let ans: number = counts[0]; + for (let i: number = 1; i < N; i++) { + let per = i + 1, cnt = counts[i], k = Math.floor(cnt / per); + if (k * per < cnt) k++; + ans += k * per; + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ -*** +--- ### 拓展 保持题目现有的条件不变,假定颜色相同的兔子至少有一只发声,问题改为「问兔子颜色数量可能有多少种」,又该如何求解呢? - --- ### 最后 diff --git "a/LeetCode/781-790/783. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/781-790/783. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273\357\274\210\347\256\200\345\215\225\357\274\211.md" index e1fd8df9..531d8de1 100644 --- "a/LeetCode/781-790/783. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/781-790/783. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\212\202\347\202\271\346\234\200\345\260\217\350\267\235\347\246\273\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -31,8 +31,8 @@ Tag : 「树的搜索」、「迭代」、「非迭代」、「中序遍历」 ``` 提示: -* 树中节点数目在范围 [2, 100] 内 -* 0 <= Node.val <= $10^5$ +* 树中节点数目在范围 $[2, 100]$ 内 +* $0 <= Node.val <= 10^5$ * 差值是一个正数,其数值等于两值之差的绝对值 --- @@ -46,7 +46,7 @@ Tag : 「树的搜索」、「迭代」、「非迭代」、「中序遍历」 将所有节点的 $val$ 存入数组,可以使用 BFS 或者 DFS。 代码: -```java [] +```Java class Solution { public int minDiffInBST(TreeNode root) { List list = new ArrayList<>(); @@ -83,7 +83,7 @@ class Solution { * 时间复杂度:$O(n\log{n})$ * 空间复杂度:$O(n)$ -*** +--- ### 中序遍历(栈模拟 & 递归) @@ -91,8 +91,8 @@ class Solution { 而二叉搜索树的中序遍历是有序的,因此我们可以直接对「二叉搜索树」进行中序遍历,保存遍历过程中的相邻元素最小值即是答案。 -代码: -```java [] +Java 代码: +```Java class Solution { int ans = Integer.MAX_VALUE; TreeNode prev = null; diff --git "a/LeetCode/801-810/810. \351\273\221\346\235\277\345\274\202\346\210\226\346\270\270\346\210\217\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/801-810/810. \351\273\221\346\235\277\345\274\202\346\210\226\346\270\270\346\210\217\357\274\210\345\233\260\351\232\276\357\274\211.md" index 8d8b839b..c71b961a 100644 --- "a/LeetCode/801-810/810. \351\273\221\346\235\277\345\274\202\346\210\226\346\270\270\346\210\217\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/801-810/810. \351\273\221\346\235\277\345\274\202\346\210\226\346\270\270\346\210\217\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -8,7 +8,9 @@ Tag : 「博弈论」、「数学」、「异或」 黑板上写着一个非负整数数组 `nums[i]` 。 -`Alice` 和 `Bob` 轮流从黑板上擦掉一个数字,`Alice` 先手。如果擦除一个数字后,剩余的所有数字按位异或运算得出的结果等于 `0` 的话,当前玩家游戏失败。 (另外,如果只剩一个数字,按位异或运算得到它本身;如果无数字剩余,按位异或运算结果为 `0`。) +`Alice` 和 `Bob` 轮流从黑板上擦掉一个数字,`Alice` 先手。 + +如果擦除一个数字后,剩余的所有数字按位异或运算得出的结果等于 `0` 的话,当前玩家游戏失败。 (另外,如果只剩一个数字,按位异或运算得到它本身;如果无数字剩余,按位异或运算结果为 `0`。) 换种说法就是,轮到某个玩家时,如果当前黑板上所有数字按位异或运算结果等于 `0`,这个玩家获胜。 @@ -122,7 +124,7 @@ $$ **综上,如果序列 `nums` 本身异或和为 $0$,天然符合「先手必胜态」的条件,答案返回 `True` ;如果序列 `nums` 异或和不为 $0$,但序列长度为偶数,那么最终会出现「后手必败态」,推导出先手必胜,答案返回 `True`。** -代码: +Java 代码: ```Java class Solution { public boolean xorGame(int[] nums) { @@ -132,6 +134,34 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool xorGame(vector& nums) { + int sumv = 0; + for (int i : nums) sumv ^= i; + return sumv == 0 || nums.size() % 2 == 0; + } +}; +``` +Python 代码: +```Python +class Solution: + def xorGame(self, nums: List[int]) -> bool: + sumv = 0 + for i in nums: + sumv ^= i + return sumv == 0 or len(nums) % 2 == 0 +``` +TypeScript 代码: +```TypeScript +function xorGame(nums: number[]): boolean { + let sum = 0; + for (const i of nums) sum ^= i; + return sum === 0 || nums.length % 2 === 0; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/81-90/81. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204 II\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/81-90/81. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204 II\357\274\210\344\270\255\347\255\211\357\274\211.md" index d4ddebd2..37d804bf 100644 --- "a/LeetCode/81-90/81. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204 II\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/81-90/81. \346\220\234\347\264\242\346\227\213\350\275\254\346\216\222\345\272\217\346\225\260\347\273\204 II\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,11 +6,15 @@ Tag : 「二分」 -已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。 +已知存在一个按非降序排列的整数数组 `nums`,数组中的值不必互不相同。 -在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。 +在传递给函数之前,`nums` 在预先未知的某个下标 `k`($0 <= k < nums.length$)上进行了旋转,使数组变为 $[nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]$(下标从 0 开始计数)。 -给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。 +例如, $[0,1,2,4,4,4,5,6,6,7]$ 在下标 `5` 处经旋转后可能变为 $[4,5,6,6,7,0,1,2,4,4]$。 + +给你旋转后的数组 `nums` 和一个整数 `target`,请你编写一个函数来判断给定的目标值是否存在于数组中。 + +如果 `nums` 中存在这个目标值 `target`,则返回 `true`,否则返回 `false`。   @@ -28,10 +32,10 @@ Tag : 「二分」 ``` 提示: -* 1 <= nums.length <= 5000 -* -$10^4$ <= nums[i] <= $10^4$ -* 题目数据保证 nums 在预先未知的某个下标上进行了旋转 -* -$10^4$ <= target <= $10^4$ +* $1 <= nums.length <= 5000$ +* $-10^4 <= nums[i] <= 10^4$ +* 题目数据保证 `nums` 在预先未知的某个下标上进行了旋转 +* $-10^4 <= target <= 10^4$ --- @@ -39,20 +43,20 @@ Tag : 「二分」 根据题意,我们知道,所谓的旋转其实就是「将某个下标前面的所有数整体移到后面,使得数组从整体有序变为分段有序」。 -但和 [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/sou-suo-xuan-zhuan-pai-xu-shu-zu-by-leetcode-solut/) 不同的是,本题元素并不唯一。 +但和 [33. 搜索旋转排序数组](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247499436&idx=1&sn=97aab321ccaf96a78fd5270cc29d75bf&chksm=fd9f55b3cae8dca5e5aa2e8c48d5a74cca297c77d677fc6ce90f5954a0ecb70d81c49732aa5d#rd) 不同的是,本题元素并不唯一。 **这意味着我们无法直接根据与** $nums[0]$ **的大小关系,将数组划分为两段,即无法通过「二分」来找到旋转点。** **因为「二分」的本质是二段性,并非单调性。只要一段满足某个性质,另外一段不满足某个性质,就可以用「二分」。** -如果你有看过我 [严格 O(logN),一起看清二分的本质](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/shua-chuan-lc-yan-ge-ologn100yi-qi-kan-q-xifo/) 这篇题解,你应该很容易就理解上句话的意思。如果没有也没关系,我们可以先解决本题,在理解后你再去做 [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/sou-suo-xuan-zhuan-pai-xu-shu-zu-by-leetcode-solut/),我认为这两题都是一样的,不存在先后关系。 +如果你有看过我 [严格 O(logN),一起看清二分的本质](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247499436&idx=1&sn=97aab321ccaf96a78fd5270cc29d75bf&chksm=fd9f55b3cae8dca5e5aa2e8c48d5a74cca297c77d677fc6ce90f5954a0ecb70d81c49732aa5d#rd) 这篇题解,你应该很容易就理解上句话的意思。如果没有也没关系,我们可以先解决本题,在理解后你再去做 [33. 搜索旋转排序数组](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247499436&idx=1&sn=97aab321ccaf96a78fd5270cc29d75bf&chksm=fd9f55b3cae8dca5e5aa2e8c48d5a74cca297c77d677fc6ce90f5954a0ecb70d81c49732aa5d#rd),我认为这两题都是一样的,不存在先后关系。 -举个🌰,我们使用数据 [0,1,2,2,2,3,4,5] 来理解为什么不同的旋转点会导致「二段性丢失」: +举个🌰,我们使用数据 `[0,1,2,2,2,3,4,5]` 来理解为什么不同的旋转点会导致「二段性丢失」: -![image.png](https://pic.leetcode-cn.com/1617852745-LoBNPK-image.png) +![](https://pic.leetcode-cn.com/1617852745-LoBNPK-image.png) -代码: -```java [] +Java 代码: +```Java class Solution { public boolean search(int[] nums, int t) { int n = nums.length; @@ -63,11 +67,8 @@ class Solution { // 第一次二分,找旋转点 while (l < r) { int mid = l + r + 1 >> 1; - if (nums[mid] >= nums[0]) { - l = mid; - } else { - r = mid - 1; - } + if (nums[mid] >= nums[0]) l = mid; + else r = mid - 1; } int idx = n; @@ -82,28 +83,125 @@ class Solution { int find(int[] nums, int l, int r, int t) { while (l < r) { int mid = l + r >> 1; - if (nums[mid] >= t) { - r = mid; - } else { - l = mid + 1; - } + if (nums[mid] >= t) r = mid; + else l = mid + 1; + } + return nums[r] == t ? r : -1; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + bool search(vector& nums, int t) { + int n = nums.size(); + int l = 0, r = n - 1; + // 恢复二段性 + while (l < r && nums[0] == nums[r]) r--; + + // 第一次二分,找旋转点 + while (l < r) { + int mid = l + r + 1 >> 1; + if (nums[mid] >= nums[0]) l = mid; + else r = mid - 1; + } + int idx = n; + if (nums[r] >= nums[0] && r + 1 < n) idx = r + 1; + + // 第二次二分,找目标值 + int ans = find(nums, 0, idx - 1, t); + if (ans != -1) return true; + ans = find(nums, idx, n - 1, t); + return ans != -1; + } + int find(vector& nums, int l, int r, int t) { + while (l < r) { + int mid = l + r >> 1; + if (nums[mid] >= t) r = mid; + else l = mid + 1; } return nums[r] == t ? r : -1; } +}; +``` +Python 代码: +```Python +class Solution: + def search(self, nums: List[int], t: int) -> bool: + n = len(nums) + l, r = 0, n - 1 + # 恢复二段性 + while l < r and nums[0] == nums[r]: + r -= 1 + + # 第一次二分,找旋转点 + while l < r: + mid = l + r + 1 >> 1 + if nums[mid] >= nums[0]: l = mid + else: r = mid - 1 + + idx = n + if nums[r] >= nums[0] and r + 1 < n: + idx = r + 1 + + # 第二次二分,找目标值 + ans = self.find(nums, 0, idx - 1, t) + if ans != -1: return True + ans = self.find(nums, idx, n - 1, t) + return ans != -1 + + def find(self, nums, l, r, t): + while l < r: + mid = l + r >> 1 + if nums[mid] >= t: r = mid + else: l = mid + 1 + return r if nums[r] == t else -1 +``` +TypeScript 代码: +```TypeScript +function find(nums: number[], l: number, r: number, t: number): number { + while (l < r) { + const mid: number = l + r >> 1; + if (nums[mid] >= t) r = mid; + else l = mid + 1; + } + return nums[r] === t ? r : -1; } +function search(nums: number[], t: number): boolean { + const n: number = nums.length; + let l: number = 0, r: number = n - 1; + // 恢复二段性 + while (l < r && nums[0] === nums[r]) r--; + + // 第一次二分,找旋转点 + while (l < r) { + const mid: number = l + r + 1 >> 1; + if (nums[mid] >= nums[0]) l = mid; + else r = mid - 1; + } + + let idx: number = n; + if (nums[r] >= nums[0] && r + 1 < n) idx = r + 1; + + // 第二次二分,找目标值 + const ans: number = find(nums, 0, idx - 1, t); + if (ans !== -1) return true; + return find(nums, idx, n - 1, t) !== -1; +}; ``` -* 时间复杂度:恢复二段性处理中,最坏的情况下(考虑整个数组都是同一个数)复杂度是 $O(n)$,而之后的找旋转点和目标值都是「二分」,复杂度为 $O(log{n})$。整体复杂度为 $O(n)$ 的。 +* 时间复杂度:恢复二段性处理中,最坏的情况下(考虑整个数组都是同一个数)复杂度是 $O(n)$,而之后的找旋转点和目标值都是「二分」,复杂度为 $O(\log{n})$。整体复杂度为 $O(n)$ 的。 * 空间复杂度:$O(1)$。 -*** +--- ### 进阶 -如果真正理解「二分」的话,本题和 [33. 搜索旋转排序数组](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/solution/sou-suo-xuan-zhuan-pai-xu-shu-zu-by-leetcode-solut/) 区别不大。 +如果真正理解「二分」的话,本题和 [33. 搜索旋转排序数组](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247499436&idx=1&sn=97aab321ccaf96a78fd5270cc29d75bf&chksm=fd9f55b3cae8dca5e5aa2e8c48d5a74cca297c77d677fc6ce90f5954a0ecb70d81c49732aa5d#rd) 区别不大。 建议大家在完成两题的基础上试试 [面试题 10.03. 搜索旋转数组](https://leetcode-cn.com/problems/search-rotate-array-lcci/) 。 -*** +--- ### 其他「二分」相关题解 diff --git "a/LeetCode/81-90/87. \346\211\260\344\271\261\345\255\227\347\254\246\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/81-90/87. \346\211\260\344\271\261\345\255\227\347\254\246\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" index b1d0b3f9..80bc80ce 100644 --- "a/LeetCode/81-90/87. \346\211\260\344\271\261\345\255\227\347\254\246\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/81-90/87. \346\211\260\344\271\261\345\255\227\347\254\246\344\270\262\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -6,13 +6,13 @@ Tag : 「DFS」、「记忆化搜索」、「区间 DP」 -使用下面描述的算法可以扰乱字符串 s 得到字符串 t : +使用下面描述的算法可以扰乱字符串 `s` 得到字符串 `t` : 1. 如果字符串的长度为 1 ,算法停止 2. 如果字符串的长度 > 1 ,执行下述步骤: - * 在一个随机下标处将字符串分割成两个非空的子字符串。即,如果已知字符串 s ,则可以将其分成两个子字符串 x 和 y ,且满足 s = x + y 。 - * 随机 决定是要「交换两个子字符串」还是要「保持这两个子字符串的顺序不变」。即,在执行这一步骤之后,s 可能是 s = x + y 或者 s = y + x 。 - * 在 x 和 y 这两个子字符串上继续从步骤 1 开始递归执行此算法。 -给你两个 长度相等 的字符串 s1 和 s2,判断 s2 是否是 s1 的扰乱字符串。如果是,返回 true ;否则,返回 false 。 + * 在一个随机下标处将字符串分割成两个非空的子字符串。即,如果已知字符串 `s` ,则可以将其分成两个子字符串 `x` 和 `y` ,且满足 `s = x + y` 。 + * 随机 决定是要「交换两个子字符串」还是要「保持这两个子字符串的顺序不变」。即,在执行这一步骤之后,`s` 可能是 `s = x + y` 或者 `s = y + x`。 + * 在 `x` 和 `y` 这两个子字符串上继续从步骤 1 开始递归执行此算法。 + 给你两个 长度相等 的字符串 `s1` 和 `s2`,判断 `s2` 是否是 `s1` 的扰乱字符串。如果是,返回 `true`;否则,返回 `false`。 示例 1: @@ -46,9 +46,9 @@ Tag : 「DFS」、「记忆化搜索」、「区间 DP」 提示: -s1.length == s2.length -1 <= s1.length <= 30 -s1 和 s2 由小写英文字母组成 +* $s1.length = s2.length$ +* $1 <= s1.length <= 30$ +* `s1` 和 `s2` 由小写英文字母组成 --- @@ -66,20 +66,20 @@ s1 和 s2 由小写英文字母组成 同时由于生成「扰乱字符串」时,可以选交换也可以选不交换。因此我们的 $s2$ 会有两种可能性: -![image.png](https://pic.leetcode-cn.com/1618540686-JVPvXl-image.png) +![](https://pic.leetcode-cn.com/1618540686-JVPvXl-image.png) -**因为对于某个确定的分割点,$s1$ 固定分为两部分,分别为 $[0,i)$ & $[i, n)$。** +因为对于某个确定的分割点,$s1$ 固定分为两部分,分别为 $[0,i)$ & $[i, n)$。 -**而 $s2$ 可能会有两种分割方式,分别 $[0,i)$ & $[i,n)$ 和 $[0, n-i)$ & $[n-i,n)$。** +而 $s2$ 可能会有两种分割方式,分别 $[0,i)$ & $[i,n)$ 和 $[0, n-i)$ & $[n-i,n)$。 -**我们只需要递归调用 $isScramble$ 检查 $s1$ 的 $[0,i)$ & $[i, n)$ 部分能否与 「$s2$ 的 $[0,i)$ & $[i,n)$」 或者 「$s2$ 的 $[0, n-i)$ & $[n-i,n)$」 匹配即可。** +我们只需要递归调用 $isScramble$ 检查 $s1$ 的 $[0,i)$ & $[i, n)$ 部分能否与 「$s2$ 的 $[0,i)$ & $[i,n)$」 或者 「$s2$ 的 $[0, n-i)$ & $[n-i,n)$」 匹配即可。 -**同时,我们将「$s1$ 和 $s2$ 相等」和「$s1$ 和 $s2$ 词频不同」作为「递归」出口。** +同时,我们将「$s1$ 和 $s2$ 相等」和「$s1$ 和 $s2$ 词频不同」作为「递归」出口。 -**理解这套做法十分重要,后续的解法都是基于此解法演变过来。** +理解这套做法十分重要,后续的解法都是基于此解法演变过来。 -代码: -```java [] +Java 代码: +```Java class Solution { public boolean isScramble(String s1, String s2) { if (s1.equals(s2)) return true; @@ -131,8 +131,8 @@ class Solution { 同样的,我们将「入参对应的子串相等」和「入参对应的子串词频不同」作为「递归」出口。 -代码: -```java [] +Java 代码: +```Java class Solution { String s1; String s2; int n; @@ -201,13 +201,13 @@ class Solution { **根据「dfs 方法的几个可变入参」作为「状态定义的几个维度」,根据「dfs 方法的返回值」作为「具体的状态值」。** -**我们可以得到状态定义 $f[i][j][len]$:** +我们可以得到状态定义 $f[i][j][len]$: **$f[i][j][len]$ 代表 $s1$ 从 $i$ 开始,$s2$ 从 $j$ 开始,后面长度为 $len$ 的字符是否能形成「扰乱字符串」(互为翻转)。** 状态转移方程其实就是翻译我们「记忆化搜索」中的 dfs 主要逻辑部分: -```java +```Java // 对应了「s1 的 [0,i) & [i,n)」匹配「s2 的 [0,i) & [i,n)」 if (dfs(i, j, k) && dfs(i + k, j + k, len - k)) { cache[i][j][len] = Y; @@ -220,12 +220,12 @@ class Solution { } ``` -**从状态定义上,我们就不难发现这是一个「区间 DP」问题,区间长度大的状态值可以由区间长度小的状态值递推而来。** +从状态定义上,我们就不难发现这是一个「区间 DP」问题,区间长度大的状态值可以由区间长度小的状态值递推而来。 -**而且由于本身我们在「记忆化搜索」里面就是从小到大枚举 $len$,因此这里也需要先将 $len$ 这层循环提前,确保我们转移 $f[i][j][len]$ 时所需要的状态都已经被计算好。** +而且由于本身我们在「记忆化搜索」里面就是从小到大枚举 $len$,因此这里也需要先将 $len$ 这层循环提前,确保我们转移 $f[i][j][len]$ 时所需要的状态都已经被计算好。 -代码: -```java [] +Java 代码: +```Java class Solution { public boolean isScramble(String s1, String s2) { if (s1.equals(s2)) return true; @@ -233,14 +233,12 @@ class Solution { int n = s1.length(); char[] cs1 = s1.toCharArray(), cs2 = s2.toCharArray(); boolean[][][] f = new boolean[n][n][n + 1]; - // 先处理长度为 1 的情况 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { f[i][j][1] = cs1[i] == cs2[j]; } } - // 再处理其余长度情况 for (int len = 2; len <= n; len++) { for (int i = 0; i <= n - len; i++) { @@ -248,9 +246,7 @@ class Solution { for (int k = 1; k < len; k++) { boolean a = f[i][j][k] && f[i + k][j + k][len - k]; boolean b = f[i][j + len - k][k] && f[i + k][j][len - k]; - if (a || b) { - f[i][j][len] = true; - } + if (a || b) f[i][j][len] = true; } } } @@ -259,7 +255,84 @@ class Solution { } } ``` - +C++ 代码: +```C++ +class Solution { +public: + bool isScramble(string s1, string s2) { + if (s1 == s2) return true; + if (s1.length() != s2.length()) return false; + int n = s1.length(); + vector>> f = vector>>(n, vector>(n, vector(n + 1, false))); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + f[i][j][1] = s1[i] == s2[j]; + } + } + for (int len = 2; len <= n; len++) { + for (int i = 0; i <= n - len; i++) { + for (int j = 0; j <= n - len; j++) { + for (int k = 1; k < len; k++) { + bool a = f[i][j][k] && f[i + k][j + k][len - k]; + bool b = f[i][j + len - k][k] && f[i + k][j][len - k]; + if (a || b) f[i][j][len] = true; + } + } + } + } + return f[0][0][n]; + } +}; +``` +Python 代码: +```Python +class Solution: + def isScramble(self, s1: str, s2: str) -> bool: + if s1 == s2: + return True + if len(s1) != len(s2): + return False + n = len(s1) + f = [[[False] * (n + 1) for _ in range(n)] for _ in range(n)] + for i in range(n): + for j in range(n): + f[i][j][1] = s1[i] == s2[j] + for lenv in range(2, n + 1): + for i in range(n - lenv + 1): + for j in range(n - lenv + 1): + for k in range(1, lenv): + a = f[i][j][k] and f[i + k][j + k][lenv - k] + b = f[i][j + lenv - k][k] and f[i + k][j][lenv - k] + if a or b: + f[i][j][lenv] = True + return f[0][0][n] +``` +TypeScript 代码: +```TypeScript +function isScramble(s1: string, s2: string): boolean { + if (s1 === s2) return true; + if (s1.length !== s2.length) return false; + const n = s1.length; + const f = new Array(n).fill(false).map(() => new Array(n).fill(false).map(() => new Array(n + 1).fill(false))); + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + f[i][j][1] = s1[i] === s2[j]; + } + } + for (let len = 2; len <= n; len++) { + for (let i = 0; i <= n - len; i++) { + for (let j = 0; j <= n - len; j++) { + for (let k = 1; k < len; k++) { + const a = f[i][j][k] && f[i + k][j + k][len - k]; + const b = f[i][j + len - k][k] && f[i + k][j][len - k]; + if (a || b) f[i][j][len] = true; + } + } + } + } + return f[0][0][n]; +}; +``` * 时间复杂度:$O(n^4)$ * 空间复杂度:$O(n^3)$ diff --git "a/LeetCode/81-90/88. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/81-90/88. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204\357\274\210\347\256\200\345\215\225\357\274\211.md" index f01e19a4..309f52bf 100644 --- "a/LeetCode/81-90/88. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/81-90/88. \345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\346\225\260\347\273\204\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -27,7 +27,7 @@ Tag : 「双指针」、「排序」 提示: * $nums1.length = m + n$ -* $nums2.length == n$ +* $nums2.length = n$ * $0 <= m, n <= 200$ * $1 <= m + n <= 200$ * $-10^9 <= nums1[i], nums2[i] <= 10^9$ diff --git "a/LeetCode/831-840/832. \347\277\273\350\275\254\345\233\276\345\203\217\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/831-840/832. \347\277\273\350\275\254\345\233\276\345\203\217\357\274\210\347\256\200\345\215\225\357\274\211.md" index 087aaadc..4977cbca 100644 --- "a/LeetCode/831-840/832. \347\277\273\350\275\254\345\233\276\345\203\217\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/831-840/832. \347\277\273\350\275\254\345\233\276\345\203\217\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,56 +6,59 @@ Tag : 「双指针」 -给定一个二进制矩阵 A,我们想先水平翻转图像,然后反转图像并返回结果。 +给定一个二进制矩阵 `A`,我们想先水平翻转图像,然后反转图像并返回结果。 水平翻转图片就是将图片的每一行都进行翻转,即逆序。例如,水平翻转 [1, 1, 0] 的结果是 [0, 1, 1]。 -反转图片的意思是图片中的 0 全部被 1 替换, 1 全部被 0 替换。例如,反转 [0, 1, 1] 的结果是 [1, 0, 0]。 +反转图片的意思是图片中的 `0` 全部被 `1` 替换,`1` 全部被 `0` 替换。 + +例如,反转 [0, 1, 1] 的结果是 [1, 0, 0]。 示例 1: ``` 输入:[[1,1,0],[1,0,1],[0,0,0]] + 输出:[[1,0,0],[0,1,0],[1,1,1]] + 解释:首先翻转每一行: [[0,1,1],[1,0,1],[0,0,0]]; 然后反转图片: [[1,0,0],[0,1,0],[1,1,1]] ``` 示例 2: ``` 输入:[[1,1,0,0],[1,0,0,1],[0,1,1,1],[1,0,1,0]] + 输出:[[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]] + 解释:首先翻转每一行: [[0,0,1,1],[1,0,0,1],[1,1,1,0],[0,1,0,1]]; 然后反转图片: [[1,1,0,0],[0,1,1,0],[0,0,0,1],[1,0,1,0]] ``` 提示: -* 1 <= A.length = A[0].length <= 20 -* 0 <= A[i][j] <= 1 +* $1 <= A.length = A[0].length <= 20$ +* $0 <= A[i][j] <= 1$ --- -### 双指针代码 - -![image.png](https://pic.leetcode-cn.com/1614132194-quqFdD-image.png) - +### 双指针 对于每行而言,我们都需要对其进行「翻转」和「反转」。 这两步可以到放到一遍循环里做: * 翻转部分:使用双指针进行数字交换 -* 反转部分:将数字存储进目标位置前,使用「异或」对 0 1 进行翻转 +* 反转部分:将数字存储进目标位置前,使用「异或」对 `0` `1` 进行翻转 当前有一些「小细节」需要注意: 1. 题目要求我们对参数图像进行翻转,并返回新图像。因此我们不能对输入直接进行修改,而要先进行拷贝再处理 2. 由于我们将「翻转」和「反转」合成了一步,因此对于「奇数」图像,需要对中间一列进行特殊处理:仅「反转」 对于 Java 的基本类型拷贝,有三种方式进行拷贝: -1. System.arraycopy() : 底层的数组拷贝接口,具体实现与操作系统相关,调用的是系统本地方法。需要自己创建好目标数组进行传入,可指定拷贝长度,实现局部拷贝。 -2. Arrays.copyOf() : 基于 `System.arraycopy()` 封装的接口,省去了自己目标数组这一步。但无法实现局部拷贝。 -3. clone() : Object 的方法。会调用每个数组成员的 clone() 方法进行拷贝。因此对于一维数组而言,可以直接使用 clone() 得到「深拷贝数组」,而对于多维数组而言,得到的是「浅拷贝数组」。 +1. `System.arraycopy()` : 底层的数组拷贝接口,具体实现与操作系统相关,调用的是系统本地方法。需要自己创建好目标数组进行传入,可指定拷贝长度,实现局部拷贝。 +2. `Arrays.copyOf()` : 基于 `System.arraycopy()` 封装的接口,省去了自己目标数组这一步。但无法实现局部拷贝。 +3. `clone() : Object` 的方法。会调用每个数组成员的 `clone()` 方法进行拷贝。因此对于一维数组而言,可以直接使用 `clone()` 得到「深拷贝数组」,而对于多维数组而言,得到的是「浅拷贝数组」。 - -```java [] +Java 代码(P1): +```Java class Solution { public int[][] flipAndInvertImage(int[][] a) { int n = a.length; @@ -76,19 +79,18 @@ class Solution { } } ``` -```java [] +Java 代码(P2): +```Java class Solution { public int[][] flipAndInvertImage(int[][] a) { int n = a.length; int[][] ans = new int[n][n]; - // 遍历每一行进行处理 for (int i = 0; i < n; i++) { // 对每一行进行拷贝(共三种方式) // ans[i] = a[i].clone(); // ans[i] = Arrays.copyOf(a[i], n); System.arraycopy(a[i], 0, ans[i], 0, n); - // 使用「双指针」对其进行数组交换,实现「翻转」 // 并通过「异或」进行 0 1 翻转,实现「反转」 int l = 0, r = n - 1; @@ -97,7 +99,6 @@ class Solution { ans[i][r--] = ans[i][l] ^ 1; ans[i][l++] = c ^ 1; } - // 由于「奇数」矩形的中间一列不会进入上述双指针逻辑 // 需要对其进行单独「反转」 if (n % 2 != 0) ans[i][r] ^= 1; @@ -109,18 +110,21 @@ class Solution { * 时间复杂度:$O(n^2)$ * 空间复杂度:使用了同等大小的空间存储答案。复杂度为 $O(n^2)$ -*** +--- + ### 补充 -Q: 那么 Arrays.copyOfRange() 与 System.arraycopy() 作用是否等同呢? +Q: 那么 `Arrays.copyOfRange()` 与 `System.arraycopy()` 作用是否等同呢? A: 不等同。 -Arrays.copyOf() 和 Arrays.copyOfRange() 都会内部创建目标数组。前者是直接创建一个和源数组等长的数组,而后者则是根据传参 to 和 from 计算出目标数组长度进行创建。 +`Arrays.copyOf()` 和 `Arrays.copyOfRange()` 都会内部创建目标数组。 + +前者是直接创建一个和源数组等长的数组,而后者则是根据传参 `to` 和 `from` 计算出目标数组长度进行创建。 它们得到的数组都是完整包含了要拷贝内容的,都无法实现目标数组的局部拷贝功能。 -例如我要拿到一个长度为 10 的数组,前面 5 个位置的内容来源于「源数组」的拷贝,后面 5 个位置我希望预留给我后面自己做操作,它们都无法满足,只有 System.arraycopy() 可以。 +例如我要拿到一个长度为 10 的数组,前面 5 个位置的内容来源于「源数组」的拷贝,后面 5 个位置我希望预留给我后面自己做操作,它们都无法满足,只有 `System.arraycopy()` 可以。 --- diff --git "a/LeetCode/871-880/872. \345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/871-880/872. \345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" index 568c3e4b..8392256b 100644 --- "a/LeetCode/871-880/872. \345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/871-880/872. \345\217\266\345\255\220\347\233\270\344\274\274\347\232\204\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -10,13 +10,12 @@ Tag : 「树的搜索」、「非递归」、「递归」、「DFS」 ![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/07/16/tree.png) -举个例子,如上图所示,给定一棵叶值序列为 (6, 7, 4, 9, 8) 的树。 +举个例子,如上图所示,给定一棵叶值序列为 `(6, 7, 4, 9, 8)` 的树。 -如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。 +如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是叶相似的。 -如果给定的两个根结点分别为 root1 和 root2 的树是叶相似的,则返回 true;否则返回 false 。 +如果给定的两个根结点分别为 `root1` 和 `root2` 的树是叶相似的,则返回 `true`;否则返回 `false`。 -  示例 1: @@ -67,13 +66,13 @@ root2 = [3,5,1,6,7,4,2,null,null,null,null,null,null,9,8] 递归写法十分简单,属于树的遍历中最简单的实现方式。 -代码: +Java 代码: ```Java class Solution { - public boolean leafSimilar(TreeNode t1, TreeNode t2) { + public boolean leafSimilar(TreeNode root1, TreeNode root2) { List l1 = new ArrayList<>(), l2 = new ArrayList<>(); - dfs(t1, l1); - dfs(t2, l2); + dfs(root1, l1); + dfs(root2, l2); if (l1.size() == l2.size()) { for (int i = 0; i < l1.size(); i++) { if (!l1.get(i).equals(l2.get(i))) return false; @@ -93,8 +92,73 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool leafSimilar(TreeNode* root1, TreeNode* root2) { + vector l1, l2; + dfs(root1, l1); + dfs(root2, l2); + if (l1.size() == l2.size()) { + return equal(l1.begin(), l1.end(), l2.begin()); + } + return false; + } + void dfs(TreeNode* root, vector& list) { + if (!root) return; + if (!root->left && !root->right) { + list.push_back(root->val); + return; + } + dfs(root->left, list); + dfs(root->right, list); + } +}; +``` +Python 代码: +```Python +class Solution: + def leafSimilar(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> bool: + l1, l2 = [], [] + self.dfs(root1, l1) + self.dfs(root2, l2) + if len(l1) == len(l2): + return l1 == l2 + return False + + def dfs(self, root, list): + if not root: + return + if not root.left and not root.right: + list.append(root.val) + return + self.dfs(root.left, list) + self.dfs(root.right, list) +``` +TypeScript 代码: +```TypeScript +function dfs(root: TreeNode | null, list: number[]): void { + if (root === null) return; + if (root.left === null && root.right === null) { + list.push(root.val); + return; + } + dfs(root.left, list); + dfs(root.right, list); +} +function leafSimilar(root1: TreeNode | null, root2: TreeNode | null): boolean { + const l1: number[] = [], l2: number[] = []; + dfs(root1, l1); + dfs(root2, l2); + if (l1.length === l2.length) { + return l1.every((val, index) => val === l2[index]); + } + return false; +}; +``` * 时间复杂度:`n` 和 `m` 分别代表两棵树的节点数量。复杂度为 $O(n + m)$ -* 空间复杂度:`n` 和 `m` 分别代表两棵树的节点数量,当两棵树都只有一层的情况,所有的节点值都会被存储在 $list$ 中。复杂度为 $O(n + m)$ +* 空间复杂度:`n` 和 `m` 分别代表两棵树的节点数量,当两棵树都只有一层的情况,所有的节点值都会被存储在 `list` 中。复杂度为 $O(n + m)$ --- @@ -104,13 +168,13 @@ class Solution { 一般简单的面试中如果问到树的遍历,面试官都不会对「递归」解法感到满意,因此掌握「迭代/非递归」写法同样重要。 -代码: +Java 代码: ```Java class Solution { - public boolean leafSimilar(TreeNode t1, TreeNode t2) { + public boolean leafSimilar(TreeNode root1, TreeNode root2) { List l1 = new ArrayList<>(), l2 = new ArrayList<>(); - process(t1, l1); - process(t2, l2); + process(root1, l1); + process(root2, l2); if (l1.size() == l2.size()) { for (int i = 0; i < l1.size(); i++) { if (!l1.get(i).equals(l2.get(i))) return false; @@ -133,8 +197,91 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool leafSimilar(TreeNode* root1, TreeNode* root2) { + vector l1, l2; + process(root1, l1); + process(root2, l2); + if (l1.size() == l2.size()) { + for (int i = 0; i < l1.size(); i++) { + if (l1[i] != l2[i]) return false; + } + return true; + } + return false; + } + void process(TreeNode* root, vector& list) { + deque d; + while (root != nullptr || !d.empty()) { + while (root != nullptr) { + d.push_back(root); + root = root->left; + } + root = d.back(); + d.pop_back(); + if (root->left == nullptr && root->right == nullptr) list.push_back(root->val); + root = root->right; + } + } +}; +``` +Python 代码: +```Python +class Solution: + def leafSimilar(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> bool: + l1, l2 = [], [] + self.process(root1, l1) + self.process(root2, l2) + if len(l1) == len(l2): + for i in range(len(l1)): + if l1[i] != l2[i]: + return False + return True + return False + + def process(self, root, l): + d = [] + while root or d: + while root: + d.append(root) + root = root.left + root = d.pop() + if not root.left and not root.right: + l.append(root.val) + root = root.right +``` +TypeScript 代码: +```TypeScript +function process(root: TreeNode | null, list: number[]): void { + const d: TreeNode[] = []; + while (root != null || d.length != 0) { + while (root != null) { + d.push(root); + root = root.left; + } + root = d.pop(); + if (root.left == null && root.right == null) list.push(root.val); + root = root.right; + } +} +function leafSimilar(root1: TreeNode | null, root2: TreeNode | null): boolean { + const l1: number[] = [], l2: number[] = []; + process(root1, l1); + process(root2, l2); + if (l1.length == l2.length) { + for (let i = 0; i < l1.length; i++) { + if (l1[i] != l2[i]) return false; + } + return true; + } + return false; +}; +``` * 时间复杂度:`n` 和 `m` 分别代表两棵树的节点数量。复杂度为 $O(n + m)$ -* 空间复杂度:`n` 和 `m` 分别代表两棵树的节点数量,当两棵树都只有一层的情况,所有的节点值都会被存储在 $list$ 中。复杂度为 $O(n + m)$ +* 空间复杂度:`n` 和 `m` 分别代表两棵树的节点数量,当两棵树都只有一层的情况,所有的节点值都会被存储在 `list` 中。复杂度为 $O(n + m)$ --- diff --git "a/LeetCode/881-890/882. \347\273\206\345\210\206\345\233\276\344\270\255\347\232\204\345\217\257\345\210\260\350\276\276\350\212\202\347\202\271\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/881-890/882. \347\273\206\345\210\206\345\233\276\344\270\255\347\232\204\345\217\257\345\210\260\350\276\276\350\212\202\347\202\271\357\274\210\345\233\260\351\232\276\357\274\211.md" index 53435567..728239a7 100644 --- "a/LeetCode/881-890/882. \347\273\206\345\210\206\345\233\276\344\270\255\347\232\204\345\217\257\345\210\260\350\276\276\350\212\202\347\202\271\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/881-890/882. \347\273\206\345\210\206\345\233\276\344\270\255\347\232\204\345\217\257\345\210\260\350\276\276\350\212\202\347\202\271\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -6,15 +6,19 @@ Tag : 「最短路」、「单源最短路」、「Dijkstra」、「SPFA」 -给你一个无向图(原始图),图中有 `n` 个节点,编号从 `0` 到 `n - 1` 。你决定将图中的每条边 细分 为一条节点链,每条边之间的新节点数各不相同。 +给你一个无向图(原始图),图中有 `n` 个节点,编号从 `0` 到 `n - 1` 。 -图用由边组成的二维数组 `edges` 表示,其中 $edges[i] = [u_{i}, v_{i}, cnt_{i}]$ 表示原始图中节点 $u_{i}$ 和 $v_{i}$ 之间存在一条边,$cnt_{i}$ 是将边 细分 后的新节点总数。注意,$cnt_{i} = 0$ 表示边不可细分。 +你决定将图中的每条边细分为一条节点链,每条边之间的新节点数各不相同。 + +图用由边组成的二维数组 `edges` 表示,其中 $edges[i] = [u_{i}, v_{i}, cnt_{i}]$ 表示原始图中节点 $u_{i}$ 和 $v_{i}$ 之间存在一条边,$cnt_{i}$ 是将边 细分 后的新节点总数。 + +注意,$cnt_{i} = 0$ 表示边不可细分。 要 细分边 $[u_{i}, v_{i}]$ ,需要将其替换为 $(cnt_{i} + 1)$ 条新边,和 $cnt_{i}$ 个新节点。新节点为 $x_1, x_2, ..., x_{cnt_{i}}$ ,新边为 $[u_{i}, x_{1}], [x_{1}, x_{2}], [x_{2}, x_{3}], ..., [x_{cnt_{i}}+1, x_{cnt_{i}}], [x_{cnt_{i}}, v_{i}]$ 。 -现在得到一个 新的细分图 ,请你计算从节点 `0` 出发,可以到达多少个节点?如果节点间距离是 `maxMoves` 或更少,则视为 可以到达 。 +现在得到一个新的细分图,请你计算从节点 `0` 出发,可以到达多少个节点?如果节点间距离是 `maxMoves` 或更少,则视为可以到达。 -给你原始图和 `maxMoves` ,返回 新的细分图中从节点 `0` 出发 可到达的节点数 。 +给你原始图和 `maxMoves` ,返回新的细分图中从节点 `0` 出发可到达的节点数。 示例 1: ``` @@ -44,7 +48,7 @@ Tag : 「最短路」、「单源最短路」、「Dijkstra」、「SPFA」 * $0 <= edges.length <= \min(n * (n - 1) / 2, 10^4)$ * $edges[i].length = 3$ * $0 <= u_{i} < v_{i} < n$ -* 图中 不存在平行边 +* 图中不存在平行边 * $0 <= cnt_{i} <= 10^4$ * $0 <= maxMoves <= 10^9$ * $1 <= n <= 3000$ diff --git "a/LeetCode/881-890/888. \345\205\254\345\271\263\347\232\204\347\263\226\346\236\234\346\243\222\344\272\244\346\215\242\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/881-890/888. \345\205\254\345\271\263\347\232\204\347\263\226\346\236\234\346\243\222\344\272\244\346\215\242\357\274\210\347\256\200\345\215\225\357\274\211.md" index 19878087..d9651ffd 100644 --- "a/LeetCode/881-890/888. \345\205\254\345\271\263\347\232\204\347\263\226\346\236\234\346\243\222\344\272\244\346\215\242\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/881-890/888. \345\205\254\345\271\263\347\232\204\347\263\226\346\236\234\346\243\222\344\272\244\346\215\242\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -7,19 +7,17 @@ Tag : 「哈希表」 -爱丽丝和鲍勃有不同大小的糖果棒:A[i] 是爱丽丝拥有的第 i 根糖果棒的大小,B[j] 是鲍勃拥有的第 j 根糖果棒的大小。 +爱丽丝和鲍勃有不同大小的糖果棒:`A[i]` 是爱丽丝拥有的第 `i` 根糖果棒的大小,`B[j]` 是鲍勃拥有的第 `j` 根糖果棒的大小。 -因为他们是朋友,所以他们想交换一根糖果棒,这样交换后,他们都有相同的糖果总量。(一个人拥有的糖果总量是他们拥有的糖果棒大小的总和。) +因为他们是朋友,所以他们想交换一根糖果棒,这样交换后,他们都有相同的糖果总量(一个人拥有的糖果总量是他们拥有的糖果棒大小的总和)。 -返回一个整数数组 ans,其中 ans[0] 是爱丽丝必须交换的糖果棒的大小,ans[1] 是 Bob 必须交换的糖果棒的大小。 +返回一个整数数组 `ans`,其中 `ans[0]` 是爱丽丝必须交换的糖果棒的大小,`ans[1]` 是鲍勃必须交换的糖果棒的大小。 如果有多个答案,你可以返回其中任何一个。 保证答案存在。 - - 示例 1: ``` 输入:A = [1,1], B = [2,2] @@ -46,12 +44,12 @@ Tag : 「哈希表」 ``` 提示: -* 1 <= A.length <= 10000 -* 1 <= B.length <= 10000 -* 1 <= A[i] <= 100000 -* 1 <= B[i] <= 100000 -* 保证爱丽丝与鲍勃的糖果总量不同。 -* 答案肯定存在。 +* $1 <= A.length <= 10000$ +* $1 <= B.length <= 10000$ +* $1 <= A[i] <= 100000$ +* $1 <= B[i] <= 100000$ +* 保证爱丽丝与鲍勃的糖果总量不同 +* 答案肯定存在 --- @@ -59,21 +57,21 @@ Tag : 「哈希表」 最终目的是让两个数组总和相等。 -我们可以先分别求得两个数组总和为 $aSum$ 和 $bSum$。 +我们可以先分别求得两个数组总和为 `aSum` 和 `bSum`。 -即有数组总和 $total = aSum + bSum$。 +即有数组总和 `total = aSum + bSum`。 -同时得数组目标总和 $target = total / 2$。 +同时得数组目标总和 `target = total / 2`。 -当前两个数组与目标总和的差值分别为 $target - aSum$ 和 $target - bSum$。 +当前两个数组与目标总和的差值分别为 `target - aSum` 和 `target - bSum`。 -我们记 $diff = target - aSum$。 +我们记 `diff = target - aSum`。 -对于某个 $a[i]$ 而言,如果 $a[i]$ 能构成答案,那么 `b` 数组中必然存在大小为 $a[i] + diff$ 的值,使得两者交换后,数组总和均为 $target$。 +对于某个 `a[i]` 而言,如果 `a[i]` 能构成答案,那么 `b` 数组中必然存在大小为 `a[i] + diff` 的值,使得两者交换后,数组总和均为 `target`。 -因此我们只需要遍历数组 `a`,查找哪一个 $a[i]$ 使得 $a[i] + diff$ 存在于数组 `b` 即可。 +因此我们只需要遍历数组 `a`,查找哪一个 `a[i]` 使得 `a[i] + diff` 存在于数组 `b` 即可。 -代码: +Java 代码: ```Java class Solution { public int[] fairCandySwap(int[] a, int[] b) { @@ -82,24 +80,78 @@ class Solution { for (int i : b) bSum += i; int total = aSum + bSum, target = total / 2; int diff = target - aSum; - int[] ans = new int[2]; + for (int i : a) { + if (find(b, i + diff)) return new int[]{i, i + diff}; + } + return null; // never + } + boolean find(int[] nums, int target) { + for (int i : nums) { + if (i == target) return true; + } + return false; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + vector fairCandySwap(vector& a, vector& b) { + int aSum = 0, bSum = 0; + for (int i : a) aSum += i; + for (int i : b) bSum += i; + int total = aSum + bSum, target = total / 2; + int diff = target - aSum; + vector ans(2); for (int i : a) { if (find(b, i + diff)) { - ans[0] = i; + ans[0] = i; ans[1] = i + diff; + break; } } return ans; } - boolean find(int[] nums, int target) { + bool find(vector& nums, int target) { for (int i : nums) { if (i == target) return true; } return false; } -} +}; +``` +Python 代码: +```Python +class Solution: + def fairCandySwap(self, a: List[int], b: List[int]) -> List[int]: + aSum, bSum = sum(a), sum(b) + total = aSum + bSum + target = total // 2 + diff = target - aSum + for i in a: + if i + diff in b: + return [i, i + diff] + return None +``` +TypeScript 代码: +```TypeScript +function fairCandySwap(a: number[], b: number[]): number[] { + let aSum = a.reduce((acc, val) => acc + val, 0); + let bSum = b.reduce((acc, val) => acc + val, 0); + let total = aSum + bSum; + let target = Math.floor(total / 2); + let diff = target - aSum; + let ans: number[] = []; + for (let i of a) { + if (b.includes(i + diff)) { + ans = [i, i + diff]; + break; + } + } + return ans; +}; ``` - * 时间复杂度: 计算总和复杂度为 $O(n)$,找到最终解复杂度为 $O(n^2)$。整体复杂度为 $O(n^2)$ * 空间复杂度:$O(1)$ @@ -107,29 +159,27 @@ class Solution { ### 查找优化 -上述解法之所以无法做到线性,是因为我们每次都要对数组 `b` 进行扫描,确定 $a[i] + diff$ 是否存在。 +上述解法之所以无法做到线性,是因为我们每次都要对数组 `b` 进行扫描,确定 `a[i] + diff` 是否存在。 -我们知道 map/set/数组 都可以实现 $O(1)$ 查找,由于这里明确给出了两个数组中出现的数的范围,因此可以使用数组进行计数。 +我们知道 `map`/`set`/`数组` 都可以实现 $O(1)$ 查找,由于这里明确给出了两个数组中出现的数的范围,因此可以使用数组进行计数。 -同时可以优化一下变量的使用,使用一个变量 $diff$ 来计算最终的差异值。 +同时可以优化一下变量的使用,使用一个变量 `diff` 来计算最终的差异值。 这种优化,是典型的空间换时间做法。 -代码: +Java 代码: ```Java class Solution { public int[] fairCandySwap(int[] a, int[] b) { // 先求得 a 的总和 int diff = 0; for (int i : a) diff += i; - // 使用 cnt 统计 b 中的数的出现次数,同时计算 a 总和与 b 总和的差值 int[] cnt = new int[100009]; for (int i : b) { diff -= i; cnt[i]++; } - // 计算出 a 中具体的替换差值是多少 diff /= -2; int[] ans = new int[2]; @@ -146,6 +196,78 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector fairCandySwap(vector& a, vector& b) { + // 先求得 a 的总和 + int diff = 0; + for (int i : a) diff += i; + // 使用 cnt 统计 b 中的数的出现次数,同时计算 a 总和与 b 总和的差值 + vector cnt(100009, 0); + for (int i : b) { + diff -= i; + cnt[i]++; + } + // 计算出 a 中具体的替换差值是多少 + diff /= -2; + vector ans(2); + for (int i : a) { + int target = i + diff; + // 如果目标替换量在合法范围,并且存在于 b 数组中。说明找到解了 + if (target >= 1 && target <= 100000 && cnt[target] > 0) { + ans[0] = i; + ans[1] = target; + break; + } + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def fairCandySwap(self, a: List[int], b: List[int]) -> List[int]: + # 先求得 a 的总和 + diff = sum(a) + # 使用 cnt 统计 b 中的数的出现次数,同时计算 a 总和与 b 总和的差值 + cnt = [0] * 100009 + for i in b: + diff -= i + cnt[i] += 1 + # 计算出 a 中具体的替换差值是多少 + diff //= -2 + for i in a: + target = i + diff + # 如果目标替换量在合法范围,并且存在于 b 数组中。说明找到解了 + if 1 <= target <= 100000 and cnt[target] > 0: + return [i, target] +``` +TypeScript 代码: +```TypeScript +function fairCandySwap(a: number[], b: number[]): number[] { + // 先求得 a 的总和 + let diff = a.reduce((acc, curr) => acc + curr, 0); + // 使用 cnt 统计 b 中的数的出现次数,同时计算 a 总和与 b 总和的差值 + let cnt: number[] = new Array(100009).fill(0); + for (let i of b) { + diff -= i; + cnt[i]++; + } + // 计算出 a 中具体的替换差值是多少 + diff = Math.floor(diff / -2); + for (let i of a) { + let target = i + diff; + // 如果目标替换量在合法范围,并且存在于 b 数组中。说明找到解了 + if (target >= 1 && target <= 100000 && cnt[target] > 0) { + return [i, target]; + } + } + return []; +} +``` * 时间复杂度:计算总和复杂度为 $O(n)$,找到最终解复杂度为 $O(n)$。整体复杂度为 $O(n)$ * 空间复杂度:使用 `cnt` 数组进行计数。复杂度为 $O(n)$ diff --git "a/LeetCode/891-900/897. \351\200\222\345\242\236\351\241\272\345\272\217\346\220\234\347\264\242\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/891-900/897. \351\200\222\345\242\236\351\241\272\345\272\217\346\220\234\347\264\242\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" index 96613f6c..80786eed 100644 --- "a/LeetCode/891-900/897. \351\200\222\345\242\236\351\241\272\345\272\217\346\220\234\347\264\242\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/891-900/897. \351\200\222\345\242\236\351\241\272\345\272\217\346\220\234\347\264\242\346\240\221\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,7 +6,7 @@ Tag : 「树的遍历」、「递归」、「非递归」 -给你一棵二叉搜索树,请你 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 +给你一棵二叉搜索树,请你按中序遍历将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。 示例 1: @@ -28,9 +28,8 @@ Tag : 「树的遍历」、「递归」、「非递归」 ``` 提示: -* 树中节点数的取值范围是 [1, 100] -* 0 <= Node.val <= 1000 - +* 树中节点数的取值范围是 $[1, 100]$ +* $0 <= Node.val <= 1000$ --- @@ -46,7 +45,7 @@ Tag : 「树的遍历」、「递归」、「非递归」 递归写法十分简单,属于树的遍历中最简单的实现方式。 -代码: +Java 代码: ```Java class Solution { List list = new ArrayList<>(); @@ -69,6 +68,50 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + vector list; + TreeNode* increasingBST(TreeNode* root) { + dfs(root); + TreeNode* dummy = new TreeNode(-1); + TreeNode* cur = dummy; + for (auto node : list) { + cur->right = node; + node->left = nullptr; + cur = node; + } + return dummy->right; + } + void dfs(TreeNode* root) { + if (!root) return; + dfs(root->left); + list.push_back(root); + dfs(root->right); + } +}; +``` +Python 代码: +```Python +class Solution: + def increasingBST(self, root: TreeNode) -> TreeNode: + self.list = [] + self.dfs(root) + dummy = TreeNode(-1) + cur = dummy + for node in self.list: + cur.right = node + node.left = None + cur = node + return dummy.right + + def dfs(self, root): + if root: + self.dfs(root.left) + self.list.append(root) + self.dfs(root.right) +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ @@ -80,11 +123,11 @@ class Solution { 一般简单的面试中如果问到树的遍历,面试官都不会对「递归」解法感到满意,因此掌握「迭代/非递归」写法同样重要。 -代码: +Java 代码: ```Java class Solution { - List list = new ArrayList<>(); public TreeNode increasingBST(TreeNode root) { + List list = new ArrayList<>(); Deque d = new ArrayDeque<>(); while (root != null || !d.isEmpty()) { while (root != null) { @@ -106,6 +149,55 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + TreeNode* increasingBST(TreeNode* root) { + vector list; + deque d; + while (root != nullptr || !d.empty()) { + while (root != nullptr) { + d.push_back(root); + root = root->left; + } + root = d.back(); + d.pop_back(); + list.push_back(root); + root = root->right; + } + TreeNode* dummy = new TreeNode(-1); + TreeNode* cur = dummy; + for (auto node : list) { + cur->right = node; + node->left = nullptr; + cur = node; + } + return dummy->right; + } +}; +``` +Python 代码: +```Python +class Solution: + def increasingBST(self, root: TreeNode) -> TreeNode: + lst = [] + d = [] + while root or d: + while root: + d.append(root) + root = root.left + root = d.pop() + lst.append(root) + root = root.right + dummy = TreeNode(-1) + cur = dummy + for node in lst: + cur.right = node + node.left = None + cur = node + return dummy.right +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/91-100/91. \350\247\243\347\240\201\346\226\271\346\263\225\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/91-100/91. \350\247\243\347\240\201\346\226\271\346\263\225\357\274\210\344\270\255\347\255\211\357\274\211.md" index 7db14c1f..ed3cc5c5 100644 --- "a/LeetCode/91-100/91. \350\247\243\347\240\201\346\226\271\346\263\225\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/91-100/91. \350\247\243\347\240\201\346\226\271\346\263\225\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,25 +6,25 @@ Tag : 「线性 DP」 -一条包含字母 A-Z 的消息通过以下映射进行了 编码 : +一条包含字母 `A-Z` 的消息通过以下映射进行了 编码 : ``` 'A' -> 1 'B' -> 2 ... 'Z' -> 26 ``` -要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为: +要解码已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。 -* "AAJF" ,将消息分组为 (1 1 10 6) -* "KJF" ,将消息分组为 (11 10 6) +例如,`"11106"` 可以映射为: -注意,消息不能分组为  (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6" 和 "06" 在映射中并不等价。 +* `"AAJF"` ,将消息分组为 `(1 1 10 6)` +* `"KJF"` ,将消息分组为 `(11 10 6)` -给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。 - -题目数据保证答案肯定是一个 32 位 的整数。 +注意,消息不能分组为  `(1 11 06)` ,因为 `"06"` 不能映射为 `"F"`,这是由于 `"6"` 和 `"06"` 在映射中并不等价。 +给你一个只含数字的非空字符串 `s`,请计算并返回解码方法的总数。 +题目数据保证答案肯定是一个 `32` 位的整数。 示例 1: @@ -63,8 +63,8 @@ Tag : 「线性 DP」 ``` 提示: -* 1 <= s.length <= 100 -* s 只包含数字,并且可能包含前导零。 +* $1 <= s.length <= 100$ +* `s` 只包含数字,并且可能包含前导零。 --- @@ -111,7 +111,7 @@ $$ 其他细节:由于题目存在前导零,而前导零属于无效 `item`。可以进行特判,但个人习惯往字符串头部追加空格作为哨兵,追加空格既可以避免讨论前导零,也能使下标从 1 开始,简化 `f[i-1]` 等负数下标的判断。 -代码: +Java 代码: ```Java class Solution { public int numDecodings(String s) { @@ -133,7 +133,58 @@ class Solution { } } ``` -* 时间复杂度:共有 `n` 个状态需要被转移。复杂度为 $O(n)$。 +C++ 代码: +```C++ +class Solution { +public: + int numDecodings(string s) { + int n = s.length(); + s = " " + s; + vector f(n + 1, 0); + f[0] = 1; + for (int i = 1; i <= n; i++) { + int a = s[i] - '0', b = (s[i - 1] - '0') * 10 + (s[i] - '0'); + if (1 <= a && a <= 9) f[i] += f[i - 1]; + if (10 <= b && b <= 26) f[i] += f[i - 2]; + } + return f[n]; + } +}; +``` +Python 代码: +```Python +class Solution: + def numDecodings(self, s: str) -> int: + n = len(s) + s = " " + s + f = [0] * (n + 1) + f[0] = 1 + for i in range(1, n + 1): + a = ord(s[i]) - ord('0') + b = (ord(s[i - 1]) - ord('0')) * 10 + a + if 1 <= a <= 9: + f[i] += f[i - 1] + if 10 <= b <= 26: + f[i] += f[i - 2] + return f[n] +``` +TypeScript 代码: +```TypeScript +function numDecodings(s: string): number { + const n: number = s.length; + s = " " + s; + const f: number[] = new Array(n + 1).fill(0); + f[0] = 1; + for (let i: number = 1; i <= n; i++) { + const a: number = parseInt(s.charAt(i)); + const b: number = parseInt(s.charAt(i - 1)) * 10 + a; + if (1 <= a && a <= 9) f[i] += f[i - 1]; + if (10 <= b && b <= 26) f[i] += f[i - 2]; + } + return f[n]; +}; +``` +* 时间复杂度:共有 `n` 个状态需要被转移,复杂度为 $O(n)$。 * 空间复杂度:$O(n)$。 --- @@ -144,7 +195,7 @@ class Solution { 因此我们可以采用与「滚动数组」类似的思路,只创建长度为 3 的数组,通过取余的方式来复用不再需要的下标。 -代码: +Java 代码: ```Java class Solution { public int numDecodings(String s) { @@ -163,7 +214,59 @@ class Solution { } } ``` -* 时间复杂度:共有 `n` 个状态需要被转移。复杂度为 $O(n)$。 +C++ 代码: +```C++ +class Solution { +public: + int numDecodings(string s) { + int n = s.length(); + s = " " + s; + vector f(3, 0); + f[0] = 1; + for (int i = 1; i <= n; i++) { + f[i % 3] = 0; + int a = s[i] - '0', b = (s[i - 1] - '0') * 10 + (s[i] - '0'); + if (1 <= a && a <= 9) f[i % 3] = f[(i - 1) % 3]; + if (10 <= b && b <= 26) f[i % 3] += f[(i - 2) % 3]; + } + return f[n % 3]; + } +}; +``` +Python 代码: +```Python +class Solution: + def numDecodings(self, s: str) -> int: + n = len(s) + s = " " + s + f = [1, 0, 0] + for i in range(1, n + 1): + f[i % 3] = 0 + a = ord(s[i]) - ord('0') + b = (ord(s[i - 1]) - ord('0')) * 10 + a + if 1 <= a <= 9: + f[i % 3] = f[(i - 1) % 3] + if 10 <= b <= 26: + f[i % 3] += f[(i - 2) % 3] + return f[n % 3] +``` +TypeScript 代码: +```TypeScript +function numDecodings(s: string): number { + const n: number = s.length; + s = " " + s; + const f: number[] = [1, 0, 0]; + for (let i: number = 1; i <= n; i++) { + f[i % 3] = 0; + const a: number = parseInt(s.charAt(i)); + const b: number = parseInt(s.charAt(i - 1)) * 10 + a; + if (1 <= a && a <= 9) f[i % 3] = f[(i - 1) % 3]; + if (10 <= b && b <= 26) f[i % 3] += f[(i - 2) % 3]; + } + return f[n % 3]; +}; +``` +* 时间复杂度:共有 `n` 个状态需要被转移,复杂度为 $O(n)$。 * 空间复杂度:$O(1)$。 --- diff --git "a/LeetCode/911-920/919. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/911-920/919. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" index 49e7fa4f..99d51b85 100644 --- "a/LeetCode/911-920/919. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/911-920/919. \345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\346\217\222\345\205\245\345\231\250\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -80,6 +80,64 @@ class CBTInserter { } } ``` +C++ 代码: +```C++ +class CBTInserter { +public: + vector list; + int idx = 0; + CBTInserter(TreeNode* root) { + list.push_back(root); + int cur = 0; + while (cur < list.size()) { + auto node = list[cur]; + if (node->left) list.push_back(node->left); + if (node->right) list.push_back(node->right); + cur++; + } + } + int insert(int val) { + auto node = new TreeNode(val); + while (list[idx]->left && list[idx]->right) idx++; + auto fa = list[idx]; + if (!fa->left) fa->left = node; + else if (!fa->right) fa->right = node; + list.push_back(node); + return fa->val; + } + TreeNode* get_root() { + return list.front(); + } +}; +``` +Python 代码: +```Python +class CBTInserter: + def __init__(self, root): + self.lz = [root] + self.idx = 0 + cur = 0 + while cur < len(self.lz): + node = self.lz[cur] + if node.left: self.lz.append(node.left) + if node.right: self.lz.append(node.right) + cur += 1 + + def insert(self, val): + node = TreeNode(val) + while self.lz[self.idx].left and self.lz[self.idx].right: + self.idx += 1 + fa = self.lz[self.idx] + if not fa.left: + fa.left = node + else: + fa.right = node + self.lz.append(node) + return fa.val + + def get_root(self): + return self.lz[0] +``` TypeScript 代码: ```TypeScript class CBTInserter { diff --git "a/LeetCode/931-940/938. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\350\214\203\345\233\264\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/931-940/938. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\350\214\203\345\233\264\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" index a9b13c54..c45673a9 100644 --- "a/LeetCode/931-940/938. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\350\214\203\345\233\264\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/931-940/938. \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\350\214\203\345\233\264\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,18 +6,22 @@ Tag : 「树的搜索」、「DFS」、「BFS」 -给定二叉搜索树的根结点 root,返回值位于范围 [low, high] 之间的所有结点的值的和。 +给定二叉搜索树的根结点 `root`,返回值位于范围 `[low, high]` 之间的所有结点的值的和。 示例 1: + ![](https://assets.leetcode.com/uploads/2020/11/05/bst1.jpg) + ``` 输入:root = [10,5,15,3,7,null,18], low = 7, high = 15 输出:32 ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2020/11/05/bst2.jpg) + ``` 输入:root = [10,5,15,3,7,13,18,1,null,6], low = 6, high = 10 @@ -25,10 +29,10 @@ Tag : 「树的搜索」、「DFS」、「BFS」 ``` 提示: -* 树中节点数目在范围 [1, 2 * $10^4$] 内 -* 1 <= Node.val <= $10^5$ -* 1 <= low <= high <= $10^5$ -* 所有 Node.val 互不相同 +* 树中节点数目在范围 $[1, 2 \times 10^4$] 内 +* $1 <= Node.val <= 10^5$ +* $1 <= low <= high <= 10^5$ +* 所有 `Node.val` 互不相同 --- @@ -48,11 +52,10 @@ Tag : 「树的搜索」、「DFS」、「BFS」 递归写法十分简单,属于树的遍历中最简单的实现方式。 -代码: +Java 代码: ```Java class Solution { - int low, high; - int ans; + int low, high, ans; public int rangeSumBST(TreeNode root, int _low, int _high) { low = _low; high = _high; dfs(root); @@ -66,6 +69,42 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int low, high, ans; + int rangeSumBST(TreeNode* root, int _low, int _high) { + low = _low; high = _high; + dfs(root); + return ans; + } + void dfs(TreeNode* root) { + if (!root) return; + dfs(root->left); + if (low <= root->val && root->val <= high) ans += root->val; + dfs(root->right); + } +}; +``` +Python 代码: +```Python +class Solution: + def rangeSumBST(self, root: TreeNode, _low: int, _high: int) -> int: + self.low = _low + self.high = _high + self.ans = 0 + self.dfs(root) + return self.ans + + def dfs(self, root): + if not root: + return + self.dfs(root.left) + if self.low <= root.val <= self.high: + self.ans += root.val + self.dfs(root.right) +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ @@ -77,7 +116,7 @@ class Solution { 一般简单的面试中如果问到树的遍历,面试官都不会对「递归」解法感到满意,因此掌握「迭代/非递归」写法同样重要。 -代码: +Java 代码: ```Java class Solution { public int rangeSumBST(TreeNode root, int low, int high) { @@ -89,15 +128,50 @@ class Solution { root = root.left; } root = d.pollLast(); - if (low <= root.val && root.val <= high) { - ans += root.val; - } + if (low <= root.val && root.val <= high) ans += root.val; root = root.right; } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int rangeSumBST(TreeNode* root, int low, int high) { + int ans = 0; + deque d; + while (root != nullptr || !d.empty()) { + while (root != nullptr) { + d.push_back(root); + root = root->left; + } + root = d.back(); + d.pop_back(); + if (low <= root->val && root->val <= high) ans += root->val; + root = root->right; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def rangeSumBST(self, root: TreeNode, low: int, high: int) -> int: + ans = 0 + stk = [] + while root is not None or stk: + while root is not None: + stk.append(root) + root = root.left + root = stk.pop() + if low <= root.val <= high: + ans += root.val + root = root.right + return ans +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ diff --git "a/LeetCode/941-950/946. \351\252\214\350\257\201\346\240\210\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/941-950/946. \351\252\214\350\257\201\346\240\210\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" index 03e9257a..f5e24661 100644 --- "a/LeetCode/941-950/946. \351\252\214\350\257\201\346\240\210\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/941-950/946. \351\252\214\350\257\201\346\240\210\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,7 +6,7 @@ Tag : 「模拟」、「栈」、「双指针」 -给定 `pushed` 和 `popped` 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 `push` 和弹出 `pop` 操作序列的结果时,返回 `true`;否则,返回 `false`。 +给定 `pushed` 和 `popped` 两个序列,每个序列中的值都不重复,只有当它们可能是在最初空栈上进行的推入 `push` 和弹出 `pop` 操作序列的结果时,返回 `true`;否则,返回 `false`。 示例 1: ``` @@ -30,7 +30,7 @@ push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1 提示: * $1 <= pushed.length <= 1000$ * $0 <= pushed[i] <= 1000$ -* `pushed` 的所有元素 互不相同 +* `pushed` 的所有元素互不相同 * $popped.length = pushed.length$ * `popped` 是 `pushed` 的一个排列 @@ -53,6 +53,33 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool validateStackSequences(vector& pushed, vector& popped) { + deque d; + for (int i = 0, j = 0; i < pushed.size(); i++) { + d.push_back(pushed[i]); + while (!d.empty() && d.back() == popped[j] && ++j >= 0) d.pop_back(); + } + return d.empty(); + } +}; +``` +Python 代码: +```Python +class Solution: + def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: + d = deque() + j = 0 + for i in range(len(pushed)): + d.append(pushed[i]) + while d and d[-1] == popped[j]: + d.pop() + j += 1 + return not d +``` Typescript 代码: ```Typescript function validateStackSequences(pushed: number[], popped: number[]): boolean { @@ -89,6 +116,34 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + bool validateStackSequences(vector& pushed, vector& popped) { + int n = pushed.size(), idx = 0; + for (int i = 0, j = 0; i < n; i++) { + pushed[idx++] = pushed[i]; + while (idx > 0 && pushed[idx - 1] == popped[j] && ++j >= 0) idx--; + } + return idx == 0; + } +}; +``` +Python 代码: +```TypeScript +class Solution: + def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool: + n, idx = len(pushed), 0 + j = 0 + for i in range(n): + pushed[idx] = pushed[i] + idx += 1 + while idx > 0 and pushed[idx - 1] == popped[j]: + j += 1 + idx -= 1 + return idx == 0 +``` TypeScript 代码: ```TypeScript function validateStackSequences(pushed: number[], popped: number[]): boolean { diff --git "a/LeetCode/941-950/950. \346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/941-950/950. \346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214\357\274\210\344\270\255\347\255\211\357\274\211.md" index 1791f08b..9f2873d1 100644 --- "a/LeetCode/941-950/950. \346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/941-950/950. \346\214\211\351\200\222\345\242\236\351\241\272\345\272\217\346\230\276\347\244\272\345\215\241\347\211\214\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,9 +6,9 @@ Tag : 「模拟」、「队列」、「排序」、「构造」 -牌组中的每张卡牌都对应有一个唯一的整数。你可以按你想要的顺序对这套卡片进行排序。 +牌组中的每张卡牌都对应有一个唯一的整数,你可以按你想要的顺序对这套卡片进行排序。 -最初,这些卡牌在牌组里是正面朝下的(即,未显示状态)。 +最初,这些卡牌在牌组里是正面朝下的(即未显示状态)。 现在,重复执行以下步骤,直到显示所有卡牌为止: diff --git "a/LeetCode/951-960/952. \346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/951-960/952. \346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217\357\274\210\345\233\260\351\232\276\357\274\211.md" index 4589dd2b..ccbd26fc 100644 --- "a/LeetCode/951-960/952. \346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/951-960/952. \346\214\211\345\205\254\345\233\240\346\225\260\350\256\241\347\256\227\346\234\200\345\244\247\347\273\204\344\273\266\345\244\247\345\260\217\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -14,21 +14,27 @@ Tag : 「数学」、「并查集」 返回 图中最大连通组件的大小 。 示例 1: + ![](https://assets.leetcode.com/uploads/2018/12/01/ex1.png) + ``` 输入:nums = [4,6,15,35] 输出:4 ``` 示例 2: + ![](https://assets.leetcode.com/uploads/2018/12/01/ex2.png) + ``` 输入:nums = [20,50,9,63] 输出:2 ``` 示例 3: + ![](https://assets.leetcode.com/uploads/2018/12/01/ex3.png) + ``` 输入:nums = [2,3,6,7,4,12,21,39] @@ -97,6 +103,86 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + static const int N = 20010; + vector p, sz; + int ans = 1; + Solution() : p(N), sz(N, 1) { + for (int i = 0; i < N; ++i) p[i] = i; + } + int find(int x) { + if (p[x] != x) p[x] = find(p[x]); + return p[x]; + } + void unions(int a, int b) { + if (find(a) == find(b)) return ; + sz[find(a)] += sz[find(b)]; + p[find(b)] = p[find(a)]; + ans = max(ans, sz[find(a)]); + } + int largestComponentSize(vector& nums) { + unordered_map> map; + for (int i = 0; i < nums.size(); i++) { + int cur = nums[i]; + for (int j = 2; j * j <= cur; j++) { + if (cur % j == 0) add(map, j, i); + while (cur % j == 0) cur /= j; + } + if (cur > 1) add(map, cur, i); + } + for (auto& pair : map) { + for (int i = 1; i < pair.second.size(); i++) { + unions(pair.second[0], pair.second[i]); + } + } + return ans; + } + void add(unordered_map>& map, int key, int val) { + map[key].push_back(val); + } +}; +``` +Python 代码: +```Python +class Solution: + def __init__(self): + self.p = [i for i in range(20010)] + self.sz = [1] * 20010 + self.ans = 1 + + def find(self, x): + if self.p[x] != x: + self.p[x] = self.find(self.p[x]) + return self.p[x] + + def union(self, a, b): + if self.find(a) == self.find(b): + return + self.sz[self.find(a)] += self.sz[self.find(b)] + self.p[self.find(b)] = self.p[self.find(a)] + self.ans = max(self.ans, self.sz[self.find(a)]) + + def largestComponentSize(self, nums): + mapping = defaultdict(list) + for i, num in enumerate(nums): + for j in range(2, int(math.sqrt(num)) + 1): + if num % j == 0: + self.add(mapping, j, i) + while num % j == 0: + num //= j + if num > 1: + self.add(mapping, num, i) + for vals in mapping.values(): + for i in range(1, len(vals)): + self.union(vals[0], vals[i]) + return self.ans + + def add(self, mapping, key, val): + mapping[key].append(val) +``` TypeScript 代码: ```TypeScript const N = 20010 diff --git "a/LeetCode/971-980/978. \346\234\200\351\225\277\346\271\215\346\265\201\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/971-980/978. \346\234\200\351\225\277\346\271\215\346\265\201\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" index fe819795..8a74ebb6 100644 --- "a/LeetCode/971-980/978. \346\234\200\351\225\277\346\271\215\346\265\201\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/971-980/978. \346\234\200\351\225\277\346\271\215\346\265\201\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -18,17 +18,21 @@ Tag : 「线性 DP」、「序列 DP」 示例 1: ``` 输入:[9,4,2,10,7,8,8,1,9] + 输出:5 + 解释:(A[1] > A[2] < A[3] > A[4] < A[5]) ``` 示例 2: ``` 输入:[4,8,12,16] + 输出:2 ``` 示例 3: ``` 输入:[100] + 输出:1 ``` @@ -70,7 +74,7 @@ Tag : 「线性 DP」、「序列 DP」 * $arr[i - 1] > arr[i]$:改点是由下降而来,能够「接着」的条件是 $i - 1$ 是由上升而来。则有:$f[i][1] = f[i - 1][0] + 1$ * $arr[i - 1] = arr[i]$:不考虑,不符合「湍流」的定义 -代码: +Java 代码: ```Java class Solution { public int maxTurbulenceSize(int[] arr) { @@ -87,6 +91,49 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxTurbulenceSize(vector& arr) { + int n = arr.size(), ans = 1; + vector> f(n, vector(2, 1)); + for (int i = 1; i < n; i++) { + if (arr[i] > arr[i - 1]) f[i][0] = f[i - 1][1] + 1; + else if (arr[i] < arr[i - 1]) f[i][1] = f[i - 1][0] + 1; + ans = max(ans, max(f[i][0], f[i][1])); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxTurbulenceSize(self, arr: List[int]) -> int: + n, ans = len(arr), 1 + f = [[1, 1] for _ in range(n)] + for i in range(1, n): + if arr[i] > arr[i - 1]: + f[i][0] = f[i - 1][1] + 1 + elif arr[i] < arr[i - 1]: + f[i][1] = f[i - 1][0] + 1 + ans = max(ans, max(f[i][0], f[i][1])) + return ans +``` +TypeScript 代码: +```TypeScript +function maxTurbulenceSize(arr: number[]): number { + let n = arr.length, ans = 1; + const f: number[][] = new Array(n).fill(0).map(() => [1, 1]); + for (let i = 1; i < n; i++) { + if (arr[i] > arr[i - 1]) f[i][0] = f[i - 1][1] + 1; + else if (arr[i] < arr[i - 1]) f[i][1] = f[i - 1][0] + 1; + ans = Math.max(ans, Math.max(f[i][0], f[i][1])); + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ @@ -100,7 +147,7 @@ class Solution { 修改的方式也十分机械,只需要改为「奇偶滚动」的维度直接修改成 $2$ ,然后该维度的所有访问方式增加 `%2` 或者 `&1` 即可: -代码: +Java 代码: ```Java class Solution { public int maxTurbulenceSize(int[] arr) { @@ -117,6 +164,52 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxTurbulenceSize(vector& arr) { + int n = arr.size(), ans = 1; + vector> f(2, vector(2, 1)); + for (int i = 1; i < n; i++) { + f[i % 2][0] = f[i % 2][1] = 1; + if (arr[i] > arr[i - 1]) f[i % 2][0] = f[(i - 1) % 2][1] + 1; + else if (arr[i] < arr[i - 1]) f[i % 2][1] = f[(i - 1) % 2][0] + 1; + ans = max(ans, max(f[i % 2][0], f[i % 2][1])); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxTurbulenceSize(self, arr: List[int]) -> int: + n, ans = len(arr), 1 + f = [[1, 1] for _ in range(2)] + for i in range(1, n): + f[i % 2][0] = f[i % 2][1] = 1 + if arr[i] > arr[i - 1]: + f[i % 2][0] = f[(i - 1) % 2][1] + 1 + elif arr[i] < arr[i - 1]: + f[i % 2][1] = f[(i - 1) % 2][0] + 1 + ans = max(ans, max(f[i % 2][0], f[i % 2][1])) + return ans +``` +TypeScript 代码: +```TypeScript +function maxTurbulenceSize(arr: number[]): number { + let n = arr.length, ans = 1; + const f: number[][] = [[1, 1], [1, 1]]; + for (let i = 1; i < n; i++) { + f[i % 2][0] = f[i % 2][1] = 1; + if (arr[i] > arr[i - 1]) f[i % 2][0] = f[(i - 1) % 2][1] + 1; + else if (arr[i] < arr[i - 1]) f[i % 2][1] = f[(i - 1) % 2][0] + 1; + ans = Math.max(ans, Math.max(f[i % 2][0], f[i % 2][1])); + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:使用固定 `2 * 2` 的数组空间。复杂度为 $O(1)$ @@ -132,7 +225,7 @@ class Solution { 但相比于「奇偶滚动」的空间优化,这种优化手段只是常数级别的优化(空间复杂度与「奇偶滚动」相同),而且优化通常涉及代码改动。 -代码: +Java 代码: ```Java class Solution { public int maxTurbulenceSize(int[] arr) { @@ -149,6 +242,50 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int maxTurbulenceSize(vector& arr) { + int n = arr.size(), ans = 1; + vector f(2, 1); + for (int i = 1; i < n; ++i) { + int a = f[0], b = f[1]; + f[0] = arr[i - 1] < arr[i] ? b + 1 : 1; + f[1] = arr[i - 1] > arr[i] ? a + 1 : 1; + ans = max(ans, max(f[0], f[1])); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxTurbulenceSize(self, arr: List[int]) -> int: + n, ans = len(arr), 1 + f = [1, 1] + for i in range(1, n): + a, b = f[0], f[1] + f[0] = b + 1 if arr[i - 1] < arr[i] else 1 + f[1] = a + 1 if arr[i - 1] > arr[i] else 1 + ans = max(ans, max(f[0], f[1])) + return ans +``` +TypeScript 代码: +```TypeScript +function maxTurbulenceSize(arr: number[]): number { + let n = arr.length, ans = 1; + let f = [1, 1]; + for (let i = 1; i < n; i++) { + let [a, b] = f; + f[0] = arr[i - 1] < arr[i] ? b + 1 : 1; + f[1] = arr[i - 1] > arr[i] ? a + 1 : 1; + ans = Math.max(ans, Math.max(f[0], f[1])); + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(1)$ diff --git "a/LeetCode/981-990/985. \346\237\245\350\257\242\345\220\216\347\232\204\345\201\266\346\225\260\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/981-990/985. \346\237\245\350\257\242\345\220\216\347\232\204\345\201\266\346\225\260\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" index 0228ea07..569fa4c7 100644 --- "a/LeetCode/981-990/985. \346\237\245\350\257\242\345\220\216\347\232\204\345\201\266\346\225\260\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/981-990/985. \346\237\245\350\257\242\345\220\216\347\232\204\345\201\266\346\225\260\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -10,9 +10,9 @@ Tag : 「模拟」 对于第 `i` 次查询,有 `val = queries[i][0]`, `index = queries[i][1]`,我们会把 `val` 加到 `A[index]` 上。然后,第 `i` 次查询的答案是 `A` 中偶数值的和。 -(此处给定的 `index = queries[i][1]` 是从 `0` 开始的索引,每次查询都会永久修改数组 `A`。) +此处给定的 `index = queries[i][1]` 是从 `0` 开始的索引,每次查询都会永久修改数组 `A`。 -返回所有查询的答案。你的答案应当以数组 `answer` 给出,`answer[i]` 为第 `i` 次查询的答案。 +返回所有查询的答案,你的答案应当以数组 `answer` 给出,`answer[i]` 为第 `i` 次查询的答案。 示例: ``` diff --git "a/LeetCode/991-1000/992. K \344\270\252\344\270\215\345\220\214\346\225\264\346\225\260\347\232\204\345\255\220\346\225\260\347\273\204\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/991-1000/992. K \344\270\252\344\270\215\345\220\214\346\225\264\346\225\260\347\232\204\345\255\220\346\225\260\347\273\204\357\274\210\345\233\260\351\232\276\357\274\211.md" index e20982f3..f01beae6 100644 --- "a/LeetCode/991-1000/992. K \344\270\252\344\270\215\345\220\214\346\225\264\346\225\260\347\232\204\345\255\220\346\225\260\347\273\204\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/991-1000/992. K \344\270\252\344\270\215\345\220\214\346\225\264\346\225\260\347\232\204\345\255\220\346\225\260\347\273\204\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -45,7 +45,7 @@ Tag : 「双指针」、「滑动窗口」 2. 找到其左边「最远」满足出现 $k - 1$ 个不同字符的下标,记为 $j$ ,这时候形成的区间为 $[j, i]$ 3. **那么对于 $j - p$ 其实就是代表以 $nums[i]$ 为右边界(必须包含 $num[i]$),不同字符数量「恰好」为 $k$ 的子数组数量** -![WechatIMG1758.png](https://pic.leetcode-cn.com/1612839352-exScZN-WechatIMG1758.png) +![](https://pic.leetcode-cn.com/1612839352-exScZN-WechatIMG1758.png) 我们使用 $lower$ 数组存起每个位置的 $p$;使用 $upper$ 数组存起每个位置的 $j$。 @@ -53,15 +53,14 @@ Tag : 「双指针」、「滑动窗口」 计算 $lower$ 数组 和 $upper$ 数组的过程可以使用双指针。 -代码: +Java 代码: ```Java class Solution { public int subarraysWithKDistinct(int[] nums, int k) { - int n = nums.length; + int n = nums.length, ans = 0; int[] lower = new int[n], upper = new int[n]; find(nums, lower, k); find(nums, upper, k - 1); - int ans = 0; for (int i = 0; i < n; i++) ans += upper[i] - lower[i]; return ans; } @@ -78,6 +77,59 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int subarraysWithKDistinct(vector& nums, int k) { + int n = nums.size(), ans = 0; + vector lower(n, 0), upper(n, 0); + find(nums, lower, k); + find(nums, upper, k - 1); + for (int i = 0; i < n; i++) ans += upper[i] - lower[i]; + return ans; + } + void find(vector& nums, vector& arr, int k) { + int n = nums.size(); + unordered_map cnt; + int i = 0, j = 0, sum = 0; + for (j = 0; j < n; j++) { + if (++cnt[nums[j]] == 1) sum++; + while (sum > k) { + if (--cnt[nums[i++]] == 0) sum--; + } + if (sum == k) arr[j] = i; + } + } +}; +``` +Python 代码: +```Python +class Solution: + def subarraysWithKDistinct(self, nums: List[int], k: int) -> int: + n, ans = len(nums), 0 + lower, upper = [0] * n, [0] * n + self.find(nums, lower, k) + self.find(nums, upper, k - 1) + return sum(upper[i] - lower[i] for i in range(n)) + + def find(self, nums, arr, k): + n = len(nums) + cnt = defaultdict(int) + i, j, sumv = 0, 0, 0 + while j < n: + if cnt[nums[j]] == 0: + sumv += 1 + cnt[nums[j]] += 1 + while sumv > k: + cnt[nums[i]] -= 1 + if cnt[nums[i]] == 0: + sumv -= 1 + i += 1 + if sumv == k: + arr[j] = i + j += 1 +``` * 时间复杂度:对数组进行常数次扫描。复杂度为 $O(n)$。 * 空间复杂度:$O(n)$ diff --git "a/LeetCode/991-1000/995. K \350\277\236\347\273\255\344\275\215\347\232\204\346\234\200\345\260\217\347\277\273\350\275\254\346\254\241\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/991-1000/995. K \350\277\236\347\273\255\344\275\215\347\232\204\346\234\200\345\260\217\347\277\273\350\275\254\346\254\241\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" index 2b0c7006..584e028a 100644 --- "a/LeetCode/991-1000/995. K \350\277\236\347\273\255\344\275\215\347\232\204\346\234\200\345\260\217\347\277\273\350\275\254\346\254\241\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" +++ "b/LeetCode/991-1000/995. K \350\277\236\347\273\255\344\275\215\347\232\204\346\234\200\345\260\217\347\277\273\350\275\254\346\254\241\346\225\260\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -6,27 +6,35 @@ Tag : 「贪心」、「差分」 -在仅包含 0 和 1 的数组 A 中,一次 K 位翻转包括选择一个长度为 K 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0。 +在仅包含 0 和 1 的数组 `A` 中,一次 `K` 位翻转包括选择一个长度为 `K` 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0。 -返回所需的 K 位翻转的最小次数,以便数组没有值为 0 的元素。如果不可能,返回 -1。 +返回所需的 `K` 位翻转的最小次数,以便数组没有值为 0 的元素。 + +如果不可能,返回 -1。 示例 1: ``` 输入:A = [0,1,0], K = 1 + 输出:2 + 解释:先翻转 A[0],然后翻转 A[2]。 ``` 示例 2: ``` 输入:A = [1,1,0], K = 2 + 输出:-1 + 解释:无论我们怎样翻转大小为 2 的子数组,我们都不能使数组变为 [1,1,1]。 ``` 示例 3: ``` 输入:A = [0,0,0,1,0,1,1,0], K = 3 + 输出:3 + 解释: 翻转 A[0],A[1],A[2]: A变成 [1,1,1,1,0,1,1,0] 翻转 A[4],A[5],A[6]: A变成 [1,1,1,1,1,0,0,0] @@ -34,24 +42,24 @@ Tag : 「贪心」、「差分」 ``` 提示: -* 1 <= A.length <= 30000 -* 1 <= K <= A.length +* $1 <= A.length <= 30000$ +* $1 <= K <= A.length$ --- -### 贪心解法 +### 贪心解法(TLE) 目标是将数组的每一位都变为 1 ,因此对于每一位 0 都需要翻转。 我们可以从前往后处理,遇到 0 则对后面的 `k` 位进行翻转。 -这样我们的算法复杂度是 $O(nk)$ 的,数据范围是 3w(数量级为 $10^4$),极限数据下单秒的运算量在 $10^8$ 以上,会有超时风险。 +这样我们的算法复杂度是 $O(n \times k)$ 的,数据范围是 3w(数量级为 $10^4$),极限数据下单秒的运算量在 $10^8$ 以上,会有超时风险。 -```java [] +Java 代码: +```Java class Solution { public int minKBitFlips(int[] nums, int k) { - int n = nums.length; - int ans = 0; + int n = nums.length, ans = 0; for (int i = 0; i < n; i++) { if (nums[i] == 0) { if (i + k > n) return -1; @@ -63,12 +71,13 @@ class Solution { } } ``` -* 时间复杂度:$O(nk)$ +* 时间复杂度:$O(n \times k)$ * 空间复杂度:$O(1)$ -*** +--- ### 补充 + 评论有同学提出了一些有价值的疑问,我觉得挺有代表性的,因此补充到题解: 1. **为什么这样的解法是「贪心解法」,而不是「暴力解法」?** @@ -95,7 +104,7 @@ class Solution { 同样是 97 号样例数据,提交给 LeetCode 执行。Java 运行 200 ms 以内,而 C++ 运行 600 ms。 -*** +--- ### 贪心 + 差分解法 @@ -107,29 +116,77 @@ class Solution { 因此可以使用差分数组来进行优化:当需要对某一段 `[l,r]` 进行 +1 的时候,只需要 `arr[l]++` 和 `arr[r + 1]--` 即可。 -```java +Java 代码: +```Java class Solution { public int minKBitFlips(int[] nums, int k) { - int n = nums.length; - int ans = 0; + int n = nums.length, ans = 0; int[] arr = new int[n + 1]; for (int i = 0, cnt = 0; i < n; i++) { cnt += arr[i]; if ((nums[i] + cnt) % 2 == 0) { if (i + k > n) return -1; - arr[i + 1]++; - arr[i + k]--; - ans++; + arr[i + 1]++; arr[i + k]--; ans++; } } return ans; } } ``` +C++ 代码: +```C++ +class Solution { +public: + int minKBitFlips(vector& nums, int k) { + int n = nums.size(), ans = 0; + vector arr(n + 1, 0); + for (int i = 0, cnt = 0; i < n; i++) { + cnt += arr[i]; + if ((nums[i] + cnt) % 2 == 0) { + if (i + k > n) return -1; + arr[i + 1]++; arr[i + k]--; ans++; + } + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def minKBitFlips(self, nums: List[int], k: int) -> int: + n, ans = len(nums), 0 + arr = [0] * (n + 1) + cnt = 0 + for i in range(n): + cnt += arr[i] + if (nums[i] + cnt) % 2 == 0: + if i + k > n: + return -1 + arr[i + 1] += 1 + arr[i + k] -= 1 + ans += 1 + return ans +``` +TypeScript 代码: +```TypeScript +function minKBitFlips(nums: number[], k: number): number { + let n = nums.length, ans = 0; + let arr = new Array(n + 1).fill(0); + for (let i = 0, cnt = 0; i < n; i++) { + cnt += arr[i]; + if ((nums[i] + cnt) % 2 === 0) { + if (i + k > n) return -1; + arr[i + 1]++; arr[i + k]--; ans++; + } + } + return ans; +}; +``` * 时间复杂度:$O(n)$ * 空间复杂度:$O(n)$ -*** +--- ### 证明 @@ -143,7 +200,7 @@ class Solution { 所以这次将这个证明过程留给大家思考 ~ -*** +--- ### 为什么要「证明」或「理解证明」? @@ -155,7 +212,7 @@ class Solution { 2. 在「面试」阶段,**你可以很清晰讲解你的思路**。让面试官从你的「思维方式」上喜欢上你 -*** +--- ### 更多与证明/分析相关的题解: diff --git "a/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 115. \351\207\215\345\273\272\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 115. \351\207\215\345\273\272\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" index 22b78ead..2890dac4 100644 --- "a/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 115. \351\207\215\345\273\272\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 115. \351\207\215\345\273\272\345\272\217\345\210\227\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -6,16 +6,22 @@ Tag : 「图论」、「拓扑排序」、「建图」、「图论 BFS」 -给定一个长度为 `n` 的整数数组 `nums` ,其中 `nums` 是范围为 $[1,n]$ 的整数的排列。还提供了一个 `2D` 整数数组 `sequences`,其中 `sequences[i]` 是 `nums` 的子序列。 +给定一个长度为 `n` 的整数数组 `nums` ,其中 `nums` 是范围为 $[1,n]$ 的整数的排列。 -检查 `nums` 是否是唯一的最短 超序列 。最短 超序列 是 长度最短 的序列,并且所有序列 `sequences[i]` 都是它的子序列。对于给定的数组 `sequences`,可能存在多个有效的 超序列 。 +还提供了一个 `2D` 整数数组 `sequences`,其中 `sequences[i]` 是 `nums` 的子序列。 -* 例如,对于 `sequences = [[1,2],[1,3]]` ,有两个最短的 超序列,`[1,2,3]` 和 `[1,3,2]` 。 -* 而对于 `sequences = [[1,2],[1,3],[1,2,3]]`,唯一可能的最短 超序列 是 `[1,2,3]` 。`[1,2,3,4]` 是可能的超序列,但不是最短的。 +检查 `nums` 是否是唯一的最短超序列。 -如果 `nums` 是序列的唯一最短 超序列 ,则返回 `true` ,否则返回 `false` 。 +最短超序列是长度最短的序列,并且所有序列 `sequences[i]` 都是它的子序列。 -子序列 是一个可以通过从另一个序列中删除一些元素或不删除任何元素,而不改变其余元素的顺序的序列。 +对于给定的数组 `sequences`,可能存在多个有效的超序列。 + +* 例如,对于 `sequences = [[1,2],[1,3]]` ,有两个最短的超序列,`[1,2,3]` 和 `[1,3,2]` 。 +* 而对于 `sequences = [[1,2],[1,3],[1,2,3]]`,唯一可能的最短超序列是 `[1,2,3]` 。`[1,2,3,4]` 是可能的超序列,但不是最短的。 + +如果 `nums` 是序列的唯一最短超序列,则返回 `true` ,否则返回 `false` 。 + +子序列是一个可以通过从另一个序列中删除一些元素或不删除任何元素,而不改变其余元素的顺序的序列。 示例 1: ``` @@ -52,14 +58,14 @@ Tag : 「图论」、「拓扑排序」、「建图」、「图论 BFS」 ``` 提示: -* $n == nums.length$ +* $n = nums.length$ * $1 <= n <= 104$ * `nums` 是 $[1, n]$ 范围内所有整数的排列 * $1 <= sequences.length <= 10^4$ * $1 <= sequences[i].length <= 104$ * $1 <= sum(sequences[i].length) <= 10^5$ * $1 <= sequences[i][j] <= n$ -* `sequences` 的所有数组都是 唯一 的 +* `sequences` 的所有数组都是唯一的 * `sequences[i]` 是 `nums` 的一个子序列 --- @@ -115,6 +121,43 @@ class Solution { } } ``` +C++ 代码: +```C++ +class Solution { +public: + int N = 10010, M = N, idx; + vector he, e, ne, in; + void add(int a, int b) { + e[idx] = b; + ne[idx] = he[a]; + he[a] = idx++; + in[b]++; + } + Solution() : he(N, -1), e(M), ne(M), in(N, 0) {} + bool sequenceReconstruction(vector& nums, vector>& ss) { + int n = nums.size(); + fill(he.begin(), he.end(), -1); + for (auto& s : ss) { + for (int i = 1; i < s.size(); i++) add(s[i - 1], s[i]); + } + deque d; + for (int i = 1; i <= n; i++) { + if (in[i] == 0) d.push_back(i); + } + int loc = 0; + while (!d.empty()) { + if (d.size() != 1) return false; + int t = d.front(); d.pop_front(); + if (nums[loc++] != t) return false; + for (int i = he[t]; i != -1; i = ne[i]) { + int j = e[i]; + if (--in[j] == 0) d.push_back(j); + } + } + return true; + } +}; +``` TypeScript 代码: ```TypeScript const N = 10010, M = N diff --git "a/LeetCode/\351\235\242\350\257\225\351\242\230/\351\235\242\350\257\225\351\242\230 03.01. \344\270\211\345\220\210\344\270\200\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/\351\235\242\350\257\225\351\242\230/\351\235\242\350\257\225\351\242\230 03.01. \344\270\211\345\220\210\344\270\200\357\274\210\347\256\200\345\215\225\357\274\211.md" index a8b2f3a7..1f09708b 100644 --- "a/LeetCode/\351\235\242\350\257\225\351\242\230/\351\235\242\350\257\225\351\242\230 03.01. \344\270\211\345\220\210\344\270\200\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/\351\235\242\350\257\225\351\242\230/\351\235\242\350\257\225\351\242\230 03.01. \344\270\211\345\220\210\344\270\200\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,28 +6,34 @@ Tag : 「栈」 +三合一。 -三合一。描述如何只用一个数组来实现三个栈。 +描述如何只用一个数组来实现三个栈。 -你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标,value表示压入的值。 +你应该实现 `push(stackNum, value)`、`pop(stackNum)`、`isEmpty(stackNum)`、`peek(stackNum)` 方法。 -构造函数会传入一个stackSize参数,代表每个栈的大小。 +`stackNum` 表示栈下标,`value` 表示压入的值。 + +构造函数会传入一个 `stackSize` 参数,代表每个栈的大小。 示例1: ``` - 输入: +输入: ["TripleInOne", "push", "push", "pop", "pop", "pop", "isEmpty"] [[1], [0, 1], [0, 2], [0], [0], [0], [0]] - 输出: + +输出: [null, null, null, 1, -1, -1, true] 说明:当栈为空时`pop, peek`返回-1,当栈满时`push`不压入元素。 ``` + 示例2: ``` - 输入: +输入: ["TripleInOne", "push", "push", "push", "pop", "pop", "pop", "peek"] [[2], [0, 1], [0, 2], [0, 3], [0], [0], [0], [0]] - 输出: + +输出: [null, null, null, null, 2, 1, -1, -1] ``` @@ -37,24 +43,22 @@ Tag : 「栈」 题目只要求我们使用「一个数组」来实现栈,并没有限制我们使用数组的维度。 -因此一个简单的做法是,建立一个二维数组 $data$ 来做。 +因此一个简单的做法是,建立一个二维数组 `data` 来做。 -二维数组的每一行代表一个栈,同时使用一个 $locations$ 记录每个栈「待插入」的下标。 +二维数组的每一行代表一个栈,同时使用一个 `locations` 记录每个栈「待插入」的下标。 代码: -```java +```Java class TripleInOne { int N = 3; // 3 * n 的数组,每一行代表一个栈 int[][] data; // 记录每个栈「待插入」位置 int[] locations; - public TripleInOne(int stackSize) { data = new int[N][stackSize]; locations = new int[N]; } - public void push(int stackNum, int value) { int[] stk = data[stackNum]; int loc = locations[stackNum]; @@ -63,7 +67,6 @@ class TripleInOne { locations[stackNum]++; } } - public int pop(int stackNum) { int[] stk = data[stackNum]; int loc = locations[stackNum]; @@ -75,49 +78,81 @@ class TripleInOne { return -1; } } - public int peek(int stackNum) { int[] stk = data[stackNum]; int loc = locations[stackNum]; + if (loc > 0) return stk[loc - 1]; + else return -1; + } + public boolean isEmpty(int stackNum) { + return locations[stackNum] == 0; + } +} +``` +C++ 代码: +```C++ +class TripleInOne { +public: + int N = 3; + vector> data; + vector locations; + TripleInOne(int stackSize) { + data.resize(N, vector(stackSize)); + locations.resize(N, 0); + } + void push(int stackNum, int value) { + vector& stk = data[stackNum]; + int loc = locations[stackNum]; + if (loc < stk.size()) { + stk[loc] = value; + locations[stackNum]++; + } + } + int pop(int stackNum) { + vector& stk = data[stackNum]; + int loc = locations[stackNum]; if (loc > 0) { - return stk[loc - 1]; + int val = stk[--loc]; + locations[stackNum] = loc; + return val; } else { return -1; } } - - public boolean isEmpty(int stackNum) { + int peek(int stackNum) { + vector& stk = data[stackNum]; + int loc = locations[stackNum]; + if (loc > 0) return stk[loc - 1]; + else return -1; + } + bool isEmpty(int stackNum) { return locations[stackNum] == 0; } -} +}; ``` * 时间复杂度:所有的操作均为 $O(1)$。 -* 空间复杂度:$O(k * n)$。k 为我们需要实现的栈的个数,n 为栈的容量。 +* 空间复杂度:$O(k \times n)$。$k$ 为我们需要实现的栈的个数,$n$ 为栈的容量。 -*** +--- ### 一维数组 当然了,我们也能使用一个一维数组来做。 -建立一个长度为 $3 * stackSize$ 的数组,并将 3 个栈的「待插入」存储在 $locations$ 数组。 +建立一个长度为 $3 \times stackSize$ 的数组,并将 3 个栈的「待插入」存储在 `locations` 数组。 代码: -```java +```Java class TripleInOne { int N = 3; - int[] data; - int[] locations; + int[] data, locations; int size; public TripleInOne(int stackSize) { size = stackSize; data = new int[size * N]; locations = new int[N]; - for (int i = 0; i < N; i++) { - locations[i] = i * size; - } + for (int i = 0; i < N; i++) locations[i] = i * size; } - public void push(int stackNum, int value) { int idx = locations[stackNum]; if (idx < (stackNum + 1) * size) { @@ -125,7 +160,6 @@ class TripleInOne { locations[stackNum]++; } } - public int pop(int stackNum) { int idx = locations[stackNum]; if (idx > stackNum * size) { @@ -135,23 +169,54 @@ class TripleInOne { return -1; } } - public int peek(int stackNum) { + int idx = locations[stackNum]; + if (idx > stackNum * size) return data[idx - 1]; + else return -1; + } + public boolean isEmpty(int stackNum) { + return locations[stackNum] == stackNum * size; + } +} +``` +C++ 代码: +```C++ +class TripleInOne { +public: + int N = 3; + vector data, locations; + int size; + TripleInOne(int stackSize) : size(stackSize), data(stackSize * N), locations(N, 0) { + for (int i = 0; i < N; i++) locations[i] = i * size; + } + void push(int stackNum, int value) { + int idx = locations[stackNum]; + if (idx < (stackNum + 1) * size) { + data[idx] = value; + locations[stackNum]++; + } + } + int pop(int stackNum) { int idx = locations[stackNum]; if (idx > stackNum * size) { + locations[stackNum]--; return data[idx - 1]; } else { return -1; } } - - public boolean isEmpty(int stackNum) { + int peek(int stackNum) { + int idx = locations[stackNum]; + if (idx > stackNum * size) return data[idx - 1]; + else return -1; + } + bool isEmpty(int stackNum) { return locations[stackNum] == stackNum * size; } -} +}; ``` * 时间复杂度:所有的操作均为 $O(1)$。 -* 空间复杂度:$O(k * n)$。k 为我们需要实现的栈的个数,n 为栈的容量。 +* 空间复杂度:$O(k \times n)$。$k$ 为我们需要实现的栈的个数,$n$ 为栈的容量。 ---