From 39b544b3712d94a0ab0fd70310c3996dd7ee03d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B8=96=E8=B6=85?= Date: Tue, 28 Jun 2022 10:28:33 +0800 Subject: [PATCH 1/7] =?UTF-8?q?Update=200004.=20=E5=AF=BB=E6=89=BE?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E6=AD=A3=E5=BA=8F=E6=95=B0=E7=BB=84=E7=9A=84?= =?UTF-8?q?=E4=B8=AD=E4=BD=8D=E6=95=B0.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...04\344\270\255\344\275\215\346\225\260.md" | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" index 4aa85a0e..bfa10515 100644 --- "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" +++ "b/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" @@ -5,13 +5,35 @@ ## 题目大意 -给定两个正序(从小到大排序)数组 nums1、nums2。要求找出并返回这两个正序数组的中位数。 +**描述**:给定两个正序(从小到大排序)数组 `nums1`、`nums2`。 + +**要求**:找出并返回这两个正序数组的中位数。 + +**说明**: + +- 算法的时间复杂度应该为 $O(log (m+n))$ 。 +- $nums1.length == m$。 +- $nums2.length == n$。 +- $0 \le m \le 1000$。 +- $0 \le n \le 1000$。 +- $1 \le m + n \le 2000$。 +- $-10^6 \le nums1[i], nums2[i] \le 10^6$。 + +**示例**: + +```Python +输入 nums1 = [1,2], nums2 = [3,4] +输出 2.50000 +解释 合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5 +``` ## 解题思路 +### 思路 1:二分查找 + 单个有序数组的中位数是中间元素位置的元素。如果中间元素位置有两个元素,则为两个元素的平均数。如果是两个有序数组,则可以使用归并排序的方式将两个数组拼接为一个大的有序数组。大的有序数组中间位置的元素,即为中位数。 -当然不合并的话,我们只需找到中位数的位置即可。我们用 n1、n2 来表示数组 nums1、nums2 的长度,则合并后的大的有序数组长度为 (n1 + n2)。 +当然不合并的话,我们只需找到中位数的位置即可。我们用 $n1$、$n2$ 来表示数组 $nums1$、$nums2$ 的长度,则合并后的大的有序数组长度为 $(n1 + n2)$。 同时可以发现:**中位数把数组分割成了左右两部分,并且左右两部分元素个数相等。** @@ -22,15 +44,15 @@ 我们用 $k$ 来表示 $\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 。现在的问题就变为了:**如何在两个有序数组中找到前 k 个元素?** -如果我们从 nums1 数组中取出 m1 个元素,那么从 nums2 就需要取出 m2 = k - m1 个元素。 +如果我们从 $nums1$ 数组中取出 $m1$ 个元素,那么从 $nums2$ 就需要取出 $m2 = k - m1$ 个元素。 -问题就可以进一步转换为:**从 nums1 数组中取出 m1 个元素,那么从 nums2 就需要取出 k - m1 个元素,使得 nums1 第 m1 个元素或 nums2 第 k - m1 个元素为中位线位置**。 +问题就可以进一步转换为:**从 $nums1$ 数组中取出 $m1$ 个元素,那么从 $nums2$ 就需要取出 $k - m1$ 个元素,使得 $nums1$ 第 $m1$ 个元素或 $nums2$ 第 $k - m1$ 个元素为中位线位置**。 -可以通过「二分查找」来找到合适的 m1 位置。 +可以通过「二分查找」来找到合适的 $m1$ 位置。 -让 left 指向 $nums1$ 的头部位置 0,right 指向 $nums1$ 的尾部位置 n1。 +让 $left$ 指向 $nums1$ 的头部位置 $0$,$right$ 指向 $nums1$ 的尾部位置 $n1$。 -每次取中间位置 $mid$,判断 $nums1$ 第 $mid+1$ 个元素和 $nums2$ 第 $k - (mid+1)$个元素之间的关系,即 $nums1[mid]$ 和 $nums2[k- mid - 1]$ 的关系。 +每次取中间位置 $mid$,判断 $nums1$ 第 $mid$ 位置上元素和 $nums2$ 第 $k - (mid+1)$ 位置上元素之间的关系,即 $nums1[mid]$ 和 $nums2[k- mid - 1]$ 的关系。 如果 $nums1[mid] < nums2[k- mid - 1]$,则 $nums1$ 中比 $nums1[mid]$ 小的元素有 $mid$ 个,$nums2$ 数组中即便是前 $k- mid - 2$ 个元素都比 $nums1[mid]$ 小,也最多有 $k- mid - 2$ 个元素比 $nums1[mid]$ 小。所以比 $nums1[mid]$ 小的元素最多有 $mid + (k - mid - 2) = k - 2$ 个。所以 $nums1$ 的前 $mid$ 个元素都不可能是第 $k$ 个元素。可以全部排除。 @@ -43,7 +65,7 @@ 找到 m1 的位置之后,还要根据两个数组长度和 $(n1 + n2)$ 的奇偶性,以及边界条件来计算对应的中位数。 -## 代码 +### 思路 1:二分查找代码 ```Python class Solution: @@ -58,7 +80,7 @@ class Solution: right = n1 while left < right: mid = left + (right - left) // 2 - if nums1[mid] < nums2[k-1-mid]: + if nums1[mid] < nums2[k - 1 - mid]: left = mid + 1 else: right = mid From 5b269bba5bd4fa6fa3268868c8da4e2072f19adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B8=96=E8=B6=85?= Date: Thu, 30 Jun 2022 17:27:47 +0800 Subject: [PATCH 2/7] =?UTF-8?q?Update=200004.=20=E5=AF=BB=E6=89=BE?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E6=AD=A3=E5=BA=8F=E6=95=B0=E7=BB=84=E7=9A=84?= =?UTF-8?q?=E4=B8=AD=E4=BD=8D=E6=95=B0.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...04\344\270\255\344\275\215\346\225\260.md" | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" index bfa10515..6af94a03 100644 --- "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" +++ "b/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" @@ -31,39 +31,50 @@ ### 思路 1:二分查找 -单个有序数组的中位数是中间元素位置的元素。如果中间元素位置有两个元素,则为两个元素的平均数。如果是两个有序数组,则可以使用归并排序的方式将两个数组拼接为一个大的有序数组。大的有序数组中间位置的元素,即为中位数。 +单个有序数组的中位数是中间元素位置的元素。如果中间元素位置有两个元素,则为两个元素的平均数。如果是两个有序数组,则可以使用归并排序的方式将两个数组拼接为一个大的有序数组。合并后有序数组中间位置的元素,即为中位数。 当然不合并的话,我们只需找到中位数的位置即可。我们用 $n1$、$n2$ 来表示数组 $nums1$、$nums2$ 的长度,则合并后的大的有序数组长度为 $(n1 + n2)$。 -同时可以发现:**中位数把数组分割成了左右两部分,并且左右两部分元素个数相等。** +我们可以发现:**中位数把数组分割成了左右两部分,并且左右两部分元素个数相等。** - 如果 $(n1 + n2)$ 是奇数时,中位数是大的有序数组中第 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 的元素,单侧元素个数为 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 个(包含中位数)。 - 如果 $(n1 + n2)$ 是偶数时,中位数是第 $\lfloor \frac{(n1 + n2)}{2} \rfloor$ 的元素和第 $\lfloor \frac{(n1 + n2)}{2} \rfloor + 1$ 的元素的平均值,单侧元素个数为 $\lfloor \frac{(n1 + n2)}{2} \rfloor$ 个。 因为是向下取整,上面两种情况综合可以写为:单侧元素个数为:$\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 个。 -我们用 $k$ 来表示 $\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 。现在的问题就变为了:**如何在两个有序数组中找到前 k 个元素?** +我们用 $k$ 来表示 $\lfloor \frac{(n1 + n2 + 1)}{2} \rfloor$ 。现在的问题就变为了:**如何在两个有序数组中找到前 k 小的元素位置?** -如果我们从 $nums1$ 数组中取出 $m1$ 个元素,那么从 $nums2$ 就需要取出 $m2 = k - m1$ 个元素。 +如果我们从 $nums1$ 数组中取出前 $m1(m1 \le k)$ 个元素,那么从 $nums2$ 就需要取出前 $m2 = k - m1$ 个元素。 -问题就可以进一步转换为:**从 $nums1$ 数组中取出 $m1$ 个元素,那么从 $nums2$ 就需要取出 $k - m1$ 个元素,使得 $nums1$ 第 $m1$ 个元素或 $nums2$ 第 $k - m1$ 个元素为中位线位置**。 +并且如果我们在 $nums1$ 数组中找到了合适的 $m1$ 位置,则 $m2$ 的位置也就确定了。 -可以通过「二分查找」来找到合适的 $m1$ 位置。 +问题就可以进一步转换为:**如何从 $nums1$ 数组中取出前 $m1$ 个元素,使得 $nums1$ 第 $m1$ 个元素或者 $nums2$ 第 $m2 = k - m1$ 个元素为中位线位置**。 -让 $left$ 指向 $nums1$ 的头部位置 $0$,$right$ 指向 $nums1$ 的尾部位置 $n1$。 +那么我们来通过「二分查找」在数组 $nums1$ 中找到合适的 $m1$ 位置,具体做法如下: -每次取中间位置 $mid$,判断 $nums1$ 第 $mid$ 位置上元素和 $nums2$ 第 $k - (mid+1)$ 位置上元素之间的关系,即 $nums1[mid]$ 和 $nums2[k- mid - 1]$ 的关系。 +1. 让 $left$ 指向 $nums1$ 的头部位置 $0$,$right$ 指向 $nums1$ 的尾部位置 $n1$。 +2. 每次取中间位置作为 $m1$,则 $m2 = k - m1$。然后判断 $nums1$ 第 $m1$ 位置上元素和 $nums2$ 第 $m2 - 1$ 位置上元素之间的关系,即 $nums1[m1]$ 和 $nums2[m2 - 1]$ 的关系。 + 1. 如果 $nums1[m1] < nums2[m2 - 1]$,则 $nums1$ 的前 $m1$ 个元素都不可能是第 $k$ 个元素,可以全部排除。 + 2. 如果 $nums1[m1] > nums2[m2 - 1]$,则 $nums2$ 的前 $m2 - 1$ 个元素都不可能是第 $k$ 个元素,可以全部排除。 + 3. 如果 $nums1[m1] == nums2[m2 - 1]$,可以按第 $2$ 种情况处理。 +3. 找到 $m1$ 的位置之后,还要根据两个数组长度和 $(n1 + n2)$ 的奇偶性,以及边界条件来计算对应的中位数。 -如果 $nums1[mid] < nums2[k- mid - 1]$,则 $nums1$ 中比 $nums1[mid]$ 小的元素有 $mid$ 个,$nums2$ 数组中即便是前 $k- mid - 2$ 个元素都比 $nums1[mid]$ 小,也最多有 $k- mid - 2$ 个元素比 $nums1[mid]$ 小。所以比 $nums1[mid]$ 小的元素最多有 $mid + (k - mid - 2) = k - 2$ 个。所以 $nums1$ 的前 $mid$ 个元素都不可能是第 $k$ 个元素。可以全部排除。 +--- -简单可以描述为: +上面之所以要判断 $nums1[m1]$ 和 $nums2[m2 - 1]$ 的关系是因为: -- 如果 $nums1[mid] < nums2[k- mid - 1]$,则 $nums1$ 的前 $mid$ 个元素都不可能是第 $k$ 个元素,可以全部排除。 +> 如果 $nums1[m1] < nums2[m2 - 1]$,则说明: +> +> - 最多有 $m1 + m2 - 1 = k - 1$ 个元素比 $nums1[m1]$ 小,所以 $nums1[m1]$ 左侧的 $m1$ 个元素都不可能是第 $k$ 个元素。可以将 $m1$ 左侧的元素全部排除,然后将 $m1$ 进行右移。 -- 如果 $nums1[mid] > nums2[k- mid - 1]$,则 $nums2$ 的前 $mid$ 个元素都不可能是第 $k$ 个元素,可以全部排除。 -- 如果 $nums1[mid] == nums2[k- mid - 1]$,可以按第一种情况处理。 +推理过程: -找到 m1 的位置之后,还要根据两个数组长度和 $(n1 + n2)$ 的奇偶性,以及边界条件来计算对应的中位数。 +如果 $nums1[m1] < nums2[m2 - 1]$,则: + +1. $nums1[m1]$ 左侧比 $nums1[m1]$ 小的一共有 $m1$ 个元素($nums1[0] ... nums1[m1 - 1]$ 共 $m1$ 个)。 +2. $nums2$ 数组最多有 $m2 - 1$ 个元素比 $nums1[m1]$ 小(即便是 $nums2[m2 - 1]$ 左侧所有元素都比 $nums1[m1]$ 小,也只有 $m2 - 1$ 个)。 +3. 综上所述,$nums1$、$nums2$ 数组中最多有 $m1 + m2 - 1 = k - 1$ 个元素比 $nums1[m1]$ 小。 +4. 所以 $nums1[m1]$ 左侧的 $m1$ 个元素($nums1[0] ... nums1[m1 - 1]$)都不可能是第 $k$ 个元素。可以将 $m1$ 左侧的元素全部排除,然后将 $m1$ 进行右移。 ### 思路 1:二分查找代码 @@ -79,16 +90,17 @@ class Solution: left = 0 right = n1 while left < right: - mid = left + (right - left) // 2 - if nums1[mid] < nums2[k - 1 - mid]: - left = mid + 1 + m1 = left + (right - left) // 2 # 在 nums1 中取前 m1 个元素 + m2 = k - m1 # 在 nums2 中取前 m2 个元素 + if nums1[m1] < nums2[m2 - 1]: # 说明 nums1 中所元素不够多, + left = m1 + 1 else: - right = mid + right = m1 m1 = left m2 = k - m1 - - c1 = max(float('-inf') if m1 <= 0 else nums1[m1-1], float('-inf') if m2 <= 0 else nums2[m2 - 1]) + + c1 = max(float('-inf') if m1 <= 0 else nums1[m1 - 1], float('-inf') if m2 <= 0 else nums2[m2 - 1]) if (n1 + n2) % 2 == 1: return c1 From c027fa95c4d2ac12427d126f109761cd4186cdd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B8=96=E8=B6=85?= Date: Thu, 30 Jun 2022 17:54:26 +0800 Subject: [PATCH 3/7] =?UTF-8?q?Update=200004.=20=E5=AF=BB=E6=89=BE?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E6=AD=A3=E5=BA=8F=E6=95=B0=E7=BB=84=E7=9A=84?= =?UTF-8?q?=E4=B8=AD=E4=BD=8D=E6=95=B0.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...204\347\232\204\344\270\255\344\275\215\346\225\260.md" | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" index 6af94a03..e18a1eff 100644 --- "a/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" +++ "b/Solutions/0004. \345\257\273\346\211\276\344\270\244\344\270\252\346\255\243\345\272\217\346\225\260\347\273\204\347\232\204\344\270\255\344\275\215\346\225\260.md" @@ -50,13 +50,12 @@ 问题就可以进一步转换为:**如何从 $nums1$ 数组中取出前 $m1$ 个元素,使得 $nums1$ 第 $m1$ 个元素或者 $nums2$ 第 $m2 = k - m1$ 个元素为中位线位置**。 -那么我们来通过「二分查找」在数组 $nums1$ 中找到合适的 $m1$ 位置,具体做法如下: +我们可以通过「二分查找」的方法,在数组 $nums1$ 中找到合适的 $m1$ 位置,具体做法如下: 1. 让 $left$ 指向 $nums1$ 的头部位置 $0$,$right$ 指向 $nums1$ 的尾部位置 $n1$。 2. 每次取中间位置作为 $m1$,则 $m2 = k - m1$。然后判断 $nums1$ 第 $m1$ 位置上元素和 $nums2$ 第 $m2 - 1$ 位置上元素之间的关系,即 $nums1[m1]$ 和 $nums2[m2 - 1]$ 的关系。 - 1. 如果 $nums1[m1] < nums2[m2 - 1]$,则 $nums1$ 的前 $m1$ 个元素都不可能是第 $k$ 个元素,可以全部排除。 - 2. 如果 $nums1[m1] > nums2[m2 - 1]$,则 $nums2$ 的前 $m2 - 1$ 个元素都不可能是第 $k$ 个元素,可以全部排除。 - 3. 如果 $nums1[m1] == nums2[m2 - 1]$,可以按第 $2$ 种情况处理。 + 1. 如果 $nums1[m1] < nums2[m2 - 1]$,则 $nums1$ 的前 $m1$ 个元素都不可能是第 $k$ 个元素。说明 $m1$ 取值有点小了,应该将 $m1$ 进行右移操作,即 $left = m1 + 1$。 + 2. 如果 $nums1[m1] \ge nums2[m2 - 1]$,则说明 $m1$ 取值可能有点大了,应该将 $m1$ 进行左移。根据二分查找排除法的思路(排除一定不存在的区间,在剩下区间中继续查找),这里应取 $right = m1$。 3. 找到 $m1$ 的位置之后,还要根据两个数组长度和 $(n1 + n2)$ 的奇偶性,以及边界条件来计算对应的中位数。 --- From e0faec53649ae6b24246699fe6fbdd559f8202ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B8=96=E8=B6=85?= Date: Fri, 1 Jul 2022 11:20:25 +0800 Subject: [PATCH 4/7] =?UTF-8?q?Update=200028.=20=E5=AE=9E=E7=8E=B0=20strSt?= =?UTF-8?q?r().md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...028. \345\256\236\347\216\260 strStr().md" | 221 +++++++++++++++++- 1 file changed, 213 insertions(+), 8 deletions(-) diff --git "a/Solutions/0028. \345\256\236\347\216\260 strStr().md" "b/Solutions/0028. \345\256\236\347\216\260 strStr().md" index 5c0b0794..30019635 100644 --- "a/Solutions/0028. \345\256\236\347\216\260 strStr().md" +++ "b/Solutions/0028. \345\256\236\347\216\260 strStr().md" @@ -5,15 +5,28 @@ ## 题目大意 -给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置(从 0 开始)。如果不存在,则返回 -1。 +**描述**:给定两个字符串 `haystack` 和 `needle`。 -## 解题思路 +**要求**:在 `haystack` 字符串中找出 `needle` 字符串出现的第一个位置(从 `0` 开始)。如果不存在,则返回 `-1`。 -字符串匹配的经典题目。常见的字符串匹配算法有:BF(Brute Force)、RK(Robin-Karp)、KMP(Knuth Morris Pratt)、BM(Boyer Moore)、Sunday 算法等。 +**说明**: -## 代码 +- 当 `needle` 为空字符串时,返回 `0`。 +- $1 \le haystack.length, needle.length \le 10^4$。 +- `haystack` 和 `needle` 仅由小写英文字符组成。 -1. BF(Brute Force)暴力检索 +**示例**: + +```Python +输入 haystack = "hello", needle = "ll" +输出 2 +``` + +## 解题思路 + +字符串匹配的经典题目。常见的字符串匹配算法有:BF(Brute Force)算法、RK(Robin-Karp)算法、KMP(Knuth Morris Pratt)算法、BM(Boyer Moore)算法、Horspool 算法、Sunday 算法等。 + +### 思路 1:BF(Brute Force)算法代码 ```Python class Solution: @@ -32,15 +45,207 @@ class Solution: j = 0 if j == len2: - return i-j + return i - j else: return -1 ``` -2. RK(Robin-Karp)哈希检索 + +### 思路 2:RK(Robin-Karp)算法代码 ```Python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + def rabinKarp(T: str, p: str) -> int: + len1, len2 = len(T), len(p) + + hash_p = hash(p) + for i in range(len1 - len2 + 1): + hash_T = hash(T[i: i + len2]) + if hash_p != hash_T: + continue + k = 0 + for j in range(len2): + if T[i + j] != p[j]: + break + k += 1 + if k == len2: + return i + return -1 + return rabinKarp(haystack, needle) +``` +### 思路 3:KMP(Knuth Morris Pratt)算法代码 + +```Python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + # KMP 匹配算法,T 为文本串,p 为模式串 + def kmp(T: str, p: str) -> int: + n, m = len(T), len(p) + + next = generateNext(p) # 生成 next 数组 + + i, j = 0, 0 + while i < n and j < m: + if j == -1 or T[i] == p[j]: + i += 1 + j += 1 + else: + j = next[j] + if j == m: + return i - j + + return -1 + + # 生成 next 数组 + # next[i] 表示坏字符在模式串中最后一次出现的位置 + def generateNext(p: str): + m = len(p) + + next = [-1 for _ in range(m)] # 初始化数组元素全部为 -1 + i, k = 0, -1 + while i < m - 1: # 生成下一个 next 元素 + if k == -1 or p[i] == p[k]: + i += 1 + k += 1 + if p[i] == p[k]: + next[i] = next[k] # 设置 next 元素 + else: + next[i] = k # 退到更短相同前缀 + else: + k = next[k] + return next + + return kmp(haystack, needle) ``` -3. KMP(Knuth Morris Pratt)算法 \ No newline at end of file +### 思路 4:BM(Boyer Moore)算法代码 + +```Python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + def boyerMoore(T: str, p: str) -> int: + n, m = len(T), len(p) + + bc_table = generateBadCharTable(p) # 生成坏字符位置表 + gs_list = generageGoodSuffixList(p) # 生成好后缀规则后移位数表 + + i = 0 + while i <= n - m: + j = m - 1 + while j > -1 and T[i + j] == p[j]: + j -= 1 + if j < 0: + return i + bad_move = j - bc_table.get(T[i + j], -1) + good_move = gs_list[j] + i += max(bad_move, good_move) + return -1 + + # 生成坏字符位置表 + def generateBadCharTable(p: str): + bc_table = dict() + + for i in range(len(p)): + bc_table[p[i]] = i # 坏字符在模式串中最后一次出现的位置 + return bc_table + + # 生成好后缀规则后移位数表 + def generageGoodSuffixList(p: str): + m = len(p) + gs_list = [m for _ in range(m)] + suffix = generageSuffixArray(p) + j = 0 + for i in range(m - 1, -1, -1): + if suffix[i] == i + 1: + while j < m - 1 - i: + if gs_list[j] == m: + gs_list[j] = m - 1 - i + j += 1 + + for i in range(m - 1): + gs_list[m - 1 - suffix[i]] = m - 1 - i + + return gs_list + + def generageSuffixArray(p: str): + m = len(p) + suffix = [m for _ in range(m)] + for i in range(m - 2, -1, -1): + start = i + while start >= 0 and p[start] == p[m - 1 - i + start]: + start -= 1 + suffix[i] = i - start + return suffix + + return boyerMoore(haystack, needle) +``` + +### 思路 5:Horspool 算法代码 + +```Python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + def horspool(T: str, p: str) -> int: + n, m = len(T), len(p) + + bc_table = generateBadCharTable(p) + + i = 0 + while i <= n - m: + j = m - 1 + while j > -1 and T[i + j] == p[j]: + j -= 1 + if j < 0: + return i + i += bc_table.get(T[i + m - 1], m) + return -1 + + # 生成后移位置表 + # bc_table[bad_char] 表示坏字符在模式串中最后一次出现的位置 + def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m - 1): + bc_table[p[i]] = m - i - 1 # 更新坏字符在模式串中最后一次出现的位置 + return bc_table + + return horspool(haystack, needle) +``` + +### 思路 6:Sunday 算法代码 + +```Python +class Solution: + def strStr(self, haystack: str, needle: str) -> int: + # sunday 算法,T 为文本串,p 为模式串 + def sunday(T: str, p: str) -> int: + n, m = len(T), len(p) + if m == 0: + return 0 + + bc_table = generateBadCharTable(p) # 生成后移位数表 + + i = 0 + while i <= n - m: + if T[i: i + m] == p: + return i # 匹配完成,返回模式串 p 在文本串 T 中的位置 + if i + m >= n: + return -1 + i += bc_table.get(T[i + m], m + 1) # 通过后移位数表,向右进行进行快速移动 + return -1 # 匹配失败 + + # 生成后移位数表 + # bc_table[bad_char] 表示遇到坏字符可以向右移动的距离 + def generateBadCharTable(p: str): + m = len(p) + bc_table = dict() + + for i in range(m): + bc_table[p[i]] = m - i # 更新遇到坏字符可向右移动的距离 + return bc_table + + return sunday(haystack, needle) +``` \ No newline at end of file From 1f95a3cd3e62499dfb9b991038778a05b3228252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B8=96=E8=B6=85?= Date: Fri, 1 Jul 2022 11:20:37 +0800 Subject: [PATCH 5/7] Update Tree-SegmentTree-Update-Interval-1.py --- Templates/07.Tree/Tree-SegmentTree-Update-Interval-1.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Templates/07.Tree/Tree-SegmentTree-Update-Interval-1.py b/Templates/07.Tree/Tree-SegmentTree-Update-Interval-1.py index 07a302ce..3175b267 100644 --- a/Templates/07.Tree/Tree-SegmentTree-Update-Interval-1.py +++ b/Templates/07.Tree/Tree-SegmentTree-Update-Interval-1.py @@ -79,6 +79,9 @@ def __update_interval(self, q_left, q_right, val, index): left = self.tree[index].left right = self.tree[index].right + if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 self.tree[index].lazy_tag = val # 将当前节点的延迟标记为区间值 interval_size = (right - left + 1) # 当前节点所在区间大小 @@ -86,8 +89,7 @@ def __update_interval(self, q_left, q_right, val, index): return - if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关 - return + self.__pushdown(index) # 向下更新节点的区间值 From be276dd1d46b8084b0f83e803da2c98919dea2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B8=96=E8=B6=85?= Date: Fri, 1 Jul 2022 12:43:07 +0800 Subject: [PATCH 6/7] =?UTF-8?q?Create=200850.=20=E7=9F=A9=E5=BD=A2?= =?UTF-8?q?=E9=9D=A2=E7=A7=AF=20II.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...345\275\242\351\235\242\347\247\257 II.md" | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 "Solutions/0850. \347\237\251\345\275\242\351\235\242\347\247\257 II.md" diff --git "a/Solutions/0850. \347\237\251\345\275\242\351\235\242\347\247\257 II.md" "b/Solutions/0850. \347\237\251\345\275\242\351\235\242\347\247\257 II.md" new file mode 100644 index 00000000..f6120b15 --- /dev/null +++ "b/Solutions/0850. \347\237\251\345\275\242\351\235\242\347\247\257 II.md" @@ -0,0 +1,160 @@ +## [0850. 矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) + +- 标签:线段树、数组、有序集合、扫描线 +- 难度:困难 + +## 题目大意 + +**描述**:给定一个二维矩形列表 `rectangles`,其中 `rectangle[i] = [x1, y1, x2, y2]` 表示第 `i` 个矩形,`(x1, y1)` 是第 `i` 个矩形左下角的坐标,`(x2, y2)` 是第 `i` 个矩形右上角的坐标。。 + +**要求**:计算 `rectangles` 中所有矩形所覆盖的总面积,并返回总面积。 + +**说明**: + +- 任何被两个或多个矩形覆盖的区域应只计算一次 。 +- 因为答案可能太大,返回 $10^9 + 7$ 的模。 +- $1 \le rectangles.length \le 200$。 +- $rectanges[i].length = 4$。 +- $0 \le x_1, y_1, x_2, y_2 \le 10^9$。 +- 矩形叠加覆盖后的总面积不会超越 $2^63 - 1$,这意味着可以用一个 $64$ 位有符号整数来保存面积结果。 + +**示例**: + +![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/06/06/rectangle_area_ii_pic.png) + +```Python +输入 rectangles = [[0,0,2,2],[1,0,2,3],[1,0,3,1]] +输出 6 +解释 如图所示,三个矩形覆盖了总面积为6的区域。 +从 (1,1) 到 (2,2),绿色矩形和红色矩形重叠。 +从 (1,0) 到 (2,3),三个矩形都重叠。 +``` + +## 解题思路 + +### 思路 1:扫描线 + 动态开点线段树 + + + +### 思路 1:扫描线 + 动态开点线段树代码 + +```Python +# 线段树的节点类 +class SegTreeNode: + def __init__(self, left=-1, right=-1, cnt=0, height=0, leftNode=None, rightNode=None): + self.left = left # 区间左边界 + self.right = right # 区间右边界 + self.mid = left + (right - left) // 2 + self.leftNode = leftNode # 区间左节点 + self.rightNode = rightNode # 区间右节点 + self.cnt = cnt # 节点值(区间值) + self.height = height # 区间问题的延迟更新标记 + + +# 线段树类 +class SegmentTree: + # 初始化线段树接口 + def __init__(self): + self.tree = SegTreeNode(0, int(1e9)) + + # 区间更新接口:将区间为 [q_left, q_right] 上的元素值修改为 val + def update_interval(self, q_left, q_right, val): + self.__update_interval(q_left, q_right, val, self.tree) + + # 区间查询接口:查询区间为 [q_left, q_right] 的区间值 + def query_interval(self, q_left, q_right): + return self.__query_interval(q_left, q_right, self.tree) + + + # 以下为内部实现方法 + + # 区间更新实现方法 + def __update_interval(self, q_left, q_right, val, node): + + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return + + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + node.cnt += val # 当前节点所在区间每个元素值改为 val + self.__pushup(node) + return + + + self.__pushdown(node) + + if q_left <= node.mid: # 在左子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.leftNode) + if q_right > node.mid: # 在右子树中更新区间值 + self.__update_interval(q_left, q_right, val, node.rightNode) + + self.__pushup(node) + + # 区间查询实现方法:在线段树的 [left, right] 区间范围中搜索区间为 [q_left, q_right] 的区间值 + def __query_interval(self, q_left, q_right, node): + if node.right < q_left or node.left > q_right: # 节点所在区间与 [q_left, q_right] 无关 + return 0 + + if node.left >= q_left and node.right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖 + return node.height # 直接返回节点值 + + self.__pushdown(node) + + res_left = 0 # 左子树查询结果 + res_right = 0 # 右子树查询结果 + if q_left <= node.mid: # 在左子树中查询 + res_left = self.__query_interval(q_left, node.mid, node.leftNode) + if q_right > node.mid: # 在右子树中查询 + res_right = self.__query_interval(node.mid + 1, q_right, node.rightNode) + + + return res_left + res_right # 返回左右子树元素值的聚合计算结果 + + # 向上更新实现方法:更新 node 节点区间值 等于 该节点左右子节点元素值的聚合计算结果 + def __pushup(self, node): + if node.cnt > 0: + node.height = node.right - node.left + 1 + else: + if node.leftNode and node.rightNode: + node.height = node.leftNode.height + node.rightNode.height + else: + node.height = 0 + + # 向下更新实现方法:更新 node 节点所在区间的左右子节点的值和懒惰标记 + def __pushdown(self, node): + if node.leftNode is None: + node.leftNode = SegTreeNode(node.left, node.mid) + if node.rightNode is None: + node.rightNode = SegTreeNode(node.mid + 1, node.right) + +class Solution: + def rectangleArea(self, rectangles) -> int: + # lines 存储每个矩阵的上下两条边 + lines = [] + + for rectangle in rectangles: + x1, y1, x2, y2 = rectangle + lines.append([x1, y1 + 1, y2, 1]) + lines.append([x2, y1 + 1, y2, -1]) + + lines.sort(key=lambda line: line[0]) + + # 建立线段树 + self.STree = SegmentTree() + + ans = 0 + mod = 10 ** 9 + 7 + prev_x = lines[0][0] + for i in range(len(lines)): + x, y1, y2, val = lines[i] + height = self.STree.query_interval(0, int(1e9)) + ans += height * (x - prev_x) + ans %= mod + self.STree.update_interval(y1, y2, val) + prev_x = x + + return ans +``` + +## 参考资料 + +- 【文章】[【hdu1542】线段树求矩形面积并 - 拦路雨偏似雪花](https://www.cnblogs.com/KonjakJuruo/p/6024266.html) From 7fe8ff497ecbb07e629e8b1e0e850c2441e189d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B8=96=E8=B6=85?= Date: Fri, 1 Jul 2022 12:43:29 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=A2=98=E8=A7=A3?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Contents/00.Introduction/04.Solutions-List.md | 3 ++- Contents/00.Introduction/05.Categories-List.md | 2 +- Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md | 2 +- README.md | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Contents/00.Introduction/04.Solutions-List.md b/Contents/00.Introduction/04.Solutions-List.md index 5ff9b672..58f47f3f 100644 --- a/Contents/00.Introduction/04.Solutions-List.md +++ b/Contents/00.Introduction/04.Solutions-List.md @@ -1,4 +1,4 @@ -# LeetCode 题解(已完成 715 道) +# LeetCode 题解(已完成 716 道) | 题号 | 标题 | 题解 | 标签 | 难度 | | :------ | :------ | :------ | :------ | :------ | @@ -399,6 +399,7 @@ | 0844 | [比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0844.%20%E6%AF%94%E8%BE%83%E5%90%AB%E9%80%80%E6%A0%BC%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、双指针、字符串、模拟 | 简单 | | 0845 | [数组中的最长山脉](https://leetcode.cn/problems/longest-mountain-in-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0845.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E9%95%BF%E5%B1%B1%E8%84%89.md) | 数组、双指针、动态规划、枚举 | 中等 | | 0846 | [一手顺子](https://leetcode.cn/problems/hand-of-straights/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0846.%20%E4%B8%80%E6%89%8B%E9%A1%BA%E5%AD%90.md) | 贪心、数组、哈希、排序 | 中等 | +| 0850 | [矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0850.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF%20II.md) | 线段树、数组、有序集合、扫描线 | 困难 | | 0851 | [喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0851.%20%E5%96%A7%E9%97%B9%E5%92%8C%E5%AF%8C%E6%9C%89.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 | | 0852 | [山脉数组的峰顶索引](https://leetcode.cn/problems/peak-index-in-a-mountain-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0852.%20%E5%B1%B1%E8%84%89%E6%95%B0%E7%BB%84%E7%9A%84%E5%B3%B0%E9%A1%B6%E7%B4%A2%E5%BC%95.md) | 数组、二分查找 | 简单 | | 0860 | [柠檬水找零](https://leetcode.cn/problems/lemonade-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0860.%20%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.md) | 贪心、数组 | 简单 | diff --git a/Contents/00.Introduction/05.Categories-List.md b/Contents/00.Introduction/05.Categories-List.md index fac87086..73158a42 100644 --- a/Contents/00.Introduction/05.Categories-List.md +++ b/Contents/00.Introduction/05.Categories-List.md @@ -538,7 +538,7 @@ | :------ | :------ | :------ | :------ | :------ | | 0218 | [天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | | 0391 | [完美矩形](https://leetcode.cn/problems/perfect-rectangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0391.%20%E5%AE%8C%E7%BE%8E%E7%9F%A9%E5%BD%A2.md) | 数组、扫描线 | 困难 | -| 0850 | 矩形面积 II | | | | +| 0850 | [矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0850.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF%20II.md) | 线段树、数组、有序集合、扫描线 | 困难 | ### 树状数组题目 diff --git a/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md b/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md index 27f104c5..774fa254 100644 --- a/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md +++ b/Contents/07.Tree/03.Segment-Tree/02.Segment-Tree-List.md @@ -33,5 +33,5 @@ | :------ | :------ | :------ | :------ | :------ | | 0218 | [天际线问题](https://leetcode.cn/problems/the-skyline-problem/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0218.%20%E5%A4%A9%E9%99%85%E7%BA%BF%E9%97%AE%E9%A2%98.md) | 树状数组、线段树、数组、分治、有序集合、扫描线、堆(优先队列) | 困难 | | 0391 | [完美矩形](https://leetcode.cn/problems/perfect-rectangle/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0391.%20%E5%AE%8C%E7%BE%8E%E7%9F%A9%E5%BD%A2.md) | 数组、扫描线 | 困难 | -| 0850 | 矩形面积 II | | | | +| 0850 | [矩形面积 II](https://leetcode.cn/problems/rectangle-area-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0850.%20%E7%9F%A9%E5%BD%A2%E9%9D%A2%E7%A7%AF%20II.md) | 线段树、数组、有序集合、扫描线 | 困难 | diff --git a/README.md b/README.md index ba26c3bd..69414eab 100644 --- a/README.md +++ b/README.md @@ -254,4 +254,4 @@ - [动态规划优化题目](./Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) ## 11. 附加内容 -## [12. LeetCode 题解(已完成 715 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file +## [12. LeetCode 题解(已完成 716 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file