拼多多笔试真题-多多的审批链(C++/Py/Java /Js/Go)

作者:无限码力日期:2026/7/20

多多的审批链

拼多多技术岗 4月26号笔试 第四题

题目内容

多多的部门中发起审批单有一套审批流程,审批关系可以抽象为一棵以 111 号节点为根的树,共有 nnn 个节点。对于每个 i(2≤i≤n)i(2 \le i \le n)i(2≤i≤n),给定它的直属上级 pip_ipi​,即审批树中存在一条从 pip_ipi​ 到 iii 的边。
对于任意节点 uuu,如果它发起一张审批单,那么审批单只能先提交给它的直属上级,再继续逐级上报到更高层。
现在多多最多可以选择 kkk 个节点作为“关键审批节点”。如果审批单从 uuu 出发,向上经过不超过 DDD 条边,就能够到达某个关键审批节点,则称节点 uuu 被覆盖。
请帮多多找到,在最多选择 kkk 个关键审批节点的前提下,求出满足条件的最小 DDD,使得整棵审批树上的所有节点都被覆盖。

输入描述

第一行输入一个整数 T(1≤T≤3)T(1 \le T \le 3)T(1≤T≤3),表示数据组数。 接下来对于每组数据:

  • 第一行包含两个整数 n(1≤n≤2×105)n(1 \le n \le 2 \times 10^5)n(1≤n≤2×105) 和 k(1≤k≤n)k(1 \le k \le n)k(1≤k≤n)。
  • 第二行包含 n−1n-1n−1 个整数 p2,p3,…,pnp_2, p_3, \dots, p_np2​,p3​,…,pn​,其中 pip_ipi​ 表示节点 iii 的父节点。

输出描述

对于每组数据,仅输出一行整数,表示满足条件的最小 DDD。

样例1

输入

11
27 2
31 1 2 2 3 3
4

输出

12
2

说明
可以把节点 111 和 222 设置为关键审批节点。

  • 节点 1、21、21、2 自身被覆盖;
  • 节点 333 向上走 111 条边可以到达节点 111;
  • 节点 4、54、54、5 向上走 111 条边可以到达节点 222;
  • 节点 6、76、76、7 向上走 222 条边可以到达节点 111。

样例2

输入

11
25 1
31 2 3 4
4

输出

14
2

说明
这是一条长度为 444 的链。
如果只能选择 111 个关键审批节点,那么根节点 111 必须被选中,否则根本自身无法被覆盖。
此时最深的节点 555 到节点 111 的距离为 444,因此答案为 444。

题解

思路

解题思路二分 + 树状数组 + 贪心

  1. 二分答案最小距离,贪心从最深未覆盖节点出发,把关键节点放在它向上D层(D级祖先节点)的位置,用 DFS 序 + 树状数组快速维护哪些节点已经被覆盖。
  2. 其中DFS序和BIT的作用是为了快速得出哪些节点已经被覆盖。利用DFS序,可以快速知道关键节点覆盖的子树节点区间范围,然后通过树状数组区间前缀和差可快速知道节点是否被覆盖。
  3. 同时为了快速知道最深未覆盖节点d级祖先节点可通过倍增数组up实现。
  4. 算法总体时间复杂度为O(nlog2n)

C++

