2025-11-22:最大好子樹分數。用go語言,給定一棵以節點 0 為根的無向樹,節點編號為 0 到 n-1。每個節點 i 有一個整數值 vals[i],其父節點由數組 par 給出。
對任一節點 u,考慮以 u 為根的那棵包含 u 本身及其所有後代的子樹。在這棵子樹裏任選若干節點(可以不選),把它們的值組成一個集合。如果把這些被選數值按十進制展開後,所有數位中每個十進制數字 0 到 9 在所有數位上最多出現一次,則稱該選取是合法的。這樣的集合的得分定義為所選節點值之和。
令 maxScore[u] 表示在以 u 為根的子樹內可以取得的合法集合的最大得分。問題要求返回所有 maxScore[u] 的總和(對 1000000007 取模)。
(説明:允許選空集合,其得分為 0;“每個數字最多出現一次”是對所有被選數值的所有數位的全侷限制。)
1 <= n == vals.length <= 500。
1 <= vals[i] <= 1000000000。
par.length == n。
par[0] == -1。
對於 [1, n - 1] 中的每一個 i ,都有 0 <= par[i] < n 。
輸入生成保證父數組 par 表示一棵有效的樹。
輸入: vals = [3,22,5], par = [-1,0,1]。
輸出: 18。
解釋:
以節點 0 為根的子樹包括節點 {0, 1, 2}。子集 {3, 22, 5} 不是好子集,因為數字 2 出現兩次。子集 {3, 5} 是好子集,此子集的分數是 3 + 5 = 8。
以節點 1 為根的子樹包括節點 {1, 2}。子集 {22, 5} 不是好子集,因為數字 2 出現兩次。子集 {5} 是好子集,此子集的分數是 5。
以節點 2 為根的子樹包括 {2}。子集 {5} 是 好的。此子集的分數是 5。
maxScore 數組為 [8, 5, 5],並且 maxScore 中所有值的總和是 8 + 5 + 5 = 18。因此,答案是 18。
題目來自力扣3575。
🔍 步驟一:預處理與初始化
代碼首先進行一些預處理工作:
- 定義常量,包括模數
mod和進制基數D=10(代表十進制)。 - 根據父節點數組
par構建樹的鄰接表g,用於表示節點間的父子關係。例如,g[p]存儲了節點p的所有直接子節點。 - 定義核心的數據結構
pair,用於記錄一個“合法數字集合”的狀態。其中mask是一個位掩碼(bitmask),表示該集合中所有數字的十進制數位使用情況(例如,第d位為1表示數字d已經出現過);val表示構成這個集合的所有節點值之和。
🌳 步驟二:深度優先搜索(DFS)
對樹進行深度優先遍歷(DFS),從根節點(節點0)開始。DFS函數 dfs(x) 返回兩個值:
f map[int]int:一個映射。鍵是位掩碼mask,值是在以節點x為根的子樹中,能夠形成該掩碼的所有合法節點集合的最大和(val)。這相當於記錄了在子樹x內,各種不同數位組合下能獲得的最大分數。single []pair:一個列表,存儲了所有僅包含單個節點的合法狀態(即只選子樹x中的某一個節點構成的合法集合)。這個列表主要用於後續的合併操作。
🔢 步驟三:處理當前節點
對於當前遍歷到的節點 x:
- 計算節點值的數位掩碼:將節點值
vals[x]的各個十進制數位提取出來。如果在提取過程中發現某個數字重複出現(通過檢查位掩碼的相應位是否已置1),則判定該節點值本身非法,其掩碼mask被設為0。否則,掩碼mask記錄所有出現過的數字。 - 初始化狀態:如果節點
x的值本身是合法的(mask > 0),那麼就將這個單節點狀態{mask, val}加入到f映射和single列表中。
🔁 步驟四:合併子樹信息
這是算法的核心部分。節點 x 需要合併其所有子節點 y 傳遞上來的狀態信息 (fy 和 singleY)。
- 啓發式合併(小的合併到大的):為了提高效率,代碼採用了啓發式合併策略。在合併子節點
y的狀態之前,會比較當前已合併的single列表和子節點y的singleY列表的長度。如果singleY更長,就交換兩者的內容,確保總是將較小的集合合併到較大的集合中。這能有效降低合併操作的總時間複雜度。 - 狀態合併與更新:合併過程遍歷子節點
y的singleY列表中的每一個狀態p。對於每個狀態p:
- 檢查其值
v是否大於當前f映射中相同掩碼msk對應的值。如果不是,則跳過,因為我們要的是最大和。 - 如果
v更大,則創建一個新的狀態映射nf(初始為f的副本),並更新nf[msk]的值為v。 - 然後,將新狀態
p的掩碼msk與f映射中所有已有的掩碼msk2進行組合。如果msk和msk2沒有公共數位(即msk & msk2 == 0),説明這兩個數字集合可以合併成一個新的、數位仍然不重複的更大集合。計算新集合的和v + s2,並嘗試更新nf[msk|msk2]的值,記錄更大的和。 - 完成所有組合檢查後,將
f更新為nf。
💯 步驟五:計算當前子樹的最大分數並累加答案
在合併完所有子節點的狀態後,遍歷當前節點 x 的最終狀態映射 f,找到其中所有合法節點集合的最大和 mx。將這個最大值 mx 累加到全局答案 ans 中。這步對應計算題目要求的 maxScore[u]。
⏱️ 步驟六:複雜度分析
- 時間複雜度:最壞情況下,每個節點的
single列表可能包含 O(2^10) = O(1024) 種不同的數位掩碼狀態(因為數字0-9最多10位)。在合併子樹時,需要組合這些狀態。由於採用了啓發式合併,合併操作的總時間複雜度可以優化到大約 O(n * logn * 2^(2*10))。考慮到 n <= 500,這個複雜度在可接受範圍內。主導項是狀態合併部分。 - 額外空間複雜度:主要消耗在DFS遞歸調用棧和存儲每個節點的狀態映射
f及列表single上。遞歸棧深度為樹高,最壞情況O(n)。每個節點存儲的狀態數量最多為 O(2^10)。因此,總的空間複雜度約為 O(n * 2^10)。
💎 總結
這個解決方案的核心思想是使用深度優先搜索結合動態規劃和位掩碼技術,通過啓發式合併來高效地計算每個子樹中滿足特定數位不重複條件的最大節點值之和。
Go完整代碼如下:
package main
import (
"fmt"
"maps"
)
func goodSubtreeSum(vals, par []int) (ans int) {
const mod = 1_000_000_007
const D = 10
n := len(par)
g := make([][]int, n)
for i := 1; i < n; i++ {
p := par[i]
g[p] = append(g[p], i)
}
type pair struct{ mask, val int }
var dfs func(int) (map[int]int, []pair)
dfs = func(x int) (f map[int]int, single []pair) {
f = map[int]int{}
// 計算 val 的數位集合 mask
val := vals[x]
mask := 0
for v := val; v > 0; v /= D {
d := v % D
if mask>>d&1 > 0 {
mask = 0
break
}
mask |= 1 << d
}
if mask > 0 {
f[mask] = val
single = append(single, pair{mask, val})
}
for _, y := range g[x] {
fy, singleY := dfs(y)
// 啓發式合併
if len(singleY) > len(single) {
single, singleY = singleY, single
f, fy = fy, f
}
single = append(single, singleY...)
// 把子樹 y 中的 mask 和 val 一個一個地加到 f 中
for _, p := range singleY {
msk, v := p.mask, p.val
if v <= f[msk] {
continue
}
nf := maps.Clone(f)
nf[msk] = v
for msk2, s2 := range f {
if msk&msk2 == 0 {
nf[msk|msk2] = max(nf[msk|msk2], v+s2)
}
}
f = nf
}
}
mx := 0
for _, s := range f {
mx = max(mx, s)
}
ans += mx
return
}
dfs(0)
return ans % mod
}
func main() {
vals := []int{3, 22, 5}
par := []int{-1, 0, 1}
result := goodSubtreeSum(vals, par)
fmt.Println(result)
}
Python完整代碼如下:
# -*-coding:utf-8-*-
from typing import List, Dict, Tuple
import copy
def goodSubtreeSum(vals: List[int], par: List[int]) -> int:
mod = 1_000_000_007
D = 10
n = len(par)
# 構建樹結構
g = [[] for _ in range(n)]
for i in range(1, n):
p = par[i]
g[p].append(i)
ans = 0
def dfs(x: int) -> Tuple[Dict[int, int], List[Tuple[int, int]]]:
nonlocal ans
f = {}
# 計算 val 的數位集合 mask
val = vals[x]
mask = 0
v = val
valid_mask = True
while v > 0:
d = v % D
if mask >> d & 1:
valid_mask = False
break
mask |= 1 << d
v //= D
single = []
if valid_mask and mask > 0:
f[mask] = val
single.append((mask, val))
for y in g[x]:
fy, singleY = dfs(y)
# 啓發式合併:確保 single 是較大的那個
if len(singleY) > len(single):
single, singleY = singleY, single
f, fy = fy, f
single.extend(singleY)
# 把子樹 y 中的 mask 和 val 一個一個地加到 f 中
for msk, v in singleY:
if v <= f.get(msk, 0):
continue
nf = copy.deepcopy(f)
nf[msk] = v
for msk2, s2 in f.items():
if msk & msk2 == 0:
combined_mask = msk | msk2
nf[combined_mask] = max(nf.get(combined_mask, 0), v + s2)
f = nf
# 更新答案
mx = 0
for s in f.values():
mx = max(mx, s)
ans += mx
return f, single
dfs(0)
return ans % mod
def main():
vals = [3, 22, 5]
par = [-1, 0, 1]
result = goodSubtreeSum(vals, par)
print(result)
if __name__ == "__main__":
main()
C++完整代碼如下:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <functional>
using namespace std;
int goodSubtreeSum(vector<int>& vals, vector<int>& par) {
const int mod = 1000000007;
const int D = 10;
int n = par.size();
// 構建樹結構
vector<vector<int>> g(n);
for (int i = 1; i < n; i++) {
int p = par[i];
g[p].push_back(i);
}
int ans = 0;
// 使用pair表示mask和val
using Pair = pair<int, int>;
function<tuple<unordered_map<int, int>, vector<Pair>>(int)> dfs;
dfs = [&](int x) -> tuple<unordered_map<int, int>, vector<Pair>> {
unordered_map<int, int> f;
vector<Pair> single;
// 計算val的數位集合mask
int val = vals[x];
int mask = 0;
int v = val;
bool valid_mask = true;
while (v > 0) {
int d = v % D;
if (mask >> d & 1) {
valid_mask = false;
break;
}
mask |= 1 << d;
v /= D;
}
if (valid_mask && mask > 0) {
f[mask] = val;
single.emplace_back(mask, val);
}
for (int y : g[x]) {
auto [fy, singleY] = dfs(y);
// 啓發式合併:確保single是較大的那個
if (singleY.size() > single.size()) {
swap(single, singleY);
swap(f, fy);
}
single.insert(single.end(), singleY.begin(), singleY.end());
// 把子樹y中的mask和val一個一個地加到f中
for (auto [msk, v_val] : singleY) {
if (v_val <= f[msk]) {
continue;
}
unordered_map<int, int> nf = f;
nf[msk] = v_val;
for (auto [msk2, s2] : f) {
if ((msk & msk2) == 0) {
int combined_mask = msk | msk2;
nf[combined_mask] = max(nf[combined_mask], v_val + s2);
}
}
f = move(nf);
}
}
// 更新答案
int mx = 0;
for (auto [_, s] : f) {
mx = max(mx, s);
}
ans = (ans + mx) % mod;
return {f, single};
};
dfs(0);
return ans;
}
int main() {
vector<int> vals = {3, 22, 5};
vector<int> par = {-1, 0, 1};
int result = goodSubtreeSum(vals, par);
cout << result << endl;
return 0;
}