合并三元组以形成目标三元组 图解题解
这道题到底在问什么
- 输入
- target = [5,5,5] triplets = [[2,5,3],[1,8,4],[1,3,5],[4,1,5],[5,2,3]]
- 输出
- true
最优解:为什么这么做
一句话答案:LeetCode 1899 合并三元组以形成目标三元组:逐位取最大只会把数往大抬、绝不调小,所以只留三位都不超 target 的三元组、逐位攒进 best,看能否正好顶成 target,一趟扫描 O(n)、空间 O(1)。
这道题到底在问能不能拼出 target
给一个三元组数组 triplets 和目标三元组 target,长度都是 3。每次挑两个三元组,把其中一个换成两者在每一位上各取较大值的结果,问反复操作能否让某个三元组恰好变成 target。题面给的 target = [5,5,5],triplets = [[2,5,3],[1,8,4],[1,3,5],[4,1,5],[5,2,3]],答案是 true;拼得出返回 true,拼不出返回 false。
模拟两两合并这条路走得通吗
看到‘每次合并两个’,容易想着照操作一步步模拟:先合哪两个、再合哪两个,把配对顺序试一遍。可顺序有指数种,铺开会直接爆掉。而逐位取最大满足交换律和结合律——先合谁后合谁、合几次,最后落在某一位上的值,都等于参与进来的三元组在这一位上的最大值。顺序既然无所谓,就不必真去演具体配对。
哪些三元组能用,哪些碰都不能碰
逐位取最大只会把数往大抬、绝不调小——某一位一旦被顶上去,后面再合别的也压不回来。麻烦就出在‘超标’:一个三元组只要有某一位 > target 对应那位,并进来后那一位就会冲过目标且再降不回去,结果必然对不上。所以能安全参与的,只有三位都 ≤ target 的三元组,任意一位越界就直接跳过。剩下的安全三元组逐位取最大,攒进一个 best,也就是从 [0,0,0] 起、逐位记着眼下最大值的三元组。这就是这道题的贪心:不纠结拿哪两个去配,只认这一位能不能安全抬到位。
写出来就三步:过滤、取最大、判相等
落到代码上就三件事:遍历每个三元组,逐位和 target 比,某一位 > target 就跳过;对留下的安全三元组,把 best 的每一位更新成它和这个三元组在该位上的较大值;扫完判断 best 是不是恰好等于 target。最容易松手的是最后这步——超标的都滤掉了,best 每一位都 ≤ target,所以不能用‘每一位都 ≥ target’这种覆盖式判断,那样没攒够的也会算成功;要的是逐位相等,best = target 才返回 true。
[5,5,5] 这个目标,best 怎么顶上去
target = [5,5,5],best 从 [0,0,0] 起。[2,5,3] 三位都不超 5,安全,best = [2,5,3]。[1,8,4] 中间那位 8 > 5 越界,跳过,best 不动。[1,3,5] 安全,末位被 5 顶起,best = [2,5,5]。[4,1,5] 安全,首位被 4 抬高,best = [4,5,5]。[5,2,3] 安全,首位被 5 顶满,best = [5,5,5]。扫完 best 和 target 一位不差,返回 true。
两个地方一手滑,答案就反了
第一个坑是没过滤就把所有三元组一股脑逐位取最大——混进一个某位超标的,那位当场冲破 target 且降不回来,结果直接错。第二个坑是最后拿‘best 每位都 ≥ target’判成功,可 best 不可能超过 target,没攒够的那位其实小于 target,覆盖式判断会放它过去,必须判 best 和 target 每位相等。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住两件事:有一位超标的三元组直接跳过;其余的全逐位取最大攒进 best。最后 best == target 就成。
- 4目标是 [5,5,5]。我们要攒一个 best,从 [0,0,0] 开始,把每个安全的三元组逐位取最大并进来。
- 5轮到第 1 个三元组 [2,5,3]。先逐位和目标 [5,5,5] 比:每一位都不超过目标,可以安全合并。
- 6安全,合并进来:best 和 [2,5,3] 逐位取最大,第 1、2、3 位被抬高,best 现在是 [2,5,3]。
- 7轮到第 2 个三元组 [1,8,4]。先逐位和目标 [5,5,5] 比:有位置超过了目标([1,8,4] vs [5,5,5]),它被污染了,跳过。
- 8跳过这个三元组。标红的是它超标的位置——一旦掺进来这些位就会顶破目标且降不回去。best 保持 [2,5,3] 不动。
- 9轮到第 3 个三元组 [3,2,1]。先逐位和目标 [5,5,5] 比:每一位都不超过目标,可以安全合并。
- 10安全,合并进来:best 和 [3,2,1] 逐位取最大,第 1 位被抬高,best 现在是 [3,5,3]。
- 11轮到第 4 个三元组 [1,3,5]。先逐位和目标 [5,5,5] 比:每一位都不超过目标,可以安全合并。
- 12安全,合并进来:best 和 [1,3,5] 逐位取最大,第 3 位被抬高,best 现在是 [3,5,5]。
- 13轮到第 5 个三元组 [6,1,2]。先逐位和目标 [5,5,5] 比:有位置超过了目标([6,1,2] vs [5,5,5]),它被污染了,跳过。
- 14跳过这个三元组。标红的是它超标的位置——一旦掺进来这些位就会顶破目标且降不回去。best 保持 [3,5,5] 不动。
- 15轮到第 6 个三元组 [4,1,5]。先逐位和目标 [5,5,5] 比:每一位都不超过目标,可以安全合并。
- 16安全,合并进来:best 和 [4,1,5] 逐位取最大,第 1 位被抬高,best 现在是 [4,5,5]。
- 17轮到第 7 个三元组 [2,2,2]。先逐位和目标 [5,5,5] 比:每一位都不超过目标,可以安全合并。
- 18安全,合并进来:best 和 [2,2,2] 逐位取最大,best 现在是 [4,5,5]。
- 19轮到第 8 个三元组 [1,9,1]。先逐位和目标 [5,5,5] 比:有位置超过了目标([1,9,1] vs [5,5,5]),它被污染了,跳过。
- 20跳过这个三元组。标红的是它超标的位置——一旦掺进来这些位就会顶破目标且降不回去。best 保持 [4,5,5] 不动。
- 21轮到第 9 个三元组 [5,2,3]。先逐位和目标 [5,5,5] 比:每一位都不超过目标,可以安全合并。
- 22安全,合并进来:best 和 [5,2,3] 逐位取最大,第 1 位被抬高,best 现在是 [5,5,5]。
- 23所有安全三元组都攒完了。best = [5,5,5],正好等于目标 [5,5,5],所以答案是 true。
⚠️ 容易写错的地方
✗ 错:没过滤就直接全部逐位取最大
✓ 对:先判断每一位都 ≤ target 才合并
有一位超标的三元组合进来,那一位会顶破目标且永远降不回去,结果必然错
✗ 错:以为要真的两两合并、模拟操作过程
✓ 对:只需把所有安全三元组逐位取最大攒进一个 best
合并次数不限、顺序不限,等价于「所有安全三元组逐位取最大」,不必模拟具体配对
✗ 错:用 best 是否「覆盖」target 判断(每位 ≥ target)
✓ 对:必须 best 恰好等于 target
逐位取最大不会超过 target(已过滤),但要的是相等;只判 ≥ 会把没攒够的也算成功
完整代码(Python / C++ / Java)
Python
def mergeTriplets(triplets, target):
best = [0, 0, 0] # 逐位攒到的最大
for t in triplets:
if all(t[j] <= target[j] # 每一位都不超目标
for j in range(3)): # 才算安全三元组
for j in range(3):
best[j] = max(best[j], t[j]) # 逐位取最大
return best == target # 攒完是否正好等于目标C++
bool mergeTriplets(vector<vector<int>>& tr, vector<int>& tg){
vector<int> best(3, 0);
for (auto& t : tr) {
if (t[0]<=tg[0] && t[1]<=tg[1] && t[2]<=tg[2])
for (int j = 0; j < 3; j++)
best[j] = max(best[j], t[j]);
}
return best == tg;
}Java
public boolean mergeTriplets(int[][] tr, int[] tg) {
int[] best = new int[3];
for (int[] t : tr) {
if (t[0]<=tg[0] && t[1]<=tg[1] && t[2]<=tg[2])
for (int j = 0; j < 3; j++)
best[j] = Math.max(best[j], t[j]);
}
return best[0]==tg[0] && best[1]==tg[1] && best[2]==tg[2];
}复杂度
时间
O(n)
每个三元组只看一次,三个位置是常数次比较
空间
O(1)
只用一个长度 3 的 best,不随输入增长
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 合并三元组以形成目标三元组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么不用真的去模拟两两合并的过程?+
因为合并次数、配对顺序都不限,而逐位取最大满足交换律和结合律:不管先合谁、合几次,最后每一位落到的值都是参与进来的三元组在这一位上的最大值。所以最终能凑出的结果,等价于把所有可用三元组逐位取最大,直接攒一个 best 就行,不必枚举配对顺序。
哪些三元组算‘可用’,为什么有一位超标就得整个丢掉?+
可用的是三位都 ≤ target 的三元组。只要某一位 > target,一旦并进来,那一位就会被顶到超过目标;而逐位取最大只增不减,之后再怎么合也压不回来,这一位就永远对不上 target 了,所以整个三元组必须排除。
最后为什么判 best 恰好等于 target,而不是 best 每位都覆盖 target?+
超标的三元组已经全滤掉了,剩下攒出的 best 每一位都 ≤ target,根本不可能超过。要的是正好凑出 target,如果某一位没攒够就会严格小于 target。用‘每位都 ≥ target’判断会把这种没攒够的也误判成功,所以必须逐位相等、best = target 才返回 true。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 合并三元组以形成目标三元组 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。