diff --git a/README.en.md b/README.en.md index 5ee6c1618..8c60cee34 100644 --- a/README.en.md +++ b/README.en.md @@ -217,6 +217,7 @@ The data structures mainly include: - [0516.longest-palindromic-subsequence](./problems/516.longest-palindromic-subsequence.md) - [0518.coin-change-2](./problems/518.coin-change-2.md) - [0547.friend-circles](./problems/547.friend-circles-en.md) 🆕✅ +- [0560.subarray-sum-equals-k](./problems/560.subarray-sum-equals-k.en.md) ✅ - [0609.find-duplicate-file-in-system](./problems/609.find-duplicate-file-in-system.md) - [0875.koko-eating-bananas](./problems/875.koko-eating-bananas.md) - [0877.stone-game](./problems/877.stone-game.md) @@ -246,14 +247,14 @@ The data structures mainly include: ### Summary of Data Structures and Algorithm -- [Data Structure](./thinkings/basic-data-structure-en.md) (Drafts) -- [Basic Algorithm](./thinkings/basic-algorithm-en.md)(Drafts) +- [Data Structure](./thinkings/basic-data-structure-en.md)✅ +- [Basic Algorithm](./thinkings/basic-algorithm-en.md)✅ - [Binary Tree Traversal](./thinkings/binary-tree-traversal.en.md)✅ -- [Dynamic Programming](./thinkings/dynamic-programming-en.md) -- [Huffman Encode and Run Length Encode](./thinkings/run-length-encode-and-huffman-encode-en.md) -- [Bloom Filter](./thinkings/bloom-filter-en.md) -- [String Problems](./thinkings/string-problems-en.md) -- [Sliding Window Technique](./thinkings/slide-window.en.md) +- [Dynamic Programming](./thinkings/dynamic-programming-en.md)✅ +- [Huffman Encode and Run Length Encode](./thinkings/run-length-encode-and-huffman-encode-en.md)✅ +- [Bloom Filter](./thinkings/bloom-filter-en.md)✅ +- [String Problems](./thinkings/string-problems-en.md)✅ +- [Sliding Window Technique](./thinkings/slide-window.en.md)✅ ### Anki Flashcards diff --git a/README.md b/README.md index f2a2ab54d..ccf5fdd79 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,7 @@ leetcode 题解,记录自己的 leetcode 解题之路。 - [0322.coin-change](./problems/322.coin-change.md) - [0328.odd-even-linked-list](./problems/328.odd-even-linked-list.md) - [0334.increasing-triplet-subsequence](./problems/334.increasing-triplet-subsequence.md) +- [0343.integer-break](./problems/343.integer-break.md)🆕 - [0365.water-and-jug-problem](./problems/365.water-and-jug-problem.md) - [0378.kth-smallest-element-in-a-sorted-matrix](./problems/378.kth-smallest-element-in-a-sorted-matrix.md) - [0380.insert-delete-getrandom-o1](./problems/380.insert-delete-getrandom-o1.md)🆕 @@ -246,25 +247,26 @@ leetcode 题解,记录自己的 leetcode 解题之路。 - [0877.stone-game](./problems/877.stone-game.md) - [0887.super-egg-drop](./problems/887.super-egg-drop.md) - [0900.rle-iterator](./problems/900.rle-iterator.md) -- [0912.sort-an-array](./problems/912.sort-an-array.md) 🆕 +- [0912.sort-an-array](./problems/912.sort-an-array.md) - [0935.knight-dialer](./problems/935.knight-dialer.md) 🆕 -- [1011.capacity-to-ship-packages-within-d-days](./problems/1011.capacity-to-ship-packages-within-d-days.md) 🆕 +- [1011.capacity-to-ship-packages-within-d-days](./problems/1011.capacity-to-ship-packages-within-d-days.md) - [1014.best-sightseeing-pair](./problems/1014.best-sightseeing-pair.md) 🆕 -- [1015.smallest-integer-divisible-by-k](./problems/1015.smallest-integer-divisible-by-k.md) 🆕 +- [1015.smallest-integer-divisible-by-k](./problems/1015.smallest-integer-divisible-by-k.md) - [1019.next-greater-node-in-linked-list](./problems/1019.next-greater-node-in-linked-list.md) 🆕 -- [1020.number-of-enclaves](./problems/1020.number-of-enclaves.md) 🆕 -- [1023.camelcase-matching](./problems/1023.camelcase-matching.md) 🆕 +- [1020.number-of-enclaves](./problems/1020.number-of-enclaves.md) +- [1023.camelcase-matching](./problems/1023.camelcase-matching.md) - [1031.maximum-sum-of-two-non-overlapping-subarrays](./problems/1031.maximum-sum-of-two-non-overlapping-subarrays.md) -- [1104.path-in-zigzag-labelled-binary-tree](./problems/1104.path-in-zigzag-labelled-binary-tree.md) 🆕 -- [1131.maximum-of-absolute-value-expression](./problems/1131.maximum-of-absolute-value-expression.md) 🆕 +- [1104.path-in-zigzag-labelled-binary-tree](./problems/1104.path-in-zigzag-labelled-binary-tree.md) +- [1131.maximum-of-absolute-value-expression](./problems/1131.maximum-of-absolute-value-expression.md) - [1186.maximum-subarray-sum-with-one-deletion](./problems/1186.maximum-subarray-sum-with-one-deletion.md) 🆕 -- [1218.longest-arithmetic-subsequence-of-given-difference](./problems/1218.longest-arithmetic-subsequence-of-given-difference.md) 🆕 -- [1227.airplane-seat-assignment-probability](./problems/1227.airplane-seat-assignment-probability.md) 🆕 -- [1261.find-elements-in-a-contaminated-binary-tree](./problems/1261.find-elements-in-a-contaminated-binary-tree.md) 🆕 -- [1262.greatest-sum-divisible-by-three](./problems/1262.greatest-sum-divisible-by-three.md) 🆕 -- [1297.maximum-number-of-occurrences-of-a-substring](./problems/1297.maximum-number-of-occurrences-of-a-substring.md) 🆕 +- [1218.longest-arithmetic-subsequence-of-given-difference](./problems/1218.longest-arithmetic-subsequence-of-given-difference.md) +- [1227.airplane-seat-assignment-probability](./problems/1227.airplane-seat-assignment-probability.md) +- [1261.find-elements-in-a-contaminated-binary-tree](./problems/1261.find-elements-in-a-contaminated-binary-tree.md) +- [1262.greatest-sum-divisible-by-three](./problems/1262.greatest-sum-divisible-by-three.md) +- [1297.maximum-number-of-occurrences-of-a-substring](./problems/1297.maximum-number-of-occurrences-of-a-substring.md) - [1310.xor-queries-of-a-subarray](./problems/1310.xor-queries-of-a-subarray.md) 🆕 - [1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance](./problems/1334.find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance.md) 🆕 +- [1371.find-the-longest-substring-containing-vowels-in-even-counts](./problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md) 🆕 #### 困难难度 @@ -305,12 +307,13 @@ leetcode 题解,记录自己的 leetcode 解题之路。 - [《构造二叉树》专题](https://lucifer.ren/blog/2020/02/08/%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%93%E9%A2%98/) - [《贪婪策略》专题](./thinkings/greedy.md) - [《深度优先遍历》专题](./thinkings/DFS.md) -- [滑动窗口(思路 + 模板)](./thinkings/slide-window.md) 🆕 +- [滑动窗口(思路 + 模板)](./thinkings/slide-window.md) - [位运算](./thinkings/bit.md) 🆕 - [设计题](./thinkings/design.md) 🆕 - [小岛问题](./thinkings/island.md) 🆕 - [最大公约数](./thinkings/GCD.md) 🆕 - [并查集](./thinkings/union-find.md) 🆕 +- [前缀和](./thinkings/prefix.md) 🆕 ### anki 卡片 diff --git a/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md b/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md new file mode 100644 index 000000000..72620d503 --- /dev/null +++ b/problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md @@ -0,0 +1,265 @@ +# 题目地址(1371. 每个元音包含偶数次的最长子字符串) + +https://leetcode-cn.com/problems/find-the-longest-substring-containing-vowels-in-even-counts/ + +## 题目描述 + +``` +给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 'a','e','i','o','u' ,在子字符串中都恰好出现了偶数次。 + +  + +示例 1: + +输入:s = "eleetminicoworoep" +输出:13 +解释:最长子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 个,以及 0 个 a,u 。 +示例 2: + +输入:s = "leetcodeisgreat" +输出:5 +解释:最长子字符串是 "leetc" ,其中包含 2 个 e 。 +示例 3: + +输入:s = "bcbcbc" +输出:6 +解释:这个示例中,字符串 "bcbcbc" 本身就是最长的,因为所有的元音 a,e,i,o,u 都出现了 0 次。 +  + +提示: + +1 <= s.length <= 5 x 10^5 +s 只包含小写英文字母。 + +``` + +## 暴力法 + 剪枝 + +### 思路 + +首先拿到这道题的时候,我想到第一反应是滑动窗口行不行。 但是很快这个想法就被我否定了,因为滑动窗口(这里是可变滑动窗口)我们需要扩张和收缩窗口大小,而这里不那么容易。因为题目要求的是奇偶性,而不是类似“元音出现最多的子串”等。 + +突然一下子没了思路。那就试试暴力法吧。暴力法的思路比较朴素和直观。 那就是`双层循环找到所有子串,然后对于每一个子串,统计元音个数,如果子串的元音个数都是偶数,则更新答案,最后返回最大的满足条件的子串长度即可`。 + +这里我用了一个小的 trick。枚举所有子串的时候,我是从最长的子串开始枚举的,这样我找到一个满足条件的直接返回就行了(early return),不必维护最大值。`这样不仅减少了代码量,还提高了效率。` + +### 代码 + +代码支持:Python3 + +Python3 Code: + +```python + +class Solution: + def findTheLongestSubstring(self, s: str) -> int: + for i in range(len(s), 0, -1): + for j in range(len(s) - i + 1): + sub = s[j:j + i] + has_odd_vowel = False + for vowel in ['a', 'e', 'i', 'o', 'u']: + if sub.count(vowel) % 2 != 0: + has_odd_vowel = True + break + if not has_odd_vowel: return i + return 0 + +``` + +**复杂度分析** + +- 时间复杂度:双层循环找出所有子串的复杂度是$O(n^2)$,统计元音个数复杂度也是$O(n)$,因此这种算法的时间复杂度为$O(n^3)$。 +- 空间复杂度:$O(1)$ + +## 前缀和 + 剪枝 + +### 思路 + +上面思路中`对于每一个子串,统计元音个数`,我们仔细观察的话,会发现有很多重复的统计。那么优化这部分的内容就可以获得更好的效率。 + +对于这种连续的数字问题,这里我们考虑使用[前缀和](https://oi-wiki.org/basic/prefix-sum/)来优化。 + +经过这种空间换时间的策略之后,我们的时间复杂度会降低到$O(n ^ 2)$,但是相应空间复杂度会上升到$O(n)$,这种取舍在很多情况下是值得的。 + +### 代码 + +代码支持:Python3,Java + +Python3 Code: + +```python +class Solution: + i_mapper = { + "a": 0, + "e": 1, + "i": 2, + "o": 3, + "u": 4 + } + def check(self, s, pre, l, r): + for i in range(5): + if s[l] in self.i_mapper and i == self.i_mapper[s[l]]: cnt = 1 + else: cnt = 0 + if (pre[r][i] - pre[l][i] + cnt) % 2 != 0: return False + return True + def findTheLongestSubstring(self, s: str) -> int: + n = len(s) + + pre = [[0] * 5 for _ in range(n)] + + # pre + for i in range(n): + for j in range(5): + if s[i] in self.i_mapper and self.i_mapper[s[i]] == j: + pre[i][j] = pre[i - 1][j] + 1 + else: + pre[i][j] = pre[i - 1][j] + for i in range(n - 1, -1, -1): + for j in range(n - i): + if self.check(s, pre, j, i + j): + return i + 1 + return 0 +``` + +Java Code: + +```java +class Solution { + public int findTheLongestSubstring(String s) { + + int len = s.length(); + + if (len == 0) + return 0; + + int[][] preSum = new int[len][5]; + int start = getIndex(s.charAt(0)); + if (start != -1) + preSum[0][start]++; + + // preSum + for (int i = 1; i < len; i++) { + + int idx = getIndex(s.charAt(i)); + + for (int j = 0; j < 5; j++) { + + if (idx == j) + preSum[i][j] = preSum[i - 1][j] + 1; + else + preSum[i][j] = preSum[i - 1][j]; + } + } + + for (int i = len - 1; i >= 0; i--) { + + for (int j = 0; j < len - i; j++) { + if (checkValid(preSum, s, i, i + j)) + return i + 1 + } + } + return 0 + } + + + public boolean checkValid(int[][] preSum, String s, int left, int right) { + + int idx = getIndex(s.charAt(left)); + + for (int i = 0; i < 5; i++) + if (((preSum[right][i] - preSum[left][i] + (idx == i ? 1 : 0)) & 1) == 1) + return false; + + return true; + } + public int getIndex(char ch) { + + if (ch == 'a') + return 0; + else if (ch == 'e') + return 1; + else if (ch == 'i') + return 2; + else if (ch == 'o') + return 3; + else if (ch == 'u') + return 4; + else + return -1; + } +} +``` + +**复杂度分析** + +- 时间复杂度:$O(n^2)$。 +- 空间复杂度:$O(n)$ + +## 前缀和 + 状态压缩 + +### 思路 + +前面的前缀和思路,我们通过空间(prefix)换取时间的方式降低了时间复杂度。但是时间复杂度仍然是平方,我们是否可以继续优化呢? + +实际上由于我们只关心奇偶性,并不关心每一个元音字母具体出现的次数。因此我们可以使用`是奇数,是偶数`两个状态来表示,由于只有两个状态,我们考虑使用位运算。 + +我们使用 5 位的二进制来表示以 i 结尾的字符串中包含各个元音的奇偶性,其中 0 表示偶数,1 表示奇数,并且最低位表示 a,然后依次是 e,i,o,u。比如 `10110` 则表示的是包含偶数个 a 和 o,奇数个 e,i,u,我们用变量 `cur` 来表示。 + +为什么用 0 表示偶数?1 表示奇数? + +回答这个问题,你需要继续往下看。 + +其实这个解法还用到了一个性质,这个性质是小学数学知识: + +- 如果两个数字奇偶性相同,那么其相减一定是偶数。 +- 如果两个数字奇偶性不同,那么其相减一定是奇数。 + +看到这里,我们再来看上面抛出的问题`为什么用 0 表示偶数?1 表示奇数?`。因为这里我们打算用异或运算,而异或的性质是: + +如果对两个二进制做异或,会对其每一位进行位运算,如果相同则位 0,否则位 1。这和上面的性质非常相似。上面说`奇偶性相同则位偶数,否则为奇数`。因此很自然地`用 0 表示偶数?1 表示奇数`会更加方便。 + +### 代码 + +代码支持:Python3 + +Python3 Code: + +```python + +class Solution: + def findTheLongestSubstring(self, s: str) -> int: + mapper = { + "a": 1, + "e": 2, + "i": 4, + "o": 8, + "u": 16 + } + seen = {0: -1} + res = cur = 0 + + for i in range(len(s)): + if s[i] in mapper: + cur ^= mapper.get(s[i]) + # 全部奇偶性都相同,相减一定都是偶数 + if cur in seen: + res = max(res, i - seen.get(cur)) + else: + seen[cur] = i + return res + +``` + +**复杂度分析** + +- 时间复杂度:$O(n)$。 +- 空间复杂度:$O(n)$ + +## 关键点解析 + +- 前缀和 +- 状态压缩 + +## 相关题目 + +- [掌握前缀表达式真的可以为所欲为!](https://lucifer.ren/blog/2020/01/09/1310.xor-queries-of-a-subarray/) diff --git a/problems/152.maximum-product-subarray.md b/problems/152.maximum-product-subarray.md index f784d48f7..3ed9c07eb 100644 --- a/problems/152.maximum-product-subarray.md +++ b/problems/152.maximum-product-subarray.md @@ -3,29 +3,26 @@ https://leetcode.com/problems/maximum-product-subarray/description/ ## 题目描述 - ``` -Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product. +给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 -Example 1: +  -Input: [2,3,-2,4] -Output: 6 -Explanation: [2,3] has the largest product 6. -Example 2: +示例 1: -Input: [-2,0,-1] -Output: 0 -Explanation: The result cannot be 2, because [-2,-1] is not a subarray. +输入: [2,3,-2,4] +输出: 6 +解释: 子数组 [2,3] 有最大乘积 6。 +示例 2: +输入: [-2,0,-1] +输出: 0 +解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。 ``` ## 思路 -> 这道题目的通过率非常低 - -这道题目要我们求解连续的 n 个数中乘积最大的积是多少。这里提到了连续,笔者首先 -想到的就是滑动窗口,但是这里比较特殊,我们不能仅仅维护一个最大值,因此最小值(比如-20)乘以一个比较小的数(比如-10) +这道题目要我们求解连续的 n 个数中乘积最大的积是多少。这里提到了连续,笔者首先想到的就是滑动窗口,但是这里比较特殊,我们不能仅仅维护一个最大值,因此最小值(比如-20)乘以一个比较小的数(比如-10) 可能就会很大。 因此这种思路并不方便。 首先来暴力求解,我们使用两层循环来枚举所有可能项,这种解法的时间复杂度是O(n^2), 代码如下: @@ -36,7 +33,6 @@ var maxProduct = function(nums) { let temp = null; for (let i = 0; i < nums.length; i++) { temp = nums[i]; - max = Math.max(temp, max); for (let j = i + 1; j < nums.length; j++) { temp *= nums[j]; max = Math.max(temp, max); @@ -47,9 +43,11 @@ var maxProduct = function(nums) { }; ``` -因此我们需要同时记录乘积最大值和乘积最小值,然后比较元素和这两个的乘积,去不断更新最大值。 -![](https://tva1.sinaimg.cn/large/0082zybply1gcatuvun39j30gr08kt9l.jpg) + +前面说了`最小值(比如-20)乘以一个比较小的数(比如-10)可能就会很大` 。因此我们需要同时记录乘积最大值和乘积最小值,然后比较元素和这两个的乘积,去不断更新最大值。当然,我们也可以选择只取当前元素。因此实际上我们的选择有三种,而如何选择就取决于哪个选择带来的价值最大(乘积最大或者最小)。 + +![](https://pic.leetcode-cn.com/7d39989d10d982d44cbd6b6f693cf5171865c0654f7c3754e27ec1afc2c0de5d.jpg) 这种思路的解法由于只需要遍历一次,其时间复杂度是O(n),代码见下方代码区。 @@ -137,3 +135,7 @@ var maxProduct = function(nums) { **复杂度分析** - 时间复杂度:$O(N)$ - 空间复杂度:$O(1)$ + +更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经30K star啦。 + +大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解 diff --git a/problems/343.integer-break.md b/problems/343.integer-break.md new file mode 100644 index 000000000..6f4e73582 --- /dev/null +++ b/problems/343.integer-break.md @@ -0,0 +1,191 @@ +## 题目地址(343. 整数拆分) + +https://leetcode-cn.com/problems/integer-break/ + +## 题目描述 + +给定一个正整数  n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 + +示例 1: + +输入: 2 +输出: 1 +解释: 2 = 1 + 1, 1 × 1 = 1。 +示例  2: + +输入: 10 +输出: 36 +解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 +说明: 你可以假设  n  不小于 2 且不大于 58。 + +## 思路 + +希望通过这篇题解让大家知道“题解区的水有多深”,让大家知道“什么才是好的题解”。 + +我看了很多人的题解直接就是两句话,然后跟上代码: + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [1] * (n + 1) + for i in range(3, n + 1): + for j in range(1, i): + dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) + return dp[n] +``` + +这种题解说实话,只针对那些”自己会, 然后去题解区看看有没有新的更好的解法的人“。但是大多数看题解的人是那种`自己没思路,不会做的人`。那么这种题解就没什么用了。 + +我认为`好的题解应该是新手友好的,并且能够将解题人思路完整展现的题解`。比如看到这个题目,我首先想到了什么(对错没有关系),然后头脑中经过怎么样的筛选将算法筛选到具体某一个或某几个。我的最终算法是如何想到的,有没有一些先行知识。 + +当然我也承认自己有很多题解也是直接给的答案,这对很多人来说用处不大,甚至有可能有反作用,给他们一种”我已经会了“的假象。实际上他们根本不懂解题人本身原本的想法, 也许是写题解的人觉得”这很自然“,也可能”只是为了秀技“。 + +Ok,下面来讲下`我是如何解这道题的`。 + +### 抽象 + +首先看到这道题,自然而然地先对问题进行抽象,这种抽象能力是必须的。LeetCode 实际上有很多这种穿着华丽外表的题,当你把这个衣服扒开的时候,会发现都是差不多的,甚至两个是一样的,这样的例子实际上有很多。 就本题来说,就有一个剑指 Offer 的原题[《剪绳子》](https://leetcode-cn.com/problems/jian-sheng-zi-lcof/)和其本质一样,只是换了描述方式。类似的有力扣 137 和 645 等等,大家可以自己去归纳总结。 + +> 137 和 645 我贴个之前写的题解 https://leetcode-cn.com/problems/single-number/solution/zhi-chu-xian-yi-ci-de-shu-xi-lie-wei-yun-suan-by-3/ + +**培养自己抽象问题的能力,不管是在算法上还是工程上。** 务必记住这句话! + +数学是一门非常抽象的学科,同时也很方便我们抽象问题。为了显得我的题解比较高级,引入一些你们看不懂的数学符号也是很有必要的(开玩笑,没有什么高级数学符号啦)。 + +> 实际上这道题可以用纯数学角度来解,但是我相信大多数人并不想看。即使你看了,大多人的感受也是“好 nb,然而并没有什么用”。 + +这道题抽象一下就是: + +令: +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3kolxoyj305o03cwef.jpg) +(图 1) +求: +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3jy6mxkj305o0360sp.jpg) +(图 2) + +## 第一直觉 + +经过上面的抽象,我的第一直觉这可能是一个数学题,我回想了下数学知识,然后用数学法 AC 了。 数学就是这么简单平凡且枯燥。 + +然而如果没有数学的加持的情况下,我继续思考怎么做。我想是否可以枚举所有的情况(如图 1),然后对其求最大值(如图 2)。 + +问题转化为如何枚举所有的情况。经过了几秒钟的思考,我发现这是一个很明显的递归问题。 具体思考过程如下: + +- 我们将原问题抽象为 f(n) +- 那么 f(n) 等价于 max(1 \* fn(n - 1), 2 \* f(n - 2), ..., (n - 1) \* f(1))。 + +用数学公式表示就是: + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3swzc9ej30co03yaa4.jpg) +(图 3) + +截止目前,是一点点数学 + 一点点递归,我们继续往下看。现在问题是不是就很简单啦?直接翻译图三为代码即可,我们来看下这个时候的代码: + +```python +class Solution: + def integerBreak(self, n: int) -> int: + if n == 2: return 1 + res = 0 + for i in range(1, n): + res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) + return res +``` + +毫无疑问,超时了。原因很简单,就是算法中包含了太多的重复计算。如果经常看我的题解的话,这句话应该不陌生。我随便截一个我之前讲过这个知识点的图。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu3zfz89jj313p0u0wnj.jpg) +(图 4) + +> 原文链接:https://github.com/azl397985856/leetcode/blob/master/thinkings/dynamic-programming.md + +大家可以尝试自己画图理解一下。 + +> 看到这里,有没有种殊途同归的感觉呢? + +## 考虑优化 + +如上,我们可以考虑使用记忆化递归的方式来解决。只是用一个 hashtable 存储计算过的值即可。 + +```python +class Solution: + @lru_cache() + def integerBreak(self, n: int) -> int: + if n == 2: return 1 + res = 0 + for i in range(1, n): + res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) + return res +``` + +为了简单起见(偷懒起见),我直接用了 lru_cache 注解, 上面的代码是可以 AC 的。 + +## 动态规划 + +看到这里的同学应该发现了,这个套路是不是很熟悉?下一步就是将其改造成动态规划了。 + +如图 4,我们的思考方式是从顶向下,这符合人们思考问题的方式。将其改造成如下图的自底向上方式就是动态规划。 + +![](https://tva1.sinaimg.cn/large/007S8ZIlly1geu4a1grbvj31eq0r0wj8.jpg) +(图 5) + +现在再来看下文章开头的代码: + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [1] * (n + 1) + for i in range(3, n + 1): + for j in range(1, i): + dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) + return dp[n] +``` + +dp table 存储的是图 3 中 f(n)的值。一个自然的想法是令 dp[i] 等价于 f(i)。而由于上面分析了原问题等价于 f(n),那么很自然的原问题也等价于 dp[n]。 + +而 dp[i]等价于 f(i),那么上面针对 f(i) 写的递归公式对 dp[i] 也是适用的,我们拿来试试。 + +``` +// 关键语句 +res = max(res, max(i * self.integerBreak(n - i),i * (n - i))) +``` + +翻译过来就是: + +``` +dp[i] = max(dp[i], max(i * dp(n - i),i * (n - i))) +``` + +而这里的 n 是什么呢?我们说了`dp是自底向下的思考方式`,那么在达到 n 之前是看不到整体的`n` 的。因此这里的 n 实际上是 1,2,3,4... n。 + +自然地,我们用一层循环来生成上面一系列的 n 值。接着我们还要生成一系列的 i 值,注意到 n - i 是要大于 0 的,因此 i 只需要循环到 n - 1 即可。 + +思考到这里,我相信上面的代码真的是`不难得出`了。 + +## 关键点 + +- 数学抽象 +- 递归分析 +- 记忆化递归 +- 动态规划 + +## 代码 + +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [1] * (n + 1) + for i in range(3, n + 1): + for j in range(1, i): + dp[i] = max(j * dp[i - j], j * (i - j), dp[i]) + return dp[n] +``` + +## 总结 + +培养自己的解题思维很重要, 不要直接看别人的答案。而是要将别人的东西变成自己的, 而要做到这一点,你就要知道“他们是怎么想到的”,“想到这点是不是有什么前置知识”,“类似题目有哪些”。 + +最优解通常不是一下子就想到了,这需要你在不那么优的解上摔了很多次跟头之后才能记住的。因此在你没有掌握之前,不要直接去看最优解。 在你掌握了之后,我不仅鼓励你去写最优解,还鼓励去一题多解,从多个解决思考问题。 到了那个时候, 萌新也会惊讶地呼喊“哇塞, 这题还可以这么解啊?”。 你也会低调地发出“害,解题就是这么简单平凡且枯燥。”的声音。 + +## 扩展 + +正如我开头所说,这种套路实在是太常见了。希望大家能够识别这种问题的本质,彻底掌握这种套路。另外我对这个套路也在我的新书《LeetCode 题解》中做了介绍,本书目前刚完成草稿的编写,如果你想要第一时间获取到我们的题解新书,那么请发送邮件到 `azl397985856@gmail.com`,标题著明“书籍《LeetCode 题解》预定”字样。。 diff --git a/problems/560.subarray-sum-equals-k.en.md b/problems/560.subarray-sum-equals-k.en.md new file mode 100644 index 000000000..c36884864 --- /dev/null +++ b/problems/560.subarray-sum-equals-k.en.md @@ -0,0 +1,146 @@ +## Problem + +https://leetcode.com/problems/subarray-sum-equals-k/description/ + +## Problem Description + +``` +Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k. + +Example 1: +Input:nums = [1,1,1], k = 2 +Output: 2 +Note: +The length of the array is in range [1, 20,000]. +The range of numbers in the array is [-1000, 1000] and the range of the integer k is [-1e7, 1e7]. +``` + +## Solution + +The simplest method is `Brute-force`. Consider every possible subarray, find the sum of the elements of each of those subarrays and check for the equality of the sum with `k`. Whenever the sum equals `k`, we increment the `count`. Time Complexity is O(n^2). Implementation is as followed. + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + for i in range(n): + for j in range(i, n): + if (sum(nums[i:j + 1]) == k): cnt += 1 + return cnt +``` + +If we implement the `sum()` method on our own, we get the time of complexity O(n^3). + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + for i in range(n): + for j in range(i, n): + sum = 0 + for x in range(i, j + 1): + sum += nums[x] + if (sum == k): cnt += 1 + return cnt +``` + +At first glance I think "maybe it can be solved by using the sliding window technique". However, I give that thought up when I find out that the given array may contain negative numbers, which makes it more complicated to expand or narrow the range of the sliding window. Then I think about using a prefix sum array, with which we can obtain the sum of the elements between every two indices by subtracting the prefix sum corresponding to the two indices. It sounds feasible, so I implement it as followed. + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + cnt, n = 0, len(nums) + pre = [0] * (n + 1) + for i in range(1, n + 1): + pre[i] = pre[i - 1] + nums[i - 1] + for i in range(1, n + 1): + for j in range(i, n + 1): + if (pre[j] - pre[i - 1] == k): cnt += 1 + return cnt +``` + +Actually, there is a more clever way to do this. Instead of using a prefix sum array, we use a hashmap to reduce the time complexity to O(n). + +Algorithm: + +- We make use of a hashmap to store the cumulative sum `acc` and the number of times the same sum occurs. We use `acc` as the `key` of the hashmap and the number of times the same `acc` occurs as the `value`. + +- We traverse over the given array and keep on finding the cumulative sum `acc`. Every time we encounter a new `acc` we add a new entry to the hashmap. If the same `acc` occurs, we increment the count corresponding to that `acc` in the hashmap. If `acc` equals `k`, obviously `count` should be incremented. If `acc - k` got, we should increment `account` by `hashmap[acc - k]`. + +- The idea behind this is that if the cumulative sum upto two indices is the same, the sum of the elements between those two indices is zero. So if the cumulative sum upto two indices is at a different of `k`, the sum of the elements between those indices is `k`. As `hashmap[acc - k]` keeps track of the number of times a subarray with sum `acc - k` has occured upto the current index, by doing a simple substraction `acc - (acc - k)` we can see that `hashmap[acc - k]` actually also determines the number of times a subarray with sum `k` has occured upto the current index. So we increment the `count` by `hashmap[acc - k]`. + +Here is a graph demonstrating this algorithm in the case of `nums = [1,2,3,3,0,3,4,2], k = 6`. + +![560.subarray-sum-equals-k](../assets/problems/560.subarray-sum-equals-k.jpg) + +When we are at `nums[3]`, the hashmap is as the picture shows, and `count` is 2 by this time. `[1, 2, 3]` accounts for one of the count, and `[3, 3]` accounts for another. + +The subarray `[3, 3]` is obtained from `hashmap[acc - k]`, which is `hashmap[9 - 6]`. + +## Key Points + +- Prefix sum array +- Make use of a hashmap to track cumulative sum and avoid repetitive calculation. + +## Code (`JavaScript/Python`) + +*JavaScript Code* +```js +/* + * @lc app=leetcode id=560 lang=javascript + * + * [560] Subarray Sum Equals K + */ +/** + * @param {number[]} nums + * @param {number} k + * @return {number} + */ +var subarraySum = function (nums, k) { + const hashmap = {}; + let acc = 0; + let count = 0; + + for (let i = 0; i < nums.length; i++) { + acc += nums[i]; + + if (acc === k) count++; + + if (hashmap[acc - k] !== void 0) { + count += hashmap[acc - k]; + } + + if (hashmap[acc] === void 0) { + hashmap[acc] = 1; + } else { + hashmap[acc] += 1; + } + } + + return count; +}; +``` + +*Python Cose* + +```py +class Solution: + def subarraySum(self, nums: List[int], k: int) -> int: + d = {} + acc = count = 0 + for num in nums: + acc += num + if acc == k: + count += 1 + if acc - k in d: + count += d[acc-k] + if acc in d: + d[acc] += 1 + else: + d[acc] = 1 + return count +``` + +## Extension + +There is a similar but a bit more complicated problem. Link to the problem: [437.path-sum-iii](https://github.com/azl397985856/leetcode/blob/master/problems/437.path-sum-iii.md)(Chinese). diff --git a/thinkings/prefix.md b/thinkings/prefix.md new file mode 100644 index 000000000..6adb11fbc --- /dev/null +++ b/thinkings/prefix.md @@ -0,0 +1,4 @@ +## 题目列表 + +- [掌握前缀表达式真的可以为所欲为!](https://lucifer.ren/blog/2020/01/09/1310.xor-queries-of-a-subarray/) +- [1371.find-the-longest-substring-containing-vowels-in-even-counts](../problems/1371.find-the-longest-substring-containing-vowels-in-even-counts.md) 🆕 diff --git a/thinkings/slide-window.en.md b/thinkings/slide-window.en.md index 079076cbc..b18a7b93f 100644 --- a/thinkings/slide-window.en.md +++ b/thinkings/slide-window.en.md @@ -84,4 +84,4 @@ Some problems here are intuitive that you know the sliding window technique woul ## Further Readings -- [LeetCode Sliding Window Series Discussion](https://leetcode.com/problems/binary-subarrays-with-sum/discuss/186683/) +- [LeetCode Sliding Window Series Discussion](https://leetcode.com/problems/binary-subarrays-with-sum/discuss/186683/)(English)