diff --git "a/Index/\345\210\206\346\262\273.md" "b/Index/\345\210\206\346\262\273.md" index 9ed59d63..942af869 100644 --- "a/Index/\345\210\206\346\262\273.md" +++ "b/Index/\345\210\206\346\262\273.md" @@ -1,6 +1,7 @@ | 题目 | 题解 | 难度 | 推荐指数 | | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- | -------- | | [4. 寻找两个正序数组的中位数 ](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/shua-chuan-lc-po-su-jie-fa-fen-zhi-jie-f-wtu2/) | 困难 | 🤩🤩🤩🤩 | +| [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximum-subarray/solutions/2534027/gong-shui-san-xie-cong-on-de-chang-gui-l-22hq/) | 中等 | 🤩🤩🤩🤩🤩 | | [108. 将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | [LeetCode 题解链接](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/solutions/2436639/gong-shui-san-xie-jian-dan-di-gui-fen-zh-nzqx/) | 简单 | 🤩🤩🤩🤩 | | [109. 有序链表转换二叉搜索树](https://leetcode.cn/problems/convert-sorted-list-to-binary-search-tree/) | [LeetCode 题解链接](https://leetcode.cn/problems/convert-sorted-list-to-binary-search-tree/solutions/2436644/gong-shui-san-xie-jian-dan-di-gui-fen-zh-6t1x/) | 中等 | 🤩🤩🤩🤩 | | [654. 最大二叉树](https://leetcode.cn/problems/maximum-binary-tree/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximum-binary-tree/solution/by-ac_oier-s0wc/) | 中等 | 🤩🤩🤩🤩🤩 | diff --git "a/Index/\345\211\215\347\274\200\345\222\214.md" "b/Index/\345\211\215\347\274\200\345\222\214.md" index e8224a9e..7111aedd 100644 --- "a/Index/\345\211\215\347\274\200\345\222\214.md" +++ "b/Index/\345\211\215\347\274\200\345\222\214.md" @@ -1,10 +1,11 @@ | 题目 | 题解 | 难度 | 推荐指数 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ---- | -------- | +| [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximum-subarray/solutions/2534027/gong-shui-san-xie-cong-on-de-chang-gui-l-22hq/) | 中等 | 🤩🤩🤩🤩 | | [85. 最大矩形](https://leetcode.cn/problems/maximal-rectangle/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximal-rectangle/solution/by-ac_oier-k02i/) | 困难 | 🤩🤩🤩 | | [187. 重复的DNA序列](https://leetcode-cn.com/problems/repeated-dna-sequences/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/repeated-dna-sequences/solution/gong-shui-san-xie-yi-ti-shuang-jie-hua-d-30pg/) | 中等 | 🤩🤩🤩🤩 | | [209. 长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) | [LeetCode 题解链接](https://leetcode.cn/problems/minimum-size-subarray-sum/solution/by-ac_oier-c5jm/) | 中等 | 🤩🤩🤩🤩🤩 | | [238. 除自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | [LeetCode 题解链接](https://leetcode.cn/problems/product-of-array-except-self/solution/by-ac_oier-fqp3/) | 中等 | 🤩🤩🤩🤩🤩 | -| [304. 二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/) | 中等 | 🤩🤩🤩🤩🤩 | +| [304. 二维区域和检索 - 矩阵不可变](https://leetcode-cn.com/problems/range-sum-query-2d-immutable/) | [LeetCode 题解链接](https://leetcode.cn/problems/range-sum-query-2d-immutable/solutions/629362/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/) | 中等 | 🤩🤩🤩🤩🤩 | | [303. 区域和检索 - 数组不可变](https://leetcode-cn.com/problems/range-sum-query-immutable/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/range-sum-query-immutable/solution/sha-shi-qian-zhui-he-ya-tu-jie-qian-zhui-0rla/) | 简单 | 🤩🤩🤩🤩🤩 | | [327. 区间和的个数](https://leetcode.cn/problems/count-of-range-sum/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-of-range-sum/solution/by-ac_oier-b36o/) | 困难 | 🤩🤩🤩 | | [363. 矩形区域不超过 K 的最大数值和](https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/solution/gong-shui-san-xie-you-hua-mei-ju-de-ji-b-dh8s/) | 困难 | 🤩🤩🤩 | diff --git "a/Index/\345\217\214\346\214\207\351\222\210.md" "b/Index/\345\217\214\346\214\207\351\222\210.md" index 0c35bbe6..44acc619 100644 --- "a/Index/\345\217\214\346\214\207\351\222\210.md" +++ "b/Index/\345\217\214\346\214\207\351\222\210.md" @@ -74,6 +74,7 @@ | [2000. 反转单词前缀](https://leetcode-cn.com/problems/reverse-prefix-of-word/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/reverse-prefix-of-word/solution/gong-shui-san-xie-jian-dan-shuang-zhi-zh-dp9u/) | 简单 | 🤩🤩🤩🤩 | | [2024. 考试的最大困扰度](https://leetcode-cn.com/problems/maximize-the-confusion-of-an-exam/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/maximize-the-confusion-of-an-exam/solution/by-ac_oier-2rii/) | 中等 | 🤩🤩🤩🤩 | | [2047. 句子中的有效单词数](https://leetcode-cn.com/problems/number-of-valid-words-in-a-sentence/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-valid-words-in-a-sentence/solution/gong-shui-san-xie-jian-dan-zi-fu-chuan-m-5pcz/) | 简单 | 🤩🤩🤩🤩 | +| [2760. 最长奇偶子数组](https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/) | [LeetCode 题解链接](https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/solutions/2528837/gong-shui-san-xie-chou-si-bo-chong-tu-ji-n9r1/) | 简单 | 🤩🤩🤩🤩🤩 | | [面试题 01.05. 一次编辑](https://leetcode.cn/problems/one-away-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/one-away-lcci/solution/by-ac_oier-7ml0/) | 中等 | 🤩🤩🤩🤩 | | [面试题 17.09. 第 k 个数](https://leetcode.cn/problems/get-kth-magic-number-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/get-kth-magic-number-lcci/solution/by-ac_oier-2czm/) | 中等 | 🤩🤩🤩🤩🤩 | | [面试题 17.11. 单词距离](https://leetcode.cn/problems/find-closest-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/find-closest-lcci/solution/by-ac_oier-0hv9/) | 中等 | 🤩🤩🤩🤩 | diff --git "a/Index/\345\223\210\345\270\214\350\241\250.md" "b/Index/\345\223\210\345\270\214\350\241\250.md" index b38e0434..84bf0cca 100644 --- "a/Index/\345\223\210\345\270\214\350\241\250.md" +++ "b/Index/\345\223\210\345\270\214\350\241\250.md" @@ -92,6 +92,7 @@ | [2006. 差的绝对值为 K 的数对数目](https://leetcode-cn.com/problems/count-number-of-pairs-with-absolute-difference-k/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/count-number-of-pairs-with-absolute-difference-k/solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-1jel/) | 简单 | 🤩🤩🤩🤩 | | [2013. 检测正方形](https://leetcode-cn.com/problems/detect-squares/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/detect-squares/solution/gong-shui-san-xie-jian-dan-ha-xi-biao-yu-748e/) | 中等 | 🤩🤩🤩🤩🤩 | | [2034. 股票价格波动](https://leetcode-cn.com/problems/stock-price-fluctuation/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/stock-price-fluctuation/solution/gong-shui-san-xie-shu-ju-jie-gou-mo-ni-t-u6f4/) | 中等 | 🤩🤩🤩🤩 | +| [2342. 数位和相等数对的最大和](https://leetcode.cn/problems/max-sum-of-a-pair-with-equal-sum-of-digits/) | [LeetCode 题解链接](https://leetcode.cn/problems/max-sum-of-a-pair-with-equal-sum-of-digits/solutions/2531511/gong-shui-san-xie-yong-bian-li-guo-cheng-kt3f/) | 中等 | 🤩🤩🤩🤩 | | [面试题 10.02. 变位词组](https://leetcode-cn.com/problems/group-anagrams-lcci/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/group-anagrams-lcci/solution/gong-shui-san-xie-tong-ji-bian-wei-ci-de-0iqe/) | 中等 | 🤩🤩🤩🤩 | | [面试题 17.10. 主要元素](https://leetcode-cn.com/problems/find-majority-element-lcci/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/find-majority-element-lcci/solution/gong-shui-san-xie-yi-ti-shuang-jie-ha-xi-zkht/) | 简单 | 🤩🤩🤩🤩 | | [剑指 Offer 35. 复杂链表的复制](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/) | [LeetCode 题解链接](https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/by-ac_oier-6atv/) | 中等 | 🤩🤩🤩 | diff --git "a/Index/\346\225\260\345\255\246.md" "b/Index/\346\225\260\345\255\246.md" index 72401182..0113f868 100644 --- "a/Index/\346\225\260\345\255\246.md" +++ "b/Index/\346\225\260\345\255\246.md" @@ -84,6 +84,7 @@ | [1775. 通过最少操作次数使数组的和相等](https://leetcode.cn/problems/equal-sum-arrays-with-minimum-number-of-operations/) | [LeetCode 题解链接](https://acoier.com/2022/12/09/1775.%20%E9%80%9A%E8%BF%87%E6%9C%80%E5%B0%91%E6%93%8D%E4%BD%9C%E6%AC%A1%E6%95%B0%E4%BD%BF%E6%95%B0%E7%BB%84%E7%9A%84%E5%92%8C%E7%9B%B8%E7%AD%89%EF%BC%88%E4%B8%AD%E7%AD%89%EF%BC%89/) | 中等 | 🤩🤩🤩🤩 | | [1780. 判断一个数字是否可以表示成三的幂的和](https://leetcode.cn/problems/check-if-number-is-a-sum-of-powers-of-three/) | [LeetCode 题解链接](https://acoier.com/2022/12/12/1780.%20%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E6%95%B0%E5%AD%97%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E8%A1%A8%E7%A4%BA%E6%88%90%E4%B8%89%E7%9A%84%E5%B9%82%E7%9A%84%E5%92%8C%EF%BC%88%E4%B8%AD%E7%AD%89%EF%BC%89/) | 中等 | 🤩🤩🤩🤩🤩 | | [1802. 有界数组中指定下标处的最大值](https://leetcode.cn/problems/maximum-value-at-a-given-index-in-a-bounded-array/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximum-value-at-a-given-index-in-a-bounded-array/solutions/2363016/gong-shui-san-xie-chang-gui-zong-he-ti-b-ohvx/) | 中等 | 🤩🤩🤩🤩 | +| [2656. K 个元素的最大和](https://leetcode.cn/problems/maximum-sum-with-exactly-k-elements/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximum-sum-with-exactly-k-elements/solutions/2527384/gong-shui-san-xie-deng-chai-shu-lie-qiu-b2g88/) | 简单 | 🤩🤩🤩 | | [剑指 Offer 44. 数字序列中某一位的数字](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/) | [LeetCode 题解链接](https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/by-ac_oier-wgr8/) | 中等 | 🤩🤩🤩🤩 | | [面试题 10.02. 变位词组](https://leetcode-cn.com/problems/group-anagrams-lcci/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/group-anagrams-lcci/solution/gong-shui-san-xie-tong-ji-bian-wei-ci-de-0iqe/) | 中等 | 🤩🤩🤩🤩 | | [面试题 17.19. 消失的两个数字](https://leetcode.cn/problems/missing-two-lcci/) | [LeetCode 题解链接](https://leetcode.cn/problems/missing-two-lcci/solution/by-ac_oier-pgeh/) | 困难 | 🤩🤩🤩🤩 | diff --git "a/Index/\346\240\221\347\212\266\346\225\260\347\273\204.md" "b/Index/\346\240\221\347\212\266\346\225\260\347\273\204.md" index 932e2049..143463bf 100644 --- "a/Index/\346\240\221\347\212\266\346\225\260\347\273\204.md" +++ "b/Index/\346\240\221\347\212\266\346\225\260\347\273\204.md" @@ -11,4 +11,5 @@ | [1310. 子数组异或查询](https://leetcode-cn.com/problems/xor-queries-of-a-subarray/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/xor-queries-of-a-subarray/solution/gong-shui-san-xie-yi-ti-shuang-jie-shu-z-rcgu/) | 中等 | 🤩🤩🤩🤩 | | [1395. 统计作战单位数](https://leetcode.cn/problems/count-number-of-teams/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-number-of-teams/solution/by-ac_oier-qm3a/) | 中等 | 🤩🤩🤩🤩🤩 | | [1893. 检查是否区域内所有整数都被覆盖](https://leetcode-cn.com/problems/check-if-all-the-integers-in-a-range-are-covered/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/check-if-all-the-integers-in-a-range-are-covered/solution/gong-shui-san-xie-yi-ti-shuang-jie-mo-ni-j83x/) | 简单 | 🤩🤩🤩🤩 | +| [2736. 最大和查询](https://leetcode.cn/problems/maximum-sum-queries/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximum-sum-queries/solutions/2530269/gong-shui-san-xie-cong-yi-wei-xian-zhi-d-ww8r/) | 困难 | 🤩🤩🤩🤩🤩 | diff --git "a/Index/\346\250\241\346\213\237.md" "b/Index/\346\250\241\346\213\237.md" index 37b13ed1..848b6166 100644 --- "a/Index/\346\250\241\346\213\237.md" +++ "b/Index/\346\250\241\346\213\237.md" @@ -246,6 +246,7 @@ | [2069. 模拟行走机器人 II](https://leetcode-cn.com/problems/walking-robot-simulation-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/walking-robot-simulation-ii/solution/by-ac_oier-6zib/) | 中等 | 🤩🤩🤩🤩 | | [2103. 环和杆](https://leetcode.cn/problems/rings-and-rods/) | [LeetCode 题解链接](https://leetcode.cn/problems/rings-and-rods/solutions/2509056/gong-shui-san-xie-liang-ge-jiao-du-jin-x-r1v1/) | 简单 | 🤩🤩🤩🤩 | | [2335. 装满杯子需要的最短总时长](https://leetcode.cn/problems/minimum-amount-of-time-to-fill-cups/) | [LeetCode 题解链接](https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247495870&idx=1&sn=a15b87852faaa33fc9d976b575ef1099) | 简单 | 🤩🤩🤩🤩🤩 | +| [2342. 数位和相等数对的最大和](https://leetcode.cn/problems/max-sum-of-a-pair-with-equal-sum-of-digits/) | [LeetCode 题解链接](https://leetcode.cn/problems/max-sum-of-a-pair-with-equal-sum-of-digits/solutions/2531511/gong-shui-san-xie-yong-bian-li-guo-cheng-kt3f/) | 中等 | 🤩🤩🤩🤩 | | [2520. 统计能整除数字的位数](https://leetcode.cn/problems/count-the-digits-that-divide-a-number/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-the-digits-that-divide-a-number/solutions/2498966/gong-shui-san-xie-jian-dan-mo-ni-ti-shi-0ad2c/) | 简单 | 🤩🤩🤩🤩🤩 | | [2586. 统计范围内的元音字符串数](https://leetcode.cn/problems/count-the-number-of-vowel-strings-in-range/) | [LeetCode 题解链接](https://leetcode.cn/problems/count-the-number-of-vowel-strings-in-range/solutions/2515898/gong-shui-san-xie-jian-dan-zi-fu-chuan-m-di1t/) | 简单 | 🤩🤩🤩 | | [2609. 最长平衡子字符串](https://leetcode.cn/problems/find-the-longest-balanced-substring-of-a-binary-string/) | [LeetCode 题解链接](https://leetcode.cn/problems/find-the-longest-balanced-substring-of-a-binary-string/solutions/2517437/gong-shui-san-xie-on-shi-jian-o1-kong-ji-i8e7/) | 简单 | 🤩🤩🤩 | diff --git "a/Index/\346\273\221\345\212\250\347\252\227\345\217\243.md" "b/Index/\346\273\221\345\212\250\347\252\227\345\217\243.md" index 6a9eba54..a76cba70 100644 --- "a/Index/\346\273\221\345\212\250\347\252\227\345\217\243.md" +++ "b/Index/\346\273\221\345\212\250\347\252\227\345\217\243.md" @@ -24,5 +24,6 @@ | [1838. 最高频元素的频数](https://leetcode-cn.com/problems/frequency-of-the-most-frequent-element/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/frequency-of-the-most-frequent-element/solution/gong-shui-san-xie-cong-mei-ju-dao-pai-xu-kxnk/) | 中等 | 🤩🤩🤩 | | [1984. 学生分数的最小差值](https://leetcode-cn.com/problems/minimum-difference-between-highest-and-lowest-of-k-scores/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/minimum-difference-between-highest-and-lowest-of-k-scores/solution/gong-shui-san-xie-pai-xu-hua-dong-chuang-ru6e/) | 简单 | 🤩🤩🤩🤩🤩 | | [2024. 考试的最大困扰度](https://leetcode-cn.com/problems/maximize-the-confusion-of-an-exam/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/maximize-the-confusion-of-an-exam/solution/by-ac_oier-2rii/) | 中等 | 🤩🤩🤩🤩 | +| [2760. 最长奇偶子数组](https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/) | [LeetCode 题解链接](https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/solutions/2528837/gong-shui-san-xie-chou-si-bo-chong-tu-ji-n9r1/) | 简单 | 🤩🤩🤩🤩🤩 | | [剑指 Offer II 009. 乘积小于 K 的子数组](https://leetcode.cn/problems/ZVAVXX/) | [LeetCode 题解链接](https://leetcode.cn/problems/ZVAVXX/solution/by-ac_oier-lop5/) | 中等 | 🤩🤩🤩🤩 | diff --git "a/Index/\347\272\277\346\200\247 DP.md" "b/Index/\347\272\277\346\200\247 DP.md" index 96e6acf7..5aa56ca3 100644 --- "a/Index/\347\272\277\346\200\247 DP.md" +++ "b/Index/\347\272\277\346\200\247 DP.md" @@ -3,6 +3,7 @@ | [10. 正则表达式匹配 ](https://leetcode-cn.com/problems/regular-expression-matching) | [LeetCode 题解链接](https://leetcode-cn.com/problems/regular-expression-matching/solution/shua-chuan-lc-dong-tai-gui-hua-jie-fa-by-zn9w/) | 困难 | 🤩🤩🤩🤩 | | [44. 通配符匹配](https://leetcode-cn.com/problems/wildcard-matching/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/wildcard-matching/solution/gong-shui-san-xie-xiang-jie-dong-tai-gui-ifyx/) | 困难 | 🤩🤩🤩🤩 | | [45. 跳跃游戏 II](https://leetcode-cn.com/problems/jump-game-ii/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/jump-game-ii/solution/xiang-jie-dp-tan-xin-shuang-zhi-zhen-jie-roh4/) | 中等 | 🤩🤩🤩🤩 | +| [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | [LeetCode 题解链接](https://leetcode.cn/problems/maximum-subarray/solutions/2534027/gong-shui-san-xie-cong-on-de-chang-gui-l-22hq/) | 中等 | 🤩🤩🤩 | | [91. 解码方法](https://leetcode-cn.com/problems/decode-ways/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/decode-ways/solution/gong-shui-san-xie-gen-ju-shu-ju-fan-wei-ug3dd/) | 中等 | 🤩🤩🤩 | | [97. 交错字符串](https://leetcode.cn/problems/interleaving-string/) | [LeetCode 题解链接](https://leetcode.cn/problems/interleaving-string/solution/gong-shui-san-xie-yi-ti-shuang-jie-ji-yi-51da/) | 中等 | 🤩🤩🤩🤩🤩 | | [115. 不同的子序列](https://leetcode-cn.com/problems/distinct-subsequences/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/distinct-subsequences/solution/xiang-jie-zi-fu-chuan-pi-pei-wen-ti-de-t-wdtk/) | 困难 | 🤩🤩🤩🤩 | diff --git "a/LeetCode/2341-2350/2342. \346\225\260\344\275\215\345\222\214\347\233\270\347\255\211\346\225\260\345\257\271\347\232\204\346\234\200\345\244\247\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/2341-2350/2342. \346\225\260\344\275\215\345\222\214\347\233\270\347\255\211\346\225\260\345\257\271\347\232\204\346\234\200\345\244\247\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" new file mode 100644 index 00000000..43f625a6 --- /dev/null +++ "b/LeetCode/2341-2350/2342. \346\225\260\344\275\215\345\222\214\347\233\270\347\255\211\346\225\260\345\257\271\347\232\204\346\234\200\345\244\247\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -0,0 +1,244 @@ +### 题目描述 + +这是 LeetCode 上的 **[2342. 数位和相等数对的最大和](https://leetcode.cn/problems/max-sum-of-a-pair-with-equal-sum-of-digits/solutions/2531511/gong-shui-san-xie-yong-bian-li-guo-cheng-kt3f/)** ,难度为 **中等**。 + +Tag : 「模拟」、「哈希表」 + + + +给你一个下标从 `0` 开始的数组 `nums`,数组中的元素都是正整数。 + +请你选出两个下标 `i` 和 `j`(`i != j`),且 `nums[i]` 的数位和与 `nums[j]` 的数位和相等。 + +请你找出所有满足条件的下标 `i` 和 `j`,找出并返回 `nums[i] + nums[j]` 可以得到的最大值。 + +示例 1: +``` +输入:nums = [18,43,36,13,7] + +输出:54 + +解释:满足条件的数对 (i, j) 为: +- (0, 2) ,两个数字的数位和都是 9 ,相加得到 18 + 36 = 54 。 +- (1, 4) ,两个数字的数位和都是 7 ,相加得到 43 + 7 = 50 。 +所以可以获得的最大和是 54 。 +``` +示例 2: +``` +输入:nums = [10,12,19,14] + +输出:-1 + +解释:不存在满足条件的数对,返回 -1 。 +``` + +提示: +* $1 <= nums.length <= 10^5$ +* $1 <= nums[i] <= 10^9$ + +--- + +### 模拟 + +既然每个 $nums[i]$ 都对应一个具体的数位和,统计每个数位和的最大值和次大值,然后在所有数位和的最大值和次大值求和中取 `max` 即是答案。 + +利用 $1 <= nums[i] <= 10^9$,我们知道数位和不会超过 $9 \times 9 = 81$,可直接起一个大小为 $100 \times 2$ 的二维数组进行统计,$val[x][0]$ 代表数位和为 $x$ 的次大值,$val[x][1]$ 代表数位和为 $x$ 的最大值。 + +Java 代码: +```Java +class Solution { + public int maximumSum(int[] nums) { + int[][] val = new int[100][2]; + for (int x : nums) { + int t = x, cur = 0; + while (t != 0) { + cur += t % 10; + t /= 10; + } + if (x >= val[cur][1]) { // 最大沦为次大, 更新最大 + val[cur][0] = val[cur][1]; + val[cur][1] = x; + } else if (x > val[cur][0]) { // 更新次大 + val[cur][0] = x; + } + } + int ans = -1; + for (int i = 0; i < 100; i++) { + if (val[i][0] != 0 && val[i][1] != 0) ans = Math.max(ans, val[i][0] + val[i][1]); + } + return ans; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + int maximumSum(vector& nums) { + vector> val(100, vector(2, 0)); + for (int x : nums) { + int t = x, cur = 0; + while (t != 0) { + cur += t % 10; + t /= 10; + } + if (x >= val[cur][1]) { + val[cur][0] = val[cur][1]; + val[cur][1] = x; + } else if (x > val[cur][0]) { + val[cur][0] = x; + } + } + int ans = -1; + for (int i = 0; i < 100; i++) { + if (val[i][0] != 0 && val[i][1] != 0) ans = max(ans, val[i][0] + val[i][1]); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maximumSum(self, nums: List[int]) -> int: + val = [[0, 0] for _ in range(100)] + for x in nums: + t, cur = x, 0 + while t != 0: + cur += t % 10 + t //= 10 + if x >= val[cur][1]: + val[cur][0], val[cur][1] = val[cur][1], x + elif x > val[cur][0]: + val[cur][0] = x + ans = -1 + for i in range(100): + if val[i][0] != 0 and val[i][1] != 0: + ans = max(ans, val[i][0] + val[i][1]) + return ans +``` +TypeScript 代码: +```TypeScript +function maximumSum(nums: number[]): number { + const val = Array.from({ length: 100 }, () => [0, 0]); + for (const x of nums) { + let t = x, cur = 0; + while (t !== 0) { + cur += t % 10; + t = Math.floor(t / 10); + } + if (x >= val[cur][1]) { + val[cur][0] = val[cur][1]; + val[cur][1] = x; + } else if (x > val[cur][0]) { + val[cur][0] = x; + } + } + let ans = -1; + for (let i = 0; i < 100; i++) { + if (val[i][0] !== 0 && val[i][1] !== 0) ans = Math.max(ans, val[i][0] + val[i][1]); + } + return ans; +}; +``` +* 时间复杂度:$O(n\log{m})$,其中 $n=1e5$ 为 `nums` 长度,$m=1e9$ 为 $nums[i]$ 值域上界 +* 空间复杂度:$O(C)$,其中 $C = 2 \times 9 \times \log{m}$ + +--- + +### 模拟 + +更进一步,我们不需要记录次大值,仅记录某个“数对和”当前的最大值即可。 + +每次计算出当前 $nums[i]$ 对应的数对 `cur` 后,检查 `cur` 是否已出现过,若出现过用两者之和更新答案,并用 $nums[i]$ 来更新 `cur` 下的最大值。 + +该做法本质是用「遍历过程」代替「次大维护」。 + + +Java 代码: +```Java +class Solution { + public int maximumSum(int[] nums) { + int[] val = new int[100]; + int ans = -1; + for (int x : nums) { + int t = x, cur = 0; + while (t != 0) { + cur += t % 10; + t /= 10; + } + if (val[cur] != 0) ans = Math.max(ans, val[cur] + x); + val[cur] = Math.max(val[cur], x); + } + return ans; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + int maximumSum(vector& nums) { + vector val(100, 0); + int ans = -1; + for (int x : nums) { + int t = x, cur = 0; + while (t != 0) { + cur += t % 10; + t /= 10; + } + if (val[cur] != 0) ans = max(ans, val[cur] + x); + val[cur] = max(val[cur], x); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maximumSum(self, nums: List[int]) -> int: + val = [0] * 100 + ans = -1 + for x in nums: + t, cur = x, 0 + while t != 0: + cur += t % 10 + t //= 10 + if val[cur] != 0: + ans = max(ans, val[cur] + x) + val[cur] = max(val[cur], x) + return ans +``` +TypeScript 代码: +```TypeScript +function maximumSum(nums: number[]): number { + const val = Array(100).fill(0); + let ans = -1; + for (const x of nums) { + let t = x, cur = 0; + while (t !== 0) { + cur += t % 10; + t = Math.floor(t / 10); + } + if (val[cur] !== 0) ans = Math.max(ans, val[cur] + x); + val[cur] = Math.max(val[cur], x); + } + return ans; +}; +``` +* 时间复杂度:$O(n\log{m})$,其中 $n=1e5$ 为 `nums` 长度,$m=1e9$ 为 $nums[i]$ 值域上界 +* 空间复杂度:$O(C)$,其中 $C = 9 \times \log{m}$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.2342` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/2651-2660/2656. K \344\270\252\345\205\203\347\264\240\347\232\204\346\234\200\345\244\247\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/2651-2660/2656. K \344\270\252\345\205\203\347\264\240\347\232\204\346\234\200\345\244\247\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" new file mode 100644 index 00000000..684950f8 --- /dev/null +++ "b/LeetCode/2651-2660/2656. K \344\270\252\345\205\203\347\264\240\347\232\204\346\234\200\345\244\247\345\222\214\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -0,0 +1,108 @@ +### 题目描述 + +这是 LeetCode 上的 **[2656. K 个元素的最大和](https://leetcode.cn/problems/maximum-sum-with-exactly-k-elements/solutions/2527384/gong-shui-san-xie-deng-chai-shu-lie-qiu-b2g88/)** ,难度为 **简单**。 + +Tag : 「数学」 + + + +给你一个下标从 `0` 开始的整数数组 `nums` 和一个整数 `k` 。 + +你需要执行以下操作恰好 `k` 次,最大化你的得分: + +1. 从 `nums` 中选择一个元素 `m` 。 +2. 将选中的元素 `m` 从数组中删除。 +3. 将新元素 `m + 1` 添加到数组中。 +4. 你的得分增加 `m` 。 + +请你返回执行以上操作恰好 `k` 次后的最大得分。 + +示例 1: +``` +输入:nums = [1,2,3,4,5], k = 3 + +输出:18 + +解释:我们需要从 nums 中恰好选择 3 个元素并最大化得分。 +第一次选择 5 。和为 5 ,nums = [1,2,3,4,6] 。 +第二次选择 6 。和为 6 ,nums = [1,2,3,4,7] 。 +第三次选择 7 。和为 5 + 6 + 7 = 18 ,nums = [1,2,3,4,8] 。 +所以我们返回 18 。 +18 是可以得到的最大答案。 +``` +示例 2: +``` +输入:nums = [5,5,5], k = 2 + +输出:11 + +解释:我们需要从 nums 中恰好选择 2 个元素并最大化得分。 +第一次选择 5 。和为 5 ,nums = [5,5,6] 。 +第二次选择 6 。和为 6 ,nums = [5,5,7] 。 +所以我们返回 11 。 +11 是可以得到的最大答案。 +``` + +提示: +* $1 <= nums.length <= 100$ +* $1 <= nums[i] <= 100$ +* $1 <= k <= 100$ + +--- + +### 数学 + +为了使得分最高,每次应从 `nums` 中选最大值,选完后重放仍为最大值。 + +假设原始 `nums` 中的最大值为 `max`,那么问题转换为「等差数列」求和:首项为 `max`,末项为 `max + k - 1`,项数为 $k$,公差为 $1$。 + +Java 代码: +```Java +class Solution { + public int maximizeSum(int[] nums, int k) { + int max = 0; + for (int x : nums) max = Math.max(max, x); + return k * (max + max + k - 1) / 2; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + int maximizeSum(vector& nums, int k) { + int maxv = 0; + for (auto x : nums) maxv = max(maxv, x); + return k * (maxv + maxv + k - 1) / 2; + } +}; +``` +Python 代码: +```Python +class Solution: + def maximizeSum(self, nums: List[int], k: int) -> int: + return k * (2 * max(nums) + k - 1) // 2 +``` +TypeScript 代码: +```TypeScript +function maximizeSum(nums: number[], k: number): number { + let max = 0; + for (const x of nums) max = Math.max(max, x); + return k * (max + max + k - 1) / 2; +}; +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(1)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.2656` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/2731-2740/2736. \346\234\200\345\244\247\345\222\214\346\237\245\350\257\242\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/2731-2740/2736. \346\234\200\345\244\247\345\222\214\346\237\245\350\257\242\357\274\210\345\233\260\351\232\276\357\274\211.md" new file mode 100644 index 00000000..d8c07166 --- /dev/null +++ "b/LeetCode/2731-2740/2736. \346\234\200\345\244\247\345\222\214\346\237\245\350\257\242\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -0,0 +1,346 @@ +### 题目描述 + +这是 LeetCode 上的 **[2736. 最大和查询](https://leetcode.cn/problems/maximum-sum-queries/solutions/2530269/gong-shui-san-xie-cong-yi-wei-xian-zhi-d-ww8r/)** ,难度为 **困难**。 + +Tag : 「排序」、「离散化」、「树状数组」 + + + +给你两个长度为 `n`、下标从 `0` 开始的整数数组 `nums1` 和 `nums2`,另给你一个下标从 `1` 开始的二维数组 `queries`,其中 $queries[i] = [x_{i}, y_{i}]$ 。 + +对于第 `i` 个查询,在所有满足 $nums1[j] >= x_{i}$ 且 $nums2[j] >= y_{i}$ 的下标 `j` ($0 <= j < n$) 中,找出 $nums1[j] + nums2[j]$ 的 最大值 ,如果不存在满足条件的 `j` 则返回 $-1$。 + +返回数组 `answer`,其中 `answer[i]` 是第 `i` 个查询的答案。 + +示例 1: +``` +输入:nums1 = [4,3,1,2], nums2 = [2,4,9,5], queries = [[4,1],[1,3],[2,5]] + +输出:[6,10,7] + +解释: +对于第 1 个查询:xi = 4 且 yi = 1 ,可以选择下标 j = 0 ,此时 nums1[j] >= 4 且 nums2[j] >= 1 。nums1[j] + nums2[j] 等于 6 ,可以证明 6 是可以获得的最大值。 +对于第 2 个查询:xi = 1 且 yi = 3 ,可以选择下标 j = 2 ,此时 nums1[j] >= 1 且 nums2[j] >= 3 。nums1[j] + nums2[j] 等于 10 ,可以证明 10 是可以获得的最大值。 +对于第 3 个查询:xi = 2 且 yi = 5 ,可以选择下标 j = 3 ,此时 nums1[j] >= 2 且 nums2[j] >= 5 。nums1[j] + nums2[j] 等于 7 ,可以证明 7 是可以获得的最大值。 +因此,我们返回 [6,10,7] 。 +``` +示例 2: +``` +输入:nums1 = [3,2,5], nums2 = [2,3,4], queries = [[4,4],[3,2],[1,1]] + +输出:[9,9,9] + +解释:对于这个示例,我们可以选择下标 j = 2 ,该下标可以满足每个查询的限制。 +``` +示例 3: +``` +输入:nums1 = [2,1], nums2 = [2,3], queries = [[3,3]] + +输出:[-1] + +解释:示例中的查询 xi = 3 且 yi = 3 。对于每个下标 j ,都只满足 nums1[j] < xi 或者 nums2[j] < yi 。因此,不存在答案。 +``` + +提示: +* $nums1.length = nums2.length$ +* $n = nums1.length$ +* $1 <= n <= 10^5$ +* $1 <= nums1[i], nums2[i] <= 10^9$ +* $1 <= queries.length <= 10^5$ +* $queries[i].length = 2$ +* $x_{i} = queries[i][1]$ +* $y_{i} = queries[i][2]$ +* $1 <= x_{i}, y_{i} <= 10^9$ + +--- + +### 离散化 + 排序 + 树状数组 + +根据题意,两个等长数组 `num1` 和 `nums2`,只能是相同下标的元素凑成一对。 + +不妨用两个一维数组 `nums1` 和 `nums2` 构建出一个二维数组 `nums`,方便后续处理,其中 $nums[i] = [nums1[i], nums2[i]]$。 + +同时对 `queries` 进行简单拓展,构建新的二维数组 `nq`,目的是对原有下标信息进行记录,其中 $nq[i] = [queries[i][0], queries[i][1], i]$(此处构建新 `nq` 的作用,下文会说)。 + +好了,现在我们有两个新的数组 `nums` 和 `nq`,接下来所有讨论都会针对新数组进行。 + +希望你牢牢记住:**$nums[i] = [nums1[i], nums2[i]]$ 是对 `nums1` 和 `nums2` 的简单合并;而 $nq[i] = [queries[i][0], queries[i][1], i]$ 是对 `queries` 原有下标的拓展记录**。 + +做完简单的预处理工作后,来思考一个前置问题: + +假设其他内容不变,要求从满足「$nums[j][0] \geq nq[i][0]$ 且 $nums[j][1] \geq nq[i][1]$」调整为仅需满足「$nums[j][0] \geq nq[i][0]$」,如何求解? + +一个简单的做法: + +1. 对 `nums` 中的第一维(即 $nums[i][0] = nums1[i]$)和 `nq` 中第一维(即 $nq[i][0] = queries[i][0] = x_{i}$)排倒序; + +2. 从前往后处理每个询问 $nq[i]$,同时使用变量 `idx` 对 `nums` 进行扫描,若满足 $nums[idx][0] \geq nq[i][0]$ 时,将 `idx` 右移,同时记录已被扫描的数对和 $nums[idx'][0] + nums[idx'][1]$ 。 + + 当 `idx` 不能再后移时,说明当前所有满足 $nums[idx][0] \geq nq[i][0]$ 要求的数对已被扫描完,在记录的数对和中取最大值,即是当前询问 $nq[i]$ 的答案 $ans[nq[i][2]]$(此处解释了为什么需要构造新数组 `nq`,因为询问处理是按照排序后进行,但在 `ans` 中需要映射回原有顺序)。 + +3. 重复步骤 $2$,直到处理完所有询问。 + +搞懂前置问题后,回到原问题,需要满足「$nums[j][0] \geq nq[i][0]$ 且 $nums[j][1] \geq nq[i][1]$」要求。 + +进一步思考,排序 + 调整询问顺序,只能解决其中一维限制要求,另一维该如何处理? + +或更直接的问题:如何在被记录的所有数对和 $nums[idx'][0] + nums[idx'][1]$ 中找出那些满足 $nums[j][1] \geq nq[i][1]$ 的最大数对和。 + +不失一般性,假设当前处理到的是 $nums[idx]$,其中 $nums[idx][0]$ 的限制要求,通过前置问题的排序方式解决了。另外的 **$nums[idx][1]$ 我们希望作为“位置信息”,数对和 $sum = nums[idx][0] + nums[idx][1]$ 作为“值信息”进行记录**。 + +由于条件 $1 \leq x_{i}, y_{i} \leq 1e9$,我们需要对将要作为“位置信息”添加到树状数组的 $nums[idx][1]$ 和 $nq[i][1]$ 进行离散化,将其映射到 $[0, m - 1]$ 范围内。 + +对于每个询问,都需要找到已遍历过的大于 $nq[i][1]$ 的位置上的最大值,把离散化后的值域换成数组坐标,相当于求后缀最大值,后缀最大值可通过相反数,变成求前缀最大值。 + +能够实现类似的前缀操作,支持“单点修改”和“区间查询”的数据结构是「树状数组」。 + +Java 代码: + +```Java +class Solution { + int sz; + int[] tr; + int lowbit(int x) { + return x & -x; + } + void add(int a, int b) { + for (int i = a; i <= sz; i += lowbit(i)) tr[i] = Math.max(tr[i], b); + } + int query(int x) { + int ans = -1; + for (int i = x; i > 0; i -= lowbit(i)) ans = Math.max(ans, tr[i]); + return ans; + } + public int[] maximumSumQueries(int[] nums1, int[] nums2, int[][] queries) { + int n = nums1.length, m = queries.length; + // 构建新的 nums 和 nq + int[][] nums = new int[n][2]; + for (int i = 0; i < n; i++) nums[i] = new int[]{nums1[i], nums2[i]}; + int[][] nq = new int[m][3]; + for (int i = 0; i < m; i++) nq[i] = new int[]{queries[i][0], queries[i][1], i}; + + // 对要添加到树状数组的 nums[i][1] 和 nq[i][1] 进行离散化(构建映射字典, 将原值映射到 [0, m - 1]) + Set set = new HashSet<>(); + for (int[] x : nums) set.add(x[1]); + for (int[] q : nq) set.add(q[1]); + List list = new ArrayList<>(set); + Collections.sort(list); + sz = list.size(); + Map map = new HashMap<>(); + for (int i = 0; i < sz; i++) map.put(list.get(i), i); + + // 调整询问顺序, 解决其中一维限制 + Arrays.sort(nums, (a,b)->b[0]-a[0]); + Arrays.sort(nq, (a,b)->b[0]-a[0]); + + tr = new int[sz + 10]; + Arrays.fill(tr, -1); + + int[] ans = new int[m]; + int idx = 0; + for (int[] q : nq) { + int x = q[0], y = q[1], i = q[2]; + // 扫描所有满足 nums[idx][0] >= x 的数对, 添加到树状数组中(其中离散值作为位置信息, 数对和作为值信息) + while (idx < n && nums[idx][0] >= x) { + add(sz - map.get(nums[idx][1]), nums[idx][0] + nums[idx][1]); + idx++; + } + ans[i] = query(sz - map.get(y)); // 查询树状数组中的最值 + } + return ans; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + int sz; + vector tr; + int lowbit(int x) { + return x & -x; + } + void add(int a, int b) { + for (int i = a; i <= sz; i += lowbit(i)) tr[i] = max(tr[i], b); + } + int query(int x) { + int ans = -1; + for (int i = x; i > 0; i -= lowbit(i)) ans = max(ans, tr[i]); + return ans; + } + vector maximumSumQueries(vector& nums1, vector& nums2, vector>& queries) { + int n = nums1.size(), m = queries.size(); + // 构建新的 nums 和 nq + vector> nums(n, vector(2)); + vector> nq(m, vector(3)); + for (int i = 0; i < n; i++) { + nums[i][0] = nums1[i]; nums[i][1] = nums2[i]; + } + for (int i = 0; i < m; i++) { + nq[i][0] = queries[i][0]; nq[i][1] = queries[i][1]; nq[i][2] = i; + } + + // 对要添加到树状数组的 nums[i][1] 和 nq[i][1] 进行离散化(构建映射字典, 将原值映射到 [0, m - 1]) + unordered_set set; + for (auto& x : nums) set.insert(x[1]); + for (auto& q : nq) set.insert(q[1]); + vector list(set.begin(), set.end()); + sort(list.begin(), list.end()); + sz = list.size(); + map map; + for (int i = 0; i < sz; i++) map[list[i]] = i; + + // 调整询问顺序, 解决其中一维限制 + sort(nums.begin(), nums.end(), [](const vector& a, const vector& b) { + return a[0] > b[0]; + }); + sort(nq.begin(), nq.end(), [](const vector& a, const vector& b) { + return a[0] > b[0]; + }); + + tr.resize(sz + 10, -1); + + vector ans(m); + int idx = 0; + for (auto& q : nq) { + int x = q[0], y = q[1], i = q[2]; + // 扫描所有满足 nums[idx][0] >= x 的数对, 添加到树状数组中(其中离散值作为位置信息, 数对和作为值信息) + while (idx < n && nums[idx][0] >= x) { + add(sz - map[nums[idx][1]], nums[idx][0] + nums[idx][1]); + idx++; + } + ans[i] = query(sz - map[y]); // 查询树状数组中的最值 + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maximumSumQueries(self, nums1: List[int], nums2: List[int], queries: List[List[int]]) -> List[int]: + sz = 0 + tr = [] + + def lowbit(x): + return x & -x + def add(a, b): + i = a + while i <= sz: + tr[i] = max(tr[i], b) + i += lowbit(i) + def query(x): + ans, i = -1, x + while i > 0: + ans = max(ans, tr[i]) + i -= lowbit(i) + return ans + + n, m = len(nums1), len(queries) + # 构建新的 nums 和 nq + nums = [(nums1[i], nums2[i]) for i in range(n)] + nq = [(queries[i][0], queries[i][1], i) for i in range(m)] + + # 对要添加到树状数组的 nums[i][1] 和 nq[i][1] 进行离散化(构建映射字典, 将原值映射到 [0, m - 1]) + unique_set = set() + for x in nums: + unique_set.add(x[1]) + for q in nq: + unique_set.add(q[1]) + unique_list = list(unique_set) + unique_list.sort() + sz = len(unique_list) + mapping = {val: idx for idx, val in enumerate(unique_list)} + + # 调整询问顺序, 解决其中一维限制 + nums.sort(key=lambda x: x[0], reverse=True) + nq.sort(key=lambda x: x[0], reverse=True) + + tr = [-1] * (sz + 10) + + ans = [0] * m + idx = 0 + for x, y, i in nq: + # 扫描所有满足 nums[idx][0] >= x 的数对, 添加到树状数组中(其中离散值作为位置信息, 数对和作为值信息) + while idx < n and nums[idx][0] >= x: + add(sz - mapping[nums[idx][1]], nums[idx][0] + nums[idx][1]) + idx += 1 + ans[i] = query(sz - mapping[y]) # 查询树状数组中的最值 + return ans +``` +TypeScript 代码: +```TypeScript +function maximumSumQueries(nums1: number[], nums2: number[], queries: number[][]): number[] { + let sz = 0; + let tr = []; + + const lowbit = function(x: number): number { + return x & -x; + } + const add = function(a: number, b: number): void { + for (let i = a; i <= sz; i += lowbit(i)) tr[i] = Math.max(tr[i], b); + } + const query = function(x: number): number { + let ans = -1; + for (let i = x; i > 0; i -= lowbit(i)) ans = Math.max(ans, tr[i]); + return ans; + } + + const n = nums1.length, m = queries.length; + // 构建新的 nums 和 nq + const nums = Array.from({ length: n }, (_, i) => [nums1[i], nums2[i]]); + const nq = Array.from({ length: m }, (_, i) => [...queries[i], i]); + + // 对要添加到树状数组的 nums[i][1] 和 nq[i][1] 进行离散化(构建映射字典, 将原值映射到 [0, m - 1]) + const set: Set = new Set(); + for (const x of nums) set.add(x[1]); + for (const q of nq) set.add(q[1]); + const list: number[] = [...set]; + list.sort((a, b) => a - b); + sz = list.length; + const mapping = new Map(); + for (let i = 0; i < sz; i++) mapping.set(list[i], i); + + // 调整询问顺序, 解决其中一维限制 + nums.sort((a, b) => b[0] - a[0]); + nq.sort((a, b) => b[0] - a[0]); + + tr = Array(sz + 10).fill(-1); + const ans = Array(m).fill(0); + let idx = 0; + for (let [x, y, i] of nq) { + // 扫描所有满足 nums[idx][0] >= x 的数对, 添加到树状数组中(其中离散值作为位置信息, 数对和作为值信息) + while (idx < n && nums[idx][0] >= x) { + add(sz - mapping.get(nums[idx][1])!, nums[idx][0] + nums[idx][1]); + idx++; + } + ans[i] = query(sz - mapping.get(y)!); // 查询树状数组中的最值 + } + return ans; +}; +``` +* 时间复杂度:令 `nums1` 长度为 $n$,`queries` 长度为 $m$,构建 `nums` 和 `nq` 的复杂度为 $O(n + m)$;离散化复杂度为 $O((n + m) \log{(n + m)})$;对 `nums` 和 `nq` 的排序复杂度为 $O(n\log{n} + m\log{m})$;构建答案复杂度为 $O(m + n\log{n})$。整体复杂度为 $O((n + m) \log {(n + m)})$ +* 空间复杂度:$O(n + m)$ + +--- + +### 进阶 + +本题解讲述了「从一维到二维偏序问题」时,可通过「一维排序,一维树状数组」求通解。 + +那三维偏序问题呢?是否存在通用的解决思路。 + +答:一维排序,一维归并,一维树状数组。 + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.2736` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/2751-2760/2760. \346\234\200\351\225\277\345\245\207\345\201\266\345\255\220\346\225\260\347\273\204\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/2751-2760/2760. \346\234\200\351\225\277\345\245\207\345\201\266\345\255\220\346\225\260\347\273\204\357\274\210\347\256\200\345\215\225\357\274\211.md" new file mode 100644 index 00000000..f73f6d48 --- /dev/null +++ "b/LeetCode/2751-2760/2760. \346\234\200\351\225\277\345\245\207\345\201\266\345\255\220\346\225\260\347\273\204\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -0,0 +1,170 @@ +### 题目描述 + +这是 LeetCode 上的 **[2698. 求一个整数的惩罚数](https://leetcode.cn/problems/longest-even-odd-subarray-with-threshold/solutions/2528837/gong-shui-san-xie-chou-si-bo-chong-tu-ji-n9r1/)** ,难度为 **简单**。 + +Tag : 「双指针」、「滑动窗口」 + + + +给你一个下标从 $0$ 开始的整数数组 `nums` 和一个整数 `threshold`。 + +请你从 `nums` 的子数组中找出以下标 `l` 开头、下标 `r` 结尾 ($0 <= l <= r < nums.length$) 且满足以下条件的 最长子数组 : + +* `nums[l] % 2 == 0` +* 对于范围 $[l, r - 1]$ 内的所有下标 `i`,`nums[i] % 2 != nums[i + 1] % 2` +* 对于范围 $[l, r]$ 内的所有下标 `i`,`nums[i] <= threshold` + +以整数形式返回满足题目要求的最长子数组的长度。 + +注意:子数组 是数组中的一个连续非空元素序列。 + +示例 1: +``` +输入:nums = [3,2,5,4], threshold = 5 + +输出:3 + +解释:在这个示例中,我们选择从 l = 1 开始、到 r = 3 结束的子数组 => [2,5,4] ,满足上述条件。 +因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。 +``` +示例 2: +``` +输入:nums = [1,2], threshold = 2 + +输出:1 + +解释: +在这个示例中,我们选择从 l = 1 开始、到 r = 1 结束的子数组 => [2] 。 +该子数组满足上述全部条件。可以证明 1 是满足题目要求的最大长度。 +``` +示例 3: +``` +输入:nums = [2,3,4,5], threshold = 4 + +输出:3 + +解释: +在这个示例中,我们选择从 l = 0 开始、到 r = 2 结束的子数组 => [2,3,4] 。 +该子数组满足上述全部条件。 +因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。 +``` + +提示: +* $1 <= nums.length <= 100$ +* $1 <= nums[i] <= 100$ +* $1 <= threshold <= 100$ + +--- + +### 双指针 + +整体题意:找 `nums` 中的最长的子数组 $[l, r]$,对于任意 $nums[i]$ 不超过 `threshold`,且从 $nums[l]$ 开始按照「先偶后奇」顺序交替。 + +假设子数组的左端点为 `i`,且“最远的”合法右端点为 `j`,那么在 $[i, j]$ 之间的任意右端点 `k`,即使能够使得 $nums[i...k]$ 合法,对统计答案而言,也是没有意义的,因为我们求的是最长。 + +基于此,我们容易想到:**找到所有的合法左端点 `i`,并统计该合法左端点的最远右端点 `j`。跳过 $[i, j]$ 之间的点作为左端点的情况,直接从结束位置 `j` 开始找下一个合法左端点。** + +该做法可将朴素的 $O(n^2)$ 做法优化至 $O(n)$。 + +但,这做法为什么是正确的? + +我们只考虑了 $[i, j]$ 中间点作为右端点的情况,那作为左端点呢?为什么跳过 $[i, j]$ 之间的 $k$ 作为左端点,正确性也不受影响?我们不是漏到了某些方案吗? + +答案:**是漏掉了,但也只是漏掉了那些必不可能是最长子数组的方案**。 + +![](https://pic.leetcode.cn/1700098290-BjVviy-image.png) + +具体的,我们重新整理上述的「双指针」做法: + +* 从前往后扫描 `nums`,变量 `i` 作为当前子数组左端点,首先确保 `i` 的合法性(跳过不满足 `nums[i] % 2 = 0` 和 `nums[i] <= threshold` 的位置) +* 随后在固定左端点 `i` 前提下,找最远的(第一个不满足要求的)右端点 `j`(值不超过 `threshold`,且奇偶性与前值交替) +* 得到当前连续段长度 $[i, j - 1]$,更新 `ans`,从当前结束位置 `j` 开始,重复上述过程,直到处理完 `nums` + +Java 代码 +```Java +class Solution { + public int longestAlternatingSubarray(int[] nums, int threshold) { + int n = nums.length, ans = 0, i = 0; + while (i < n) { + if ((nums[i] % 2 != 0 || nums[i] > threshold) && ++i >= 0) continue; + int j = i + 1, cur = nums[i] % 2; + while (j < n) { + if (nums[j] > threshold || nums[j] % 2 == cur) break; + cur = nums[j++] % 2; + } + ans = Math.max(ans, j - i); + i = j; + } + return ans; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + int longestAlternatingSubarray(vector& nums, int threshold) { + int n = nums.size(), ans = 0, i = 0; + while (i < n) { + if ((nums[i] % 2 != 0 || nums[i] > threshold) && ++i >= 0) continue; + int j = i + 1, cur = nums[i] % 2; + while (j < n) { + if (nums[j] > threshold || nums[j] % 2 == cur) break; + cur = nums[j++] % 2; + } + ans = max(ans, j - i); + i = j; + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def longestAlternatingSubarray(self, nums: List[int], threshold: int) -> int: + n, ans, i = len(nums), 0, 0 + while i < n: + if nums[i] % 2 != 0 or nums[i] > threshold: + i += 1 + continue + j, cur = i + 1, nums[i] % 2 + while j < n: + if nums[j] > threshold or nums[j] % 2 == cur: break + cur, j = nums[j] % 2, j + 1 + ans = max(ans, j - i) + i = j + return ans +``` +TypeScript 代码: +```TypeScript +function longestAlternatingSubarray(nums: number[], threshold: number): number { + let n = nums.length, ans = 0, i = 0 + while (i < n) { + if ((nums[i] % 2 != 0 || nums[i] > threshold) && ++i >= 0) continue; + let j = i + 1, cur = nums[i] % 2; + while (j < n) { + if (nums[j] > threshold || nums[j] % 2 == cur) break; + cur = nums[j++] % 2; + } + ans = Math.max(ans, j - i); + i = j; + } + return ans; +}; +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(1)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.2760` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/301-310/303. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\344\270\215\345\217\257\345\217\230\357\274\210\347\256\200\345\215\225\357\274\211.md" "b/LeetCode/301-310/303. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\344\270\215\345\217\257\345\217\230\357\274\210\347\256\200\345\215\225\357\274\211.md" index 437ff107..753d9594 100644 --- "a/LeetCode/301-310/303. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\344\270\215\345\217\257\345\217\230\357\274\210\347\256\200\345\215\225\357\274\211.md" +++ "b/LeetCode/301-310/303. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\344\270\215\345\217\257\345\217\230\357\274\210\347\256\200\345\215\225\357\274\211.md" @@ -6,11 +6,11 @@ Tag : 「前缀和」、「区间求和问题」 -给定一个整数数组  nums,求出数组从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点。 +给定一个整数数组 `nums`,求出数组从索引 `i` 到 `j`(`i ≤ j`)范围内元素的总和,包含 `i`、`j` 两点。 -实现 NumArray 类: -* NumArray(int[] nums) 使用数组 nums 初始化对象 -* int sumRange(int i, int j) 返回数组 nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1], ... , nums[j])) +实现 `NumArray` 类: +* `NumArray(int[] nums)` 使用数组 `nums` 初始化对象 +* `int sumRange(int i, int j)` 返回数组 `nums` 从索引 `i` 到 `j`(`i ≤ j`)范围内元素的总和,包含 i、j 两点(也就是 `sum(nums[i], nums[i + 1], ... , nums[j])`) 示例: @@ -29,31 +29,34 @@ numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1)) ``` 提示: -* 0 <= nums.length <= $10^4$ -* -$10^5$ <= nums[i] <= $10^5$ -* 0 <= i <= j < nums.length -* 最多调用 $10^4# 次 sumRange 方法 +* $0 <= nums.length <= 10^4$ +* $-10^5 <= nums[i] <= 10^5$ +* $0 <= i <= j < nums.length$ +* 最多调用 $10^4$ 次 `sumRange` 方法 --- -### 前缀和解法(一维) +### 前缀和(一维) -这是一道前缀和的裸题。 +这是一道前缀和裸题。 -当需要我们求「某一段」区域的和的时候,我们要很自然的想到「前缀和」。 +当要我们求「连续段」区域和的时候,要很自然的想到「前缀和」。 -前缀和的作用就是为了帮助我们快速求某一段的和,是「差分」的逆运算。 +所谓前缀和,是指对原数组“累计和”的描述,通常是指一个与原数组等长的数组。 -**前缀和数组 `sum` 的每一位记录的是当前位置距离起点位置,这连续一段的和区间和。** +设前缀和数组为 `sum`,**`sum` 的每一位记录的是从「起始位置」到「当前位置」的元素和**。例如 $sum[x]$ 是指原数组中“起始位置”到“位置 `x`”这一连续段的元素和。 -因此当我们要求特定的一段 [i,j] 的区域和的时候,可以直接利用前缀和数组快速求解:`ans = sum[j] - sum[i - 1]`。 +有了前缀和数组 `sum`,当我们求连续段 $[i, j]$ 的区域和时,利用「容斥原理」,便可进行快速求解。 + +通用公式:`ans = sum[j] - sum[i - 1]`。 ![image.png](https://pic.leetcode-cn.com/1614563503-eNCXNU-image.png) -由于涉及 -1 操作,为了减少一些边界处理,我们可以使前缀和数组下标从 1 开始记录,然后在进行答案计算的时候,根据源数组下标是否从 1 开始决定是否产生相应的偏移: +由于涉及 `-1` 操作,为减少边界处理,我们可让前缀和数组下标从 $1$ 开始。在进行快速求和时,再根据原数组下标是否从 $1$ 开始,决定是否进行相应的下标偏移。 -```java +Java 代码: +```Java class NumArray { int[] sum; public NumArray(int[] nums) { @@ -71,14 +74,70 @@ class NumArray { } } ``` +C++ 代码: +```C++ +class NumArray { +public: + vector sum; + NumArray(std::vector& nums) { + int n = nums.size(); + // 前缀和数组下标从 1 开始,因此设定长度为 n + 1(模板部分) + sum.resize(n + 1, 0); + // 预处理除前缀和数组(模板部分) + for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1]; + } + int sumRange(int i, int j) { + // 求某一段区域和 [i, j] 的模板是 sum[j] - sum[i - 1](模板部分) + // 但由于我们源数组下标从 0 开始,因此要在模板的基础上进行 + 1 + i++; j++; + return sum[j] - sum[i - 1]; + } +}; +``` +Python 代码: +```Python +class NumArray: + def __init__(self, nums: List[int]): + n = len(nums) + # 前缀和数组下标从 1 开始,因此设定长度为 n + 1(模板部分) + self.sum = [0] * (n + 1) + # 预处理除前缀和数组(模板部分) + for i in range(1, n + 1): + self.sum[i] = self.sum[i - 1] + nums[i - 1] + + def sumRange(self, i: int, j: int) -> int: + # 求某一段区域和 [i, j] 的模板是 sum[j] - sum[i - 1](模板部分) + # 但由于我们源数组下标从 0 开始,因此要在模板的基础上进行 + 1 + i, j = i + 1, j + 1 + return self.sum[j] - self.sum[i - 1] +``` +TypeScript 代码: +```TypeScript +class NumArray { + private sum: number[]; + constructor(nums: number[]) { + const n: number = nums.length; + // 前缀和数组下标从 1 开始,因此设定长度为 n + 1(模板部分) + this.sum = new Array(n + 1).fill(0); + // 预处理除前缀和数组(模板部分) + for (let i = 1; i <= n; i++) this.sum[i] = this.sum[i - 1] + nums[i - 1]; + } + sumRange(i: number, j: number): number { + // 求某一段区域和 [i, j] 的模板是 sum[j] - sum[i - 1](模板部分) + // 但由于我们源数组下标从 0 开始,因此要在模板的基础上进行 + 1 + i++; j++; + return this.sum[j] - this.sum[i - 1]; + } +} +``` * 时间复杂度:预处理前缀和数组需要对原数组进行线性扫描,复杂度为 $O(n)$,计算结果复杂度为 $O(1)$。整体复杂度为 $O(n)$ * 空间复杂度:$O(n)$ -*** +--- ### 一维前缀和模板 -我们再来整理下一维「前缀和」的模板代码: +我们再来整理下一维「前缀和」的模板代码(`Java` 为例): ```java // 预处理前缀和数组 @@ -94,7 +153,7 @@ class NumArray { } ``` -*** +--- ### 总结 diff --git "a/LeetCode/51-60/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/51-60/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" new file mode 100644 index 00000000..98289753 --- /dev/null +++ "b/LeetCode/51-60/53. \346\234\200\345\244\247\345\255\220\346\225\260\347\273\204\345\222\214\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -0,0 +1,284 @@ +### 题目描述 + +这是 LeetCode 上的 **[53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/solutions/2534027/gong-shui-san-xie-cong-on-de-chang-gui-l-22hq/)** ,难度为 **中等**。 + +Tag : 「前缀和」、「区间求和问题」、「线性 DP」、「分治」 + + + +给你一个整数数组 `nums`,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 + +子数组是数组中的一个连续部分。 + +示例 1: +``` +输入:nums = [-2,1,-3,4,-1,2,1,-5,4] + +输出:6 + +解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。 +``` +示例 2: +``` +输入:nums = [1] + +输出:1 +``` +示例 3: +``` +输入:nums = [5,4,-1,7,8] + +输出:23 +``` + +提示: +* $1 <= nums.length <= 10^5$ +* $-10^4 <= nums[i] <= 10^4$ + + +进阶:如果你已经实现复杂度为 $O(n)$ 的解法,尝试使用更为精妙的分治法求解。 + + +--- + +### 前缀和 or 线性 DP + +当要我们求「连续段」区域和的时候,要很自然的想到「前缀和」。 + +所谓前缀和,是指对原数组“累计和”的描述,通常是指一个与原数组等长的数组。 + +设前缀和数组为 `sum`,**`sum` 的每一位记录的是从「起始位置」到「当前位置」的元素和**。例如 $sum[x]$ 是指原数组中“起始位置”到“位置 `x`”这一连续段的元素和。 + +有了前缀和数组 `sum`,当我们求连续段 $[i, j]$ 的区域和时,利用「容斥原理」,便可进行快速求解。 + +通用公式:`ans = sum[j] - sum[i - 1]`。 + +![image.png](https://pic.leetcode-cn.com/1614563503-eNCXNU-image.png) + +由于涉及 `-1` 操作,为减少边界处理,我们可让前缀和数组下标从 $1$ 开始。在进行快速求和时,再根据原数组下标是否从 $1$ 开始,决定是否进行相应的下标偏移。 + +学习完一维前缀和后,回到本题。 + +先用 `nums` 预处理出前缀和数组 `sum`,然后在遍历子数组右端点 `j` 的过程中,通过变量 `m` 动态记录已访问的左端点 `i` 的前缀和最小值。最终,在所有 `sum[j] - m` 的取值中选取最大值作为答案。 + +代码实现上,我们无需明确计算前缀和数组 `sum`,而是使用变量 `s` 表示当前累计的前缀和(充当右端点),并利用变量 `m` 记录已访问的前缀和的最小值(充当左端点)即可。 + +**本题除了将其看作为「前缀和裸题用有限变量进行空间优化」以外,还能以「线性 DP」角度进行理解。** + +定义 $f[i]$ 为考虑前 $i$ 个元素,且第 $nums[i]$ 必选的情况下,形成子数组的最大和。 + +不难发现,仅考虑前 $i$ 个元素,且 $nums[i]$ 必然参与的子数组中。要么是 $nums[i]$ 自己一个成为子数组,要么与前面的元素共同组成子数组。 + +因此,状态转移方程: +$$ +f[i] = \max(f[i - 1] + nums[i], nums[i]) +$$ + +由于 $f[i]$ 仅依赖于 $f[i - 1]$ 进行转移,可使用有限变量进行优化,因此写出来的代码也是和上述前缀和角度分析的类似。 + +Java 代码: +```Java +class Solution { + public int maxSubArray(int[] nums) { + int s = 0, m = 0, ans = -10010; + for (int x : nums) { + s += x; + ans = Math.max(ans, s - m); + m = Math.min(m, s); + } + return ans; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + int maxSubArray(vector& nums) { + int s = 0, m = 0, ans = -10010; + for (int x : nums) { + s += x; + ans = max(ans, s - m); + m = min(m, s); + } + return ans; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + s, m, ans = 0, 0, -10010 + for x in nums: + s += x + ans = max(ans, s - m) + m = min(m, s) + return ans +``` +TypeScript 代码: +```TypeScript +function maxSubArray(nums: number[]): number { + let s = 0, m = 0, ans = -10010; + for (let x of nums) { + s += x; + ans = Math.max(ans, s - m); + m = Math.min(m, s); + } + return ans; +}; +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(1)$ + +--- + +### 分治 + +“分治法”的核心思路是将大问题拆分成更小且相似的子问题,通过递归解决这些子问题,最终合并子问题的解来得到原问题的解。 + +实现分治,关键在于对“递归函数”的设计(入参 & 返回值)。 + +在涉及数组的分治题中,左右下标 `l` 和 `r` 必然会作为函数入参,因为它能用于表示当前所处理的区间,即小问题的范围。 + +对于本题,仅将最大子数组和(答案)作为返回值并不足够,因为单纯从小区间的解无法直接推导出大区间的解,我们需要一些额外信息来辅助求解。 + +具体的,我们可以将返回值设计成四元组,分别代表 `区间和`,`前缀最大值`,`后缀最大值` 和 `最大子数组和`,用 `[sum, lm, rm, max]` 表示。 + +有了完整的函数签名 `int[] dfs(int[] nums, int l, int r)`,考虑如何实现分治: + +1. 根据当前区间 $[l, r]$ 的长度进行分情况讨论: + 1. 若 $l = r$,只有一个元素,区间和为 $nums[l]$,而 最大子数组和、前缀最大值 和 后缀最大值 由于允许“空数组”,因此均为 $\max(nums[l], 0)$ + 2. 否则,将当前问题划分为两个子问题,通常会划分为两个相同大小的子问题,划分为 $[l, mid]$ 和 $[mid + 1, r]$ 两份,递归求解,其中 $mid = \left \lfloor \frac{l + r}2{} \right \rfloor$ + +随后考虑如何用“子问题”的解合并成“原问题”的解: + +1. **合并区间和 (`sum`):** 当前问题的区间和等于左右两个子问题的区间和之和,即 `sum = left[0] + right[0]`。 +2. **合并前缀最大值 (`lm`):** 当前问题的前缀最大值可以是左子问题的前缀最大值,或者左子问题的区间和加上右子问题的前缀最大值。即 `lm = max(left[1], left[0] + right[1])`。 +3. **合并后缀最大值 (`rm`):** 当前问题的后缀最大值可以是右子问题的后缀最大值,或者右子问题的区间和加上左子问题的后缀最大值。即 `rm = max(right[2], right[0] + left[2])`。 +4. **合并最大子数组和 (`max`):** 当前问题的最大子数组和可能出现在左子问题、右子问题,或者跨越左右两个子问题的边界。因此,`max` 可以通过 `max(left[3], right[3], left[2] + right[1])` 来得到。 + +一些细节:由于我们在计算 `lm`、`rm` 和 `max` 的时候允许数组为空,而答案对子数组的要求是至少包含一个元素。因此对于 `nums` 全为负数的情况,我们会错误得出最大子数组和为 `0` 的答案。针对该情况,需特殊处理,遍历一遍 `nums`,若最大值为负数,直接返回最大值。 + +Java 代码: + +```Java +class Solution { + // 返回值: [sum, max, lm, rm] = [区间和, 最大子数组和, 前缀最大值, 后缀最大值] + int[] dfs(int[] nums, int l, int r) { + if (l == r) { + int t = Math.max(nums[l], 0); + return new int[]{nums[l], t, t, t}; + } + // 划分成两个子区间,分别求解 + int mid = l + r >> 1; + int[] left = dfs(nums, l, mid), right = dfs(nums, mid + 1, r); + // 组合左右子区间的信息,得到当前区间的信息 + int[] ans = new int[4]; + ans[0] = left[0] + right[0]; // 当前区间和 + ans[1] = Math.max(left[1], left[0] + right[1]); // 当前区间前缀最大值 + ans[2] = Math.max(right[2], right[0] + left[2]); // 当前区间后缀最大值 + ans[3] = Math.max(Math.max(left[3], right[3]), left[2] + right[1]); // 最大子数组和 + return ans; + } + public int maxSubArray(int[] nums) { + int m = nums[0]; + for (int x : nums) m = Math.max(m, x); + if (m <= 0) return m; + return dfs(nums, 0, nums.length - 1)[3]; + } +} +``` +C++ 代码: +```C++ +class Solution { +public: + // 返回值: [sum, max, lm, rm] = [区间和, 最大子数组和, 前缀最大值, 后缀最大值] + vector dfs(vector& nums, int l, int r) { + if (l == r) { + int t = max(nums[l], 0); + return {nums[l], t, t, t}; + } + // 划分成两个子区间,分别求解 + int mid = l + r >> 1; + auto left = dfs(nums, l, mid), right = dfs(nums, mid + 1, r); + // 组合左右子区间的信息,得到当前区间的信息 + vector ans(4); + ans[0] = left[0] + right[0]; // 当前区间和 + ans[1] = max(left[1], left[0] + right[1]); // 当前区间前缀最大值 + ans[2] = max(right[2], right[0] + left[2]); // 当前区间后缀最大值 + ans[3] = max({left[3], right[3], left[2] + right[1]}); // 最大子数组和 + return ans; + } + int maxSubArray(vector& nums) { + int m = nums[0]; + for (int x : nums) m = max(m, x); + if (m <= 0) return m; + return dfs(nums, 0, nums.size() - 1)[3]; + } +}; +``` +Python 代码: +```Python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + def dfs(l, r): + if l == r: + t = max(nums[l], 0) + return [nums[l], t, t, t] + # 划分成两个子区间,分别求解 + mid = (l + r) // 2 + left, right = dfs(l, mid), dfs(mid + 1, r) + # 组合左右子区间的信息,得到当前区间的信息 + ans = [0] * 4 + ans[0] = left[0] + right[0] # 当前区间和 + ans[1] = max(left[1], left[0] + right[1]) # 当前区间前缀最大值 + ans[2] = max(right[2], right[0] + left[2]) # 当前区间后缀最大值 + ans[3] = max(left[3], right[3], left[2] + right[1]) # 最大子数组和 + return ans + + m = max(nums) + if m <= 0: + return m + return dfs(0, len(nums) - 1)[3] +``` +TypeScript 代码: +```TypeScript +function maxSubArray(nums: number[]): number { + const dfs = function (l: number, r: number): number[] { + if (l == r) { + const t = Math.max(nums[l], 0); + return [nums[l], t, t, t]; + } + // 划分成两个子区间,分别求解 + const mid = (l + r) >> 1; + const left = dfs(l, mid), right = dfs(mid + 1, r); + // 组合左右子区间的信息,得到当前区间的信息 + const ans = Array(4).fill(0); + ans[0] = left[0] + right[0]; // 当前区间和 + ans[1] = Math.max(left[1], left[0] + right[1]); // 当前区间前缀最大值 + ans[2] = Math.max(right[2], right[0] + left[2]); // 当前区间后缀最大值 + ans[3] = Math.max(left[3], right[3], left[2] + right[1]); // 最大子数组和 + return ans; + } + + const m = Math.max(...nums); + if (m <= 0) return m; + return dfs(0, nums.length - 1)[3]; +}; +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(\log{n})$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.53` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 +