From 104901bae0a6003d779ee20d12f921a631fa26b5 Mon Sep 17 00:00:00 2001 From: AC_Oier Date: Sun, 28 Aug 2022 11:00:10 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feat:=20add=20239=E3=80=81793?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "Index/\344\272\214\345\210\206.md" | 1 + "Index/\345\210\206\346\262\273.md" | 1 + ...71\346\226\245\345\216\237\347\220\206.md" | 1 + "Index/\346\225\260\345\255\246.md" | 1 + ...10\345\233\260\351\232\276\357\274\211.md" | 243 ++++++++++++++++++ ...10\344\270\255\347\255\211\357\274\211.md" | 24 +- ...10\345\233\260\351\232\276\357\274\211.md" | 119 +++++++++ ...10\344\270\255\347\255\211\357\274\211.md" | 2 +- 8 files changed, 382 insertions(+), 10 deletions(-) create mode 100644 "LeetCode/231-240/239. \346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274\357\274\210\345\233\260\351\232\276\357\274\211.md" create mode 100644 "LeetCode/791-800/793. \351\230\266\344\271\230\345\207\275\346\225\260\345\220\216 K \344\270\252\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" diff --git "a/Index/\344\272\214\345\210\206.md" "b/Index/\344\272\214\345\210\206.md" index f4be06cf..e7621df4 100644 --- "a/Index/\344\272\214\345\210\206.md" +++ "b/Index/\344\272\214\345\210\206.md" @@ -41,6 +41,7 @@ | [744. 寻找比目标字母大的最小字母](https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/solution/by-ac_oier-to07/) | 简单 | 🤩🤩🤩🤩🤩 | | [778. 水位上升的泳池中游泳](https://leetcode-cn.com/problems/swim-in-rising-water/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/swim-in-rising-water/solution/gong-shui-san-xie-yi-ti-shuang-jie-krusk-7c6o/) | 困难 | 🤩🤩🤩 | | [786. 第 K 个最小的素数分数](https://leetcode-cn.com/problems/k-th-smallest-prime-fraction/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/k-th-smallest-prime-fraction/solution/gong-shui-san-xie-yi-ti-shuang-jie-you-x-8ymk/) | 中等 | 🤩🤩🤩 | +| [793. 阶乘函数后 K 个零](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/) | [LeetCode 题解链接](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/solution/by-ac_oier-pk9g/) | 困难 | 🤩🤩🤩🤩 | | [852. 山脉数组的峰顶索引](https://leetcode-cn.com/problems/peak-index-in-a-mountain-array/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/peak-index-in-a-mountain-array/solution/gong-shui-san-xie-er-fen-san-fen-cha-zhi-5gfv/) | 简单 | 🤩🤩🤩🤩🤩 | | [875. 爱吃香蕉的珂珂](https://leetcode.cn/problems/koko-eating-bananas/) | [LeetCode 题解链接](https://leetcode.cn/problems/koko-eating-bananas/solution/by-ac_oier-4z7i/) | 中等 | 🤩🤩🤩🤩 | | [911. 在线选举](https://leetcode-cn.com/problems/online-election/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/online-election/solution/gong-shui-san-xie-er-fen-yun-yong-ti-by-5y3hi/) | 中等 | 🤩🤩🤩🤩🤩 | diff --git "a/Index/\345\210\206\346\262\273.md" "b/Index/\345\210\206\346\262\273.md" index fe8068f0..2edbcf4c 100644 --- "a/Index/\345\210\206\346\262\273.md" +++ "b/Index/\345\210\206\346\262\273.md" @@ -1,4 +1,5 @@ | 题目 | 题解 | 难度 | 推荐指数 | | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---- | -------- | | [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/) | 困难 | 🤩🤩🤩🤩 | +| [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\256\271\346\226\245\345\216\237\347\220\206.md" "b/Index/\345\256\271\346\226\245\345\216\237\347\220\206.md" index fff8864d..20b4e10a 100644 --- "a/Index/\345\256\271\346\226\245\345\216\237\347\220\206.md" +++ "b/Index/\345\256\271\346\226\245\345\216\237\347\220\206.md" @@ -17,6 +17,7 @@ | [673. 最长递增子序列的个数](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/solution/gong-shui-san-xie-lis-de-fang-an-shu-wen-obuz/) | 中等 | 🤩🤩🤩🤩 | | [689. 三个无重叠子数组的最大和](https://leetcode-cn.com/problems/maximum-sum-of-3-non-overlapping-subarrays/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/maximum-sum-of-3-non-overlapping-subarrays/solution/gong-shui-san-xie-jie-he-qian-zhui-he-de-ancx/) | 困难 | 🤩🤩🤩 | | [724. 寻找数组的中心下标](https://leetcode-cn.com/problems/find-pivot-index/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/find-pivot-index/solution/shi-yong-shao-bing-ji-qiao-liang-bian-qi-vkju/) | 简单 | 🤩🤩🤩🤩🤩 | +| [793. 阶乘函数后 K 个零](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/) | [LeetCode 题解链接](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/solution/by-ac_oier-pk9g/) | 困难 | 🤩🤩🤩🤩 | | [825. 适龄的朋友](https://leetcode-cn.com/problems/friends-of-appropriate-ages/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/friends-of-appropriate-ages/solution/gong-shui-san-xie-yi-ti-shuang-jie-pai-x-maa8/) | 中等 | 🤩🤩🤩🤩 | | [926. 将字符串翻转到单调递增](https://leetcode.cn/problems/flip-string-to-monotone-increasing/) | [LeetCode 题解链接](https://leetcode.cn/problems/flip-string-to-monotone-increasing/solution/by-ac_oier-h0we/) | 中等 | 🤩🤩🤩🤩 | | [930. 和相同的二元子数组](https://leetcode-cn.com/problems/binary-subarrays-with-sum/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/binary-subarrays-with-sum/solution/gong-shui-san-xie-yi-ti-shuang-jie-qian-hfoc0/) | 中等 | 🤩🤩🤩 | diff --git "a/Index/\346\225\260\345\255\246.md" "b/Index/\346\225\260\345\255\246.md" index e65334dc..8972f709 100644 --- "a/Index/\346\225\260\345\255\246.md" +++ "b/Index/\346\225\260\345\255\246.md" @@ -48,6 +48,7 @@ | [650. 只有两个键的键盘](https://leetcode-cn.com/problems/2-keys-keyboard/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/2-keys-keyboard/solution/gong-shui-san-xie-yi-ti-san-jie-dong-tai-f035/) | 中等 | 🤩🤩🤩🤩 | | [780. 到达终点](https://leetcode-cn.com/problems/reaching-points/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/reaching-points/solution/by-ac_oier-hw11/) | 困难 | 🤩🤩🤩🤩🤩 | | [789. 逃脱阻碍者](https://leetcode-cn.com/problems/escape-the-ghosts/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/escape-the-ghosts/solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-w69gr/) | 中等 | 🤩🤩🤩🤩🤩 | +| [793. 阶乘函数后 K 个零](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/) | [LeetCode 题解链接](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/solution/by-ac_oier-pk9g/) | 困难 | 🤩🤩🤩🤩 | | [810. 黑板异或游戏](https://leetcode-cn.com/problems/chalkboard-xor-game/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/chalkboard-xor-game/solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-ges7k/) | 困难 | 🤩🤩🤩🤩 | | [829. 连续整数求和](https://leetcode.cn/problems/consecutive-numbers-sum/) | [LeetCode 题解链接](https://leetcode.cn/problems/consecutive-numbers-sum/solution/by-ac_oier-220q/) | 困难 | 🤩🤩🤩🤩 | | [869. 重新排序得到 2 的幂](https://leetcode-cn.com/problems/reordered-power-of-2/) | [LeetCode 题解链接](https://leetcode-cn.com/problems/reordered-power-of-2/solution/gong-shui-san-xie-yi-ti-shuang-jie-dfs-c-3s1e/) | 中等 | 🤩🤩🤩🤩 | diff --git "a/LeetCode/231-240/239. \346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/231-240/239. \346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274\357\274\210\345\233\260\351\232\276\357\274\211.md" new file mode 100644 index 00000000..33596d78 --- /dev/null +++ "b/LeetCode/231-240/239. \346\273\221\345\212\250\347\252\227\345\217\243\346\234\200\345\244\247\345\200\274\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -0,0 +1,243 @@ +### 题目描述 + +这是 LeetCode 上的 **[239. 滑动窗口最大值]()** ,难度为 **困难**。 + +Tag : 「优先队列(堆)」、「线段树」、「分块」、「单调队列」、「RMQ」 + + + +给你一个整数数组 `nums`,有一个大小为 `k` 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 `k` 个数字。滑动窗口每次只向右移动一位。 + +返回滑动窗口中的最大值 。 + +示例 1: +``` +输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 + +输出:[3,3,5,5,6,7] + +解释: +滑动窗口的位置 最大值 +--------------- ----- +[1 3 -1] -3 5 3 6 7 3 + 1 [3 -1 -3] 5 3 6 7 3 + 1 3 [-1 -3 5] 3 6 7 5 + 1 3 -1 [-3 5 3] 6 7 5 + 1 3 -1 -3 [5 3 6] 7 6 + 1 3 -1 -3 5 [3 6 7] 7 +``` +示例 2: +``` +输入:nums = [1], k = 1 + +输出:[1] +``` + +提示: +* $1 <= nums.length <= 10^5$ +* $-10^4 <= nums[i] <= 10^4$ +* $1 <= k <= nums.length$ + +--- + +### 优先队列(堆) + +根据题意,容易想到优先队列(大根堆),同时为了确保滑动窗口的大小合法性,我们以二元组 $(idx, nums[idx])$ 的形式进行入队。 + +当下标达到首个滑动窗口的右端点后,每次尝试从优先队列(大根堆)中取出最大值(若堆顶元素的下标小于当前滑动窗口左端点时,则丢弃该元素)。 + +代码: +```Java +class Solution { + public int[] maxSlidingWindow(int[] nums, int k) { + PriorityQueue q = new PriorityQueue<>((a,b)->b[1]-a[1]); + int n = nums.length, m = n - k + 1, idx = 0; + int[] ans = new int[m]; + for (int i = 0; i < n; i++) { + q.add(new int[]{i, nums[i]}); + if (i >= k - 1) { + while (q.peek()[0] <= i - k) q.poll(); + ans[idx++] = q.peek()[1]; + } + } + return ans; + } +} +``` +* 时间复杂度:$O(n\log{n})$ +* 空间复杂度:$O(n)$ + +--- + +### 线段树 + +容易将问题转换为「区间求和」问题:使用原始的 `nums` 构建线段树等价于在位置 $i$ 插入 $nums[i]$,即单点操作,而查询每个滑动窗口最大值,则对应的区间查询。 + +由于只涉及单点修改,无须实现懒标记 `pushdown` 操作,再结合 $n$ 的数据范围为 $10^5$,无须进行动态开点。 + +直接写 `build` 四倍空间的线段树数组实现即可。 + +代码: +```Java +class Solution { + class Node { + int l, r, val; + Node (int _l, int _r) { + l = _l; r = _r; val = Integer.MIN_VALUE; + } + } + Node[] tr = new Node[100010 * 4]; + void build(int u, int l, int r) { + tr[u] = new Node(l, r); + if (l == r) return ; + int mid = l + r >> 1; + build(u << 1, l, mid); + build(u << 1 | 1, mid + 1, r); + } + void update(int u, int x, int v) { + if (tr[u].l == x && tr[u].r == x) { + tr[u].val = Math.max(tr[u].val, v); + return ; + } + int mid = tr[u].l + tr[u].r >> 1; + if (x <= mid) update(u << 1, x, v); + else update(u << 1 | 1, x, v); + pushup(u); + } + int query(int u, int l, int r) { + if (l <= tr[u].l && tr[u].r <= r) return tr[u].val; + int mid = tr[u].l + tr[u].r >> 1, ans = Integer.MIN_VALUE; + if (l <= mid) ans = query(u << 1, l, r); + if (r > mid) ans = Math.max(ans, query(u << 1 | 1, l, r)); + return ans; + } + void pushup(int u) { + tr[u].val = Math.max(tr[u << 1].val, tr[u << 1 | 1].val); + } + public int[] maxSlidingWindow(int[] nums, int k) { + int n = nums.length, m = n - k + 1; + int[] ans = new int[m]; + build(1, 1, n); + for (int i = 0; i < n; i++) update(1, i + 1, nums[i]); + for (int i = k; i <= n; i++) ans[i - k] = query(1, i - k + 1, i); + return ans; + } +} +``` +* 时间复杂度:建立线段树复杂度为 $O(n\log{n})$;构建答案复杂度为 $O(n\log{n})$。整体复杂度为 $O(n\log{n})$ +* 空间复杂度:$O(n)$ + +--- + +### 分块 + +另外一个做法是分块,又名「优雅的暴力」,也是莫队算法的基础。 + +具体的,除了给定的 `nums` 以外,我们构建一个分块数组 `region`,其中 `region[idx] = x` 含义为块编号为 `idx` 的最大值为 `x`,其中一个块对应一个原始区间 $[l, r]$。 + +如何定义块大小是实现分块算法的关键。 + +对于本题,本质是求若干个大小为 $k$ 的区间最大值。 + +我们可以设定块大小为 $\sqrt{k}$,这样所需创建的分块数组大小为 $\frac{n}{\sqrt{k}}$。分块数组的更新操作为 $O(1)$,而查询则为 $\sqrt{k}$。 + +容易证明查询操作的复杂度:对于每个长度为 $k$ 的 $[l, r]$ 查询操作而言,最多遍历两个(左右端点对应的块)的块内元素,复杂度为 $O(\sqrt{k})$,同时最多遍历 $\sqrt{k}$ 个块,复杂度同为 $O(\sqrt{k})$。 + +因此最多两步复杂度为 $O(\sqrt{k})$ 的块内操作,最多 $\sqrt{k}$ 步复杂度为 $O(1)$ 的块间操作,整体复杂度为 $O(\sqrt{k})$。 + +因此使用分块算法总的计算量为 $n\times\sqrt{k} = 10^6$,可以过。 + +分块算法的几个操作函数: + +* `int getIdx(int x)` :计算原始下标对应的块编号; +* `void add(int x, int v)` :计算原始下标 `x` 对应的下标 `idx`,并将 `region[idx]` 和 `v` 取 `max` 来更新 `region[idx]`; +* `int query(int l, int r)` :查询 $[l, r]$ 中的最大值,如果 $l$ 和 $r$ 所在块相同,直接遍历 $[l, r]$ 进行取值;若 $l$ 和 $r$ 不同块,则处理 $l$ 和 $r$ 对应的块内元素后,对块编号在 $(getIdx(l), getIdx(r))$ 之间的块进行遍历。 + +代码: +```Java +class Solution { + int n, m, len; + int[] nums, region; + int getIdx(int x) { + return x / len; + } + void add(int x, int v) { + region[getIdx(x)] = Math.max(region[getIdx(x)], v); + } + int query(int l, int r) { + int ans = Integer.MIN_VALUE; + if (getIdx(l) == getIdx(r)) { + for (int i = l; i <= r; i++) ans = Math.max(ans, nums[i]); + } else { + int i = l, j = r; + while (getIdx(i) == getIdx(l)) ans = Math.max(ans, nums[i++]); + while (getIdx(j) == getIdx(r)) ans = Math.max(ans, nums[j--]); + for (int k = getIdx(i); k <= getIdx(j); k++) ans = Math.max(ans, region[k]); + } + return ans; + } + public int[] maxSlidingWindow(int[] _nums, int k) { + nums = _nums; + n = nums.length; len = (int) Math.sqrt(k); m = n / len + 10; + region = new int[m]; + Arrays.fill(region, Integer.MIN_VALUE); + for (int i = 0; i < n; i++) add(i, nums[i]); + int[] ans = new int[n - k + 1]; + for (int i = 0; i < n - k + 1; i++) ans[i] = query(i, i + k - 1); + return ans; + } +} +``` +* 时间复杂度:数组大小为 $n$,块大小为 $\sqrt{k}$,分块数组大小为 $\frac{n}{\sqrt{k}}$。预处理分块数组复杂度为 $O(n)$(即 `add` 操作复杂度为 $O(1)$ );构造答案复杂度为 $O(n\times\sqrt{k})$(即 `query` 操作复杂度为 $O(\sqrt{k})$,最多有 $n$ 次查询) +* 空间复杂度:$\frac{n}{\sqrt{k}}$ + +--- + +### 单调队列 + +关于 `RMQ` 的另外一个优秀做法通常是使用「单调队列/单调栈」。 + +当然,我们也能不依靠经验,而从问题的本身出发,逐步分析出该做法。 + +假设我们当前处理到某个长度为 $k$ 的窗口,此时窗口往后滑动一格,会导致后一个数(新窗口的右端点)添加进来,同时会导致前一个数(旧窗口的左端点)移出窗口。 + +随着窗口的不断平移,该过程会一直发生。**若同一时刻存在两个数 $nums[j]$ 和 $nums[i]$($j < i$)所在一个窗口内,下标更大的数会被更晚移出窗口,此时如果有 $nums[j] <= nums[i]$ 的话,可以完全确定 $nums[j]$ 将不会成为后续任何一个窗口的最大值,此时可以将必然不会是答案的 $nums[j]$ 从候选中进行移除**。 + +不难发现,当我们将所有必然不可能作为答案的元素(即所有满足的小于等于 $nums[i]$ )移除后,候选集合满足「单调递减」特性,即集合首位元素为当前窗口中的最大值(为了满足窗口长度为 $k$ 的要求,在从集合头部取答案时需要先将下标小于的等于的 $i - k$ 的元素移除)。 + +为方便从尾部添加元素,从头部获取答案,我们可使用「双端队列」存储所有候选元素。 + +代码: +```Java +class Solution { + public int[] maxSlidingWindow(int[] nums, int k) { + Deque d = new ArrayDeque<>(); + int n = nums.length, m = n - k + 1; + int[] ans = new int[m]; + for (int i = 0; i < n; i++) { + while (!d.isEmpty() && nums[d.peekLast()] <= nums[i]) d.pollLast(); + d.addLast(i); + if (i >= k - 1) { + while (!d.isEmpty() && d.peekFirst() <= i - k) d.pollFirst(); + ans[i - k + 1] = nums[d.peekFirst()]; + } + } + return ans; + } +} +``` +* 时间复杂度:$O(n)$ +* 空间复杂度:$O(n)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.239` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/661-670/662. \344\272\214\345\217\211\346\240\221\346\234\200\345\244\247\345\256\275\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/661-670/662. \344\272\214\345\217\211\346\240\221\346\234\200\345\244\247\345\256\275\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" index 2e19da44..77ca7506 100644 --- "a/LeetCode/661-670/662. \344\272\214\345\217\211\346\240\221\346\234\200\345\244\247\345\256\275\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/661-670/662. \344\272\214\345\217\211\346\240\221\346\234\200\345\244\247\345\256\275\345\272\246\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -56,6 +56,10 @@ Tag : 「二叉树」、「DFS」、「哈希表」 而实现上,我们可以利用先 `DFS` 左节点,再 `DFS` 右节点的性质可知,每层的最左节点必然是最先被遍历到,因此我们只需要记录当前层最先被遍历到点编号(即当前层最小节点编号),并在 `DFS` 过程中计算宽度,更新答案即可。 +> 看到评论区有同学讨论关于编号溢出问题,之所以溢出仍能 `AC` 是因为测试数组中没有同层内「宽度」左端点不溢出,右端点溢出,同时该层就是最大宽度的数据点。 +我们可以通过 `u = u - map.get(depth) + 1` 操作来对同层内的节点进行重新编号(使得同层最靠左的非空节点编号为 $1$)。 +通过重编号操作 我们可以消除由于深度加深带来的编号溢出问题,同时 `TS` 代码不再需要使用 `bigint`。 + Java 代码: ```Java class Solution { @@ -69,6 +73,7 @@ class Solution { if (root == null) return ; if (!map.containsKey(depth)) map.put(depth, u); ans = Math.max(ans, u - map.get(depth) + 1); + u = u - map.get(depth) + 1; dfs(root.left, u << 1, depth + 1); dfs(root.right, u << 1 | 1, depth + 1); } @@ -76,20 +81,21 @@ class Solution { ``` TypeScript 代码: ```TypeScript -let map = new Map() -let ans = 0n +let map = new Map() +let ans = 0 function widthOfBinaryTree(root: TreeNode | null): number { map.clear() - ans = 0n - dfs(root, 1n, 0) - return Number(ans) + ans = 0 + dfs(root, 1, 0) + return ans }; -function dfs(root: TreeNode | null, u: bigint, depth: number): void { +function dfs(root: TreeNode | null, u: number, depth: number): void { if (root == null) return if (!map.has(depth)) map.set(depth, u) - if (u - map.get(depth) + 1n > ans) ans = u - map.get(depth) + 1n - dfs(root.left, u << 1n, depth + 1) - dfs(root.right, u << 1n | 1n, depth + 1) + ans = Math.max(ans, u - map.get(depth) + 1) + u = u - map.get(depth) + 1 + dfs(root.left, u << 1, depth + 1) + dfs(root.right, u << 1 | 1, depth + 1) } ``` * 时间复杂度:$O(n)$ diff --git "a/LeetCode/791-800/793. \351\230\266\344\271\230\345\207\275\346\225\260\345\220\216 K \344\270\252\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" "b/LeetCode/791-800/793. \351\230\266\344\271\230\345\207\275\346\225\260\345\220\216 K \344\270\252\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" new file mode 100644 index 00000000..809e6055 --- /dev/null +++ "b/LeetCode/791-800/793. \351\230\266\344\271\230\345\207\275\346\225\260\345\220\216 K \344\270\252\351\233\266\357\274\210\345\233\260\351\232\276\357\274\211.md" @@ -0,0 +1,119 @@ +### 题目描述 + +这是 LeetCode 上的 **[793. 阶乘函数后 K 个零](https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function/solution/by-ac_oier-pk9g/)** ,难度为 **困难**。 + +Tag : 「数学」、「二分」、「容斥原理」 + + + +`f(x)` 是 `x!` 末尾是 `0` 的数量。回想一下 `x! = 1 * 2 * 3 * ... * x`,且 `0! = 1`。 + +例如, `f(3) = 0`,因为 `3! = 6` 的末尾没有 `0` ;而 `f(11) = 2`,因为 `11!= 39916800` 末端有 `2` 个 `0` 。 + +给定 `k`,找出返回能满足 `f(x) = k` 的非负整数 `x` 的数量。 + +示例 1: +``` +输入:k = 0 + +输出:5 + +解释:0!, 1!, 2!, 3!, 和 4! 均符合 k = 0 的条件。 +``` +示例 2: +``` +输入:k = 5 + +输出:0 + +解释:没有匹配到这样的 x!,符合 k = 5 的条件。 +``` +示例 3: +``` +输入: k = 3 + +输出: 5 +``` + +提示: +* $0 <= k <= 10^9$ + +--- + +### 数学 + 二分 + +对于一个 $n! = 1 \times 2 \times ... \times (n - 1) \times n$ 而言,其最终结果尾部包含 $0$ 的数量取决于其被累乘 $10$ 的次数,而 $10$ 可通过质因数 $2$ 和 $5$ 相乘而来,因此假设对 $n!$ 中的每一数进行阶乘分解,最终分解出 $2^p$ 和 $5^q$ 的话,那么最终结果尾部包含 $0$ 的个数为 $q$ 个(可证明 $p <= q$ 始终满足)。 + +因此原问题转化为:在非负整数中,有多少个数进行阶乘分解后,所含质因数 $5$ 的个数为 $k$ 个。 + +同时我们可知:随着 $n$ 的增大,其所能分解出来的 $5$ 的个数必然是递增的。 + +基于此,我们可以通过「二分 + 容斥原理」来得出分解 $5$ 个数恰好为 $k$ 的连续段长度。假设我们存在函数 `f(k)` 可得到非负整数中分解 $5$ 个数为小于等于 `k` 的个数,那么最终 `f(k) - f(k - 1)` 即答案。 + +在单调函数上求解小于等于 `k` 分割点,可通过「二分」来做,剩下的问题是,如何求得给定 `x` 时,其阶乘分解所包含 $5$ 个个数,这可以通过 $O(\log{x})$ 的筛法来做。 + +最后还要确定二分的值域,由于我们是求阶乘分解中 $5$ 的个数,因此值域的上界为 $5k$,利用 $k$ 的范围为 $1e9$,直接取成 $1e10$ 即可。 + +Java 代码: +```Java +class Solution { + public int preimageSizeFZF(int k) { + if (k <= 1) return 5; + return f(k) - f(k - 1); + } + int f(int x) { + long l = 0, r = (long) 1e10; + while (l < r) { + long mid = l + r + 1 >> 1; + if (getCnt(mid) <= x) l = mid; + else r = mid - 1; + } + return (int)r; + } + long getCnt(long x) { + long ans = 0; + while (x != 0) { + ans += x / 5; x /= 5; + } + return ans; + } +} +``` +TypeScript 代码: +```TypeScript +function preimageSizeFZF(k: number): number { + if (k <= 1) return 5 + return f(k) - f(k - 1) +}; +function f(x: number): number { + let l = 0n, r = BigInt("10000000000") + while (l < r) { + const mid = l + r + 1n >> 1n + if (getCnt(mid) <= x) l = mid + else r = mid - 1n + } + return Number(r) +} +function getCnt(x: bigint): bigint { + let ans = 0n + while (x != 0n) { + ans += BigInt(Math.floor(Number(x / 5n))); x = BigInt(Math.floor(Number(x / 5n))) + } + return ans +} +``` +* 时间复杂度:$O(\log{k})$ +* 空间复杂度:$O(1)$ + +--- + +### 最后 + +这是我们「刷穿 LeetCode」系列文章的第 `No.793` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 + +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 + +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 + +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 + diff --git "a/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 008. \345\222\214\345\244\247\344\272\216\347\255\211\344\272\216 target \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" "b/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 008. \345\222\214\345\244\247\344\272\216\347\255\211\344\272\216 target \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" index 5c1e9c59..7e8e273a 100644 --- "a/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 008. \345\222\214\345\244\247\344\272\216\347\255\211\344\272\216 target \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" +++ "b/LeetCode/\345\211\221\346\214\207 Offer II/\345\211\221\346\214\207 Offer II 008. \345\222\214\345\244\247\344\272\216\347\255\211\344\272\216 target \347\232\204\346\234\200\347\237\255\345\255\220\346\225\260\347\273\204\357\274\210\344\270\255\347\255\211\357\274\211.md" @@ -8,7 +8,7 @@ Tag : 「前缀和」、「二分」 给定一个含有 `n` 个正整数的数组和一个正整数 `target`。 -找出该数组中满足其和 `≥ target` 的长度最小的 连续子数组 $[nums_l, nums_{l+1}, ..., nums_{r-1}, nums_r]$ ,并返回其长度。如果不存在符合条件的子数组,返回 $0$ 。 +找出该数组中满足其和 `≥ target` 的长度最小的 连续子数组 $[nums_{l}, nums_{l+1}, ..., nums_{r-1}, nums_{r}]$ ,并返回其长度。如果不存在符合条件的子数组,返回 $0$ 。 示例 1: ```