更新题目 2771. Longest Non-decreasing Subarray From Two Arrays
题目 lc2771 · 修改(rev 2) · · 2026-10-26 23:11
body
@@ -1,5 +1,13 @@ ## 题目 -两个等长数组 nums1、nums2。构造 nums3:每个位置从 nums1[i] 或 nums2[i] 里选一个。求 nums3 里最长的非递减连续子数组的长度的最大可能值。 +给两个长度都是 `n` 的整数数组 `nums1`、`nums2`。构造一个新数组 `nums3`:每个下标 `i` 上,`nums3[i]` 从 `nums1[i]` 和 `nums2[i]` 里任选一个。求 `nums3` 里最长的非递减连续子数组的长度,在所有选法里最大能是多少。函数返回一个整数。 + +非递减指每个数都不小于前一个数,相等也算。LeetCode 的范围是 `1 <= n <= 10^5`,`1 <= nums1[i], nums2[i] <= 10^9`。 + +| `nums1` | `nums2` | 输出 | 怎么选 | +| --- | --- | --- | --- | +| `[2, 3, 1]` | `[1, 2, 1]` | `2` | 选成 `[2, 2, 1]`,前两个非递减 | +| `[1, 3, 2, 1]` | `[2, 2, 3, 4]` | `4` | 选成 `[1, 2, 3, 4]`,整个数组非递减 | +| `[1, 1]` | `[2, 2]` | `2` | 选成 `[1, 2]` 或 `[1, 1]` | [LeetCode 原题](https://leetcode.com/problems/longest-non-decreasing-subarray-from-two-arrays/) @@ -7,32 +15,73 @@ ## 面经里怎么考的 -Visa 26NG 的 HackerRank OA 第三题(2026-04,楼主挂了):楼主写的题面和这题一致(每个下标从 A 或 B 选一个,求最长的连续非递减段),没写题号。同一场第一题是 [[q:visa-double-b-max-min-sum]]。 +Visa 26NG 的 HackerRank OA 第三题(2026-04,楼主挂了):楼主写的题面和这题一致(每个下标从 A 或 B 选一个,求最长的连续非递减段),没写题号,对应到 2771 是【推断】。同一场第一题是 [[q:visa-double-b-max-min-sum]]。下面按 LeetCode 原题写。 ## 思路 -状态 DP:`d1` 表示「以位置 i 结尾、位置 i 选 nums1[i]」时的最长长度,`d2` 是选 nums2[i] 的。位置 i 选值 v 时,能接在前一个位置的哪个选择后面,取决于 v 是否不小于它;接不上就从 1 重新开始。 +用两个状态做 DP,按下标从左往右推: + +- `end_on_1`:以下标 `i` 结尾、并且下标 `i` 选了 `nums1[i]` 的非递减段,最长有多长。 +- `end_on_2`:同样以下标 `i` 结尾,下标 `i` 选的是 `nums2[i]`。 + +两个状态就够用。一段以下标 `i` 结尾的段能不能接上下一个数,只看它的最后一个值,而最后一个值只有 `nums1[i]`、`nums2[i]` 两种,所以每种最后一个值只记最长的那一段。 + +转移:下标 `i` 选值 `v` 时,`v >= nums1[i - 1]` 就能接在 `end_on_1` 那段后面,长度是 `end_on_1 + 1`。`v >= nums2[i - 1]` 就能接在 `end_on_2` 那段后面,长度是 `end_on_2 + 1`。两边都接不上,就从 `v` 自己开始,长度为 1。三者取最大。答案是扫描过程中所有 `end_on_1`、`end_on_2` 的最大值。 + +> [!say] I keep two running lengths, one for ending on nums1[i] and one for ending on nums2[i], and each new value extends whichever previous choice it doesn't go below. ## 代码 ```python -def max_non_decreasing_length(nums1, nums2): - best = d1 = d2 = 1 +from typing import List + + +def max_non_decreasing_length(nums1: List[int], nums2: List[int]) -> int: + """Longest non-decreasing subarray of nums3, where nums3[i] is nums1[i] or nums2[i].""" + if len(nums1) != len(nums2): + raise ValueError("nums1 and nums2 must have the same length") + if not nums1: + return 0 + + best = 1 + end_on_1 = end_on_2 = 1 # longest run ending at i that picks nums1[i] / nums2[i] for i in range(1, len(nums1)): - a, b = nums1[i], nums2[i] - p1, p2 = nums1[i - 1], nums2[i - 1] - n1 = max(d1 + 1 if a >= p1 else 1, d2 + 1 if a >= p2 else 1) - n2 = max(d1 + 1 if b >= p1 else 1, d2 + 1 if b >= p2 else 1) - d1, d2 = n1, n2 - best = max(best, d1, d2) + prev1, prev2 = nums1[i - 1], nums2[i - 1] + cur1, cur2 = nums1[i], nums2[i] + end_on_1, end_on_2 = ( + longest_run_ending_with(cur1, prev1, prev2, end_on_1, end_on_2), + longest_run_ending_with(cur2, prev1, prev2, end_on_1, end_on_2), + ) + best = max(best, end_on_1, end_on_2) return best + + +def longest_run_ending_with(value: int, prev1: int, prev2: int, end_on_1: int, end_on_2: int) -> int: + """Longest run ending with `value`, given the two runs ending at the previous index.""" + length = 1 + if value >= prev1: + length = max(length, end_on_1 + 1) + if value >= prev2: + length = max(length, end_on_2 + 1) + return length ``` -本机跑过官方例子。 +招聘方说代码写法也计分,这一版有两处考虑: + +- 转移拆成 `longest_run_ending_with`,两个新状态用同一个函数算,主循环里不出现两行几乎一样的长表达式。两个新值放在一条语句里同时赋值,原因见「易错点」第三条。 +- 两个数组长度不同时抛 `ValueError`。LeetCode 保证 `n >= 1`,HackerRank 的题面不一定写,空数组里没有子数组,这里返回 0。 + +2026-10-26 用 Python 3.13 实际跑过: + +- 三个例子分别得 2、4、2。空数组返回 0,长度不同抛 `ValueError`。 +- 和暴力解对拍(暴力解枚举全部 `2^n` 种选法)。长度 1 到 5、每个数 0 到 3 的全部 1,118,480 对数组,加上 3000 组长度不超过 10 的随机数组,结果全部一致。 +- `n = 10^5` 时,随机数组耗时 28 毫秒,两个数组都是 `1, 2, …, 10^5` 时返回 `10^5`,耗时 35 毫秒。 ## 复杂度 -O(n) 时间,O(1) 空间。 +时间 `O(n)`,扫一遍,每个下标做常数次比较。额外空间 `O(1)`,只存两个状态和当前最大值。 -## 英文讲法 +## 易错点 -> [!say] I keep two running lengths, one for ending on nums1[i] and one for ending on nums2[i], and each new value extends whichever previous choice it doesn't go below. +- **只记一段、贪心挑能接上的较小值**。贪心会一直守着当前这段,不肯在较大的值后面断开、换成较小的值重新开始。`nums1 = [2, 1, 1, 1]`、`nums2 = [2, 2, 1, 1]` 时,贪心选成 `[2, 2, 1, 1]`,最长 2。正确选法是 `[2, 1, 1, 1]`,长度 3。实测暴力解和正确版本都给 3,贪心给 2。DP 同时保留两种结尾,才不会漏掉这种选法。 +- **只和同一个数组的上一位比**。写成 `nums1[i]` 只接 `nums1[i - 1]`、`nums2[i]` 只接 `nums2[i - 1]`,就漏了在两个数组之间来回换的选法。实测 `nums1 = [1, 3, 1]`、`nums2 = [3, 1, 4]`,正确答案是 3(选成 `[1, 3, 4]`),这个写法给 2。 +- **先改 `end_on_1`、再用它算 `end_on_2`**。分两行赋值时,算 `end_on_2` 拿到的是下标 `i` 的新 `end_on_1`,却拿它和 `nums1[i - 1]` 比。实测 `nums1 = nums2 = [1, 1]`,正确答案是 2,这个写法给 3,比数组还长。
meta
@@ -1,3 +1,6 @@ { - "lc_slug": "longest-non-decreasing-subarray-from-two-arrays" + "lc_slug": "longest-non-decreasing-subarray-from-two-arrays", + "depth_guide": "2026-10-23", + "rewritten_by": "cloud:20261026-1620", + "rewritten_at": "2026-10-27" }