1#include <bits/stdc++.h>
2using namespace std;
3
4
5// 树状数组:区间加,单点查询
6struct BIT {
7    int n;
8    vector<int> tree;
9
10    BIT(int n = 0) {
11        init(n);
12    }
13
14    void init(int n) {
15        this->n = n;
16        tree.assign(n + 2, 0);
17    }
18
19    void add(int x, int v) {
20        while (x <= n) {
21            tree[x] += v;
22            x += x & -x;
23        }
24    }
25
26    // 区间[l,r]增加v
27    void rangeAdd(int l, int r, int v) {
28        add(l, v);
29        add(r + 1, -v);
30    }
31
32    // 查询单点
33    int query(int x) {
34        int res = 0;
35        while (x > 0) {
36            res += tree[x];
37            x -= x & -x;
38        }
39        return res;
40    }
41};
42
43
44int n, k;
45int LOG;
46
47vector<vector<int>> g;
48vector<vector<int>> up;
49
50vector<int> depth;
51// 子树第一次被访问编号 遍历完子树最大访问编号
52vector<int> tin, tout;
53// 遍历顺序
54vector<int> order;
55
56int timerCnt;
57
58
59// DFS序 + 倍增第一层
60void dfs(int u) {
61    tin[u] = ++timerCnt;
62    order.push_back(u);
63
64    for (int v : g[u]) {
65        depth[v] = depth[u] + 1;
66        up[0][v] = u;
67        dfs(v);
68    }
69
70    tout[u] = timerCnt;
71}
72
73
74// u向上走d步
75int jump(int u, int d) {
76    for (int i = 0; i < LOG; i++) {
77        if (d & (1 << i)) {
78            u = up[i][u];
79
80            // 超过根节点
81            if (u == 0) {
82                return 1;
83            }
84        }
85    }
86    return u;
87}
88
89
90// 判断D是否可行
91bool check(int D) {
92
93    BIT bit(n + 2);
94
95    int cnt = 0;
96
97    // 深度大的节点优先处理
98    for (int u : order) {
99        // 当前节点已经被覆盖
100        if (bit.query(tin[u]) > 0) {
101            continue;
102        }
103        // 当前节点没有覆盖
104        // 选择u向上D步的位置作为关键节点
105        int x = jump(u, D);
106        cnt++;
107        if (cnt > k) {
108            return false;
109        }
110        // 关键节点x覆盖自己的子树
111        bit.rangeAdd(tin[x], tout[x], 1);
112    }
113    return true;
114}
115
116
117int main() {
118    ios::sync_with_stdio(false);
119    cin.tie(nullptr);
120    int T;
121    cin >> T;
122
123    while (T--) {
124        cin >> n >> k;
125        g.assign(n + 1, {});
126
127        for (int i = 2; i <= n; i++) {
128            int p;
129            cin >> p;
130            g[p].push_back(i);
131        }
132
133
134        LOG = 1;
135        while ((1 << LOG) <= n) {
136            LOG++;
137        }
138
139
140        up.assign(LOG, vector<int>(n + 1));
141
142        depth.assign(n + 1, 0);
143        tin.assign(n + 1, 0);
144        tout.assign(n + 1, 0);
145
146        order.clear();
147
148        timerCnt = 0;
149
150
151        dfs(1);
152
153        // 倍增表
154        for (int j = 1; j < LOG; j++) {
155            for (int i = 1; i <= n; i++) {
156                up[j][i] = up[j - 1][up[j - 1][i]];
157            }
158        }
159
160
161        // 深度降序
162        sort(order.begin(), order.end(),
163             [&](int a, int b) {
164                 return depth[a] > depth[b];
165        });
166
167        int l = 0;
168        int r = n;
169        int ans = n;
170        // 二分答案
171        while (l <= r) {
172            int mid = (l + r) / 2;
173            if (check(mid)) {
174                ans = mid;
175                r = mid - 1;
176            } else {
177                l = mid + 1;
178            }
179        }
180        cout << ans << '\n';
181    }
182
183
184    return 0;
185}
186

java

