普通视图

发现新文章,点击刷新页面。
今天 — 2026年1月8日LeetCode 每日一题题解

每日一题-两个子序列的最大点积🔴

2026年1月8日 00:00

给你两个数组 nums1 和 nums2 。

请你返回 nums1nums2 中两个长度相同的 非空 子序列的最大点积。

数组的非空子序列是通过删除原数组中某些元素(可能一个也不删除)后剩余数字组成的序列,但不能改变数字间相对顺序。比方说,[2,3,5] 是 [1,2,3,4,5] 的一个子序列而 [1,5,3] 不是。

 

示例 1:

输入:nums1 = [2,1,-2,5], nums2 = [3,0,-6]
输出:18
解释:从 nums1 中得到子序列 [2,-2] ,从 nums2 中得到子序列 [3,-6] 。
它们的点积为 (2*3 + (-2)*(-6)) = 18 。

示例 2:

输入:nums1 = [3,-2], nums2 = [2,-6,7]
输出:21
解释:从 nums1 中得到子序列 [3] ,从 nums2 中得到子序列 [7] 。
它们的点积为 (3*7) = 21 。

示例 3:

输入:nums1 = [-1,-1], nums2 = [1,1]
输出:-1
解释:从 nums1 中得到子序列 [-1] ,从 nums2 中得到子序列 [1] 。
它们的点积为 -1 。

 

提示:

  • 1 <= nums1.length, nums2.length <= 500
  • -1000 <= nums1[i], nums2[i] <= 1000

 

点积:

定义 a = [a1a2,…, an] b = [b1b2,…, bn] 的点积为:

\mathbf{a}\cdot \mathbf{b} = \sum_{i=1}^n a_ib_i = a_1b_1 + a_2b_2 + \cdots + a_nb_n 

这里的 Σ 指示总和符号。

教你一步步思考 DP:从记忆化搜索到递推到空间优化(Python/Java/C++/Go)

作者 endlesscheng
2025年12月31日 12:12

一、寻找子问题

为方便描述,下文把 $\textit{nums}_1$ 和 $\textit{nums}_2$ 简称为 $a$ 和 $b$。

在示例 1 中,我们要解决的问题(原问题)是:

  • 从 $a=[2,1,-2,5]$ 和 $b=[3,0,-6]$ 中选两个长度相等的非空子序列 $c$ 和 $d$,计算 $c$ 和 $d$ 的点积的最大值。

注意:选出的子序列必须是非空的。

考虑从右往左选数字,用「选或不选」分类讨论:

  • 选 $a[3]$ 和 $b[2]$,需要解决的子问题为:从 $a=[2,1,-2]$ 和 $b=[3,0]$ 中选两个长度相等的子序列,计算两个子序列点积的最大值。由于我们选了元素,所以子序列可以为空。但这样思考的话,子问题就和原问题不相似了。为了保证子问题和原问题相似,我们可以再细分为两种情况:
    • 选 $a[3]$ 和 $b[2]$,且前面不再选数字。这意味着点积就是 $a[3]\cdot b[2]$。
    • 选 $a[3]$ 和 $b[2]$,且前面还要选数字。需要解决的子问题为:从 $a=[2,1,-2]$ 和 $b=[3,0]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值。
  • 不选 $a[3]$,需要解决的子问题为:从 $a=[2,1,-2]$ 和 $b=[3,0,-6]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值。
  • 不选 $b[2]$,需要解决的子问题为:从 $a=[2,1,-2,5]$ 和 $b=[3,0]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值。

由于选或不选都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。

注 1:从右往左思考,主要是为了方便把递归翻译成递推。从左往右思考也是可以的。

注 2:动态规划有「选或不选」和「枚举选哪个」两种基本思考方式。子序列相邻无关一般是「选或不选」,子序列相邻相关(例如 LIS 问题)一般是「枚举选哪个」。本题用到的是「选或不选」。

二、状态定义与状态转移方程

根据上面的讨论,定义状态为 $\textit{dfs}(i,j)$,表示从 $a$ 的前缀 $[0,i]$ 和 $b$ 的前缀 $[0,j]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值。

接下来,思考如何从一个状态转移到另一个状态。

用「选或不选」分类讨论:

  • 选 $a[i]$ 和 $b[j]$,且前面不再选数字。这意味着点积就是 $a[i]\cdot b[j]$。
  • 选 $a[i]$ 和 $b[j]$,且前面还要选数字。需要解决的子问题为:从 $a$ 的前缀 $[0,i-1]$ 和 $b$ 的前缀 $[0,j-1]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值,即 $\textit{dfs}(i-1,j-1)$。再加上 $a[i]\cdot b[j]$,就得到了 $\textit{dfs}(i,j)$。
  • 前两种情况可以合并为:$\max(\textit{dfs}(i-1,j-1), 0) + a[i]\cdot b[j]$。
  • 不选 $a[i]$,需要解决的子问题为:从 $a$ 的前缀 $[0,i-1]$ 和 $b$ 的前缀 $[0,j]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值,即 $\textit{dfs}(i-1,j)$。
  • 不选 $b[j]$,需要解决的子问题为:从 $a$ 的前缀 $[0,i]$ 和 $b$ 的前缀 $[0,j-1]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值,即 $\textit{dfs}(i,j-1)$。

这四种情况取最大值,就得到了 $\textit{dfs}(i,j)$,即

$$
\textit{dfs}(i,j) = \max{\max(\textit{dfs}(i-1,j-1), 0) + a[i]\cdot b[j], \textit{dfs}(i-1,j),\textit{dfs}(i,j-1)}
$$

递归边界:$\textit{dfs}(-1,j)=\textit{dfs}(i,-1)=-\infty$。此时其中一个数组没有元素,无法选出非空子序列,不合法。用 $-\infty$ 表示不合法的状态,从而保证 $\max$ 不会取到不合法的状态。

递归入口:$\textit{dfs}(n-1,m-1)$,这是原问题,也是答案。其中 $n$ 是 $a$ 的长度,$m$ 是 $b$ 的长度。

三、递归搜索 + 保存递归返回值 = 记忆化搜索

考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:

  • 如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 $\textit{memo}$ 数组中。
  • 如果一个状态不是第一次遇到($\textit{memo}$ 中保存的结果不等于 $\textit{memo}$ 的初始值),那么可以直接返回 $\textit{memo}$ 中保存的结果。

注意:$\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$,但本题子序列点积可能是负数,可以初始化为 $\infty$。

Python 用户可以无视上面这段,直接用 @cache 装饰器。

具体请看视频讲解 动态规划入门:从记忆化搜索到递推【基础算法精讲 17】,其中包含把记忆化搜索 1:1 翻译成递推的技巧。

###py

class Solution:
    def maxDotProduct(self, nums1: List[int], nums2: List[int]) -> int:
        # 返回从 nums1[:i+1] 和 nums2[:j+1] 中选两个长度相同的【非空】子序列的最大点积
        @cache  # 缓存装饰器,避免重复计算 dfs(一行代码实现记忆化)
        def dfs(i: int, j: int) -> int:
            if i < 0 or j < 0:
                # 其中一个数组没有元素,无法选出非空子序列
                return -inf  # 下面计算 max 不会取到无解情况

            # 选 nums1[i] 和 nums2[j]
            # 和前面的子序列拼起来,或者不拼(作为子序列的第一个数)
            res1 = max(dfs(i - 1, j - 1), 0) + nums1[i] * nums2[j]

            # 不选 nums1[i]
            res2 = dfs(i - 1, j)

            # 不选 nums2[j]
            res3 = dfs(i, j - 1)

            return max(res1, res2, res3)

        return dfs(len(nums1) - 1, len(nums2) - 1)

###java

class Solution {
    public int maxDotProduct(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;

        int[][] memo = new int[n][m];
        for (int[] row : memo) {
            Arrays.fill(row, Integer.MAX_VALUE);
        }

        return dfs(n - 1, m - 1, nums1, nums2, memo);
    }

    // 从 nums1[0,i] 和 nums2[0,j] 中选两个长度相同的【非空】子序列的最大点积
    private int dfs(int i, int j, int[] nums1, int[] nums2, int[][] memo) {
        if (i < 0 || j < 0) {
            // 其中一个数组没有元素,无法选出非空子序列
            return Integer.MIN_VALUE; // 下面计算 max 不会取到无解情况
        }

        if (memo[i][j] != Integer.MAX_VALUE) { // 之前计算过
            return memo[i][j];
        }

        // 选 nums1[i] 和 nums2[j]
        // 和前面的子序列拼起来,或者不拼(作为子序列的第一个数)
        int res1 = Math.max(dfs(i - 1, j - 1, nums1, nums2, memo), 0) + nums1[i] * nums2[j];

        // 不选 nums1[i]
        int res2 = dfs(i - 1, j, nums1, nums2, memo);

        // 不选 nums2[j]
        int res3 = dfs(i, j - 1, nums1, nums2, memo);

        memo[i][j] = Math.max(res1, Math.max(res2, res3)); // 记忆化
        return memo[i][j];
    }
}

###cpp

class Solution {
public:
    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        int m = nums2.size();
        vector memo(n, vector<int>(m, INT_MAX));

        // 从 nums1[0,i] 和 nums2[0,j] 中选两个长度相同的【非空】子序列的最大点积
        auto dfs = [&](this auto&& dfs, int i, int j) -> int {
            if (i < 0 || j < 0) {
                // 其中一个数组没有元素,无法选出非空子序列
                return INT_MIN; // 下面计算 max 不会取到无解情况
            }

            int& res = memo[i][j]; // 注意这里是引用
            if (res != INT_MAX) { // 之前计算过
                return res;
            }

            // 选 nums1[i] 和 nums2[j]
            // 和前面的子序列拼起来,或者不拼(作为子序列的第一个数)
            res = max(dfs(i - 1, j - 1), 0) + nums1[i] * nums2[j];

            // 不选 nums1[i]
            res = max(res, dfs(i - 1, j));

            // 不选 nums2[j]
            res = max(res, dfs(i, j - 1));

            return res;
        };

        return dfs(n - 1, m - 1);
    }
};

###go

func maxDotProduct(nums1, nums2 []int) int {
n := len(nums1)
m := len(nums2)
memo := make([][]int, n)
for i := range memo {
memo[i] = make([]int, m)
for j := range memo[i] {
memo[i][j] = math.MaxInt
}
}

// 从 nums1[:i+1] 和 nums2[:j+1] 中选两个长度相同的【非空】子序列的最大点积
var dfs func(int, int) int
dfs = func(i, j int) int {
if i < 0 || j < 0 {
// 其中一个数组没有元素,无法选出非空子序列
return math.MinInt // 下面计算 max 不会取到无解情况
}

p := &memo[i][j]
if *p != math.MaxInt { // 之前计算过
return *p
}

// 选 nums1[i] 和 nums2[j]
// 和前面的子序列拼起来,或者不拼(作为子序列的第一个数)
res1 := max(dfs(i-1, j-1), 0) + nums1[i]*nums2[j]

// 不选 nums1[i]
res2 := dfs(i-1, j)

// 不选 nums2[j]
res3 := dfs(i, j-1)

*p = max(res1, res2, res3) // 记忆化
return *p
}

return dfs(n-1, m-1)
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(nm)$,其中 $n$ 是 $\textit{nums}_1$ 的长度,$m$ 是 $\textit{nums}_2$ 的长度。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\times$ 单个状态的计算时间。本题状态个数等于 $\mathcal{O}(nm)$,单个状态的计算时间为 $\mathcal{O}(1)$,所以总的时间复杂度为 $\mathcal{O}(nm)$。
  • 空间复杂度:$\mathcal{O}(nm)$。保存多少状态,就需要多少空间。

四、1:1 翻译成递推

我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。

具体来说,$f[i+1][j+1]$ 的定义和 $\textit{dfs}(i,j)$ 的定义是一样的,都表示从 $a$ 的前缀 $[0,i]$ 和 $b$ 的前缀 $[0,j]$ 中选两个长度相等的非空子序列,计算两个子序列点积的最大值。这里 $+1$ 是为了把 $\textit{dfs}(-1,j)$ 和 $\textit{dfs}(i,-1)$ 也翻译过来,这样我们可以把 $f[0][j]$ 和 $f[i][0]$ 作为初始值。

相应的递推式(状态转移方程)也和 $\textit{dfs}$ 一样:

$$
f[i+1][j+1] = \max{\max(f[i][j], 0) + a[i]\cdot b[j], f[i][j+1],f[i+1][j]}
$$

初始值:$f$ 第一行和第一列初始化成 $-\infty$,翻译自递归边界 $\textit{dfs}(-1,j)=\textit{dfs}(i,-1)=-\infty$。

答案为 $f[n][m]$,翻译自递归入口 $\textit{dfs}(n-1,m-1)$。

###py

class Solution:
    def maxDotProduct(self, nums1: List[int], nums2: List[int]) -> int:
        n, m = len(nums1), len(nums2)
        f = [[-inf] * (m + 1) for _ in range(n + 1)]
        for i, x in enumerate(nums1):
            for j, y in enumerate(nums2):
                f[i + 1][j + 1] = max(max(f[i][j], 0) + x * y, f[i][j + 1], f[i + 1][j])
        return f[n][m]

###java

class Solution {
    public int maxDotProduct(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;

        int[][] f = new int[n + 1][m + 1];
        for (int[] row : f) {
            Arrays.fill(row, Integer.MIN_VALUE);
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                f[i + 1][j + 1] = Math.max(
                    Math.max(f[i][j], 0) + nums1[i] * nums2[j],
                    Math.max(f[i][j + 1], f[i + 1][j])
                );
            }
        }
        return f[n][m];
    }
}

