一、问题描述
IP地址归属城市查询
题目描述
某业务需要根据终端的IP地址获取该终端归属的城市。可以根据公开的IP地址池信息查询归属城市。
地址池格式
城市名=起始IP,结束IP
起始和结束地址按照英文逗号分隔,多个地址段采用英文分号分隔。例如:
City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6
一个城市可以有多个IP段,比如City1有2个IP段。
城市间也可能存在包含关系,如City3的IP段包含City2的IP段范围。
输入描述
输入共2行。
- 第一行为城市的IP段列表,多个IP段采用英文分号 ‘;’ 分隔,IP段列表最大不超过500000。城市名称只包含英文字母、数字和下划线。最多不超过100000个。IP段包含关系可能有多层,但不超过100层。
- 第二行为查询的IP列表,多个IP采用英文逗号 ‘,’ 分隔,最多不超过10000条。
输出描述
最佳匹配的城市名列表,采用英文逗号 ‘,’ 分隔,城市列表长度应该跟查询的IP列表长度一致。
备注
- 无论是否查到匹配正常都要输出分隔符。举例:假如输入IP列表为IPa,IPb,两个IP均未有匹配城市,此时输出为",",即只有一个逗号分隔符,两个城市均为空;
- 可以假定用例中的所有输入均合法,IP地址均为合法的ipv4地址,满足 (1255).(0255).(0255).(0255) 的格式,且可以假定用例中不会出现组播和广播地址;
用例
输入
City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6
1.1.1.15,3.3.3.5,2.2.2.3
输出
City1,City2,City3
说明
- City1有2个IP段,City3的IP段包含City2的IP段;
- 1.1.1.15仅匹配City1=1.1.1.11,1.1.1.16,所以City1就是最佳匹配;2.2.2.3仅匹配City3=2.2.2.2,6.6.6.6,所以City3是最佳匹配;3.3.3.5同时匹配为City2=3.3.3.3,4.4.4.4和City3=2.2.2.2,6.6.6.6,但是City2=3.3.3.3,4.4.4.4的IP段范围更小,所以City3为最佳匹配;
题目解析
本题的主要难点在于判断一个IP地址是否属于一个IP段范围。
解决思路
-
IP地址转换为整型数值:
- IP地址本质上是4个8位二进制数,因此可以将每个IP地址转换为一个整型数值。例如,IP地址
1.1.1.1
可以转换为16843009
。 - 通过将IP地址转换为整型数值,可以方便地通过数值大小关系判断某个IP地址是否属于某段IP范围。
- IP地址本质上是4个8位二进制数,因此可以将每个IP地址转换为一个整型数值。例如,IP地址
-
遍历待查询的IP地址:
- 对于每个待查询的IP地址,遍历所有的IP段范围,判断该IP地址是否属于某个IP段。
- 如果该IP地址属于多个IP段,则选择范围最小的那个IP段对应的城市作为最佳匹配。
-
输出最佳匹配的城市列表:
- 对于每个查询的IP地址,输出其最佳匹配的城市名。如果没有匹配的城市,则输出空字符串。
关键点
- IP地址转换:将IP地址转换为整型数值是解决问题的关键步骤。
- 范围匹配:通过比较IP地址的整型数值与IP段的起始和结束数值,判断IP地址是否属于该IP段。
- 最佳匹配:在多个匹配的IP段中,选择范围最小的那个作为最佳匹配。
通过以上步骤,可以有效地解决IP地址归属城市查询的问题。
二、JavaScript算法源码
以下是代码的详细注释和讲解,帮助你理解每一部分的功能和实现逻辑:
代码结构
-
输入输出处理:
- 使用
readline
模块读取输入数据。 - 将城市IP列表和待查询的IP列表分别解析为数组。
- 使用
-
IP地址转整型:
- 实现
ip2dec
函数,将IPv4地址转换为32位整型数值。
- 实现
-
Range类:
- 用于存储每个城市IP段的信息,包括城市名称、起始IP的整型值、结束IP的整型值以及IP段的长度。
-
匹配逻辑:
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到最佳匹配的城市。
-
输出结果:
- 将匹配结果以逗号分隔的形式输出。
代码逐行讲解
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
- 使用
readline
模块创建接口,用于从标准输入读取数据。 iter
是一个异步迭代器,用于逐行读取输入。readline
是一个异步函数,每次调用会返回输入的一行。
void (async function () {
// 城市IP列表
const cities = (await readline()).split(";");
// 带查询的IP列表
const queryIps = (await readline()).split(",");
- 使用
await readline()
读取输入的第一行(城市IP列表),并按;
分割成数组。 - 使用
await readline()
读取输入的第二行(待查询的IP列表),并按,
分割成数组。
// IP地址转整型
function ip2dec(ip) {
let res = 0;
const blocks = ip.split(".");
for (let block of blocks) {
res = parseInt(block) | (res << 8);
}
return res;
}
ip2dec
函数将IPv4地址转换为32位整型数值。- 将IP地址按
.
分割成4个部分(例如1.1.1.1
分割为["1", "1", "1", "1"]
)。 - 遍历每个部分,将其转换为整数,并通过位运算
(res << 8)
和|
操作拼接到结果中。 - 最终返回一个32位整型数值。
- 将IP地址按
class Range {
constructor(city, startIpStr, endIpStr) {
this.city = city;
// 将IP地址转为整型
this.startIpDec = ip2dec(startIpStr);
this.endIpDec = ip2dec(endIpStr);
this.ipLen = this.endIpDec - this.startIpDec + 1;
}
}
Range
类用于存储每个城市IP段的信息。city
:城市名称。startIpDec
:起始IP地址的整型值。endIpDec
:结束IP地址的整型值。ipLen
:IP段的长度(结束IP - 起始IP + 1)。
const ranges = [];
for (let s of cities) {
const [city, startIpStr, endIpStr] = s.split(/[=,]/);
ranges.push(new Range(city, startIpStr, endIpStr));
}
- 遍历城市IP列表
cities
,将每个IP段解析为Range
对象,并存入ranges
数组。- 使用正则表达式
/[=,]/
分割字符串,提取城市名称、起始IP和结束IP。 - 创建
Range
对象并添加到ranges
数组中。
- 使用正则表达式
const ans = [];
// 遍历待查询的IP地址
for (let ip of queryIps) {
const ipDec = ip2dec(ip);
// 记录该目标IP地址的最佳匹配城市
let city = "";
// 记录最佳匹配城市IP段的长度
let minLen = Infinity;
// 将带查询IP与城市IP段列表逐一匹配
for (let range of ranges) {
// 如果带查询的IP地址 在某城市的IP段范围内,且该城市的IP段长度更小,则该城市为待查询IP的最佳匹配城市
if (
ipDec >= range.startIpDec &&
ipDec <= range.endIpDec &&
minLen > range.ipLen
) {
city = range.city;
minLen = range.ipLen;
}
}
ans.push(city);
}
- 遍历待查询的IP列表
queryIps
,对每个IP地址进行匹配:- 将IP地址转换为整型值
ipDec
。 - 初始化
city
为空字符串,minLen
为无穷大(Infinity
)。 - 遍历
ranges
数组,检查当前IP地址是否在某个IP段范围内:- 如果满足
ipDec >= range.startIpDec && ipDec <= range.endIpDec
,则说明IP地址在该IP段范围内。 - 如果当前IP段的长度
range.ipLen
小于minLen
,则更新city
和minLen
。
- 如果满足
- 将最佳匹配的城市名称
city
添加到ans
数组中。
- 将IP地址转换为整型值
console.log(ans.join(","));
})();
- 将匹配结果数组
ans
用逗号连接成字符串,并输出。
代码逻辑总结
-
输入处理:
- 读取城市IP列表和待查询的IP列表,并解析为数组。
-
IP地址转换:
- 将IPv4地址转换为32位整型数值,方便后续比较。
-
IP段存储:
- 使用
Range
类存储每个城市IP段的信息。
- 使用
-
匹配逻辑:
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到范围最小且包含该IP的IP段。
-
输出结果:
- 将匹配结果以逗号分隔的形式输出。
示例运行
输入:
City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6
1.1.1.15,3.3.3.5,2.2.2.3
输出:
City1,City2,City3
解释:
1.1.1.15
匹配City1=1.1.1.11,1.1.1.16
。3.3.3.5
匹配City2=3.3.3.3,4.4.4.4
。2.2.2.3
匹配City3=2.2.2.2,6.6.6.6
。
通过以上注释和讲解,你应该能够清晰地理解代码的每一部分及其实现逻辑。
三、Java算法源码
以下是代码的详细注释和讲解,帮助你理解每一部分的功能和实现逻辑:
代码结构
-
Range类:
- 用于存储每个城市IP段的信息,包括城市名称、起始IP的整型值、结束IP的整型值以及IP段的长度。
-
主函数:
- 读取输入数据并解析。
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到最佳匹配的城市。
- 输出匹配结果。
-
IP地址转整型:
- 实现
ip2dec
方法,将IPv4地址转换为64位整型数值。
- 实现
代码逐行讲解
import java.util.ArrayList;
import java.util.Scanner;
import java.util.StringJoiner;
- 导入所需的Java工具类:
ArrayList
:用于动态存储城市IP段信息。Scanner
:用于读取输入数据。StringJoiner
:用于将匹配结果以逗号分隔的形式输出。
public class Main {
static class Range {
String city;
long startIpDec;
long endIpDec;
long ipLen;
public Range(String city, String startIpStr, String endIpStr) {
this.city = city;
// 将IP地址转为整型
this.startIpDec = ip2dec(startIpStr);
this.endIpDec = ip2dec(endIpStr);
this.ipLen = this.endIpDec - this.startIpDec + 1;
}
}
- Range类:
city
:城市名称。startIpDec
:起始IP地址的整型值。endIpDec
:结束IP地址的整型值。ipLen
:IP段的长度(结束IP - 起始IP + 1)。- 构造函数接收城市名称、起始IP和结束IP,并将IP地址转换为整型值。
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ArrayList<Range> ranges = new ArrayList<>();
// 城市IP列表
String[] cities = sc.nextLine().split(";");
// 带查询的IP列表
String[] queryIps = sc.nextLine().split(",");
- 主函数:
- 创建
Scanner
对象用于读取输入。 - 创建
ArrayList<Range>
用于存储城市IP段信息。 - 读取第一行输入(城市IP列表),并按
;
分割成数组。 - 读取第二行输入(待查询的IP列表),并按
,
分割成数组。
- 创建
// 提取各个城市IP列表信息
for (String city : cities) {
String[] tmp = city.split("[=,]");
ranges.add(new Range(tmp[0], tmp[1], tmp[2]));
}
- 遍历城市IP列表
cities
,将每个IP段解析为Range
对象,并存入ranges
列表。- 使用正则表达式
[=,]
分割字符串,提取城市名称、起始IP和结束IP。 - 创建
Range
对象并添加到ranges
列表中。
- 使用正则表达式
StringJoiner sj = new StringJoiner(",");
// 遍历待查询的IP地址
for (String ip : queryIps) {
long ipDec = ip2dec(ip);
// 记录该目标IP地址的最佳匹配城市
String city = "";
// 记录最佳匹配城市IP段的长度
long minLen = Long.MAX_VALUE;
// 将带查询IP与城市IP段列表逐一匹配
for (Range range : ranges) {
// 如果带查询的IP地址 在某城市的IP段范围内,且该城市的IP段长度更小,则该城市为待查询IP的最佳匹配城市
if (ipDec >= range.startIpDec && ipDec <= range.endIpDec && minLen > range.ipLen) {
city = range.city;
minLen = range.ipLen;
}
}
sj.add(city);
}
System.out.println(sj);
}
- 匹配逻辑:
- 创建
StringJoiner
对象sj
,用于存储匹配结果。 - 遍历待查询的IP列表
queryIps
,对每个IP地址进行匹配:- 将IP地址转换为整型值
ipDec
。 - 初始化
city
为空字符串,minLen
为Long.MAX_VALUE
。 - 遍历
ranges
列表,检查当前IP地址是否在某个IP段范围内:- 如果满足
ipDec >= range.startIpDec && ipDec <= range.endIpDec
,则说明IP地址在该IP段范围内。 - 如果当前IP段的长度
range.ipLen
小于minLen
,则更新city
和minLen
。
- 如果满足
- 将最佳匹配的城市名称
city
添加到sj
中。
- 将IP地址转换为整型值
- 输出匹配结果。
- 创建
// IP地址转整型
public static long ip2dec(String ip) {
long res = 0;
String[] blocks = ip.split("\\.");
for (String block : blocks) {
res = (Integer.parseInt(block)) | (res << 8);
}
return res;
}
- IP地址转整型:
- 将IPv4地址转换为64位整型数值。
- 将IP地址按
.
分割成4个部分(例如1.1.1.1
分割为["1", "1", "1", "1"]
)。 - 遍历每个部分,将其转换为整数,并通过位运算
(res << 8)
和|
操作拼接到结果中。 - 最终返回一个64位整型数值。
代码逻辑总结
-
输入处理:
- 读取城市IP列表和待查询的IP列表,并解析为数组。
-
IP段存储:
- 使用
Range
类存储每个城市IP段的信息。
- 使用
-
匹配逻辑:
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到范围最小且包含该IP的IP段。
-
输出结果:
- 将匹配结果以逗号分隔的形式输出。
示例运行
输入:
City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6
1.1.1.15,3.3.3.5,2.2.2.3
输出:
City1,City2,City3
解释:
1.1.1.15
匹配City1=1.1.1.11,1.1.1.16
。3.3.3.5
匹配City2=3.3.3.3,4.4.4.4
。2.2.2.3
匹配City3=2.2.2.2,6.6.6.6
。
通过以上注释和讲解,你应该能够清晰地理解代码的每一部分及其实现逻辑。
四、Python算法源码
以下是代码的详细注释和讲解,帮助你理解每一部分的功能和实现逻辑:
代码结构
-
IP地址转整型:
- 实现
ip2dec
函数,将IPv4地址转换为32位整型数值。
- 实现
-
Range类:
- 用于存储每个城市IP段的信息,包括城市名称、起始IP的整型值、结束IP的整型值以及IP段的长度。
-
输入处理:
- 读取城市IP列表和待查询的IP列表,并解析为数组。
-
匹配逻辑:
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到最佳匹配的城市。
-
输出结果:
- 将匹配结果以逗号分隔的形式输出。
代码逐行讲解
import re
import sys
- 导入所需的Python模块:
re
:用于正则表达式操作。sys
:用于获取系统相关的值(如sys.maxsize
)。
# IP地址转整型
def ip2dec(ipStr):
res = 0
blocks = ipStr.split(".")
for block in blocks:
res = int(block) | (res << 8)
return res
ip2dec
函数:- 将IPv4地址转换为32位整型数值。
- 将IP地址按
.
分割成4个部分(例如1.1.1.1
分割为["1", "1", "1", "1"]
)。 - 遍历每个部分,将其转换为整数,并通过位运算
(res << 8)
和|
操作拼接到结果中。 - 最终返回一个32位整型数值。
class Range:
def __init__(self, city, startIpStr, endIpStr):
self.city = city
# 将IP地址转为整型
self.startIp = ip2dec(startIpStr)
self.endIp = ip2dec(endIpStr)
self.ipLen = self.endIp - self.startIp + 1
- Range类:
city
:城市名称。startIp
:起始IP地址的整型值。endIp
:结束IP地址的整型值。ipLen
:IP段的长度(结束IP - 起始IP + 1)。- 构造函数接收城市名称、起始IP和结束IP,并将IP地址转换为整型值。
# 输入获取
cities = input().split(";") # 城市IP列表
queryIps = input().split(",") # 带查询的IP列表
- 读取输入数据并解析:
- 使用
input()
读取第一行输入(城市IP列表),并按;
分割成数组。 - 使用
input()
读取第二行输入(待查询的IP列表),并按,
分割成数组。
- 使用
# 核心代码
ranges = []
# 提取各个城市IP列表信息
for s in cities:
# * 用于函数参数自动解构
ranges.append(Range(*(re.split(r"[=,]", s))))
- 遍历城市IP列表
cities
,将每个IP段解析为Range
对象,并存入ranges
列表。- 使用正则表达式
r"[=,]"
分割字符串,提取城市名称、起始IP和结束IP。 - 使用
*
解构参数,创建Range
对象并添加到ranges
列表中。
- 使用正则表达式
ans = []
# 遍历待查询的IP地址
for ip in queryIps:
ipDec = ip2dec(ip)
# 记录该目标IP地址的最佳匹配城市
best_city = ""
# 记录最佳匹配城市IP段的长度
minLen = sys.maxsize
# 将带查询IP与城市IP段列表逐一匹配
for ran in ranges:
# 如果带查询的IP地址 在某城市的IP段范围内,且该城市的IP段长度更小,则该城市为待查询IP的最佳匹配城市
if ran.endIp >= ipDec >= ran.startIp and minLen > ran.ipLen:
best_city = ran.city
minLen = ran.ipLen
ans.append(best_city)
- 匹配逻辑:
- 创建空列表
ans
,用于存储匹配结果。 - 遍历待查询的IP列表
queryIps
,对每个IP地址进行匹配:- 将IP地址转换为整型值
ipDec
。 - 初始化
best_city
为空字符串,minLen
为sys.maxsize
(表示最大整数值)。 - 遍历
ranges
列表,检查当前IP地址是否在某个IP段范围内:- 如果满足
ran.endIp >= ipDec >= ran.startIp
,则说明IP地址在该IP段范围内。 - 如果当前IP段的长度
ran.ipLen
小于minLen
,则更新best_city
和minLen
。
- 如果满足
- 将最佳匹配的城市名称
best_city
添加到ans
列表中。
- 将IP地址转换为整型值
- 创建空列表
print(",".join(ans))
- 将匹配结果列表
ans
用逗号连接成字符串,并输出。
代码逻辑总结
-
输入处理:
- 读取城市IP列表和待查询的IP列表,并解析为数组。
-
IP段存储:
- 使用
Range
类存储每个城市IP段的信息。
- 使用
-
匹配逻辑:
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到范围最小且包含该IP的IP段。
-
输出结果:
- 将匹配结果以逗号分隔的形式输出。
示例运行
输入:
City1=1.1.1.1,1.1.1.2;City1=1.1.1.11,1.1.1.16;City2=3.3.3.3,4.4.4.4;City3=2.2.2.2,6.6.6.6
1.1.1.15,3.3.3.5,2.2.2.3
输出:
City1,City2,City3
解释:
1.1.1.15
匹配City1=1.1.1.11,1.1.1.16
。3.3.3.5
匹配City2=3.3.3.3,4.4.4.4
。2.2.2.3
匹配City3=2.2.2.2,6.6.6.6
。
通过以上注释和讲解,你应该能够清晰地理解代码的每一部分及其实现逻辑。
五、C/C++算法源码:
以下是 C语言 和 C++ 版本的代码实现,并附带详细的中文注释和讲解。
C语言版本
代码结构
-
Range结构体:
- 用于存储每个城市IP段的信息,包括城市名称、起始IP的整型值、结束IP的整型值以及IP段的长度。
-
IP地址转整型:
- 实现
ip2dec
函数,将IPv4地址转换为32位整型数值。
- 实现
-
输入处理:
- 读取城市IP列表和待查询的IP列表,并解析为数组。
-
匹配逻辑:
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到最佳匹配的城市。
-
输出结果:
- 将匹配结果以逗号分隔的形式输出。
代码逐行讲解
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#define CITY_NAME_LENGTH 100
#define CITY_LIST_LENGTH 500000
#define QUERY_LIST_LENGTH 700000
typedef struct Range {
char city[CITY_NAME_LENGTH];
long startIpDec;
long endIpDec;
long ipLen;
} Range;
- Range结构体:
city
:城市名称。startIpDec
:起始IP地址的整型值。endIpDec
:结束IP地址的整型值。ipLen
:IP段的长度(结束IP - 起始IP + 1)。
// IP地址转整型
long ip2dec(char *ip) {
long res = 0;
int n1, n2, n3, n4;
sscanf(ip, "%d.%d.%d.%d", &n1, &n2, &n3, &n4);
res = n1 | (res << 8);
res = n2 | (res << 8);
res = n3 | (res << 8);
res = n4 | (res << 8);
return res;
}
ip2dec
函数:- 将IPv4地址转换为32位整型数值。
- 使用
sscanf
将IP地址按.
分割成4个部分。 - 通过位运算
(res << 8)
和|
操作拼接到结果中。 - 最终返回一个32位整型数值。
Range *new_Range(char *city, char *startIpStr, char *endIpStr) {
Range *range = (Range *) malloc(sizeof(Range));
strcpy(range->city, city);
range->startIpDec = ip2dec(startIpStr);
range->endIpDec = ip2dec(endIpStr);
range->ipLen = range->endIpDec - range->startIpDec + 1;
return range;
}
new_Range
函数:- 创建一个
Range
结构体对象。 - 将城市名称、起始IP和结束IP转换为整型值,并计算IP段长度。
- 返回创建的
Range
对象。
- 创建一个
// 第一行输入
char s1[CITY_LIST_LENGTH];
// 第二行输入
char s2[QUERY_LIST_LENGTH];
Range *ranges[CITY_LIST_LENGTH];
int ranges_size = 0;
- 定义全局变量:
s1
:存储第一行输入(城市IP列表)。s2
:存储第二行输入(待查询的IP列表)。ranges
:存储所有城市IP段信息。ranges_size
:记录当前ranges
的大小。
int main() {
gets(s1);
char *token1 = strtok(s1, ";");
while (token1 != NULL) {
// 提取各个城市IP列表信息
char city[CITY_NAME_LENGTH] = {'\0'};
char startIpStr[10] = {'\0'};
char endIpStr[10] = {'\0'};
sscanf(token1, "%[^=]=%[^,],%[^,]", city, startIpStr, endIpStr);
ranges[ranges_size++] = new_Range(city, startIpStr, endIpStr);
token1 = strtok(NULL, ";");
}
- 输入处理:
- 使用
gets
读取第一行输入(城市IP列表)。 - 使用
strtok
按;
分割字符串,提取每个城市IP段。 - 使用
sscanf
提取城市名称、起始IP和结束IP。 - 创建
Range
对象并存入ranges
数组。
- 使用
gets(s2);
// 遍历待查询的IP地址
char *token2 = strtok(s2, ",");
while (token2 != NULL) {
long ipDec = ip2dec(token2);
// 记录该目标IP地址的最佳匹配城市
char *city = "";
// 记录最佳匹配城市IP段的长度
long minLen = LONG_MAX;
// 将带查询IP与城市IP段列表逐一匹配
for (int i = 0; i < ranges_size; i++) {
// 如果带查询的IP地址 在某城市的IP段范围内,且该城市的IP段长度更小,则该城市为待查询IP的最佳匹配城市
if (ipDec >= ranges[i]->startIpDec && ipDec <= ranges[i]->endIpDec && minLen > ranges[i]->ipLen) {
city = ranges[i]->city;
minLen = ranges[i]->ipLen;
}
}
printf("%s", city);
token2 = strtok(NULL, ",");
if(token2 != NULL) {
printf(",");
}
}
return 0;
}
- 匹配逻辑:
- 使用
gets
读取第二行输入(待查询的IP列表)。 - 使用
strtok
按,
分割字符串,提取每个待查询的IP地址。 - 将IP地址转换为整型值
ipDec
。 - 遍历
ranges
数组,找到范围最小且包含该IP的IP段。 - 输出最佳匹配的城市名称。
- 使用
C++版本
代码结构
-
Range类:
- 用于存储每个城市IP段的信息。
-
IP地址转整型:
- 实现
ip2dec
函数,将IPv4地址转换为32位整型数值。
- 实现
-
输入处理:
- 读取城市IP列表和待查询的IP列表,并解析为数组。
-
匹配逻辑:
- 遍历待查询的IP地址,逐一与所有城市IP段进行匹配,找到最佳匹配的城市。
-
输出结果:
- 将匹配结果以逗号分隔的形式输出。
代码逐行讲解
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <climits>
using namespace std;
class Range {
public:
string city;
long startIpDec;
long endIpDec;
long ipLen;
Range(string city, string startIpStr, string endIpStr) {
this->city = city;
this->startIpDec = ip2dec(startIpStr);
this->endIpDec = ip2dec(endIpStr);
this->ipLen = this->endIpDec - this->startIpDec + 1;
}
static long ip2dec(string ip) {
long res = 0;
int n1, n2, n3, n4;
sscanf(ip.c_str(), "%d.%d.%d.%d", &n1, &n2, &n3, &n4);
res = n1 | (res << 8);
res = n2 | (res << 8);
res = n3 | (res << 8);
res = n4 | (res << 8);
return res;
}
};
- Range类:
city
:城市名称。startIpDec
:起始IP地址的整型值。endIpDec
:结束IP地址的整型值。ipLen
:IP段的长度(结束IP - 起始IP + 1)。ip2dec
函数:将IPv4地址转换为32位整型数值。
int main() {
string s1, s2;
getline(cin, s1); // 读取城市IP列表
getline(cin, s2); // 读取待查询的IP列表
vector<Range> ranges;
stringstream ss1(s1);
string token1;
while (getline(ss1, token1, ';')) {
string city, startIpStr, endIpStr;
size_t pos1 = token1.find('=');
size_t pos2 = token1.find(',');
city = token1.substr(0, pos1);
startIpStr = token1.substr(pos1 + 1, pos2 - pos1 - 1);
endIpStr = token1.substr(pos2 + 1);
ranges.emplace_back(city, startIpStr, endIpStr);
}
- 输入处理:
- 使用
getline
读取两行输入。 - 使用
stringstream
和getline
按;
分割字符串,提取每个城市IP段。 - 使用
substr
提取城市名称、起始IP和结束IP。 - 创建
Range
对象并存入ranges
向量。
- 使用
stringstream ss2(s2);
string token2;
bool first = true;
while (getline(ss2, token2, ',')) {
long ipDec = Range::ip2dec(token2);
string city;
long minLen = LONG_MAX;
for (auto &range : ranges) {
if (ipDec >= range.startIpDec && ipDec <= range.endIpDec && minLen > range.ipLen) {
city = range.city;
minLen = range.ipLen;
}
}
if (!first) {
cout << ",";
}
cout << city;
first = false;
}
return 0;
}
- 匹配逻辑:
- 使用
stringstream
和getline
按,
分割字符串,提取每个待查询的IP地址。 - 将IP地址转换为整型值
ipDec
。 - 遍历
ranges
向量,找到范围最小且包含该IP的IP段。 - 输出最佳匹配的城市名称。
- 使用
总结
- C语言 和 C++ 版本的实现逻辑基本相同。
- C++ 版本使用了
string
、vector
等STL容器,代码更简洁。 - C语言 版本使用了
strtok
和sscanf
进行字符串处理,适合对性能要求较高的场景。
希望以上注释和讲解对你有帮助!
六、尾言
什么是华为OD?
华为OD(Outsourcing Developer,外包开发工程师)是华为针对软件开发工程师岗位的一种招聘形式,主要包括笔试、技术面试以及综合面试等环节。尤其在笔试部分,算法题的机试至关重要。
为什么刷题很重要?
-
机试是进入技术面的第一关:
华为OD机试(常被称为机考)主要考察算法和编程能力。只有通过机试,才能进入后续的技术面试环节。 -
技术面试需要手撕代码:
技术一面和二面通常会涉及现场编写代码或算法题。面试官会注重考察候选人的思路清晰度、代码规范性以及解决问题的能力。因此提前刷题、多练习是通过面试的重要保障。 -
入职后的可信考试:
入职华为后,还需要通过“可信考试”。可信考试分为三个等级:- 入门级:主要考察基础算法与编程能力。
- 工作级:更贴近实际业务需求,可能涉及复杂的算法或与工作内容相关的场景题目。
- 专业级:最高等级,考察深层次的算法以及优化能力,与薪资直接挂钩。
刷题策略与说明:
2024年8月14日之后,华为OD机试的题库转为 E卷,由往年题库(D卷、A卷、B卷、C卷)和全新题目组成。刷题时可以参考以下策略:
-
关注历年真题:
- 题库中的旧题占比较大,建议优先刷历年的A卷、B卷、C卷、D卷题目。
- 对于每道题目,建议深度理解其解题思路、代码实现,以及相关算法的适用场景。
-
适应新题目:
- E卷中包含全新题目,需要掌握全面的算法知识和一定的灵活应对能力。
- 建议关注新的刷题平台或交流群,获取最新题目的解析和动态。
-
掌握常见算法:
华为OD考试通常涉及以下算法和数据结构:- 排序算法(快速排序、归并排序等)
- 动态规划(背包问题、最长公共子序列等)
- 贪心算法
- 栈、队列、链表的操作
- 图论(最短路径、最小生成树等)
- 滑动窗口、双指针算法
-
保持编程规范:
- 注重代码的可读性和注释的清晰度。
- 熟练使用常见编程语言,如C++、Java、Python等。
如何获取资源?
-
官方参考:
- 华为招聘官网或相关的招聘平台会有一些参考信息。
- 华为OD的相关公众号可能也会发布相关的刷题资料或学习资源。
-
加入刷题社区:
- 找到可信的刷题交流群,与其他备考的小伙伴交流经验。
- 关注知名的刷题网站,如LeetCode、牛客网等,这些平台上有许多华为OD的历年真题和解析。
-
寻找系统性的教程:
- 学习一本经典的算法书籍,例如《算法导论》《剑指Offer》《编程之美》等。
- 完成系统的学习课程,例如数据结构与算法的在线课程。
积极心态与持续努力:
刷题的过程可能会比较枯燥,但它能够显著提升编程能力和算法思维。无论是为了通过华为OD的招聘考试,还是为了未来的职业发展,这些积累都会成为重要的财富。
考试注意细节
-
本地编写代码
- 在本地 IDE(如 VS Code、PyCharm 等)上编写、保存和调试代码,确保逻辑正确后再复制粘贴到考试页面。这样可以减少语法错误,提高代码准确性。
-
调整心态,保持冷静
- 遇到提示不足或实现不确定的问题时,不必慌张,可以采用更简单或更有把握的方法替代,确保思路清晰。
-
输入输出完整性
- 注意训练和考试时都需要编写完整的输入输出代码,尤其是和题目示例保持一致。完成代码后务必及时调试,确保功能符合要求。
-
快捷键使用
- 删除行可用
Ctrl+D
,复制、粘贴和撤销分别为Ctrl+C
,Ctrl+V
,Ctrl+Z
,这些可以正常使用。 - 避免使用
Ctrl+S
,以免触发浏览器的保存功能。
- 删除行可用
-
浏览器要求
- 使用最新版的 Google Chrome 浏览器完成考试,确保摄像头开启并正常工作。考试期间不要切换到其他网站,以免影响考试成绩。
-
交卷相关
- 答题前,务必仔细查看题目示例,避免遗漏要求。
- 每完成一道题后,点击【保存并调试】按钮,多次保存和调试是允许的,系统会记录得分最高的一次结果。完成所有题目后,点击【提交本题型】按钮。
- 确保在考试结束前提交试卷,避免因未保存或调试失误而丢分。
-
时间和分数安排
- 总时间:150 分钟;总分:400 分。
- 试卷结构:2 道一星难度题(每题 100 分),1 道二星难度题(200 分)。及格分为 150 分。合理分配时间,优先完成自己擅长的题目。
-
考试环境准备
- 考试前请备好草稿纸和笔。考试中尽量避免离开座位,确保监控画面正常。
- 如需上厕所,请提前规划好时间以减少中途离开监控的可能性。
-
技术问题处理
- 如果考试中遇到断电、断网、死机等技术问题,可以关闭浏览器并重新打开试卷链接继续作答。
- 出现其他问题,请第一时间联系 HR 或监考人员进行反馈。
祝你考试顺利,取得理想成绩!