1import java.io.*;
2import java.util.*;
3
4public class Main {
5
6    // 树状数组:区间加,单点查询
7    static class BIT {
8        int n;
9        int[] tree;
10
11        BIT(int n) {
12            this.n = n;
13            tree = new int[n + 2];
14        }
15
16        void add(int x, int v) {
17            while (x <= n) {
18                tree[x] += v;
19                x += x & -x;
20            }
21        }
22
23        // 区间[l,r]增加v
24        void rangeAdd(int l, int r, int v) {
25            add(l, v);
26            add(r + 1, -v);
27        }
28
29        // 查询单点
30        int query(int x) {
31            int res = 0;
32            while (x > 0) {
33                res += tree[x];
34                x -= x & -x;
35            }
36            return res;
37        }
38    }
39
40
41    static int n, k;
42    static int LOG;
43
44    static ArrayList<Integer>[] g;
45    static int[][] up;
46
47    static int[] depth;
48
49    // 子树第一次被访问编号 遍历完子树最大访问编号
50    static int[] tin, tout;
51
52    // 遍历顺序
53    static ArrayList<Integer> order;
54
55    static int timerCnt;
56
57
58    // DFS序 + 倍增第一层
59    static void dfs(int u) {
60        tin[u] = ++timerCnt;
61        order.add(u);
62
63        for (int v : g[u]) {
64            depth[v] = depth[u] + 1;
65            up[0][v] = u;
66            dfs(v);
67        }
68
69        tout[u] = timerCnt;
70    }
71
72
73    // u向上走d步
74    static int jump(int u, int d) {
75        for (int i = 0; i < LOG; i++) {
76            if ((d & (1 << i)) != 0) {
77                u = up[i][u];
78
79                // 超过根节点
80                if (u == 0) {
81                    return 1;
82                }
83            }
84        }
85        return u;
86    }
87
88
89    // 判断D是否可行
90    static boolean check(int D) {
91
92        BIT bit = new BIT(n + 2);
93
94        int cnt = 0;
95
96        // 深度大的节点优先处理
97        for (int u : order) {
98
99            // 当前节点已经被覆盖
100            if (bit.query(tin[u]) > 0) {
101                continue;
102            }
103
104
105            // 当前节点没有覆盖
106            // 选择u向上D步的位置作为关键节点
107            int x = jump(u, D);
108
109            cnt++;
110
111            if (cnt > k) {
112                return false;
113            }
114
115
116            // 关键节点x覆盖自己的子树
117            bit.rangeAdd(tin[x], tout[x], 1);
118        }
119
120        return true;
121    }
122
123
124    public static void main(String[] args) throws Exception {
125
126        FastScanner fs = new FastScanner(System.in);
127
128        StringBuilder sb = new StringBuilder();
129
130        int T = fs.nextInt();
131
132        while (T-- > 0) {
133
134            n = fs.nextInt();
135            k = fs.nextInt();
136
137
138            g = new ArrayList[n + 1];
139
140            for (int i = 1; i <= n; i++) {
141                g[i] = new ArrayList<>();
142            }
143
144
145            for (int i = 2; i <= n; i++) {
146                int p = fs.nextInt();
147                g[p].add(i);
148            }
149
150
151            LOG = 1;
152            while ((1 << LOG) <= n) {
153                LOG++;
154            }
155
156
157            up = new int[LOG][n + 1];
158
159            depth = new int[n + 1];
160
161            tin = new int[n + 1];
162            tout = new int[n + 1];
163
164
165            order = new ArrayList<>();
166
167            timerCnt = 0;
168
169
170            dfs(1);
171
172
173            // 倍增表
174            for (int j = 1; j < LOG; j++) {
175                for (int i = 1; i <= n; i++) {
176                    up[j][i] = up[j - 1][up[j - 1][i]];
177                }
178            }
179
180
181            // 深度降序
182            order.sort((a, b) -> depth[b] - depth[a]);
183
184
185            int l = 0;
186            int r = n;
187            int ans = n;
188
189
190            // 二分答案
191            while (l <= r) {
192
193                int mid = (l + r) / 2;
194
195                if (check(mid)) {
196                    ans = mid;
197                    r = mid - 1;
198                } else {
199                    l = mid + 1;
200                }
201            }
202
203
204            sb.append(ans).append('\n');
205        }
206
207
208        System.out.print(sb);
209    }
210
211
212    static class FastScanner {
213
214        private final InputStream in;
215        private final byte[] buffer = new byte[1 << 16];
216        private int ptr = 0, len = 0;
217
218
219        FastScanner(InputStream is) {
220            in = is;
221        }
222
223
224        int read() throws IOException {
225            if (ptr >= len) {
226                len = in.read(buffer);
227                ptr = 0;
228
229                if (len <= 0)
230                    return -1;
231            }
232
233            return buffer[ptr++];
234        }
235
236
237        int nextInt() throws IOException {
238
239            int c;
240
241            do {
242                c = read();
243            } while (c <= ' ');
244
245
246            int sign = 1;
247
248            if (c == '-') {
249                sign = -1;
250                c = read();
251            }
252
253
254            int val = 0;
255
256            while (c > ' ') {
257                val = val * 10 + c - '0';
258                c = read();
259            }
260
261            return val * sign;
262        }
263    }
264}
265

