连接后等于目标字符串的字符串对 图解题解
这道题到底在问什么
- 输入
- nums=["777","7","77","77"], target="7777"
- 输出
- 4
- 输入
- nums=["123","4","12","34"], target="1234"
- 输出
- 2
- 输入
- nums=["1","1","1"], target="11"
- 输出
- 6
先想最直接的笨办法
四轮枚举全部走完。命中的有序对是 (0, 1)、(1, 0)、(2, 3)、(3, 2):"777"+"7" 和反过来 "7"+"777" 各一对,两个 "77" 正反接成 "77"+"77" 又各一对,合起来正好 4 对,和题面答案对上了。整道题的窍门就一句:枚举所有 i ≠ j 的有序对,拼一次比一次,数出等于 target 的有几对。(动画第 25 步)
最优解:为什么这么做
一句话答案:LeetCode 2023 连接后等于目标字符串的字符串对:数有多少有序对 (i,j)、i≠j 且 nums[i]+nums[j]=target,逐对拼接比较,时间 O(n²·m);因 target 只一条接缝,改哈希按切分点计数更快。
要数的是哪种下标对,为什么不能当整数比
给一个数字字符串数组 nums 和一个数字字符串 target,问有多少有序对 (i,j) 满足 i≠j,且 nums[i] 接在前、nums[j] 接在后拼成的串正好等于 target。题面例子 nums=["777","7","77","77"]、target="7777",答案 4。两点先钉死:一是有序对,(i,j) 和 (j,i) 算两种不同的对;二是按字符串原样拼,转成整数会丢前导 0、比错。
两层循环把每一对都拼一遍,账是多少
外层固定左串、内层轮流换右串,每凑一对就拼一次、和 target 比一次,只要 i≠j 就数进去。n 个串共 n×n 个有序对,扣掉 i=j 的 n 个都要试;每对都现拼新串逐字符比、约 O(m)。合起来 O(n²·m)。本题 n 和 target 长度都不超过 100,平方级跑得动,参考代码就是这种老实枚举。
target 只有一条接缝,能不能别逐对硬拼
拼出来要等于 target,那 nums[i] 必占 target 前半段、nums[j] 占后半段,接缝只能落在 target 内部某个位置。于是不必逐对硬拼:先拿一张哈希表,也就是记「串 → 出现几次」的账本,把每个串数清次数;再沿 target 挨个位置切一刀、切成前半串和后半串,命中这一刀的有序对数,等于两个串各自的出现次数相乘。
哈希配对怎么走一遍,相同串为什么要减一
target 有几个字符就有几减一个切分点,逐个切、逐个累加:前半串和后半串各查一次账本,出现次数相乘。要当心两半是同一个串时,同一个下标不能既当左又当右(否则 i=j),出现 k 次的串自己配自己只贡献 k×(k−1)、不是 k×k。
题面例子:账本是 "777" 一次、"7" 一次、"77" 两次。target="7777" 切成 "7"|"777" 得 1×1=1,切成 "77"|"77" 前后同串得 2×(2−1)=2,切成 "777"|"7" 得 1×1=1,加起来 1+2+1=4,和逐对枚举一样。
配成 "7777" 的到底是哪四对
把命中的对亲手数一遍。固定 "777" 当左串,只有下标 1 的 "7" 接上去拼成 "7777" 命中,其余长度都对不上。固定 "7" 当左串,配下标 0 的 "777" 拼成 "7777" 命中,和上一对顺序正好反过来。剩下两个 "77" 互相拼:下标 2 配 3、下标 3 配 2,各拼成 "7777" 一次,但谁都不能和自己拼(i≠j)。命中的有序对是 (0,1)、(1,0)、(2,3)、(3,2),答案 4。
复杂度账,以及相同串一多答案涨得有多快
逐对枚举是 O(n²·m):n² 个有序对、每对拼接加比较约 O(m);空间 O(m),临时拼出的新串用完即弃、不随对数膨胀。改哈希计数只枚举 m−1 个切分点、每刀查两次账本,比逐对少扫全部下标对。
边界校准:数组只有 2 个串最多顺拼中一次记 1;两个完全相同的串正反各中一次记 2。相同串越多涨得越猛,同一个串出现 3 次且自拼等于 target,有序对是 3×2=6,先挑两个不同下标、再考虑左右顺序。
▶ 动画逐步走查(共 23 步)——想跟着动画一帧帧对照就展开
- 3记牢这一句:外层固定左串,内层轮流拿右串接上去拼一次、和 target 比一次,i ≠ j 就好。下面每一帧都在套这句。
- 4先看清画面。上面是 nums = ["777","7","77","77"],一共 4 个数字串。我们的目标串 target = "7777"。答案 ans 从 0 起步。接下来紫色的 i 指针会固定一个左串,再让每个右串轮流接上去拼一次、和 target 比一次:拼中了那格标绿,拼不中标红,i 和 j 撞在一起就跳过。先从左串 nums[0] 开始。
- 5第 0 轮开始。紫色 i 指针停在下标 0,把 nums[0] = "777" 定为这一轮的左串。接下来让右串 j 从下标 0 一路走到 3,每停一格就把那格的串接到 "777" 后面,拼出来和 target 对一对。
- 6右串 j 走到下标 0,正好和左串的 i 撞在同一格。题目要求 i ≠ j,同一个位置的字符串不能和它自己拼,所以这一格直接跳过,不拼也不比。j 继续往后走。
- 7右串 j 停在下标 1,把 "7" 接到左串 "777" 后面,拼成 "7777"。它和 target = "7777" 一模一样,命中!这一对 (0, 1) 有效,那格标绿,ans 加到 1。
- 8右串 j 停在下标 2,把 "77" 接到 "777" 后面,拼成 "77777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 1。
- 9右串 j 停在下标 3,把 "77" 接到 "777" 后面,拼成 "77777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 1。
- 10第 1 轮开始。紫色 i 指针停在下标 1,把 nums[1] = "7" 定为这一轮的左串。接下来让右串 j 从下标 0 一路走到 3,每停一格就把那格的串接到 "7" 后面,拼出来和 target 对一对。
- 11右串 j 停在下标 0,把 "777" 接到左串 "7" 后面,拼成 "7777"。它和 target = "7777" 一模一样,命中!这一对 (1, 0) 有效,那格标绿,ans 加到 2。
- 12右串 j 走到下标 1,正好和左串的 i 撞在同一格。题目要求 i ≠ j,同一个位置的字符串不能和它自己拼,所以这一格直接跳过,不拼也不比。j 继续往后走。
- 13右串 j 停在下标 2,把 "77" 接到 "7" 后面,拼成 "777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 2。
- 14右串 j 停在下标 3,把 "77" 接到 "7" 后面,拼成 "777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 2。
- 15第 2 轮开始。紫色 i 指针停在下标 2,把 nums[2] = "77" 定为这一轮的左串。接下来让右串 j 从下标 0 一路走到 3,每停一格就把那格的串接到 "77" 后面,拼出来和 target 对一对。
- 16右串 j 停在下标 0,把 "777" 接到 "77" 后面,拼成 "77777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 2。
- 17右串 j 停在下标 1,把 "7" 接到 "77" 后面,拼成 "777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 2。
- 18右串 j 走到下标 2,正好和左串的 i 撞在同一格。题目要求 i ≠ j,同一个位置的字符串不能和它自己拼,所以这一格直接跳过,不拼也不比。j 继续往后走。
- 19右串 j 停在下标 3,把 "77" 接到左串 "77" 后面,拼成 "7777"。它和 target = "7777" 一模一样,命中!这一对 (2, 3) 有效,那格标绿,ans 加到 3。
- 20第 3 轮开始。紫色 i 指针停在下标 3,把 nums[3] = "77" 定为这一轮的左串。接下来让右串 j 从下标 0 一路走到 3,每停一格就把那格的串接到 "77" 后面,拼出来和 target 对一对。
- 21右串 j 停在下标 0,把 "777" 接到 "77" 后面,拼成 "77777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 3。
- 22右串 j 停在下标 1,把 "7" 接到 "77" 后面,拼成 "777"。它和 target = "7777" 不一样(长度都对不上),不算数,那格标红,ans 不变,还是 3。
- 23右串 j 停在下标 2,把 "77" 接到左串 "77" 后面,拼成 "7777"。它和 target = "7777" 一模一样,命中!这一对 (3, 2) 有效,那格标绿,ans 加到 4。
- 24右串 j 走到下标 3,正好和左串的 i 撞在同一格。题目要求 i ≠ j,同一个位置的字符串不能和它自己拼,所以这一格直接跳过,不拼也不比。j 继续往后走。
- 25四轮枚举全部走完。命中的有序对是 (0, 1)、(1, 0)、(2, 3)、(3, 2):"777"+"7" 和反过来 "7"+"777" 各一对,两个 "77" 正反接成 "77"+"77" 又各一对,合起来正好 4 对,和题面答案对上了。整道题的窍门就一句:枚举所有 i ≠ j 的有序对,拼一次比一次,数出等于 target 的有几对。
⚠️ 容易写错的地方
✗ 错:忘了 i ≠ j,把同一个下标和自己拼也算进去
✓ 对:内层判断里必须带上 i ≠ j 这个条件
题目明确要求两个下标不同。若不排除 i = j,当某个 nums[i] 恰好正反拼自己等于 target 时会多算,更常见的是让计数逻辑跑偏,答案偏大
✗ 错:以为 (i, j) 和 (j, i) 是同一对,只数一次
✓ 对:有序对:两种顺序各拼各比,分别计数
nums[i] + nums[j] 和 nums[j] + nums[i] 拼出来通常不同,题目要的就是有序对。像 "777"+"7" 和 "7"+"777" 都等于 "7777",是两对,漏掉一种答案就少一半
✗ 错:Java 里用等号等号比较拼出来的字符串和 target
✓ 对:用 target.equals(nums[i] + nums[j]) 比内容
Java 的等号等号比的是对象引用是否相同,拼接得到的是新对象,内容一样但引用不同,会被判成不相等。字符串内容比较必须用 equals
✗ 错:拼接前不看长度,任意两串都拼完整个 target 再比
✓ 对:拼出来长度和 target 不一致时可提前判否
只有当 nums[i] 与 nums[j] 长度之和等于 target 长度时才可能相等。长度对不上直接落空,这是一个可选的剪枝,能省下不必要的逐字比较
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def numOfPairs(self, nums: List[str], target: str) -> int:
n = len(nums)
return sum(
i != j and nums[i] + nums[j] == target for i in range(n) for j in range(n)
)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
int numOfPairs(vector<string>& nums, string target) {
int n = nums.size();
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i != j && nums[i] + nums[j] == target) ++ans;
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int numOfPairs(String[] nums, String target) {
int n = nums.length;
int ans = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (i != j && target.equals(nums[i] + nums[j])) {
++ans;
}
}
}
return ans;
}
}复杂度
时间
O(n² · m)
n 是数组长度,m 是 target 的长度。外层 i、内层 j 各扫 n 次,一共 n 乘 n 个有序对;每对都要做一次字符串拼接和一次比较,拼接与比较的代价随串长走,约为 O(m)。所以总时间是 O(n² · m)。本题 n 和 m 都不超过 100,平方级完全跑得动
空间
O(m)
按峰值算。除计数变量外,每次拼接会临时生成一个长度约为 m 的新字符串来和 target 比,用完即弃,峰值就是这一个临时串的长度 O(m)。不额外开随 n 增长的表,所以空间只和串长有关,不随下标对数量膨胀
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 连接后等于目标字符串的字符串对 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么不能把这些数字串转成整数再比大小?+
因为整数会丢前导 0,也就是开头的那些 0。字符串 "007" 转成整数是 7,"07" 和 "7" 转完也都成了同一个数,可它们首尾拼接出来的串明明不一样。这题要的是拼出来的字符串和 target 逐字符相等,一旦转整数,前导 0 被抹平、串长也变了,比对结果就错了。所以从头到尾都拿字符串原样拼、原样比,别碰数值转换。
有没有比两层循环更快的做法?+
有。target 一旦确定,能拼成它的接缝只有一处,就是把 target 从中间某个位置切成前半和后半。先用哈希表数清每个串出现几次,再枚举 target 的每个切分位置:这一刀贡献的有序对数等于前半串出现次数乘后半串出现次数;当前半串和后半串是同一个串时,要把自己配自己的减掉,即出现 k 次时算 k×(k−1)。只需枚举 m−1 个切分点,比逐对枚举少扫全部下标对。参考代码为了直观用的是暴力枚举,这个哈希切分法可作进阶答案。
三种语言实现上要注意什么?+
最容易栽的是 Java 的字符串比较。Java 里的等号等号比的是对象引用是不是同一个,拼接得到的是新对象,内容一样但引用不同,会被判成不相等,所以必须用 target.equals(nums[i]+nums[j]) 比内容。C++ 给 string 重载了等号运算符,可以直接用等号等号比内容。Python 更省事,一行生成器表达式把布尔条件求和就是答案,真算 1、假算 0。三家逻辑一致,差别只在字符串怎么比、循环怎么写。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 连接后等于目标字符串的字符串对 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。