2025-07-16:最长相邻绝对差递减子序列。用go语言,给定一个整数数组 nums,需要找出其中的一个最长子序列 seq,满足该子序列相邻元素间的绝对差值构成一个非递增序列。也就是说,对于子序列中的元素 seq0, seq1, seq2, …, seqm,要求绝对差 |seq1 - seq0| 不小于 |seq2 - seq1|,|seq2 - seq1| 不小于 |seq3 - seq2|,依此类推。
目标是求出满足上述条件的最长子序列的长度。
2 <= nums.length <= 10000。
1 <= nums[i] <= 300。
输入:nums = [16,6,3]。
输出:3。
解释:
最长子序列是 [16, 6, 3] ,相邻绝对差值为 [10, 3] 。
题目来自力扣3409。
解决思路
动态规划
动态规划是解决最长子序列问题的常见方法。我们需要设计一个状态表示和状态转移方程来高效地计算最长子序列的长度。
状态定义
定义 f[x][d]
表示以数字 x
结尾,且前一个数字与 x
的绝对差为 d
的最长子序列的长度。这里的 d
是当前子序列中最后两个元素的绝对差。
状态转移
对于数组中的每个数字 x
,我们需要考虑所有可能的 d
(即 x
可能与前面某个数字 y
的差为 d
),并更新 f[x][d]
的值。具体步骤如下:
- 初始化
f
为一个二维数组,大小为(max_num + 1) x (max_diff + 1)
,其中max_num
是数组中的最大值,max_diff
是数组中最大可能的差值(即max_num - min_num
)。 - 对于数组中的每个数字
x
,初始化fx
为 1(因为x
本身可以作为一个子序列)。 - 对于每个可能的差值
d
(从max_diff
到 0):- 如果存在
y = x - d
且y
在合法范围内(即y >= 0
),则可以尝试从f[y][d]
转移过来,即fx = max(fx, f[y][d] + 1)
。 - 如果存在
y = x + d
且y
在合法范围内(即y <= max_num
),则可以尝试从f[y][d]
转移过来,即fx = max(fx, f[y][d] + 1)
。 - 更新
f[x][d] = fx
,并更新全局最大值ans
。
- 如果存在
- 最终
ans
就是最长满足条件的子序列的长度。
为什么从 max_diff
到 0 遍历 d
?
因为我们需要保证非递增的差值序列。从大到小遍历 d
可以确保在更新 f[x][d]
时,d
是当前子序列的最后两个元素的差值,而前面的差值必须大于或等于 d
。这样可以自然地满足非递增的条件。
具体步骤
- 计算
max_num
和min_num
,得到max_diff = max_num - min_num
。 - 初始化二维数组
f
,大小为(max_num + 1) x (max_diff + 1)
,所有元素初始为 0。 - 初始化
ans = 0
。 - 对于
nums
中的每个数字x
:- 初始化
fx = 1
。 - 对于
d
从max_diff
到 0:- 如果
x - d >= 0
,则fx = max(fx, f[x - d][d] + 1)
。 - 如果
x + d <= max_num
,则fx = max(fx, f[x + d][d] + 1)
。 - 更新
f[x][d] = fx
。 - 更新
ans = max(ans, fx)
。
- 如果
- 初始化
- 返回
ans
。
时间复杂度和空间复杂度
时间复杂度
- 外层循环遍历
nums
,共n
次(n
是数组长度)。 - 内层循环遍历
d
,共max_diff + 1
次(max_diff
是max_num - min_num
)。 - 每次内层循环的操作是常数时间。
- 因此,总时间复杂度为
O(n * max_diff)
。- 由于
max_num
和min_num
的约束(nums[i] <= 300
),max_diff
最大为300 - 1 = 299
。 - 因此,时间复杂度可以视为
O(n * 300) = O(n)
(因为 300 是常数)。
- 由于
空间复杂度
- 二维数组
f
的大小为(max_num + 1) x (max_diff + 1)
。max_num + 1
最大为301
(因为nums[i] <= 300
)。max_diff + 1
最大为300
(因为max_diff <= 299
)。
- 因此,空间复杂度为
O(300 * 300) = O(1)
(常数空间)。
示例运行
以 nums = [16, 6, 3]
为例:
max_num = 16
,min_num = 3
,max_diff = 13
。- 初始化
f
为(17) x (14)
的二维数组(初始全 0)。 - 遍历
nums
:x = 16
:fx = 1
。- 遍历
d
从 13 到 0:- 对于每个
d
,f[16][d] = 1
。
- 对于每个
ans = 1
。
x = 6
:fx = 1
。- 遍历
d
从 13 到 0:d = 10
:y = 6 - 10 = -4
(不合法)。y = 6 + 10 = 16
(合法),f[16][10] = 1
,因此fx = max(1, 1 + 1) = 2
。
- 其他
d
不会更新fx
。 f[6][d]
更新为fx
(对于d = 10
是 2,其他是 1)。
ans = 2
。
x = 3
:fx = 1
。- 遍历
d
从 13 到 0:d = 3
:y = 3 - 3 = 0
(合法,但f[0][3]
未更新过,默认为 0)。y = 3 + 3 = 6
(合法),f[6][3] = 1
,因此fx = max(1, 1 + 1) = 2
。
d = 7
:y = 3 - 7 = -4
(不合法)。y = 3 + 7 = 10
(合法,但f[10][7]
未更新过,默认为 0)。
d = 10
:y = 3 - 10 = -7
(不合法)。y = 3 + 10 = 13
(合法,但f[13][10]
未更新过,默认为 0)。
d = 13
:y = 3 - 13 = -10
(不合法)。y = 3 + 13 = 16
(合法),f[16][13] = 1
,因此fx = max(1, 1 + 1) = 2
。
- 其他
d
不会更新fx
。 f[3][d]
更新为fx
(对于d = 3
是 2,其他是 1)。
ans
仍为 2(但实际应为 3,这里可能需要更细致的分析)。
- 实际正确运行:
- 对于
x = 3
,d = 3
:y = 6
(f[6][3]
应为 2,因为6
可以和16
形成差值 10,然后3
和6
形成差值 3)。- 因此
fx = max(1, 2 + 1) = 3
。
ans = 3
。
- 对于
总结
- 动态规划的状态设计是关键,通过
f[x][d]
记录以x
结尾且前一个差为d
的最长子序列长度。 - 通过从大到小遍历
d
确保差值的非递增性。 - 时间复杂度和空间复杂度均为线性(因为
max_diff
和max_num
是常数)。
Go完整代码如下:
package main
import (
"fmt"
"slices"
)
func longestSubsequence(nums []int) (ans int) {
mx := slices.Max(nums)
maxD := mx - slices.Min(nums)
f := make([][]int, mx+1)
for i := range f {
f[i] = make([]int, maxD+1)
}
for _, x := range nums {
fx := 1
for j := maxD; j >= 0; j-- {
if x-j >= 0 {
fx = max(fx, f[x-j][j]+1)
}
if x+j <= mx {
fx = max(fx, f[x+j][j]+1)
}
f[x][j] = fx
ans = max(ans, fx)
}
}
return
}
func main() {
nums := []int{16,6,3}
result := longestSubsequence(nums)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def longest_subsequence(nums):
mx = max(nums)
mn = min(nums)
maxD = mx - mn
# 创建二维数组 f,大小为 (mx+1) x (maxD+1),初始化为0
f = [[0] * (maxD + 1) for _ in range(mx + 1)]
ans = 0
for x in nums:
fx = 1
for j in range(maxD, -1, -1):
if x - j >= 0:
fx = max(fx, f[x - j][j] + 1)
if x + j <= mx:
fx = max(fx, f[x + j][j] + 1)
f[x][j] = fx
ans = max(ans, fx)
return ans
if __name__ == "__main__":
nums = [16, 6, 3]
result = longest_subsequence(nums)
print(result)