python

1import sys
2
3sys.setrecursionlimit(1 << 25)
4
5
6# 树状数组:区间加,单点查询
7class BIT:
8
9    def __init__(self, n):
10        self.n = n
11        self.tree = [0] * (n + 2)
12
13    def add(self, x, v):
14        while x <= self.n:
15            self.tree[x] += v
16            x += x & -x
17
18    # 区间[l,r]增加v
19    def rangeAdd(self, l, r, v):
20        self.add(l, v)
21        self.add(r + 1, -v)
22
23    # 查询单点
24    def query(self, x):
25        res = 0
26        while x > 0:
27            res += self.tree[x]
28            x -= x & -x
29        return res
30
31
32def main():
33
34    data = list(map(int, sys.stdin.buffer.read().split()))
35    idx = 0
36
37    T = data[idx]
38    idx += 1
39
40    ans_list = []
41
42    for _ in range(T):
43
44        n = data[idx]
45        k = data[idx + 1]
46        idx += 2
47
48
49        g = [[] for _ in range(n + 1)]
50
51        for i in range(2, n + 1):
52            p = data[idx]
53            idx += 1
54            g[p].append(i)
55
56
57        LOG = 1
58        while (1 << LOG) <= n:
59            LOG += 1
60
61
62        up = [[0] * (n + 1) for _ in range(LOG)]
63
64        depth = [0] * (n + 1)
65
66        # 子树第一次被访问编号 遍历完子树最大访问编号
67        tin = [0] * (n + 1)
68        tout = [0] * (n + 1)
69
70        # 遍历顺序
71        order = []
72
73        timer = 0
74
75
76        def dfs(u):
77
78            nonlocal timer
79
80            timer += 1
81            tin[u] = timer
82            order.append(u)
83
84            for v in g[u]:
85                depth[v] = depth[u] + 1
86                up[0][v] = u
87                dfs(v)
88
89            tout[u] = timer
90
91
92        # DFS序 + 倍增第一层
93        dfs(1)
94
95
96        # 倍增表
97        for j in range(1, LOG):
98            for i in range(1, n + 1):
99                up[j][i] = up[j - 1][up[j - 1][i]]
100
101
102        # 深度降序
103        order.sort(key=lambda x: depth[x], reverse=True)
104
105
106        def jump(u, d):
107
108            # u向上走d步
109            for i in range(LOG):
110                if d & (1 << i):
111                    u = up[i][u]
112
113                    # 超过根节点
114                    if u == 0:
115                        return 1
116
117            return u
118
119
120        def check(D):
121
122            bit = BIT(n + 2)
123
124            cnt = 0
125
126            # 深度大的节点优先处理
127            for u in order:
128
129                # 当前节点已经被覆盖
130                if bit.query(tin[u]) > 0:
131                    continue
132
133
134                # 当前节点没有覆盖
135                # 选择u向上D步的位置作为关键节点
136                x = jump(u, D)
137
138                cnt += 1
139
140                if cnt > k:
141                    return False
142
143
144                # 关键节点x覆盖自己的子树
145                bit.rangeAdd(tin[x], tout[x], 1)
146
147            return True
148
149
150        l, r = 0, n
151        ans = n
152
153
154        # 二分答案
155        while l <= r:
156
157            mid = (l + r) // 2
158
159            if check(mid):
160                ans = mid
161                r = mid - 1
162            else:
163                l = mid + 1
164
165
166        ans_list.append(str(ans))
167
168
169    print("\n".join(ans_list))
170
171
172if __name__ == "__main__":
173    main()
174