###cpp

class Solution {
public:
    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        int m = nums2.size();
        vector f(n + 1, vector<int>(m + 1, INT_MIN));
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                int res1 = max(f[i][j], 0) + nums1[i] * nums2[j];
                // 注:max({...}) 比 max(..., max(...)) 慢
                f[i + 1][j + 1] = max(res1, max(f[i][j + 1], f[i + 1][j]));
            }
        }
        return f[n][m];
    }
};

###go

func maxDotProduct(nums1, nums2 []int) int {
n := len(nums1)
m := len(nums2)
f := make([][]int, n+1)
for i := range f {
f[i] = make([]int, m+1)
for j := range f[i] {
f[i][j] = math.MinInt
}
}

for i, x := range nums1 {
for j, y := range nums2 {
f[i+1][j+1] = max(max(f[i][j], 0)+x*y, f[i][j+1], f[i+1][j])
}
}
return f[n][m]
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(nm)$,其中 $n$ 是 $\textit{nums}_1$ 的长度,$m$ 是 $\textit{nums}_2$ 的长度。
  • 空间复杂度:$\mathcal{O}(nm)$。

五、空间优化

类似 1143. 最长公共子序列 的空间优化方法,只用一个长为 $m+1$ 的一维数组,原理讲解请看 最长公共子序列 编辑距离【基础算法精讲 19】

###py

# 更快的写法见【Python3 手写 max】
class Solution:
    def maxDotProduct(self, nums1: List[int], nums2: List[int]) -> int:
        m = len(nums2)
        f = [-inf] * (m + 1)
        for x in nums1:
            pre = f[0]
            for j, y in enumerate(nums2):
                tmp = f[j + 1]
                f[j + 1] = max(max(pre, 0) + x * y, f[j + 1], f[j])
                pre = tmp
        return f[m]

###py

class Solution:
    def maxDotProduct(self, nums1: List[int], nums2: List[int]) -> int:
        m = len(nums2)
        f = [-inf] * (m + 1)
        for x in nums1:
            pre = f[0]
            for j, y in enumerate(nums2):
                tmp = f[j + 1]
                res = x * y
                if pre > 0: res += pre
                if f[j] > res: res = f[j]
                if f[j + 1] > res: res = f[j + 1]
                f[j + 1] = res
                pre = tmp
        return f[m]

###java

class Solution {
    public int maxDotProduct(int[] nums1, int[] nums2) {
        int m = nums2.length;
        int[] f = new int[m + 1];
        Arrays.fill(f, Integer.MIN_VALUE);
        for (int x : nums1) {
            int pre = f[0];
            for (int j = 0; j < m; j++) {
                int tmp = f[j + 1];
                f[j + 1] = Math.max(Math.max(pre, 0) + x * nums2[j], Math.max(f[j + 1], f[j]));
                pre = tmp;
            }
        }
        return f[m];
    }
}

###cpp

class Solution {
public:
    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int m = nums2.size();
        vector<int> f(m + 1, INT_MIN);
        for (int x : nums1) {
            int pre = f[0];
            for (int j = 0; j < m; j++) {
                int tmp = f[j + 1];
                f[j + 1] = max(max(pre, 0) + x * nums2[j], max(f[j + 1], f[j]));
                pre = tmp;
            }
        }
        return f[m];
    }
};

###go

func maxDotProduct(nums1, nums2 []int) int {
m := len(nums2)
f := make([]int, m+1)
for i := range f {
f[i] = math.MinInt
}
for _, x := range nums1 {
pre := f[0]
for j, y := range nums2 {
tmp := f[j+1]
f[j+1] = max(max(pre, 0)+x*y, f[j+1], f[j])
pre = tmp
}
}
return f[m]
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(nm)$,其中 $n$ 是 $\textit{nums}_1$ 的长度,$m$ 是 $\textit{nums}_2$ 的长度。
  • 空间复杂度:$\mathcal{O}(m)$。

专题训练

见下面动态规划题单的「§4.1 最长公共子序列(LCS)」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
  7. 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

记忆化搜索->递推->空间优化

作者 yi-cheng-8i
2024年7月9日 10:28

灵神dp题单打卡

思路

定义$dfs(i,j) $,代表了以前$i$和$j$个数中$nums1$和$nums2$最大点积
(即$nums1[0...i]$和$nums2[0...j]$的最大点积)。

  • 如果选当前位置,那么算出当前位置点积为$nums1[i] * nums2[j]$,同时看前面位置的最大点积$dfs(i - 1,j - 1)$是否大于0,如果小于0的话,越加越小,不如不要,跟0取max就可以实现。状态方程如下:
    $$dfs(i,j) = max(dfs(i - 1,j - 1),0) + nums1[i] * nums2[j]$$
  • 如果不选当前位置,也就是跳过一格,状态方程如下:
    $$dfs(i,j) = max(dfs(i - 1,j),dfs(i,j - 1))$$

递推是记忆化搜索1: 1翻译而来,而空间优化则是在二维的基础上,观察值如何转移的优化的。具体可见b站灵神算法精讲中的内容,有总结。

Code

###C++

class Solution {
public:
    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(),m = nums2.size();
        vector<vector<int>> memo(n,vector<int>(m,INT_MIN));
        function<int(int,int)> dfs = [&](int i,int j) -> int{
            if(i < 0 || j < 0) return INT_MIN;
            if(memo[i][j] != INT_MIN) return memo[i][j];
            //选
            memo[i][j] = max(dfs(i - 1,j - 1),0) + nums1[i] * nums2[j];
            memo[i][j] = max({memo[i][j],dfs(i - 1,j),dfs(i,j - 1)});
            return memo[i][j];
        };
        return dfs(n - 1,m - 1);
    }
};

###cpp

class Solution {
public:
    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(),m = nums2.size();
        vector<vector<int>> memo(n,vector<int>(m,INT_MIN));
        //新一点的递归写法
        auto dfs = [&](this auto&& self,int i,int j) -> int{
            if(i < 0 || j < 0) return INT_MIN;
            if(memo[i][j] != INT_MIN) return memo[i][j];
            //选
            memo[i][j] = max(self(i - 1,j - 1),0) + nums1[i] * nums2[j];
            memo[i][j] = max({memo[i][j],self(i - 1,j),self(i,j - 1)});
            return memo[i][j];
        };
        return dfs(n - 1,m - 1);
    }
};

###c++

    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(),m = nums2.size();
        vector<vector<int>> f(n + 1,vector<int>(m + 1,INT_MIN));
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                f[i + 1][j + 1] = max({max(f[i][j],0) + nums1[i] * nums2[j],
                                    f[i + 1][j],f[i][j + 1]});
            }
        }
        return f[n][m];
    }

###c++

    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(),m = nums2.size();
        vector<int> f(m + 1,INT_MIN);
        for(int i = 0;i < n;i++){
            f[0] = INT_MIN;//这里相当于初始化第一列。
            int pre = INT_MIN;
            for(int j = 0;j < m;j++){
                int t = f[j + 1];
                f[j + 1] = max({max(pre,0) + nums1[i] * nums2[j],
                                    f[j],f[j + 1]});
                pre = t;
            }
        }
        return f[m];
    }

c++ 动态规划 易懂

作者 smilyt_
2020年5月24日 12:09

题意

本题就是求两个子序列点积的最大值。题意很明确,直接说解法。
很显然这题用dp做,但是状态转移方程怎么写,dp[i][j]代表什么意思,依然是一个值得写一下的问题。

首先我们考虑dp[i][j]代表什么意思。

dp[i][j]的含义

第一种想法:
dp[i][j]的含义是以nums1[i]和nums2[j]结尾的子序列的最大点积。
第二种想法:
dp[i][j]的含义是到nums1[i]和nums2[j]为止的子序列的最大点积。

这两种是不一样的:
第一种想法一定要包含nums1[i]和nums2[j],因为以它们结尾。
但是第二种想法就没有这个限制,以谁结尾无所谓,最主要是大。

我们应该使用第二种,具体原因是因为状态转移方程。

状态转移方程

第一种想法的状态转移方程怎么写呢?

dp[i][j]=max(nums1[i]*nums2[j] , nums1[i]*nums2[j]+ maxVal);  

首先我们知道nums1[i]*nums2[j]这个值在第一种想法中是一定要有的。
接下来我们可以选择只有这两项或者包含前面的子序列点积最大值:
假如只有这两项,那么就什么都不加;假如也包含前面的就加上前面子序列点积的最大值maxVal。

来算一下时间复杂度:
首先算n^2个dp值
在每次dp计算中都要找到前面子序列点积的最大值,又要花费n^2的时间
所以时间复杂度为n^4,(500)^4是超时的

第二种想法的状态转移方程怎么写呢?
第二种可以选择nums1[i]和nums2[j],所以我们可以通过这个来写状态转移方程:
(其实对于子序列的很多dp题来讲,都可以使用选不选来写状态转移方程)

1.选择nums1[i]和nums2[j]

1.1不选择前面的 dp[i][j]=nums1[i]*nums2[j]
1.2也选择前面的 dp[i][j]=max(dp[i][j],nums1[i]*nums2[j]+dp[i-1][j-1])
因为dp[i][j]是截止到nums1[i]和nums2[j]中的最大点积,所以只需要dp[i-1][j-1]就可以了  
事实上从这里可以看出想法一就是想法二的情况之一

2.选择nums1[i],不选择nums2[j]

等价于dp[i][j-1]
dp[i][j]=max(dp[i][j],dp[i][j-1])

3.不选择nums1[i],选择nums2[j]

等价于dp[i-1][j]
dp[i][j]=max(dp[i][j],dp[i-1][j])

4.???

聪明的你肯定知道了
状态方程你来写吧:dp[i][j]=max(dp[i][j],???)

代码

###cpp


class Solution {
public:
    int maxDotProduct(vector<int>& nums1, vector<int>& nums2) {
        int sz1=nums1.size(),sz2=nums2.size();
        vector<vector<int>> dp(sz1+1,vector<int>(sz2+1,-1e8));

        for(int i=1;i<=sz1;i++){
            for(int j=1;j<=sz2;j++){
                //1.1
                dp[i][j]=nums1[i-1]*nums2[j-1];
                //1.2
                dp[i][j]=max(dp[i][j],nums1[i-1]*nums2[j-1]+dp[i-1][j-1]);
                //2
                dp[i][j]=max(dp[i][j],dp[i][j-1]);
                //3
                dp[i][j]=max(dp[i][j],dp[i-1][j]);
                //4
                dp[i][j]=max(dp[i][j],dp[i-1][j-1]);
            }
        }
        return dp[sz1][sz2];
    }
};

哦,对,求个赞

有疑问评论区可以交流,看到一定回

昨天 — 2026年1月7日LeetCode 每日一题题解

每日一题-分裂二叉树的最大乘积🟡

2026年1月7日 00:00

给你一棵二叉树,它的根为 root 。请你删除 1 条边,使二叉树分裂成两棵子树,且它们子树和的乘积尽可能大。

由于答案可能会很大,请你将结果对 10^9 + 7 取模后再返回。

 

示例 1:

输入:root = [1,2,3,4,5,6]
输出:110
解释:删除红色的边,得到 2 棵子树,和分别为 11 和 10 。它们的乘积是 110 (11*10)

示例 2:

输入:root = [1,null,2,3,4,null,null,5,6]
输出:90
解释:移除红色的边,得到 2 棵子树,和分别是 15 和 6 。它们的乘积为 90 (15*6)

