一、问题描述
题目描述
有 M
个端口组 (1 <= M <= 10),
每个端口组是长度为 N
的整数数组 (1 <= N <= 100),
如果端口组间存在 2 个及以上不同端口相同,则认为这 2 个端口组互相关联,可以合并。
输入描述
第一行输入端口组个数 M
,再输入 M
行,每行逗号分割,代表端口组。
备注
- 端口组内数字可以重复。
- 如果
M
或N
不在限定范围内,统一输出一组空数组[[]]
。
输出描述
输出合并后的端口组,用二维数组表示。
- 组内相同端口仅保留一个,从小到大排序。
- 组外顺序保持输入顺序。
用例
用例 1
输入:
4
4
2,3,2
1,2
5
输出:
[[4],[2,3],[1,2],[5]]
说明:
仅有一个端口 2 相同,不可以合并。
用例 2
输入:
3
2,3,1
4,3,2
5
输出:
[[1,2,3,4],[5]]
说明:
端口组 2,3,1
和 4,3,2
有 2 个及以上不同端口相同,可以合并。
用例 3
输入:
6
10
4,2,1
9
3,6,9,2
6,3,4
8
输出:
[[10],[1,2,3,4,6,9],[9],[8]]
说明:
端口组 4,2,1
和 3,6,9,2
有 2 个及以上不同端口相同,可以合并。
用例 4
输入:
11
输出:
[[]]
说明:
M
超出范围,输出空数组。
解题思路
- 读取输入:读取端口组个数
M
和每个端口组的内容。 - 检查范围:如果
M
或N
超出范围,直接输出[[]]
。 - 处理每个端口组:将每个端口组转换为集合,去除重复元素,并排序。
- 合并端口组:使用并查集或图的连通分量算法来合并互相关联的端口组。
- 输出结果:将合并后的端口组按输入顺序输出。
题目解析
题目中的描述“如果端口组间存在2个及以上不同端口相同,则认为这2个端口组互相关联,可以合并”确实存在一定的歧义。这里的“不同端口”可以有两种理解:
-
两个端口组,如果可以形成2对端口值相同的端口对,那么也可以合并:
- 例如,端口组
a = [3, 3, 5]
和b = [1, 3, 3]
,虽然a[0]
和b[1]
相同,a[1]
和b[2]
相同,但这些相同的端口值都是3,因此可以认为这两个端口组可以合并。
- 例如,端口组
-
两个端口组,只有形成2对端口值不同的端口对,那么才可以合并:
- 例如,端口组
a = [3, 3, 5]
和b = [1, 3, 3]
,虽然a[0]
和b[1]
相同,a[1]
和b[2]
相同,但这些相同的端口值都是3,因此不能认为这两个端口组可以合并。只有当两个端口组中有两个不同的端口值相同,例如a = [3, 5]
和b = [3, 5]
,才可以合并。
- 例如,端口组
解题思路
为了确保代码的通用性和正确性,我们假设采用第一种理解,即两个端口组如果可以形成2对端口值相同的端口对,那么也可以合并。这种理解更符合题目的描述,也更符合实际应用中的需求。
二、JavaScript算法源码
以下是 JavaScript 代码的详细中文注释和逻辑讲解:
JavaScript 代码实现
/* JavaScript Node ACM模式 控制台输入获取 */
const readline = require("readline");// 创建 readline 接口,用于从控制台读取输入
const rl = readline.createInterface({input: process.stdin,output: process.stdout,
});// 存储输入的行数据
const lines = [];
let m; // 端口组的数量// 监听输入事件
rl.on("line", (line) => {lines.push(line); // 将输入的行存入 lines 数组// 当输入的第一行是端口组数量 m 时if (lines.length === 1) {m = lines[0] - 0; // 将字符串转换为数字// 如果 m 不在限定范围内(1 <= m <= 10),输出空数组 [[]] 并重置 linesif (m > 10 || m < 1) {console.log("[[]]");lines.length = 0; // 清空 lines 数组return;}}// 当输入的行数等于 m + 1 时(第一行是 m,后面是 m 个端口组)if (m && lines.length === m + 1) {lines.shift(); // 移除第一行(m)const ports = lines.map((line) => line.split(",").map(Number)); // 将每行转换为数字数组// 检查每个端口组的长度是否在限定范围内(1 <= n <= 100)for (let port of ports) {const n = port.length;if (n < 1 || n > 100) {console.log("[[]]"); // 如果不在范围内,输出空数组 [[]]lines.length = 0; // 清空 lines 数组return;}}// 调用算法入口函数,输出结果console.log(getResult(ports));lines.length = 0; // 清空 lines 数组}
});// 算法入口函数
function getResult(ports) {outer: while (true) {// 倒序遍历端口组,确保组外顺序保持输入顺序for (let i = ports.length - 1; i >= 0; i--) {for (let j = i - 1; j >= 0; j--) {// 判断两个端口组是否可以合并if (canUnion(ports[i], ports[j])) {// 将后面的端口组(ports[i])并入前面的端口组(ports[j])ports[j].push(...ports[i]);ports.splice(i, 1); // 移除被合并的端口组continue outer; // 继续外层循环}}}break; // 如果没有可以合并的端口组,退出循环}// 对每个端口组去重并排序const ans = ports.map((port) => [...new Set(port)].sort((a, b) => a - b));return JSON.stringify(ans); // 将结果转换为 JSON 字符串
}// 判断两个端口组是否可以合并
function canUnion(port1, port2) {// 对两个端口组进行升序排序port1.sort((a, b) => a - b);port2.sort((a, b) => a - b);let same = 0; // 记录相同端口的数量let i = 0; // port1 的指针let j = 0; // port2 的指针// 双指针遍历两个端口组while (i < port1.length && j < port2.length) {if (port1[i] == port2[j]) {i++;j++;if (++same >= 2) return true; // 如果有 2 个及以上相同端口,返回 true} else if (port1[i] > port2[j]) {j++; // port2 的当前值较小,移动 port2 的指针} else {i++; // port1 的当前值较小,移动 port1 的指针}}return false; // 如果没有 2 个及以上相同端口,返回 false
}
代码讲解
1. 输入处理
- 使用
readline
模块从控制台读取输入。 - 第一行是端口组的数量
m
,后续每行是一个端口组,用逗号分隔。 - 检查
m
和每个端口组的长度是否在限定范围内(1 <= m <= 10
,1 <= n <= 100
),如果不在范围内,输出空数组[[]]
。
2. 主逻辑:getResult
函数
- 倒序遍历端口组,确保组外顺序保持输入顺序。
- 对于每一对端口组,调用
canUnion
函数判断是否可以合并。 - 如果可以合并,将后面的端口组并入前面的端口组,并移除被合并的端口组。
- 最终对每个端口组去重并排序,返回结果。
3. 判断是否可以合并:canUnion
函数
- 对两个端口组进行升序排序。
- 使用双指针遍历两个端口组,统计相同端口的数量。
- 如果有 2 个及以上相同端口,返回
true
,表示可以合并;否则返回false
。
示例解析
输入
3
1,2,3
2,3,4
5,6,7
运行结果
[[1,2,3,4],[5,6,7]]
- 解析:
- 端口组
[1,2,3]
和[2,3,4]
有 2 个相同端口(2
和3
),可以合并为[1,2,3,4]
。 - 端口组
[5,6,7]
没有与其他端口组相同的端口,保持不变。 - 最终结果为
[[1,2,3,4],[5,6,7]]
。
- 端口组
总结
- 该代码通过双指针和排序的方式,判断端口组是否可以合并。
- 使用倒序遍历确保组外顺序保持输入顺序。
- 代码逻辑清晰,适用于解决类似合并问题的场景。
如果有其他问题,欢迎随时提问!
三、Java算法源码
以下是 Java 代码的详细中文注释和逻辑讲解:
Java 代码实现
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);// 读取端口组的数量 mint m = Integer.parseInt(sc.nextLine());// 如果 m 不在限定范围内(1 <= m <= 10),输出空数组 [[]] 并结束程序if (m > 10 || m < 1) {System.out.println("[[]]");return;}// 使用 ArrayList 存储端口组,方便后续合并操作ArrayList<ArrayList<Integer>> ports = new ArrayList<>();// 读取每个端口组for (int i = 0; i < m; i++) {// 将输入的端口组按逗号分隔,并转换为 Integer 数组Integer[] tmp =Arrays.stream(sc.nextLine().split(",")).map(Integer::parseInt).toArray(Integer[]::new);// 检查端口组的长度是否在限定范围内(1 <= n <= 100)int n = tmp.length;if (n < 1 || n > 100) {System.out.println("[[]]"); // 如果不在范围内,输出空数组 [[]]return;}// 将 Integer 数组转换为 ArrayList 并存入 portsArrayList<Integer> tmpList = new ArrayList<>(Arrays.asList(tmp));ports.add(tmpList);}// 调用算法入口函数,输出结果System.out.println(getResult(ports));}// 算法入口函数public static String getResult(ArrayList<ArrayList<Integer>> ports) {outer:while (true) {// 倒序遍历端口组,确保组外顺序保持输入顺序for (int i = ports.size() - 1; i >= 0; i--) {for (int j = i - 1; j >= 0; j--) {// 判断两个端口组是否可以合并if (canUnion(ports.get(i), ports.get(j))) {// 将后面的端口组(ports.get(i))并入前面的端口组(ports.get(j))ports.get(j).addAll(ports.get(i));ports.remove(i); // 移除被合并的端口组continue outer; // 继续外层循环}}}break; // 如果没有可以合并的端口组,退出循环}// 使用 StringJoiner 构建输出结果StringJoiner out = new StringJoiner(",", "[", "]");for (ArrayList<Integer> port : ports) {// 对每个端口组去重并排序StringJoiner in = new StringJoiner(",", "[", "]");for (Integer v : new TreeSet<Integer>(port)) { // 使用 TreeSet 去重并排序in.add(v + ""); // 将端口值转换为字符串并加入 in}out.add(in.toString()); // 将处理后的端口组加入 out}return out.toString(); // 返回最终的 JSON 字符串}// 判断两个端口组是否可以合并public static boolean canUnion(ArrayList<Integer> port1, ArrayList<Integer> port2) {// 对两个端口组进行升序排序port1.sort((a, b) -> a - b);port2.sort((a, b) -> a - b);int same = 0; // 记录相同端口的数量int i = 0; // port1 的指针int j = 0; // port2 的指针// 双指针遍历两个端口组while (i < port1.size() && j < port2.size()) {if (port1.get(i) - port2.get(j) == 0) { // 如果当前端口值相同i++;j++;if (++same >= 2) return true; // 如果有 2 个及以上相同端口,返回 true} else if (port1.get(i) > port2.get(j)) {j++; // port2 的当前值较小,移动 port2 的指针} else {i++; // port1 的当前值较小,移动 port1 的指针}}return false; // 如果没有 2 个及以上相同端口,返回 false}
}
代码讲解
1. 输入处理
- 使用
Scanner
从控制台读取输入。 - 第一行是端口组的数量
m
,后续每行是一个端口组,用逗号分隔。 - 检查
m
和每个端口组的长度是否在限定范围内(1 <= m <= 10
,1 <= n <= 100
),如果不在范围内,输出空数组[[]]
。
2. 主逻辑:getResult
函数
- 倒序遍历端口组,确保组外顺序保持输入顺序。
- 对于每一对端口组,调用
canUnion
函数判断是否可以合并。 - 如果可以合并,将后面的端口组并入前面的端口组,并移除被合并的端口组。
- 最终对每个端口组去重并排序,返回结果。
3. 判断是否可以合并:canUnion
函数
- 对两个端口组进行升序排序。
- 使用双指针遍历两个端口组,统计相同端口的数量。
- 如果有 2 个及以上相同端口,返回
true
,表示可以合并;否则返回false
。
4. 结果格式化
- 使用
StringJoiner
构建输出结果。 - 对每个端口组去重并排序,使用
TreeSet
实现。
示例解析
输入
3
1,2,3
2,3,4
5,6,7
运行结果
[[1,2,3,4],[5,6,7]]
- 解析:
- 端口组
[1,2,3]
和[2,3,4]
有 2 个相同端口(2
和3
),可以合并为[1,2,3,4]
。 - 端口组
[5,6,7]
没有与其他端口组相同的端口,保持不变。 - 最终结果为
[[1,2,3,4],[5,6,7]]
。
- 端口组
总结
- 该代码通过双指针和排序的方式,判断端口组是否可以合并。
- 使用倒序遍历确保组外顺序保持输入顺序。
- 代码逻辑清晰,适用于解决类似合并问题的场景。
如果有其他问题,欢迎随时提问!
四、Python算法源码
以下是 Python 代码的详细中文注释和逻辑讲解:
Python 代码实现
import re# 判断两个端口组是否可以合并
def canUnion(port1, port2):# 对两个端口组进行升序排序port1.sort()port2.sort()same = 0 # 记录相同端口的数量i = 0 # port1 的指针j = 0 # port2 的指针# 双指针遍历两个端口组while i < len(port1) and j < len(port2):if port1[i] == port2[j]: # 如果当前端口值相同i += 1j += 1same += 1if same >= 2: # 如果有 2 个及以上相同端口,返回 Truereturn Trueelif port1[i] > port2[j]: # port2 的当前值较小,移动 port2 的指针j += 1else: # port1 的当前值较小,移动 port1 的指针i += 1return False # 如果没有 2 个及以上相同端口,返回 False# 从头开始尝试合并端口组
def forPorts(ports):# 倒序遍历端口组,确保组外顺序保持输入顺序for i in range(len(ports) - 1, -1, -1):for j in range(i - 1, -1, -1):# 判断两个端口组是否可以合并if canUnion(ports[i], ports[j]):# 将后面的端口组(ports[i])并入前面的端口组(ports[j])ports[j].extend(ports[i])ports.pop(i) # 移除被合并的端口组return True # 继续尝试合并return False # 合并尝试结束# 对端口组去重并排序
def distinctAndSort(port):tmp = list(set(port)) # 使用 set 去重tmp.sort() # 对端口组进行升序排序return tmp# 算法入口函数
def getResult(ports):while True:if not forPorts(ports): # 尝试合并端口组,直到无法合并为止break# 返回处理后的端口组,去除空格return re.sub(r"\s", "", str(list(map(distinctAndSort, ports))))# 输入获取
m = int(input()) # 读取端口组的数量 m# 如果 m 不在限定范围内(1 <= m <= 10),输出空数组 [[]]
if m < 1 or m > 10:print("[[]]")
else:# 读取每个端口组,并将其转换为整数列表ports = [list(map(int, input().split(","))) for _ in range(m)]# 检查每个端口组的长度是否在限定范围内(1 <= n <= 100)if len(list(filter(lambda p: len(p) < 1 or len(p) > 100, ports))) > 0:print("[[]]") # 如果不在范围内,输出空数组 [[]]else:# 调用算法入口函数,输出结果print(getResult(ports))
代码讲解
1. 输入处理
- 使用
input()
函数从控制台读取输入。 - 第一行是端口组的数量
m
,后续每行是一个端口组,用逗号分隔。 - 检查
m
和每个端口组的长度是否在限定范围内(1 <= m <= 10
,1 <= n <= 100
),如果不在范围内,输出空数组[[]]
。
2. 主逻辑:getResult
函数
- 调用
forPorts
函数尝试合并端口组,直到无法合并为止。 - 对每个端口组去重并排序,使用
distinctAndSort
函数实现。 - 使用
re.sub
去除结果中的空格,返回最终的 JSON 字符串。
3. 合并端口组:forPorts
函数
- 倒序遍历端口组,确保组外顺序保持输入顺序。
- 对于每一对端口组,调用
canUnion
函数判断是否可以合并。 - 如果可以合并,将后面的端口组并入前面的端口组,并移除被合并的端口组。
4. 判断是否可以合并:canUnion
函数
- 对两个端口组进行升序排序。
- 使用双指针遍历两个端口组,统计相同端口的数量。
- 如果有 2 个及以上相同端口,返回
True
,表示可以合并;否则返回False
。
5. 去重并排序:distinctAndSort
函数
- 使用
set
对端口组去重。 - 对去重后的端口组进行升序排序。
示例解析
输入
3
1,2,3
2,3,4
5,6,7
运行结果
[[1,2,3,4],[5,6,7]]
- 解析:
- 端口组
[1,2,3]
和[2,3,4]
有 2 个相同端口(2
和3
),可以合并为[1,2,3,4]
。 - 端口组
[5,6,7]
没有与其他端口组相同的端口,保持不变。 - 最终结果为
[[1,2,3,4],[5,6,7]]
。
- 端口组
总结
- 该代码通过双指针和排序的方式,判断端口组是否可以合并。
- 使用倒序遍历确保组外顺序保持输入顺序。
- 代码逻辑清晰,适用于解决类似合并问题的场景。
如果有其他问题,欢迎随时提问!
五、C/C++算法源码:
以下为 C++ 代码的实现,并附上详细的中文注释和逻辑讲解:
C++ 代码实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
#include <sstream>
#include <regex>using namespace std;// 判断两个端口组是否可以合并
bool canUnion(vector<int>& port1, vector<int>& port2) {// 对两个端口组进行升序排序sort(port1.begin(), port1.end());sort(port2.begin(), port2.end());int same = 0; // 记录相同端口的数量int i = 0; // port1 的指针int j = 0; // port2 的指针// 双指针遍历两个端口组while (i < port1.size() && j < port2.size()) {if (port1[i] == port2[j]) { // 如果当前端口值相同i++;j++;same++;if (same >= 2) return true; // 如果有 2 个及以上相同端口,返回 true} else if (port1[i] > port2[j]) { // port2 的当前值较小,移动 port2 的指针j++;} else { // port1 的当前值较小,移动 port1 的指针i++;}}return false; // 如果没有 2 个及以上相同端口,返回 false
}// 从头开始尝试合并端口组
bool forPorts(vector<vector<int>>& ports) {// 倒序遍历端口组,确保组外顺序保持输入顺序for (int i = ports.size() - 1; i >= 0; i--) {for (int j = i - 1; j >= 0; j--) {// 判断两个端口组是否可以合并if (canUnion(ports[i], ports[j])) {// 将后面的端口组(ports[i])并入前面的端口组(ports[j])ports[j].insert(ports[j].end(), ports[i].begin(), ports[i].end());ports.erase(ports.begin() + i); // 移除被合并的端口组return true; // 继续尝试合并}}}return false; // 合并尝试结束
}// 对端口组去重并排序
vector<int> distinctAndSort(vector<int>& port) {set<int> tmp(port.begin(), port.end()); // 使用 set 去重vector<int> result(tmp.begin(), tmp.end()); // 转换为 vectorsort(result.begin(), result.end()); // 对端口组进行升序排序return result;
}// 算法入口函数
string getResult(vector<vector<int>>& ports) {while (true) {if (!forPorts(ports)) { // 尝试合并端口组,直到无法合并为止break;}}// 构建输出结果stringstream ss;ss << "[";for (size_t i = 0; i < ports.size(); i++) {vector<int> tmp = distinctAndSort(ports[i]); // 去重并排序ss << "[";for (size_t j = 0; j < tmp.size(); j++) {ss << tmp[j];if (j < tmp.size() - 1) ss << ",";}ss << "]";if (i < ports.size() - 1) ss << ",";}ss << "]";// 去除空格string result = ss.str();result.erase(remove(result.begin(), result.end(), ' '), result.end());return result;
}// 输入获取
int main() {int m;cin >> m; // 读取端口组的数量 m// 如果 m 不在限定范围内(1 <= m <= 10),输出空数组 [[]]if (m < 1 || m > 10) {cout << "[[]]" << endl;return 0;}vector<vector<int>> ports(m);for (int i = 0; i < m; i++) {string line;cin >> line; // 读取一行输入stringstream ss(line);string token;while (getline(ss, token, ',')) { // 按逗号分隔ports[i].push_back(stoi(token)); // 将字符串转换为整数并存入 ports}}// 检查每个端口组的长度是否在限定范围内(1 <= n <= 100)for (const auto& port : ports) {if (port.size() < 1 || port.size() > 100) {cout << "[[]]" << endl; // 如果不在范围内,输出空数组 [[]]return 0;}}// 调用算法入口函数,输出结果cout << getResult(ports) << endl;return 0;
}
代码讲解
1. 输入处理
- 使用
cin
从控制台读取输入。 - 第一行是端口组的数量
m
,后续每行是一个端口组,用逗号分隔。 - 检查
m
和每个端口组的长度是否在限定范围内(1 <= m <= 10
,1 <= n <= 100
),如果不在范围内,输出空数组[[]]
。
2. 主逻辑:getResult
函数
- 调用
forPorts
函数尝试合并端口组,直到无法合并为止。 - 对每个端口组去重并排序,使用
distinctAndSort
函数实现。 - 使用
stringstream
构建输出结果,并去除空格。
3. 合并端口组:forPorts
函数
- 倒序遍历端口组,确保组外顺序保持输入顺序。
- 对于每一对端口组,调用
canUnion
函数判断是否可以合并。 - 如果可以合并,将后面的端口组并入前面的端口组,并移除被合并的端口组。
4. 判断是否可以合并:canUnion
函数
- 对两个端口组进行升序排序。
- 使用双指针遍历两个端口组,统计相同端口的数量。
- 如果有 2 个及以上相同端口,返回
true
,表示可以合并;否则返回false
。
5. 去重并排序:distinctAndSort
函数
- 使用
set
对端口组去重。 - 对去重后的端口组进行升序排序。
示例解析
输入
3
1,2,3
2,3,4
5,6,7
运行结果
[[1,2,3,4],[5,6,7]]
- 解析:
- 端口组
[1,2,3]
和[2,3,4]
有 2 个相同端口(2
和3
),可以合并为[1,2,3,4]
。 - 端口组
[5,6,7]
没有与其他端口组相同的端口,保持不变。 - 最终结果为
[[1,2,3,4],[5,6,7]]
。
- 端口组
总结
- 该代码通过双指针和排序的方式,判断端口组是否可以合并。
- 使用倒序遍历确保组外顺序保持输入顺序。
- 代码逻辑清晰,适用于解决类似合并问题的场景。
如果有其他问题,欢迎随时提问!