javascript

1const readline = require("readline");
2
3const rl = readline.createInterface({
4    input: process.stdin,
5    output: process.stdout
6});
7
8let input = [];
9
10rl.on("line", line => {
11    input.push(...line.trim().split(/\s+/));
12});
13
14rl.on("close", () => {
15
16    let idx = 0;
17
18    const T = Number(input[idx++]);
19
20    let output = [];
21
22
23    // 树状数组:区间加,单点查询
24    class BIT {
25
26        constructor(n) {
27            this.n = n;
28            this.tree = new Array(n + 2).fill(0);
29        }
30
31
32        add(x, v) {
33            while (x <= this.n) {
34                this.tree[x] += v;
35                x += x & -x;
36            }
37        }
38
39
40        // 区间[l,r]增加v
41        rangeAdd(l, r, v) {
42            this.add(l, v);
43            this.add(r + 1, -v);
44        }
45
46
47        // 查询单点
48        query(x) {
49
50            let res = 0;
51
52            while (x > 0) {
53                res += this.tree[x];
54                x -= x & -x;
55            }
56
57            return res;
58        }
59    }
60
61
62    while (idx < input.length) {
63
64
65        let n = Number(input[idx++]);
66        let k = Number(input[idx++]);
67
68
69        let g = Array.from(
70            {
71                length: n + 1
72            },
73            () => []
74        );
75
76
77        for (let i = 2; i <= n; i++) {
78
79            let p = Number(input[idx++]);
80
81            g[p].push(i);
82        }
83
84
85        let LOG = 1;
86
87        while ((1 << LOG) <= n) {
88            LOG++;
89        }
90
91
92        let up = Array.from(
93            {
94                length: LOG
95            },
96            () => new Array(n + 1).fill(0)
97        );
98
99
100        let depth = new Array(n + 1).fill(0);
101
102
103        // 子树第一次被访问编号 遍历完子树最大访问编号
104        let tin = new Array(n + 1).fill(0);
105        let tout = new Array(n + 1).fill(0);
106
107
108        // 遍历顺序
109        let order = [];
110
111
112        let timerCnt = 0;
113
114
115        // DFS序 + 倍增第一层
116        function dfs(u) {
117
118            tin[u] = ++timerCnt;
119
120            order.push(u);
121
122
123            for (let v of g[u]) {
124
125                depth[v] = depth[u] + 1;
126
127                up[0][v] = u;
128
129                dfs(v);
130            }
131
132
133            tout[u] = timerCnt;
134        }
135
136
137        dfs(1);
138
139
140        // 倍增表
141        for (let j = 1; j < LOG; j++) {
142
143            for (let i = 1; i <= n; i++) {
144
145                up[j][i] =
146                    up[j - 1][
147                        up[j - 1][i]
148                    ];
149            }
150        }
151
152
153        // u向上走d步
154        function jump(u, d) {
155
156            for (let i = 0; i < LOG; i++) {
157
158                if (d & (1 << i)) {
159
160                    u = up[i][u];
161
162
163                    // 超过根节点
164                    if (u === 0) {
165                        return 1;
166                    }
167                }
168            }
169
170            return u;
171        }
172
173
174        // 判断D是否可行
175        function check(D) {
176
177
178            let bit = new BIT(n + 2);
179
180            let cnt = 0;
181
182
183            // 深度大的节点优先处理
184            for (let u of order) {
185
186
187                // 当前节点已经被覆盖
188                if (bit.query(tin[u]) > 0) {
189                    continue;
190                }
191
192
193                // 当前节点没有覆盖
194                // 选择u向上D步的位置作为关键节点
195                let x = jump(u, D);
196
197
198                cnt++;
199
200
201                if (cnt > k) {
202                    return false;
203                }
204
205
206                // 关键节点x覆盖自己的子树
207                bit.rangeAdd(
208                    tin[x],
209                    tout[x],
210                    1
211                );
212            }
213
214
215            return true;
216        }
217
218
219        // 深度降序
220        order.sort((a, b) => depth[b] - depth[a]);
221
222
223        let l = 0;
224
225        let r = n;
226
227        let ans = n;
228
229
230        // 二分答案
231        while (l <= r) {
232
233            let mid = Math.floor((l + r) / 2);
234
235
236            if (check(mid)) {
237
238                ans = mid;
239
240                r = mid - 1;
241
242            } else {
243
244                l = mid + 1;
245            }
246        }
247
248
249        output.push(String(ans));
250    }
251
252
253    console.log(output.join("\n"));
254});
255