示例 3:

输入:root = [2,3,9,10,7,8,6,5,4,11,1]
输出:1025

示例 4:

输入:root = [1,1]
输出:1

 

提示:

  • 每棵树最多有 50000 个节点,且至少有 2 个节点。
  • 每个节点的值在 [1, 10000] 之间。

两次 DFS,或者提前保存子树和(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2026年1月1日 12:07

第一次 DFS:计算整棵树的点权和 $\textit{total}$。

第二次 DFS:计算子树点权和 $s$,那么删除当前节点到其父节点的边后,另一部分的和就是 $\textit{total} - s$,二者乘积为

$$
s\cdot(\textit{total} - s)
$$

用其更新答案的最大值。

由于本题保证点权是非负,我们无需判断当前节点是根节点的特殊情况(无父节点),此时上式为 $0$,不影响答案。

如果有负数点权,就需要跳过当前节点是根节点的情况。

也可以在第一次 DFS 时,把子树和保存到一个列表中,第二次只需遍历这个列表,见 Python3 的第一份代码。

###py

class Solution:
    def maxProduct(self, root: Optional[TreeNode]) -> int:
        def dfs(node: Optional[TreeNode]) -> int:
            if node is None:
                return 0
            s = node.val + dfs(node.left) + dfs(node.right)
            sub_sum.append(s)
            return s

        sub_sum = []
        total = dfs(root)

        ans = max(s * (total - s) for s in sub_sum)
        return ans % 1_000_000_007

###py

class Solution:
    def maxProduct(self, root: Optional[TreeNode]) -> int:
        def dfs1(node: Optional[TreeNode]) -> int:
            if node is None:
                return 0
            return node.val + dfs1(node.left) + dfs1(node.right)

        def dfs2(node: Optional[TreeNode]) -> int:
            if node is None:
                return 0
            s = node.val + dfs2(node.left) + dfs2(node.right)
            nonlocal ans
            ans = max(ans, s * (total - s))
            return s

        total = dfs1(root)

        ans = 0
        dfs2(root)

        return ans % 1_000_000_007

###java

class Solution {
    private static final int MOD = 1_000_000_007;
    private long ans = 0;

    public int maxProduct(TreeNode root) {
        int total = dfs1(root);
        dfs2(root, total);
        return (int) (ans % MOD);
    }

    private int dfs1(TreeNode node) {
        if (node == null) {
            return 0;
        }
        return node.val + dfs1(node.left) + dfs1(node.right);
    }

    private int dfs2(TreeNode node, int total) {
        if (node == null) {
            return 0;
        }
        int s = node.val + dfs2(node.left, total) + dfs2(node.right, total);
        ans = Math.max(ans, (long) s * (total - s));
        return s;
    }
}

###cpp

class Solution {
public:
    int maxProduct(TreeNode* root) {
        auto dfs1 = [&](this auto&& dfs1, TreeNode* node) -> int {
            if (node == nullptr) {
                return 0;
            }
            return node->val + dfs1(node->left) + dfs1(node->right);
        };
        long long total = dfs1(root);

        long long ans = 0;
        auto dfs2 = [&](this auto&& dfs2, TreeNode* node) -> int {
            if (node == nullptr) {
                return 0;
            }
            int s = node->val + dfs2(node->left) + dfs2(node->right);
            ans = max(ans, s * (total - s));
            return s;
        };
        dfs2(root);

        return ans % 1'000'000'007;
    }
};

###c

#define MAX(a, b) ((b) > (a) ? (b) : (a))

int maxProduct(struct TreeNode* root) {
    int dfs1(struct TreeNode* node) {
        if (node == NULL) {
            return 0;
        }
        return node->val + dfs1(node->left) + dfs1(node->right);
    }

    long long total = dfs1(root);
    long long ans = 0;

    int dfs2(struct TreeNode* node) {
        if (node == NULL) {
            return 0;
        }
        int s = node->val + dfs2(node->left) + dfs2(node->right);
        ans = MAX(ans, s * (total - s));
        return s;
    }

    dfs2(root);
    return ans % 1000000007;
}

###go

func maxProduct(root *TreeNode) (ans int) {
var dfs1 func(*TreeNode) int
dfs1 = func(node *TreeNode) int {
if node == nil {
return 0
}
return node.Val + dfs1(node.Left) + dfs1(node.Right)
}
total := dfs1(root)

var dfs2 func(*TreeNode) int
dfs2 = func(node *TreeNode) int {
if node == nil {
return 0
}
s := node.Val + dfs2(node.Left) + dfs2(node.Right)
ans = max(ans, s*(total-s))
return s
}
dfs2(root)

return ans % 1_000_000_007
}

###js

var maxProduct = function(root) {
    function dfs1(node) {
        if (node === null) {
            return 0;
        }
        return node.val + dfs1(node.left) + dfs1(node.right);
    }

    function dfs2(node) {
        if (node === null) {
            return 0;
        }
        const s = node.val + dfs2(node.left) + dfs2(node.right);
        ans = Math.max(ans, s * (total - s));
        return s;
    }

    const total = dfs1(root);

    let ans = 0;
    dfs2(root);

    return ans % 1_000_000_007;
};

###rust

use std::rc::Rc;
use std::cell::RefCell;

impl Solution {
    pub fn max_product(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
        fn dfs1(node: &Option<Rc<RefCell<TreeNode>>>) -> i32 {
            if let Some(n) = node {
                let n = n.borrow();
                n.val + dfs1(&n.left) + dfs1(&n.right)
            } else {
                0
            }
        }

        fn dfs2(node: &Option<Rc<RefCell<TreeNode>>>, total: i32, ans: &mut i64) -> i32 {
            if let Some(n) = node {
                let n = n.borrow();
                let s = n.val + dfs2(&n.left, total, ans) + dfs2(&n.right, total, ans);
                *ans = (*ans).max(s as i64 * (total - s) as i64);
                s
            } else {
                0
            }
        }

        let total = dfs1(&root);

        let mut ans = 0;
        dfs2(&root, total, &mut ans);

        (ans % 1_000_000_007) as _
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是二叉树的节点个数。
  • 空间复杂度:$\mathcal{O}(h)$,其中 $h$ 是二叉树的高度。递归需要消耗 $\mathcal{O}(h)$ 的栈空间。

专题训练

见下面树题单的「§2.3 自底向上 DFS」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

JAVA 简单易懂的代码

作者 luoweian
2020年2月2日 17:13

分为两个步骤:
1.求整个二叉树的和
2.遍历所有子树和,求最大值

    double ans = Double.MIN_VALUE;
    double allSum;
    double nodeSum;
    public int maxProduct(TreeNode root) {
        allSum = sum(root);
        dfs(root);
        return (int)(ans % (int)(1e9 + 7));
    }

    public double sum(TreeNode node){
        if(node == null) return 0;
        return node.val + sum(node.left) + sum(node.right);
    }

    public double dfs(TreeNode node){
        if(node == null)    return 0 ;
        nodeSum = node.val + dfs(node.left) + dfs(node.right);
        ans = Math.max(ans, (allSum - nodeSum) * nodeSum);
        return nodeSum;
    }

C++ 后序遍历

作者 hypogump-2
2020年2月2日 13:15

解题思路

  1. 后序遍历得到分别以各个节点为根的子树总和
  2. 去掉一条边的乘积 = 子树总和 * (总和 - 子树总和),取最大值
  3. 不能提前取模。比如 1e9 + 7(取模后为0) 和 1e9 + 6(取模后不变),提前取模会导致错误

代码

###cpp

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<long> sums;
    static const int mod = 1e9 + 7;
    int maxProduct(TreeNode* root) {
        postOrder(root);
        long res = -1;
        for (int i = 0; i < sums.size() - 1; ++i) {
            // 取最大值时不能取模,应该用long型存结果
            res = max(res, sums[i] * (sums.back() - sums[i]));
        }
        return (int)(res % mod);
    }

    long postOrder(TreeNode* root) {
        if (root == nullptr) return 0;
        long res = root->val + postOrder(root->left) + postOrder(root->right);
        sums.push_back(res);
        return res;
    }
};
昨天以前LeetCode 每日一题题解

每日一题-最大层内元素和🟡

2026年1月6日 00:00

给你一个二叉树的根节点 root。设根节点位于二叉树的第 1 层,而根节点的子节点位于第 2 层,依此类推。

返回总和 最大 的那一层的层号 x。如果有多层的总和一样大,返回其中 最小 的层号 x

 

示例 1:

输入:root = [1,7,0,7,-8,null,null]
输出:2
解释:
第 1 层各元素之和为 1,
第 2 层各元素之和为 7 + 0 = 7,
第 3 层各元素之和为 7 + -8 = -1,
所以我们返回第 2 层的层号,它的层内元素之和最大。

示例 2:

输入:root = [989,null,10250,98693,-89388,null,null,null,-32127]
输出:2

 

提示:

  • 树中的节点数在 [1, 104]范围内
  • -105 <= Node.val <= 105

两种方法:BFS / DFS(Python/Java/C++/Go)

作者 endlesscheng
2026年1月3日 09:10

方法一:BFS

本题需要计算每一层的节点值之和,适合用 BFS原理讲解【基础算法精讲 13】

维护两个信息:最大层和 $\textit{maxSum}$,以及对应的层号 $\textit{ans}$。如果当前层的和大于 $\textit{maxSum}$,那么更新 $\textit{maxSum}$ 和 $\textit{ans}$。

class Solution:
    def maxLevelSum(self, root: Optional[TreeNode]) -> int:
        max_sum = -inf
        ans = 0

        q = [root]
        level = 1
        while q:
            tmp = q
            q = []
            s = 0
            for node in tmp:
                s += node.val
                if node.left:
                    q.append(node.left)
                if node.right:
                    q.append(node.right)

            if s > max_sum:
                max_sum = s
                ans = level

            level += 1

        return ans
class Solution {
    public int maxLevelSum(TreeNode root) {
        int max_sum = Integer.MIN_VALUE;
        int ans = 0;

        List<TreeNode> q = new ArrayList<>();
        q.add(root);

        for (int level = 1; !q.isEmpty(); level++) {
            List<TreeNode> tmp = q;
            q = new ArrayList<>();
            int s = 0;

            for (TreeNode node : tmp) {
                s += node.val;
                if (node.left != null) {
                    q.add(node.left);
                }
                if (node.right != null) {
                    q.add(node.right);
                }
            }

            if (s > max_sum) {
                max_sum = s;
                ans = level;
            }
        }

        return ans;
    }
}
class Solution {
public:
    int maxLevelSum(TreeNode* root) {
        int max_sum = INT_MIN;
        int ans = 0;

        vector<TreeNode*> q = {root};
        for (int level = 1; !q.empty(); level++) {
            auto tmp = q;
            q.clear();
            int s = 0;

            for (auto node : tmp) {
                s += node->val;
                if (node->left) {
                    q.push_back(node->left);
                }
                if (node->right) {
                    q.push_back(node->right);
                }
            }

            if (s > max_sum) {
                max_sum = s;
                ans = level;
            }
        }

        return ans;
    }
};
func maxLevelSum(root *TreeNode) (ans int) {
maxSum := math.MinInt
q := []*TreeNode{root}

for level := 1; q != nil; level++ {
tmp := q
q = nil
s := 0

for _, node := range tmp {
s += node.Val
if node.Left != nil {
q = append(q, node.Left)
}
if node.Right != nil {
q = append(q, node.Right)
}
}

if s > maxSum {
maxSum = s
ans = level
}
}

return ans
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是二叉树的节点个数。
  • 空间复杂度:$\mathcal{O}(n)$。

方法二:DFS

创建一个列表 $\textit{rowSum}$,维护每一层的元素和。

在 DFS 的同时,把节点值加到这一层的元素和中。

最后求出最大层和对应的层号。

class Solution:
    def maxLevelSum(self, root: Optional[TreeNode]) -> int:
        def dfs(node: Optional[TreeNode], level: int) -> None:
            if node is None:
                return

            if len(row_sum) == level:  # 首次访问 level 层
                row_sum.append(node.val)  # 节点值作为层和的初始值
            else:
                row_sum[level] += node.val

            dfs(node.left, level + 1)
            dfs(node.right, level + 1)

        row_sum = []
        dfs(root, 0)
        return row_sum.index(max(row_sum)) + 1  # 层号从 1 开始
class Solution {

    private void dfs(TreeNode node, int level, List<Integer> rowSum) {
        if (node == null) {
            return;
        }

        if (rowSum.size() == level) { // 首次访问 level 层
            rowSum.add(node.val); // 节点值作为层和的初始值
        } else {
            rowSum.set(level, rowSum.get(level) + node.val);
        }

        dfs(node.left, level + 1, rowSum);
        dfs(node.right, level + 1, rowSum);
    }

    public int maxLevelSum(TreeNode root) {
        List<Integer> rowSum = new ArrayList<>();
        dfs(root, 0, rowSum);

        int ans = 0;
        for (int i = 1; i < rowSum.size(); i++) {
            if (rowSum.get(i) > rowSum.get(ans)) {
                ans = i;
            }
        }
        return ans + 1; // 层号从 1 开始
    }
}
class Solution {
public:
    int maxLevelSum(TreeNode* root) {
        vector<int> row_sum;

        auto dfs = [&](this auto&& dfs, TreeNode* node, int level) -> void {
            if (node == nullptr) {
                return;
            }

            if (row_sum.size() == level) { // 首次访问 level 层
                row_sum.push_back(node->val); // 节点值作为层和的初始值
            } else {
                row_sum[level] += node->val;
            }

            dfs(node->left, level + 1);
            dfs(node->right, level + 1);
        };

        dfs(root, 0);
        return ranges::max_element(row_sum) - row_sum.begin() + 1; // 层号从 1 开始
    }
};
func maxLevelSum(root *TreeNode) (ans int) {
rowSum := []int{}

var dfs func(*TreeNode, int)
dfs = func(node *TreeNode, level int) {
if node == nil {
return
}

if len(rowSum) == level { // 首次访问 level 层
rowSum = append(rowSum, node.Val) // 节点值作为层和的初始值
} else {
rowSum[level] += node.Val
}

dfs(node.Left, level+1)
dfs(node.Right, level+1)
}

dfs(root, 0)

for i, s := range rowSum {
if s > rowSum[ans] {
ans = i
}
}
return ans + 1 // 层号从 1 开始
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是二叉树的节点个数。
  • 空间复杂度:$\mathcal{O}(n)$。

专题训练

见下面树题单的「§2.13 二叉树 BFS」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

【宫水三叶】层序遍历运用题

作者 AC_OIer
2022年7月31日 09:32

层序遍历

根据题意,使用 BFS 进行层序遍历即可。

每次以「层」为单位进行拓展,统计该层的元素和,维护处理过程中的最大值层数和,以及层深度。

代码:

###Java

class Solution {
    public int maxLevelSum(TreeNode root) {
        Deque<TreeNode> d = new ArrayDeque<>();
        int max = -0x3f3f3f3f, depth = 1, ans = 0;
        d.addLast(root);
        while (!d.isEmpty()) {
            int sz = d.size(), cur = 0;
            while (sz-- > 0) {
                TreeNode t = d.pollFirst();
                if (t.left != null) d.addLast(t.left);
                if (t.right != null) d.addLast(t.right);
                cur += t.val;
            }
            if (cur > max) {
                max = cur; ans = depth;
            }
            depth++;
        }
        return ans;
    }
}

###TypeScript

function maxLevelSum(root: TreeNode | null): number {
    const d: TreeNode[] = new Array<TreeNode>()
    let he = 0, ta = 0
    d[ta++] = root
    let max = -0x3f3f3f3f, depth = 1, ans = 0
    while (he < ta) {
        let sz = ta - he, cur = 0
        while (sz-- > 0) {
            const t = d[he++]
            if (t.left != null) d[ta++] = t.left
            if (t.right != null) d[ta++] = t.right
            cur += t.val
        }
        if (cur > max) {
            max = cur; ans = depth
        }
        depth++
    }
    return ans
};
  • 时间复杂度:$O(n)$
  • 空间复杂度:$O(n)$

最后

如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ ("▔□▔)/

也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。

所有题解已经加入 刷题指南,欢迎 star 哦 ~

最大层内元素和

2022年7月29日 16:38

方法一:深度优先搜索

我们可以采用深度优先搜索来遍历这棵二叉树,递归的同时记录当前的层号。

相比哈希表,这里我们采用效率更高的动态数组来维护每一层的元素之和,如果当前层号达到了数组的长度,则将节点元素添加到数组末尾,否则更新对应层号的元素之和。

然后遍历数组,找到元素之和最大,且层号最小的元素。

###Python

class Solution:
    def maxLevelSum(self, root: Optional[TreeNode]) -> int:
        sum = []
        def dfs(node: TreeNode, level: int) -> None:
            if level == len(sum):
                sum.append(node.val)
            else:
                sum[level] += node.val
            if node.left:
                dfs(node.left, level + 1)
            if node.right:
                dfs(node.right, level + 1)
        dfs(root, 0)
        return sum.index(max(sum)) + 1  # 层号从 1 开始

###C++

class Solution {
    vector<int> sum;

    void dfs(TreeNode *node, int level) {
        if (level == sum.size()) {
            sum.push_back(node->val);
        } else {
            sum[level] += node->val;
        }
        if (node->left) {
            dfs(node->left, level + 1);
        }
        if (node->right) {
            dfs(node->right, level + 1);
        }
    }

public:
    int maxLevelSum(TreeNode *root) {
        dfs(root, 0);
        int ans = 0;
        for (int i = 0; i < sum.size(); ++i) {
            if (sum[i] > sum[ans]) {
                ans = i;
            }
        }
        return ans + 1; // 层号从 1 开始
    }
};

###Java

class Solution {
    private List<Integer> sum = new ArrayList<Integer>();

    public int maxLevelSum(TreeNode root) {
        dfs(root, 0);
        int ans = 0;
        for (int i = 0; i < sum.size(); ++i) {
            if (sum.get(i) > sum.get(ans)) {
                ans = i;
            }
        }
        return ans + 1; // 层号从 1 开始
    }

    private void dfs(TreeNode node, int level) {
        if (level == sum.size()) {
            sum.add(node.val);
        } else {
            sum.set(level, sum.get(level) + node.val);
        }
        if (node.left != null) {
            dfs(node.left, level + 1);
        }
        if (node.right != null) {
            dfs(node.right, level + 1);
        }
    }
}

###C#

public class Solution {
    private IList<int> sum = new List<int>();

    public int MaxLevelSum(TreeNode root) {
        DFS(root, 0);
        int ans = 0;
        for (int i = 0; i < sum.Count; ++i) {
            if (sum[i] > sum[ans]) {
                ans = i;
            }
        }
        return ans + 1; // 层号从 1 开始
    }

    private void DFS(TreeNode node, int level) {
        if (level == sum.Count) {
            sum.Add(node.val);
        } else {
            sum[level] += node.val;
        }
        if (node.left != null) {
            DFS(node.left, level + 1);
        }
        if (node.right != null) {
            DFS(node.right, level + 1);
        }
    }
}

###go

func maxLevelSum(root *TreeNode) (ans int) {
    sum := []int{}
    var dfs func(*TreeNode, int)
    dfs = func(node *TreeNode, level int) {
        if level == len(sum) {
            sum = append(sum, node.Val)
        } else {
            sum[level] += node.Val
        }
        if node.Left != nil {
            dfs(node.Left, level+1)
        }
        if node.Right != nil {
            dfs(node.Right, level+1)
        }
    }
    dfs(root, 0)
    for i, s := range sum {
        if s > sum[ans] {
            ans = i
        }
    }
    return ans + 1 // 层号从 1 开始
}

###C

#define MAX_NODE_SIZE 10000

void dfs(struct TreeNode *node, int level, int *sum, int *sumSize) {
    if (level == *sumSize) {
        sum[*sumSize] = node->val;
        (*sumSize)++;
    } else {
        sum[level] += node->val;
    }
    if (node->left) {
        dfs(node->left, level + 1, sum, sumSize);
    }
    if (node->right) {
        dfs(node->right, level + 1, sum, sumSize);
    }
}

int maxLevelSum(struct TreeNode* root) {
    int *sum = (int *)malloc(sizeof(int) * MAX_NODE_SIZE);
    int sumSize = 0;
    dfs(root, 0, sum, &sumSize);
    int ans = 0;
    for (int i = 0; i < sumSize; ++i) {
        if (sum[i] > sum[ans]) {
            ans = i;
        }
    }
    return ans + 1; // 层号从 1 开始
}

###JavaScript

var maxLevelSum = function(root) {
    const sum = [];
    const dfs = (node, level) => {
        if (level === sum.length) {
            sum.push(node.val);
        } else {
            sum.splice(level, 1, sum[level] + node.val);
        }
        if (node.left) {
            dfs(node.left, level + 1);
        }
        if (node.right) {
            dfs(node.right, level + 1);
        }
    }
    dfs(root, 0);
    let ans = 0;
    for (let i = 0; i < sum.length; ++i) {
        if (sum[i] > sum[ans]) {
            ans = i;
        }
    }
    return ans + 1; // 层号从 1 开始
};

###TypeScript

function maxLevelSum(root: TreeNode | null): number {
    const sum: number[] = [];
    const dfs = (node: TreeNode, level: number): void => {
        if (level === sum.length) {
            sum.push(node.val);
        } else {
            sum[level] += node.val;
        }
        
        if (node.left) {
            dfs(node.left, level + 1);
        }
        if (node.right) {
            dfs(node.right, level + 1);
        }
    };
    
    if (root) {
        dfs(root, 0);
    }
    
    let maxIndex = 0;
    let maxSum = -Infinity;
    for (let i = 0; i < sum.length; i++) {
        if (sum[i] > maxSum) {
            maxSum = sum[i];
            maxIndex = i;
        }
    }
    
    return maxIndex + 1; // 层号从 1 开始
};

###Rust

use std::rc::Rc;
use std::cell::RefCell;
use std::collections::VecDeque;

impl Solution {
    pub fn max_level_sum(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
        let mut sum = Vec::new();
        
        // 使用 DFS 递归遍历
        fn dfs(node: &Rc<RefCell<TreeNode>>, level: usize, sum: &mut Vec<i32>) {
            let node_ref = node.borrow();
            
            if level == sum.len() {
                sum.push(node_ref.val);
            } else {
                sum[level] += node_ref.val;
            }
            
            if let Some(left) = &node_ref.left {
                dfs(left, level + 1, sum);
            }
            
            if let Some(right) = &node_ref.right {
                dfs(right, level + 1, sum);
            }
        }
        
        if let Some(root_node) = root {
            dfs(&root_node, 0, &mut sum);
        }
        
        // 找到最大和的层级
        let mut max_index = 0;
        let mut max_sum = i32::MIN;
        for (i, &s) in sum.iter().enumerate() {
            if s > max_sum {
                max_sum = s;
                max_index = i;
            }
        }
        
        (max_index + 1) as i32 // 层号从 1 开始
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是二叉树的节点个数。

  • 空间复杂度:$O(n)$。最坏情况下二叉树是一条链,需要 $O(n)$ 的数组空间以及 $O(n)$ 的递归栈空间。

方法二:广度优先搜索

由于计算的是每层的元素之和,用广度优先搜索来遍历这棵树会更加自然。

对于广度优先搜索,我们可以用队列来实现。初始时,队列只包含根节点;然后不断出队,将子节点入队,直到队列为空。

如果直接套用方法一的思路,我们需要在队列中存储节点和节点的层号。另一种做法是一次遍历完一整层的节点,遍历的同时,累加该层的节点的元素之和,同时用这层的节点得到下一层的节点,这种做法不需要记录层号。

为了代码实现的方便,我们可以使用两个动态数组,第一个数组 $q$ 为当前层的节点,第二个数组 $\textit{nq}$ 为下一层的节点。遍历 $q$ 中节点的同时,把子节点加到 $\textit{nq}$ 中。遍历完当前层后,将 $q$ 置为 $\textit{nq}$。

###Python

class Solution:
    def maxLevelSum(self, root: Optional[TreeNode]) -> int:
        ans, maxSum = 1, root.val
        level, q = 1, [root]
        while q:
            sum, nq = 0, []
            for node in q:
                sum += node.val
                if node.left:
                    nq.append(node.left)
                if node.right:
                    nq.append(node.right)
            if sum > maxSum:
                ans, maxSum = level, sum
            q = nq
            level += 1
        return ans

###C++

class Solution {
public:
    int maxLevelSum(TreeNode *root) {
        int ans = 1, maxSum = root->val;
        vector<TreeNode*> q = {root};
        for (int level = 1; !q.empty(); ++level) {
            vector<TreeNode*> nq;
            int sum = 0;
            for (auto node : q) {
                sum += node->val;
                if (node->left) {
                    nq.emplace_back(node->left);
                }
                if (node->right) {
                    nq.emplace_back(node->right);
                }
            }
            if (sum > maxSum) {
                maxSum = sum;
                ans = level;
            }
            q = move(nq);
        }
        return ans;
    }
};

###Java

class Solution {
    public int maxLevelSum(TreeNode root) {
        int ans = 1, maxSum = root.val;
        List<TreeNode> q = new ArrayList<TreeNode>();
        q.add(root);
        for (int level = 1; !q.isEmpty(); ++level) {
            List<TreeNode> nq = new ArrayList<TreeNode>();
            int sum = 0;
            for (TreeNode node : q) {
                sum += node.val;
                if (node.left != null) {
                    nq.add(node.left);
                }
                if (node.right != null) {
                    nq.add(node.right);
                }
            }
            if (sum > maxSum) {
                maxSum = sum;
                ans = level;
            }
            q = nq;
        }
        return ans;
    }
}

###C#

public class Solution {
    public int MaxLevelSum(TreeNode root) {
        int ans = 1, maxSum = root.val;
        IList<TreeNode> q = new List<TreeNode>();
        q.Add(root);
        for (int level = 1; q.Count > 0; ++level) {
            IList<TreeNode> nq = new List<TreeNode>();
            int sum = 0;
            foreach (TreeNode node in q) {
                sum += node.val;
                if (node.left != null) {
                    nq.Add(node.left);
                }
                if (node.right != null) {
                    nq.Add(node.right);
                }
            }
            if (sum > maxSum) {
                maxSum = sum;
                ans = level;
            }
            q = nq;
        }
        return ans;
    }
}

###go

func maxLevelSum(root *TreeNode) int {
    ans, maxSum := 1, root.Val
    q := []*TreeNode{root}
    for level := 1; len(q) > 0; level++ {
        tmp := q
        q = nil
        sum := 0
        for _, node := range tmp {
            sum += node.Val
            if node.Left != nil {
                q = append(q, node.Left)
            }
            if node.Right != nil {
                q = append(q, node.Right)
            }
        }
        if sum > maxSum {
            ans, maxSum = level, sum
        }
    }
    return ans
}

###C

#define MAX_NODE_SIZE 10000

int maxLevelSum(struct TreeNode* root){
    int ans = 1, maxSum = root->val;
    struct TreeNode **q = (struct TreeNode **)malloc(sizeof(struct TreeNode *) * MAX_NODE_SIZE);
    struct TreeNode **nq = (struct TreeNode **)malloc(sizeof(struct TreeNode *) * MAX_NODE_SIZE);
    int qSize = 0;
    q[qSize++] = root;
    for (int level = 1; qSize > 0; ++level) {
        int sum = 0, nqSize = 0;
        for (int i = 0; i < qSize; i++) {
            sum += q[i]->val;
            if (q[i]->left) {
                nq[nqSize++] = q[i]->left;
            }
            if (q[i]->right) {
                nq[nqSize++] = q[i]->right;
            }
        }
        if (sum > maxSum) {
            maxSum = sum;
            ans = level;
        }
        struct TreeNode *tmp = q;
        q = nq;
        nq = tmp;
        qSize = nqSize;
    }
    free(q);
    free(nq);
    return ans;
}

###JavaScript

var maxLevelSum = function(root) {
    let ans = 1, maxSum = root.val;
    let q = [];
    q.push(root);
    for (let level = 1; q.length > 0; ++level) {
        const nq = [];
        let sum = 0;
        for (const node of q) {
            sum += node.val;
            if (node.left) {
                nq.push(node.left);
            }
            if (node.right) {
                nq.push(node.right);
            }
        }
        if (sum > maxSum) {
            maxSum = sum;
            ans = level;
        }
        q = nq;
    }
    return ans;
};

###TypeScript

function  maxLevelSum(root: TreeNode | null): number {
    if (!root) {
        return 1;
    }
    let ans = 1;
    let maxSum = root.val;
    let q: TreeNode[] = [root];
    
    for (let level = 1; q.length > 0; level++) {
        const nq: TreeNode[] = [];
        let sum = 0;
        
        for (const node of q) {
            sum += node.val;
            if (node.left) {
                nq.push(node.left);
            }
            if (node.right) {
                nq.push(node.right);
            }
        }
        
        if (sum > maxSum) {
            maxSum = sum;
            ans = level;
        }
        
        q = nq;
    }
    
    return ans;
}

###Rust

use std::rc::Rc;
use std::cell::RefCell;
use std::collections::VecDeque;

impl Solution {
    pub fn max_level_sum(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
        let mut ans = 1;
        let mut max_sum = i32::MIN;
        let mut level = 1;
        
        let mut queue = VecDeque::new();
        if let Some(root_node) = root {
            queue.push_back(root_node);
        }
        
        while !queue.is_empty() {
            let level_size = queue.len();
            let mut sum = 0;
            
            for _ in 0..level_size {
                let node = queue.pop_front().unwrap();
                let node_ref = node.borrow();
                sum += node_ref.val;
                
                if let Some(left) = &node_ref.left {
                    queue.push_back(left.clone());
                }
                if let Some(right) = &node_ref.right {
                    queue.push_back(right.clone());
                }
            }
            
            if sum > max_sum {
                max_sum = sum;
                ans = level;
            }
            
            level += 1;
        }
        
        ans
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是二叉树的节点个数。

  • 空间复杂度:$O(n)$。最坏情况下,数组中有 $O(n)$ 个节点。

每日一题-最大方阵和🟡

2026年1月5日 00:00

给你一个 n x n 的整数方阵 matrix 。你可以执行以下操作 任意次 :

  • 选择 matrix 中 相邻 两个元素,并将它们都 乘以 -1 。

如果两个元素有 公共边 ,那么它们就是 相邻 的。

你的目的是 最大化 方阵元素的和。请你在执行以上操作之后,返回方阵的 最大 和。

 

示例 1:

输入:matrix = [[1,-1],[-1,1]]
输出:4
解释:我们可以执行以下操作使和等于 4 :
- 将第一行的 2 个元素乘以 -1 。
- 将第一列的 2 个元素乘以 -1 。

示例 2:

输入:matrix = [[1,2,3],[-1,-2,-3],[1,2,3]]
输出:16
解释:我们可以执行以下操作使和等于 16 :
- 将第二行的最后 2 个元素乘以 -1 。

 

提示:

  • n == matrix.length == matrix[i].length
  • 2 <= n <= 250
  • -105 <= matrix[i][j] <= 105

最大方阵和

2021年8月22日 17:11

方法一:贪心

提示 $1$

为了使得操作后方阵总和最大,我们需要使得负数元素的总和尽可能大

对于方阵中的两个负数元素,一定存在一系列的操作使得这两个负数元素均变为正数,且其余元素不变。

对于方阵中的一个正数元素和一个负数元素,一定存在一系列的操作使得这两个元素交换正负,且其余元素不变。

提示 $1$ 解释

第一部分是显然的。

对于第二部分,我们可以任意选择一条连接两个负数元素的有向路径,按顺序对路径上(除终点以外)的每个元素和它对应的下一个元素都执行一次操作。最终路径上除了两个端点以外的其他元素都被执行了两次操作,因此数值不变;两个端点元素都被执行了一次操作二变为正数。

由于方阵是网格,因此上述路径一定存在。

对于第三部分,将第二部分中的一个负数更改为正数即可证明。

提示 $2$

如果方阵中存在一个元素为 $0$,另一个元素为负数。那么一定存在一系列的操作使得负数元素变为正数,且其余元素不变。

提示 $2$ 解释

类似 提示 $1$,将一个负数元素更改为 $0$ 即可证明。

提示 $3$

如果方阵中存在 $0$,那么一定可以通过一系列的操作使得方阵中所有元素均为非负数;

如果方阵中不存在 $0$,那么:

  • 如果方阵中有奇数个负数元素,那么一定可以通过一系列的操作使得方阵中只有一个负数元素,且该负数元素可以在任何位置。同时,无论如何操作,方阵中必定存在负数元素。

  • 如果方阵中有偶数个负数元素,那么一定可以通过一系列的操作使得方阵中不存在负数元素。

提示 $3$ 解释

对于第一部分,反复对 $0$ 和负数元素进行 提示 $2$ 的操作即可。

对于第二部分,我们首先可以证明如果方阵不存在 $0$,那么负数元素数量奇偶性不会改变。然后,我们可以根据 提示 $1$ 构造出一系列操作从而达到对应的要求。

思路与算法

根据 提示 $3$,我们可以按照方阵的元素分为以下几种情况:

  • 方阵中有 $0$,那么最大方阵和即为所有元素的绝对值之和;

  • 方阵中没有 $0$,且负数元素数量为偶数,那么最大方阵和即为所有元素的绝对值之和;

  • 方阵中没有 $0$,且负数元素数量为奇数,那么最大方阵和即为所有元素的绝对值之和减去所有元素最小绝对值的两倍。

其中,第一种情况也可以按照负数元素数量的奇偶性划入后两种情况中(此时最小绝对值一定为 $0$)。

我们遍历方阵,维护负数元素的数量、元素的最小绝对值以及所有元素的绝对值之和。随后,我们按照负数元素数量的奇偶性计算对应的最大元素和并返回。

最后,矩阵所有元素绝对值之和可能超过 $32$ 位整数的上限,因此对于 $\texttt{C++}$ 等语言,需要使用 $64$ 位整数来维护。

代码

###C++

class Solution {
public:
    long long maxMatrixSum(vector<vector<int>>& matrix) {
        int n = matrix.size();
        int cnt = 0;   // 负数元素的数量
        long long total = 0;   // 所有元素的绝对值之和
        int mn = INT_MAX;   // 方阵元素的最小绝对值
        for (int i = 0; i < n; ++i){
            for (int j = 0; j < n; ++j){
                mn = min(mn, abs(matrix[i][j]));
                if (matrix[i][j] < 0){
                    ++cnt;
                }
                total += abs(matrix[i][j]);
            }
        }
        // 按照负数元素的数量的奇偶性讨论
        if (cnt % 2 == 0){
            return total;
        }
        else{
            return total - 2 * mn;
        }
    }
};

###Python

class Solution:
    def maxMatrixSum(self, matrix: List[List[int]]) -> int:
        n = len(matrix)
        cnt = 0   # 负数元素的数量
        total = 0   # 所有元素的绝对值之和
        mn = float("INF")   # 方阵元素的最小绝对值
        for i in range(n):
            for j in range(n):
                mn = min(mn, abs(matrix[i][j]))
                if matrix[i][j] < 0:
                    cnt += 1
                total += abs(matrix[i][j])
        # 按照负数元素的数量的奇偶性讨论
        if cnt % 2 == 0:
            return total
        else:
            return total - 2 * mn

###Java

class Solution {
    public long maxMatrixSum(int[][] matrix) {
        int n = matrix.length;
        int cnt = 0;   // 负数元素的数量
        long total = 0;   // 所有元素的绝对值之和
        int mn = Integer.MAX_VALUE;   // 方阵元素的最小绝对值
        for (int i = 0; i < n; ++i){
            for (int j = 0; j < n; ++j){
                mn = Math.min(mn, Math.abs(matrix[i][j]));
                if (matrix[i][j] < 0){
                    ++cnt;
                }
                total += Math.abs(matrix[i][j]);
            }
        }
        // 按照负数元素的数量的奇偶性讨论
        if (cnt % 2 == 0){
            return total;
        } else {
            return total - 2 * mn;
        }
    }
}

###C#

public class Solution {
    public long MaxMatrixSum(int[][] matrix) {
        int n = matrix.Length;
        int cnt = 0;   // 负数元素的数量
        long total = 0;   // 所有元素的绝对值之和
        int mn = int.MaxValue;   // 方阵元素的最小绝对值
        for (int i = 0; i < n; ++i){
            for (int j = 0; j < n; ++j){
                mn = Math.Min(mn, Math.Abs(matrix[i][j]));
                if (matrix[i][j] < 0){
                    ++cnt;
                }
                total += Math.Abs(matrix[i][j]);
            }
        }
        // 按照负数元素的数量的奇偶性讨论
        if (cnt % 2 == 0){
            return total;
        } else{
            return total - 2 * mn;
        }
    }
}

###Go

func maxMatrixSum(matrix [][]int) int64 {
    n := len(matrix)
    cnt := 0   // 负数元素的数量
    total := int64(0)   // 所有元素的绝对值之和
    mn := 1 << 30   // 方阵元素的最小绝对值
    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            mn = min(mn, abs(matrix[i][j]))
            if matrix[i][j] < 0 {
                cnt++
            }
            total += int64(abs(matrix[i][j]))
        }
    }
    // 按照负数元素的数量的奇偶性讨论
    if cnt % 2 == 0 {
        return total
    } else {
        return total - int64(2 * mn)
    }
}

func abs(x int) int {
    if x < 0 {
        return -x
    }
    return x
}

###C

long long maxMatrixSum(int** matrix, int matrixSize, int* matrixColSize) {
    int n = matrixSize;
    int cnt = 0;   // 负数元素的数量
    long long total = 0;   // 所有元素的绝对值之和
    int mn = INT_MAX;   // 方阵元素的最小绝对值
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            int abs_val = abs(matrix[i][j]);
            if (abs_val < mn) {
                mn = abs_val;
            }
            if (matrix[i][j] < 0) {
                ++cnt;
            }
            total += abs_val;
        }
    }
    // 按照负数元素的数量的奇偶性讨论
    if (cnt % 2 == 0) {
        return total;
    } else {
        return total - 2 * mn;
    }
}

###JavaScript

var maxMatrixSum = function(matrix) {
    const n = matrix.length;
    let cnt = 0;   // 负数元素的数量
    let total = 0;   // 所有元素的绝对值之和
    let mn = Number.MAX_SAFE_INTEGER;   // 方阵元素的最小绝对值
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            const absVal = Math.abs(matrix[i][j]);
            mn = Math.min(mn, absVal);
            if (matrix[i][j] < 0) {
                cnt++;
            }
            total += absVal;
        }
    }
    // 按照负数元素的数量的奇偶性讨论
    if (cnt % 2 === 0) {
        return total;
    } else {
        return total - 2 * mn;
    }
};

###TypeScript

function maxMatrixSum(matrix: number[][]): number {
    const n = matrix.length;
    let cnt = 0;   // 负数元素的数量
    let total = 0;   // 所有元素的绝对值之和
    let mn = Number.MAX_SAFE_INTEGER;   // 方阵元素的最小绝对值
    for (let i = 0; i < n; i++) {
        for (let j = 0; j < n; j++) {
            const absVal = Math.abs(matrix[i][j]);
            mn = Math.min(mn, absVal);
            if (matrix[i][j] < 0) {
                cnt++;
            }
            total += absVal;
        }
    }
    // 按照负数元素的数量的奇偶性讨论
    if (cnt % 2 === 0) {
        return total;
    } else {
        return total - 2 * mn;
    }
}

###Rust

impl Solution {
    pub fn max_matrix_sum(matrix: Vec<Vec<i32>>) -> i64 {
        let n = matrix.len();
        let mut cnt = 0;   // 负数元素的数量
        let mut total: i64 = 0;   // 所有元素的绝对值之和
        let mut mn = i32::MAX;   // 方阵元素的最小绝对值
        for i in 0..n {
            for j in 0..n {
                let abs_val = matrix[i][j].abs();
                mn = mn.min(abs_val);
                if matrix[i][j] < 0 {
                    cnt += 1;
                }
                total += abs_val as i64;
            }
        }
        // 按照负数元素的数量的奇偶性讨论
        if cnt % 2 == 0 {
            total
        } else {
            total - 2 * mn as i64
        }
    }
}

复杂度分析

  • 时间复杂度:$O(mn)$,其中 $m$ 为 $\textit{matrix}$ 的行数,$n$ 为 $\textit{matrix}$ 的列数。

  • 空间复杂度:$O(1)$。

5835. 最大方阵和 - 贪心

作者 MGAronya
2021年8月22日 16:06

5835. 最大方阵和

第二题

刚开始我用了深度搜索,结果越写越觉得不对劲,最后发现:

其实如果负数数量是双数,我们总是能把他们都翻成正数

如果负数数量是单数,我们怎么翻都会至少留下一个负数

以上,记录下矩阵里绝对值最小的数即可

(我是笨比)

模拟

`
###c++

class Solution {
public:
    long long maxMatrixSum(vector<vector<int>>& matrix) {
        int minn = abs(matrix[0][0]);
        long long ans = 0;
        bool flag = false; //记录是负数的个数是单数还是双数
        for(auto &vec: matrix)
            for(int num: vec){
                if(num < 0){  //是负数的话
                    flag = !flag;  
                    num = -num;  //取绝对值
                }
                ans += num;  //记录绝对值的和
                minn = min(num, minn);  //记录最小值
            }
        if(flag) //是单数
        return ans - 2 * minn;
        return ans;//是双数
    }
};

脑筋急转弯 + 分类讨论(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2021年8月22日 00:13

虽然题目规定我们只能操作相邻的元素,但我们可以通过多次操作,把任意两个元素都乘以 $-1$。

lc1975c.png

每次操作,恰好改变两个数的正负号。

多次操作,恰好改变偶数个数的正负号。

分类讨论:

  • 如果 $\textit{matrix}$ 有偶数个负数,那么可以把所有数都变成非负数。
  • 如果 $\textit{matrix}$ 有奇数个负数且没有 $0$,那么最终必然剩下奇数个负数。剩下一个负数是最优的。贪心地,选择 $\textit{matrix}$ 中的绝对值最小的数,给它加上负号。
  • 如果 $\textit{matrix}$ 有奇数个负数且有 $0$,那么可以把一个 $0$ 和最终剩下的一个负数操作一次,从而把所有数都变成非负数。

代码实现时,无需特判是否有 $0$。如果有 $0$,那么代码中的 $\textit{mn}=0$,对 $\textit{total}$ 无影响。

class Solution:
    def maxMatrixSum(self, matrix: List[List[int]]) -> int:
        total = neg_cnt = 0
        mn = inf
        for row in matrix:
            for x in row:
                if x < 0:
                    neg_cnt += 1
                    x = -x  # 先把负数都变成正数
                mn = min(mn, x)
                total += x

        if neg_cnt % 2:
            total -= mn * 2  # 给绝对值最小的数添加负号
        return total
class Solution {
    public long maxMatrixSum(int[][] matrix) {
        long total = 0;
        int negCnt = 0;
        int mn = Integer.MAX_VALUE;
        for (int[] row : matrix) {
            for (int x : row) {
                if (x < 0) {
                    negCnt++;
                    x = -x; // 先把负数都变成正数
                }
                mn = Math.min(mn, x);
                total += x;
            }
        }

        if (negCnt % 2 > 0) {
            total -= mn * 2; // 给绝对值最小的数添加负号
        }
        return total;
    }
}
class Solution {
public:
    long long maxMatrixSum(vector<vector<int>>& matrix) {
        long long total = 0;
        int neg_cnt = 0;
        int mn = INT_MAX;
        for (auto& row : matrix) {
            for (int x : row) {
                if (x < 0) {
                    neg_cnt++;
                    x = -x; // 先把负数都变成正数
                }
                mn = min(mn, x);
                total += x;
            }
        }

        if (neg_cnt % 2) {
            total -= mn * 2; // 给绝对值最小的数添加负号
        }
        return total;
    }
};
#define MIN(a, b) ((b) < (a) ? (b) : (a))

long long maxMatrixSum(int** matrix, int matrixSize, int* matrixColSize) {
    long long total = 0;
    int neg_cnt = 0;
    int mn = INT_MAX;
    for (int i = 0; i < matrixSize; i++) {
        for (int j = 0; j < matrixColSize[i]; j++) {
            int x = matrix[i][j];
            if (x < 0) {
                neg_cnt++;
                x = -x; // 先把负数都变成正数
            }
            mn = MIN(mn, x);
            total += x;
        }
    }

    if (neg_cnt % 2) {
        total -= mn * 2; // 给绝对值最小的数添加负号
    }
    return total;
}
func maxMatrixSum(matrix [][]int) int64 {
total, negCnt, mn := 0, 0, math.MaxInt
for _, row := range matrix {
for _, x := range row {
if x < 0 {
negCnt++
x = -x // 先把负数都变成正数
}
mn = min(mn, x)
total += x
}
}

if negCnt%2 > 0 {
total -= mn * 2 // 给绝对值最小的数添加负号
}
return int64(total)
}
var maxMatrixSum = function(matrix) {
    let total = 0;
    let negCnt = 0;
    let mn = Infinity;
    for (const row of matrix) {
        for (let x of row) {
            if (x < 0) {
                negCnt++;
                x = -x; // 先把负数都变成正数
            }
            mn = Math.min(mn, x);
            total += x;
        }
    }

    if (negCnt % 2) {
        total -= mn * 2; // 给绝对值最小的数添加负号
    }
    return total;
};
impl Solution {
    pub fn max_matrix_sum(matrix: Vec<Vec<i32>>) -> i64 {
        let mut total = 0;
        let mut neg_cnt = 0;
        let mut mn = i32::MAX;
        for row in matrix {
            for mut x in row {
                if x < 0 {
                    neg_cnt += 1;
                    x = -x; // 先把负数都变成正数
                }
                mn = mn.min(x);
                total += x as i64;
            }
        }

        if neg_cnt % 2 > 0 {
            total -= (mn * 2) as i64; // 给绝对值最小的数添加负号
        }
        total
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别是 $\textit{matrix}$ 的行数和列数。
  • 空间复杂度:$\mathcal{O}(1)$。

专题训练

见下面贪心与思维题单的「§5.2 脑筋急转弯」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

每日一题-四因数🟡

2026年1月4日 00:00

给你一个整数数组 nums,请你返回该数组中恰有四个因数的这些整数的各因数之和。如果数组中不存在满足题意的整数,则返回 0

 

示例 1:

输入:nums = [21,4,7]
输出:32
解释:
21 有 4 个因数:1, 3, 7, 21
4 有 3 个因数:1, 2, 4
7 有 2 个因数:1, 7
答案仅为 21 的所有因数的和。

示例 2:

输入: nums = [21,21]
输出: 64

示例 3:

输入: nums = [1,2,3,4,5]
输出: 0

 

提示:

  • 1 <= nums.length <= 104
  • 1 <= nums[i] <= 105

【模板】预处理每个数的因子个数、因子和(Python/Java/C++/Go)

作者 endlesscheng
2025年12月28日 20:13

对于一个数,它有哪些因子?这不能一眼看出来。

但反过来,对于一个数 $i$,$i$ 的倍数都有因子 $i$。

枚举 $i=1,2,3,\ldots,10^5$,以及 $i$ 的倍数 $j$,把 $j$ 的因子个数加一,因子和加上 $i$。

对于 $\textit{nums}$ 中的因子个数为 $4$ 的元素,累加这些元素的因子和,即为答案。

###py

MX = 100_001
divisor_num = [0] * MX
divisor_sum = [0] * MX
for i in range(1, MX):
    for j in range(i, MX, i):  # 枚举 i 的倍数 j
        divisor_num[j] += 1  # i 是 j 的因子
        divisor_sum[j] += i

class Solution:
    def sumFourDivisors(self, nums: List[int]) -> int:
        ans = 0
        for x in nums:
            if divisor_num[x] == 4:
                ans += divisor_sum[x]
        return ans

###java

class Solution {
    private static final int MX = 100_001;
    private static final int[] divisorNum = new int[MX];
    private static final int[] divisorSum = new int[MX];
    private static boolean initialized = false;

    // 这样写比 static block 快
    public Solution() {
        if (initialized) {
            return;
        }
        initialized = true;

        for (int i = 1; i < MX; i++) {
            for (int j = i; j < MX; j += i) { // 枚举 i 的倍数 j
                divisorNum[j]++; // i 是 j 的因子
                divisorSum[j] += i;
            }
        }
    }

    public int sumFourDivisors(int[] nums) {
        int ans = 0;
        for (int x : nums) {
            if (divisorNum[x] == 4) {
                ans += divisorSum[x];
            }
        }
        return ans;
    }
}

###cpp

constexpr int MX = 100'001;
int divisor_num[MX];
int divisor_sum[MX];

int init = [] {
    for (int i = 1; i < MX; i++) {
        for (int j = i; j < MX; j += i) { // 枚举 i 的倍数 j
            divisor_num[j]++; // i 是 j 的因子
            divisor_sum[j] += i;
        }
    }
    return 0;
}();

class Solution {
public:
    int sumFourDivisors(vector<int>& nums) {
        int ans = 0;
        for (int x : nums) {
            if (divisor_num[x] == 4) {
                ans += divisor_sum[x];
            }
        }
        return ans;
    }
};

###go

const mx = 100_001

var divisorNum, divisorSum [mx]int

func init() {
for i := 1; i < mx; i++ {
for j := i; j < mx; j += i { // 枚举 i 的倍数 j
divisorNum[j]++ // i 是 j 的因子
divisorSum[j] += i
}
}
}

func sumFourDivisors(nums []int) (ans int) {
for _, x := range nums {
if divisorNum[x] == 4 {
ans += divisorSum[x]
}
}
return
}

复杂度分析

设 $U=\max(\textit{nums})$。预处理的时间为 $\mathcal{O}(U\log U)$(调和级数和),空间为 $\mathcal{O}(U)$,不计入。

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

专题训练

见下面数学题单的「§1.5 因子」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

四因数

2020年3月23日 18:55

方法一:枚举

我们可以遍历数组 nums 中的每个元素,依次判断这些元素是否恰好有四个因数。对于任一元素 x,我们可以用类似质数判定的方法得到它的因数个数,其本质为:如果整数 x 有因数 y,那么也必有因数 x/y,并且 yx/y 中至少有一个不大于 sqrt(x)。这样我们只需要在 [1, sqrt(x)] 的区间内枚举可能为整数 x 的因数 y,并通过 x/y 得到整数 x 的其它因数,时间复杂度为 $O(\sqrt{x})$。

如果 x 恰好有四个因数,我们就将其因数之和累加到答案中。

###C++

class Solution {
public:
    int sumFourDivisors(vector<int>& nums) {
        int ans = 0;
        for (int num: nums) {
            // factor_cnt: 因数的个数
            // factor_sum: 因数的和
            int factor_cnt = 0, factor_sum = 0;
            for (int i = 1; i * i <= num; ++i) {
                if (num % i == 0) {
                    ++factor_cnt;
                    factor_sum += i;
                    if (i * i != num) {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                        ++factor_cnt;
                        factor_sum += num / i;
                    }
                }
            }
            if (factor_cnt == 4) {
                ans += factor_sum;
            }
        }
        return ans;
    }
};

###Java

class Solution {
    public int sumFourDivisors(int[] nums) {
        int ans = 0;
        for (int num : nums) {
            // factor_cnt: 因数的个数
            // factor_sum: 因数的和
            int factor_cnt = 0, factor_sum = 0;
            for (int i = 1; i * i <= num; ++i) {
                if (num % i == 0) {
                    ++factor_cnt;
                    factor_sum += i;
                    if (i * i != num) {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                        ++factor_cnt;
                        factor_sum += num / i;
                    }
                }
            }
            if (factor_cnt == 4) {
                ans += factor_sum;
            }
        }
        return ans;
    }
}

###Python

class Solution:
    def sumFourDivisors(self, nums: List[int]) -> int:
        ans = 0
        for num in nums:
            # factor_cnt: 因数的个数
            # factor_sum: 因数的和
            factor_cnt = factor_sum = 0
            i = 1
            while i * i <= num:
                if num % i == 0:
                    factor_cnt += 1
                    factor_sum += i
                    if i * i != num:   # 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                        factor_cnt += 1
                        factor_sum += num // i
                i += 1
            if factor_cnt == 4:
                ans += factor_sum
        return ans

###C#

public class Solution {
    public int SumFourDivisors(int[] nums) {
        int ans = 0;
        foreach (int num in nums) {
            // factor_cnt: 因数的个数
            // factor_sum: 因数的和
            int factor_cnt = 0, factor_sum = 0;
            for (int i = 1; i * i <= num; ++i) {
                if (num % i == 0) {
                    ++factor_cnt;
                    factor_sum += i;
                    if (i * i != num) {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                        ++factor_cnt;
                        factor_sum += num / i;
                    }
                }
            }
            if (factor_cnt == 4) {
                ans += factor_sum;
            }
        }
        return ans;
    }
}

###Go

func sumFourDivisors(nums []int) int {
    ans := 0
    for _, num := range nums {
        // factor_cnt: 因数的个数
        // factor_sum: 因数的和
        factor_cnt, factor_sum := 0, 0
        for i := 1; i*i <= num; i++ {
            if num%i == 0 {
                factor_cnt++
                factor_sum += i
                if i*i != num {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                    factor_cnt++
                    factor_sum += num / i
                }
            }
        }
        if factor_cnt == 4 {
            ans += factor_sum
        }
    }
    return ans
}

###C

int sumFourDivisors(int* nums, int numsSize) {
    int ans = 0;
    for (int idx = 0; idx < numsSize; idx++) {
        int num = nums[idx];
        // factor_cnt: 因数的个数
        // factor_sum: 因数的和
        int factor_cnt = 0, factor_sum = 0;
        for (int i = 1; i * i <= num; ++i) {
            if (num % i == 0) {
                ++factor_cnt;
                factor_sum += i;
                if (i * i != num) {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                    ++factor_cnt;
                    factor_sum += num / i;
                }
            }
        }
        if (factor_cnt == 4) {
            ans += factor_sum;
        }
    }
    return ans;
}

###JavaScript

var sumFourDivisors = function(nums) {
    let ans = 0;
    for (const num of nums) {
        // factor_cnt: 因数的个数
        // factor_sum: 因数的和
        let factor_cnt = 0, factor_sum = 0;
        for (let i = 1; i * i <= num; ++i) {
            if (num % i === 0) {
                ++factor_cnt;
                factor_sum += i;
                if (i * i !== num) {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                    ++factor_cnt;
                    factor_sum += num / i;
                }
            }
        }
        if (factor_cnt === 4) {
            ans += factor_sum;
        }
    }
    return ans;
};

###TypeScript

function sumFourDivisors(nums: number[]): number {
    let ans = 0;
    for (const num of nums) {
        // factor_cnt: 因数的个数
        // factor_sum: 因数的和
        let factor_cnt = 0, factor_sum = 0;
        for (let i = 1; i * i <= num; ++i) {
            if (num % i === 0) {
                ++factor_cnt;
                factor_sum += i;
                if (i * i !== num) {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                    ++factor_cnt;
                    factor_sum += num / i;
                }
            }
        }
        if (factor_cnt === 4) {
            ans += factor_sum;
        }
    }
    return ans;
}

###Rust

impl Solution {
    pub fn sum_four_divisors(nums: Vec<i32>) -> i32 {
        let mut ans = 0;
        for &num in &nums {
            // factor_cnt: 因数的个数
            // factor_sum: 因数的和
            let mut factor_cnt = 0;
            let mut factor_sum = 0;
            let mut i = 1;
            while i * i <= num {
                if num % i == 0 {
                    factor_cnt += 1;
                    factor_sum += i;
                    if i * i != num {   // 判断 i 和 num/i 是否相等,若不相等才能将 num/i 看成新的因数
                        factor_cnt += 1;
                        factor_sum += num / i;
                    }
                }
                i += 1;
            }
            if factor_cnt == 4 {
                ans += factor_sum;
            }
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$O(N\sqrt{C})$,其中 $N$ 是数组 nums 的长度,$C$ 是数组 nums 中元素值的范围,在本题中 $C$ 不超过 $10^5$。

  • 空间复杂度:$O(1)$。

方法二:预处理

预备知识

分析与算法

直觉告诉我们,恰好有四个因数的整数不会有很多,我们是否可以预先找出它们呢?

根据「算数基本定理」(又叫「唯一分解定理」),如果整数 $x$ 可以分解为:

$$
x = p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_k^{\alpha_k}
$$

其中 $p_i$ 为互不相同的质数(即 $x$ 的质因数)。那么 $x$ 的因数个数为:

$$
\textit{factor_count}(x) = \prod_{i=1}^k (\alpha_i + 1)
$$

如果 $\textit{factor_count}(x)$ 的值为 $4$,那么只有两种可能:

  • 整数 $x$ 只有一个质因数,对应的指数为 $3$,此时 $\textit{factor_count}(x) = (3+1) = 4$;

  • 整数 $x$ 有两个质因数,对应的指数均为 $1$,此时 $\textit{factor_count}(x) = (1+1)(1+1) = 4$。

对于第一种情况,我们需要找到所有不大于 $C^{1/3}$ 的质数;对于第二种情况,我们需要找到所有不大于 $C$ 的质数,再将它们两两相乘并筛去超过 $C$ 的那些结果。这里 $C$ 的定义与方法一中的复杂度分析部分一致。综上所述,我们需要找到所有不大于 $C$ 的质数。

我们如何找出所有不大于 $C$ 的质数呢?这时就需要「埃拉托斯特尼筛法」或「欧拉筛法」的帮助了。它们可以帮助我们快速找到这些质数。这两种筛法的算法细节不是这篇题解的重点,这里不再赘述。在找到了这些质数后,我们就可以构造出所有满足上述两种可能的 $x$ 了。我们将 $x$ 以及它的因数之和存入哈希映射(HashMap)中,这样就可以在 $O(1)$ 的时间判断数组 nums 中的每个元素是否满足要求,并统计满足要求的元素的因数之和了。

下面的代码给出了 Python 和 C++ 语言的「埃拉托斯特尼筛法」以及「欧拉筛法」的实现。

###C++

class Solution {
public:
    int sumFourDivisors(vector<int>& nums) {
        // C 是数组 nums 元素的上限,C3 是 C 的立方根
        int C = 100000, C3 = 46;
        
        vector<int> isprime(C + 1, 1);
        vector<int> primes;

        // 埃拉托斯特尼筛法
        for (int i = 2; i <= C; ++i) {
            if (isprime[i]) {
                primes.push_back(i);
            }
            for (int j = i + i; j <= C; j += i) {
                isprime[j] = 0;
            }
        }

        // 欧拉筛法
        /*
        for (int i = 2; i <= C; ++i) {
            if (isprime[i]) {
                primes.push_back(i);
            }
            for (int prime: primes) {
                if (i * prime > C) {
                    break;
                }
                isprime[i * prime] = 0;
                if (i % prime == 0) {
                    break;
                }
            }
        }
        */
        
        // 通过质数表构造出所有的四因数
        unordered_map<int, int> factor4;
        for (int prime: primes) {
            if (prime <= C3) {
                factor4[prime * prime * prime] = 1 + prime + prime * prime + prime * prime * prime;
            }
        }
        for (int i = 0; i < primes.size(); ++i) {
            for (int j = i + 1; j < primes.size(); ++j) {
                if (primes[i] <= C / primes[j]) {
                    factor4[primes[i] * primes[j]] = 1 + primes[i] + primes[j] + primes[i] * primes[j];
                }
                else {
                    break;
                }
            }
        }

        int ans = 0;
        for (int num: nums) {
            if (factor4.count(num)) {
                ans += factor4[num];
            }
        }
        return ans;
    }
};

###Java

class Solution {
    public int sumFourDivisors(int[] nums) {
        // C 是数组 nums 元素的上限,C3 是 C 的立方根
        int C = 100000, C3 = 46;
        
        boolean[] isPrime = new boolean[C + 1];
        Arrays.fill(isPrime, true);
        List<Integer> primes = new ArrayList<Integer>();

        // 埃拉托斯特尼筛法
        for (int i = 2; i <= C; ++i) {
            if (isPrime[i]) {
                primes.add(i);
            }
            for (int j = i + i; j <= C; j += i) {
                isPrime[j] = false;
            }
        }

        // 欧拉筛法
        /*
        for (int i = 2; i <= C; ++i) {
            if (isPrime[i]) {
                primes.add(i);
            }
            for (int prime : primes) {
                if (i * prime > C) {
                    break;
                }
                isPrime[i * prime] = false;
                if (i % prime == 0) {
                    break;
                }
            }
        }
        */
        
        // 通过质数表构造出所有的四因数
        Map<Integer, Integer> factor4 = new HashMap<Integer, Integer>();
        for (int prime : primes) {
            if (prime <= C3) {
                factor4.put(prime * prime * prime, 1 + prime + prime * prime + prime * prime * prime);
            }
        }
        for (int i = 0; i < primes.size(); ++i) {
            for (int j = i + 1; j < primes.size(); ++j) {
                if (primes.get(i) <= C / primes.get(j)) {
                    factor4.put(primes.get(i) * primes.get(j), 1 + primes.get(i) + primes.get(j) + primes.get(i) * primes.get(j));
                } else {
                    break;
                }
            }
        }

        int ans = 0;
        for (int num : nums) {
            if (factor4.containsKey(num)) {
                ans += factor4.get(num);
            }
        }
        return ans;
    }
}

###Python

class Solution:
    def sumFourDivisors(self, nums: List[int]) -> int:
        # C 是数组 nums 元素的上限,C3 是 C 的立方根
        C, C3 = 100000, 46

        isprime = [True] * (C + 1)
        primes = list()

        # 埃拉托斯特尼筛法
        for i in range(2, C + 1):
            if isprime[i]:
                primes.append(i)
            for j in range(i + i, C + 1, i):
                isprime[j] = False
        
        # 欧拉筛法
        """
        for i in range(2, C + 1):
            if isprime[i]:
                primes.append(i)
            for prime in primes:
                if i * prime > C:
                    break
                isprime[i * prime] = False
                if i % prime == 0:
                    break
        """
        
        # 通过质数表构造出所有的四因数
        factor4 = dict()
        for prime in primes:
            if prime <= C3:
                factor4[prime**3] = 1 + prime + prime**2 + prime**3
        for i in range(len(primes)):
            for j in range(i + 1, len(primes)):
                if primes[i] * primes[j] <= C:
                    factor4[primes[i] * primes[j]] = 1 + primes[i] + primes[j] + primes[i] * primes[j]
                else:
                    break
        
        ans = 0
        for num in nums:
            if num in factor4:
                ans += factor4[num]
        return ans

###C#

public class Solution {
    public int SumFourDivisors(int[] nums) {
        // C 是数组 nums 元素的上限,C3 是 C 的立方根
        int C = 100000, C3 = 46;
        
        int[] isprime = new int[C + 1];
        for (int i = 2; i <= C; i++) isprime[i] = 1;
        List<int> primes = new List<int>();

        // 埃拉托斯特尼筛法
        for (int i = 2; i <= C; ++i) {
            if (isprime[i] == 1) {
                primes.Add(i);
            }
            for (int j = i + i; j <= C; j += i) {
                isprime[j] = 0;
            }
        }

        // 欧拉筛法
        /*
        for (int i = 2; i <= C; ++i) {
            if (isprime[i] == 1) {
                primes.Add(i);
            }
            foreach (int prime in primes) {
                if (i * prime > C) {
                    break;
                }
                isprime[i * prime] = 0;
                if (i % prime == 0) {
                    break;
                }
            }
        }
        */
        
        // 通过质数表构造出所有的四因数
        Dictionary<int, int> factor4 = new Dictionary<int, int>();
        foreach (int prime in primes) {
            if (prime <= C3) {
                factor4[prime * prime * prime] = 1 + prime + prime * prime + prime * prime * prime;
            }
        }
        for (int i = 0; i < primes.Count; ++i) {
            for (int j = i + 1; j < primes.Count; ++j) {
                if (primes[i] <= C / primes[j]) {
                    factor4[primes[i] * primes[j]] = 1 + primes[i] + primes[j] + primes[i] * primes[j];
                }
                else {
                    break;
                }
            }
        }

        int ans = 0;
        foreach (int num in nums) {
            if (factor4.ContainsKey(num)) {
                ans += factor4[num];
            }
        }
        return ans;
    }
}

###Go

func sumFourDivisors(nums []int) int {
    // C 是数组 nums 元素的上限,C3 是 C 的立方根
    C, C3 := 100000, 46
    
    isprime := make([]int, C+1)
    for i := 2; i <= C; i++ {
        isprime[i] = 1
    }
    primes := []int{}

    // 埃拉托斯特尼筛法
    for i := 2; i <= C; i++ {
        if isprime[i] == 1 {
            primes = append(primes, i)
        }
        for j := i + i; j <= C; j += i {
            isprime[j] = 0
        }
    }

    // 欧拉筛法
    /*
    for i := 2; i <= C; i++ {
        if isprime[i] == 1 {
            primes = append(primes, i)
        }
        for _, prime := range primes {
            if i * prime > C {
                break
            }
            isprime[i * prime] = 0
            if i % prime == 0 {
                break
            }
        }
    }
    */
    
    // 通过质数表构造出所有的四因数
    factor4 := make(map[int]int)
    for _, prime := range primes {
        if prime <= C3 {
            factor4[prime * prime * prime] = 1 + prime + prime * prime + prime * prime * prime
        }
    }
    for i := 0; i < len(primes); i++ {
        for j := i + 1; j < len(primes); j++ {
            if primes[i] <= C / primes[j] {
                factor4[primes[i] * primes[j]] = 1 + primes[i] + primes[j] + primes[i] * primes[j]
            } else {
                break
            }
        }
    }

    ans := 0
    for _, num := range nums {
        if val, exists := factor4[num]; exists {
            ans += val
        }
    }
    return ans
}

###C

int sumFourDivisors(int* nums, int numsSize) {
    // C 是数组 nums 元素的上限,C3 是 C 的立方根
    const int C = 100000, C3 = 46;
    
    int* isprime = (int*)malloc((C + 1) * sizeof(int));
    memset(isprime, 0, (C + 1) * sizeof(int));
    int* primes = (int*)malloc((C + 1) * sizeof(int));
    int primeCount = 0;

    // 埃拉托斯特尼筛法
    for (int i = 2; i <= C; ++i) {
        isprime[i] = 1;
    }
    for (int i = 2; i <= C; ++i) {
        if (isprime[i]) {
            primes[primeCount++] = i;
        }
        for (int j = i + i; j <= C; j += i) {
            isprime[j] = 0;
        }
    }

    // 欧拉筛法
    /*
    for (int i = 2; i <= C; ++i) {
        if (isprime[i]) {
            primes[primeCount++] = i;
        }
        for (int j = 0; j < primeCount; ++j) {
            if (i * primes[j] > C) {
                break;
            }
            isprime[i * primes[j]] = 0;
            if (i % primes[j] == 0) {
                break;
            }
        }
    }
    */
    
    // 通过质数表构造出所有的四因数
    int* factor4_keys = (int*)malloc(primeCount * primeCount * sizeof(int));
    int* factor4_values = (int*)malloc(primeCount * primeCount * sizeof(int));
    int factor4_count = 0;
    
    for (int i = 0; i < primeCount; ++i) {
        int prime = primes[i];
        if (prime <= C3) {
            factor4_keys[factor4_count] = prime * prime * prime;
            factor4_values[factor4_count] = 1 + prime + prime * prime + prime * prime * prime;
            factor4_count++;
        }
    }
    for (int i = 0; i < primeCount; ++i) {
        for (int j = i + 1; j < primeCount; ++j) {
            if (primes[i] <= C / primes[j]) {
                factor4_keys[factor4_count] = primes[i] * primes[j];
                factor4_values[factor4_count] = 1 + primes[i] + primes[j] + primes[i] * primes[j];
                factor4_count++;
            } else {
                break;
            }
        }
    }

    int ans = 0;
    for (int idx = 0; idx < numsSize; ++idx) {
        int num = nums[idx];
        for (int i = 0; i < factor4_count; ++i) {
            if (factor4_keys[i] == num) {
                ans += factor4_values[i];
                break;
            }
        }
    }
    
    free(isprime);
    free(primes);
    free(factor4_keys);
    free(factor4_values);
    
    return ans;
}

###JavaScript

var sumFourDivisors = function(nums) {
    // C 是数组 nums 元素的上限,C3 是 C 的立方根
    const C = 100000, C3 = 46;
    
    let isprime = new Array(C + 1).fill(0);
    let primes = [];

    // 埃拉托斯特尼筛法
    for (let i = 2; i <= C; i++) {
        isprime[i] = 1;
    }
    for (let i = 2; i <= C; i++) {
        if (isprime[i]) {
            primes.push(i);
        }
        for (let j = i + i; j <= C; j += i) {
            isprime[j] = 0;
        }
    }

    // 欧拉筛法
    /*
    for (let i = 2; i <= C; i++) {
        if (isprime[i]) {
            primes.push(i);
        }
        for (let prime of primes) {
            if (i * prime > C) {
                break;
            }
            isprime[i * prime] = 0;
            if (i % prime === 0) {
                break;
            }
        }
    }
    */
    
    // 通过质数表构造出所有的四因数
    let factor4 = new Map();
    for (let prime of primes) {
        if (prime <= C3) {
            factor4.set(prime * prime * prime, 1 + prime + prime * prime + prime * prime * prime);
        }
    }
    for (let i = 0; i < primes.length; i++) {
        for (let j = i + 1; j < primes.length; j++) {
            if (primes[i] <= C / primes[j]) {
                factor4.set(primes[i] * primes[j], 1 + primes[i] + primes[j] + primes[i] * primes[j]);
            } else {
                break;
            }
        }
    }

    let ans = 0;
    for (let num of nums) {
        if (factor4.has(num)) {
            ans += factor4.get(num);
        }
    }
    return ans;
};

###TypeScript

function sumFourDivisors(nums: number[]): number {
    // C 是数组 nums 元素的上限,C3 是 C 的立方根
    const C: number = 100000, C3: number = 46;
    
    let isprime: number[] = new Array(C + 1).fill(0);
    let primes: number[] = [];

    // 埃拉托斯特尼筛法
    for (let i = 2; i <= C; i++) {
        isprime[i] = 1;
    }
    for (let i = 2; i <= C; i++) {
        if (isprime[i]) {
            primes.push(i);
        }
        for (let j = i + i; j <= C; j += i) {
            isprime[j] = 0;
        }
    }

    // 欧拉筛法
    /*
    for (let i = 2; i <= C; i++) {
        if (isprime[i]) {
            primes.push(i);
        }
        for (let prime of primes) {
            if (i * prime > C) {
                break;
            }
            isprime[i * prime] = 0;
            if (i % prime === 0) {
                break;
            }
        }
    }
    */
    
    // 通过质数表构造出所有的四因数
    let factor4: Map<number, number> = new Map();
    for (let prime of primes) {
        if (prime <= C3) {
            factor4.set(prime * prime * prime, 1 + prime + prime * prime + prime * prime * prime);
        }
    }
    for (let i = 0; i < primes.length; i++) {
        for (let j = i + 1; j < primes.length; j++) {
            if (primes[i] <= C / primes[j]) {
                factor4.set(primes[i] * primes[j], 1 + primes[i] + primes[j] + primes[i] * primes[j]);
            } else {
                break;
            }
        }
    }

    let ans: number = 0;
    for (let num of nums) {
        if (factor4.has(num)) {
            ans += factor4.get(num)!;
        }
    }
    return ans;
}

###Rust

use std::collections::HashMap;

impl Solution {
    pub fn sum_four_divisors(nums: Vec<i32>) -> i32 {
        // C 是数组 nums 元素的上限,C3 是 C 的立方根
        const C: i32 = 100000;
        const C3: i32 = 46;
        
        let mut isprime = vec![0; (C + 1) as usize];
        let mut primes = Vec::new();

        // 埃拉托斯特尼筛法
        for i in 2..=C {
            isprime[i as usize] = 1;
        }
        for i in 2..=C {
            if isprime[i as usize] == 1 {
                primes.push(i);
            }
            let mut j = i + i;
            while j <= C {
                isprime[j as usize] = 0;
                j += i;
            }
        }

        // 欧拉筛法
        /*
        for i in 2..=C {
            if isprime[i as usize] == 1 {
                primes.push(i);
            }
            for &prime in &primes {
                if i * prime > C {
                    break;
                }
                isprime[(i * prime) as usize] = 0;
                if i % prime == 0 {
                    break;
                }
            }
        }
        */
        
        // 通过质数表构造出所有的四因数
        let mut factor4 = HashMap::new();
        for &prime in &primes {
            if prime <= C3 {
                let key = prime * prime * prime;
                let value = 1 + prime + prime * prime + prime * prime * prime;
                factor4.insert(key, value);
            }
        }
        for i in 0..primes.len() {
            for j in i + 1..primes.len() {
                if primes[i] <= C / primes[j] {
                    let key = primes[i] * primes[j];
                    let value = 1 + primes[i] + primes[j] + primes[i] * primes[j];
                    factor4.insert(key, value);
                } else {
                    break;
                }
            }
        }

        let mut ans = 0;
        for num in nums {
            if let Some(&value) = factor4.get(&num) {
                ans += value;
            }
        }
        ans
    }
}

复杂度分析

  • 时间复杂度:$O(\pi^2(C) + C\log\log C + N)$ 或 $O(\pi^2(C) + C + N)$,其中 $\pi(X)$ 为「质数计数函数」,表示不超过 $X$ 的质数个数。「埃拉托斯特尼筛法」的时间复杂度为 $O(C\log\log C)$,「欧拉筛法」的时间复杂度为 $O(C)$;通过质数表构造出所有四因数的时间复杂度为 $O(\pi(C^{1/3})) + O(\pi^2(C)) = O(\pi^2(C))$,遍历数组 nums 中的所有元素并检查是否为四因数的时间复杂度为 $O(N)$。

  • 空间复杂度:$O(C + \pi(C))$,无论哪一种筛法,都需要长度为 $C$ 的数组记录每个数是否为质数,以及长度为 $\pi(C)$ 的数组存储所有的质数。

❌
❌