Go

1package main
2
3import (
4	"bufio"
5	"fmt"
6	"os"
7	"sort"
8)
9
10
11// 树状数组:区间加,单点查询
12type BIT struct {
13	n int
14	tree []int
15}
16
17
18func NewBIT(n int) *BIT {
19
20	return &BIT{
21		n:n,
22		tree:make([]int,n+2),
23	}
24}
25
26
27func (b *BIT) add(x int, v int) {
28
29	for x <= b.n {
30
31		b.tree[x] += v
32
33		x += x & -x
34	}
35}
36
37
38// 区间[l,r]增加v
39func (b *BIT) rangeAdd(l int, r int, v int) {
40
41	b.add(l,v)
42
43	b.add(r+1,-v)
44}
45
46
47// 查询单点
48func (b *BIT) query(x int) int {
49
50	res := 0
51
52	for x > 0 {
53
54		res += b.tree[x]
55
56		x -= x & -x
57	}
58
59	return res
60}
61
62
63var (
64	n,k int
65	LOG int
66
67	g [][]int
68
69	up [][]int
70
71	depth []int
72
73	// 子树第一次被访问编号 遍历完子树最大访问编号
74	tin []int
75	tout []int
76
77
78	// 遍历顺序
79	order []int
80
81	timerCnt int
82)
83
84
85// DFS序 + 倍增第一层
86func dfs(u int) {
87
88	timerCnt++
89
90	tin[u] = timerCnt
91
92	order = append(order,u)
93
94
95	for _,v := range g[u] {
96
97		depth[v] = depth[u]+1
98
99		up[0][v] = u
100
101		dfs(v)
102	}
103
104
105	tout[u] = timerCnt
106}
107
108
109// u向上走d步
110func jump(u int,d int) int {
111
112
113	for i:=0;i<LOG;i++ {
114
115		if d&(1<<i) != 0 {
116
117			u = up[i][u]
118
119
120			// 超过根节点
121			if u==0 {
122
123				return 1
124			}
125		}
126	}
127
128	return u
129}
130
131
132// 判断D是否可行
133func check(D int) bool {
134
135
136	bit := NewBIT(n+2)
137
138	cnt := 0
139
140
141	// 深度大的节点优先处理
142	for _,u := range order {
143
144
145		// 当前节点已经被覆盖
146		if bit.query(tin[u]) > 0 {
147
148			continue
149		}
150
151
152		// 当前节点没有覆盖
153		// 选择u向上D步的位置作为关键节点
154		x := jump(u,D)
155
156
157		cnt++
158
159
160		if cnt > k {
161
162			return false
163		}
164
165
166		// 关键节点x覆盖自己的子树
167		bit.rangeAdd(
168			tin[x],
169			tout[x],
170			1,
171		)
172	}
173
174
175	return true
176}
177
178
179func main(){
180
181	in := bufio.NewReader(os.Stdin)
182
183	out := bufio.NewWriter(os.Stdout)
184
185	defer out.Flush()
186
187
188	var T int
189
190	fmt.Fscan(in,&T)
191
192
193	for ;T>0;T-- {
194
195
196		fmt.Fscan(in,&n,&k)
197
198
199		g = make([][]int,n+1)
200
201
202		for i:=2;i<=n;i++ {
203
204			var p int
205
206			fmt.Fscan(in,&p)
207
208			g[p]=append(g[p],i)
209		}
210
211
212		LOG=1
213
214		for (1<<LOG)<=n {
215
216			LOG++
217		}
218
219
220		up = make([][]int,LOG)
221
222		for i:=0;i<LOG;i++ {
223
224			up[i]=make([]int,n+1)
225		}
226
227
228		depth=make([]int,n+1)
229
230		tin=make([]int,n+1)
231
232		tout=make([]int,n+1)
233
234
235		order=nil
236
237		timerCnt=0
238
239
240		dfs(1)
241
242
243		// 倍增表
244		for j:=1;j<LOG;j++ {
245
246			for i:=1;i<=n;i++ {
247
248				up[j][i]=up[j-1][up[j-1][i]]
249			}
250		}
251
252
253		// 深度降序
254		sort.Slice(order,func(i,j int)bool{
255
256			return depth[order[i]] >
257				depth[order[j]]
258		})
259
260
261		l:=0
262
263		r:=n
264
265		ans:=n
266
267
268		// 二分答案
269		for l<=r {
270
271			mid:=(l+r)/2
272
273
274			if check(mid) {
275
276				ans=mid
277
278				r=mid-1
279
280			}else{
281
282				l=mid+1
283			}
284		}
285
286
287		fmt.Fprintln(out,ans)
288	}
289}
290

拼多多笔试真题-多多的审批链(C++/Py/Java /Js/Go)》 是转载文章,点击查看原文


相关推荐


图片是 Web 性能监控的重灾区:LCP 和 CLS 到底怎么测、怎么定位到具体那张图
谙忆10242026/7/12

做前端性能这几年,我踩过一个反复出现的坑:本地 Lighthouse 跑出来 95 分,一到线上真实用户那边,投诉页面"卡""跳"的却一堆。后来盯着数据看明白了——问题几乎都出在图片上,而且实验室环境根本复现不出来。 这篇就把"图片相关的 Web 性能怎么测、怎么监控、怎么定位到罪魁祸首那张图"讲清楚。重点是测量和监控这条链路,不是又一篇"图片懒加载十种写法"。 先分清两组概念:实验室数据 vs 真实用户数据 这是很多人一上来就混的地方,先掰开。 实验室数据(Lab):Lighthouse、We


别再只会 if err != nil:Go error 从错误链到工程实战详解
唐青枫2026/7/4

简介 Go 代码里最常见的错误处理大概是这样: result, err := doSomething() if err != nil { return err } 这几行代码不难,真正容易出问题的是后面的选择: 应该新建错误,还是包装原错误? 应该使用 ==,还是 errors.Is? 什么时候需要自定义错误类型? 错误应该在哪一层记录日志? 多个清理操作同时失败,应该返回哪一个错误? 普通错误、panic 和 recover 到底怎么分工? Go 没有把错误处理藏进异常机制,而是把错误当


Java 虚拟线程实战指南:从 Thread API 到 Spring Boot 高并发应用
唐青枫2026/6/26

简介 虚拟线程的英文名是 Virtual Thread,它是 Project Loom 带来的轻量级线程实现。 虚拟线程在 JDK 19、JDK 20 中经历了两轮预览,到了 JDK 21 正式发布。 简单理解: 平台线程:Java 线程长期绑定操作系统线程 虚拟线程:大量 Java 线程由 JVM 调度到少量操作系统线程上 传统 Java 服务经常采用“一请求一线程”的处理方式。 代码很直观,但平台线程数量有限。当大量请求都在等待数据库、HTTP 接口、文件或消息队列时,线程本身会先成为瓶颈


Java Flyway 实战指南:用 SQL 脚本管理数据库版本
唐青枫2026/6/17

简介 Flyway 是一个数据库迁移工具。 它解决的问题和 Liquibase 类似: 数据库结构怎么跟着项目版本一起演进。 不过 Flyway 的风格更简单直接。 它主要通过 SQL 文件管理数据库变更。 比如: V1__create_users_table.sql V2__add_user_email_column.sql V3__create_orders_table.sql V4__insert_init_data.sql 应用启动或命令执行时,Flyway 会检查哪些脚本已经执行过


AI 代理只会在本地打转?我用 MCP 给它接上手脚,3 步接通第一个外部服务
大鹏AI教育2026/6/10

AI 代理只会在本地打转?我用 MCP 给它接上手脚,3 步接通第一个外部服务 先说结论:很多人觉得自己的 AI 代理"不够聪明",其实它不笨,是够不着外面的世界——能读本地文件、能跑命令,却连不上你的数据库、内部接口、第三方服务。我一开始也卡在这儿,把 MCP 跑通后才明白:问题从来不在模型,在它有没有"手脚"。 这篇我把给 OpenClaw 小龙虾(Claude Code 同款)接第一个 MCP 服务的过程讲一遍,连我踩的三个坑和边界判断一起给你。 1. 真问题:AI 写得出脚本,却发不出请


前端跨域完全指南:从 JSONP 到 Nginx 反向代理,一次性彻底搞懂
不会敲代码12026/6/2

前端跨域完全指南:从 JSONP 到 Nginx 反向代理,一次性彻底搞懂 同源策略是浏览器最坚实的护城河,而跨域方案就是一道道精心设计的城门。 前言 前后端分离开发早已成为标配。前端跑 localhost:5173,后端跑 localhost:3000,端口不同,跨域就来了。再加上调用第三方 API、对接合作商接口,跨域问题几乎是每个前端开发者的必修课。 这篇文章从「为什么会有跨域」出发,一次性梳理 JSONP、CORS、WebSocket、postMessage、Vite Proxy、N


详解MySQL事务(超详细版)
一条泥憨鱼2026/5/25

🌈个人主页:一条泥憨鱼(欢迎各位大佬莅临) 🎬精选专栏:数据结构与算法,JavaSE ,苍穹外卖日记 前言: “事务(Transaction)”是数据库开发里非常重要的知识。 简单来说: 事务就是“一组操作,要么全部成功,要么全部失败”。 它主要用于: 转账 下订单 库存扣减 支付系统 多表更新 这些场景都不能只执行一半,否则数据就会出错。 一、为什么需要事务? 先看一个经典案例: 银行转账 假设: 张三账户:1000 元


OpenClaw梦境系统使用介绍
handsomestWei2026/5/3

OpenClaw梦境系统使用介绍 全文链接:OpenClaw梦境系统使用介绍 本文整理 OpenClaw 2.x / 2.5 路线上围绕 Dream Engine(梦境 / 记忆抽象系统) 的能力划分、工作流、指令与场景示例。安装方式、子命令与频道行为会随版本迭代变化,以当前环境 openclaw --help 与官方文档为准。 一、2.x 新功能概览 功能模块关键改进使用上的直接收益① Dream Engine(记忆 / 梦境系统)引入 Dream 概念:对原始 Memor


DeepSeek-V4-Pro 写代码到底行不行?我拿 GLM-5.1 跟它硬碰硬比了一轮
孟健AI编程2026/4/24

大家好,我是孟健。 DeepSeek-V4-Pro 发了,官方说代码能力大幅升级。这种话我听得多了,每次新模型发布都这么说。 但我确实好奇:V4 在写代码这件事上,到底有没有追上 GLM-5.1? GLM-5.1 是我日常写代码的主力模型,用了几个月了,它什么水平我心里有数。所以这次我不跑 benchmark,不拼跑分,就拿我实际工作中的四个场景,让两个模型正面硬刚。 四个场景:源码分析、功能实现、大文件拆分、项目架构分析。 最后再算笔账,看看成本谁更划算。 场景一:项目分析,分析 Claude


飞书机器人权限批量导入
itmanll2026/4/15

{ "scopes": { "tenant": [ "contact:contact.base:readonly", "im:app_feed_card:write", "im:biz_entity_tag_relation:read", "im:biz_entity_tag_relation:write", "im:chat", "im:chat.access_event.bot_p2p_chat:read",

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读