目录
题目
思路
Code
题目
题目内容:
某企业新申请到一个完整的 IPv4 主网段,网络管理员需要根据各部门申报的最小主机需求数量制定子网划分方案。
请编写程序,根据主网段信息、部门数量以及各部门需求,自动计算满足所有部门需求的最小主机数网段,并按申请部门信息连续分配等长子网。
输入描述:
第一行输入合法的 IPv4 主网段,CIDR 格式为 IP地址/掩码位数,例如 192.168.10.0/24。
第二行输入正整数 N,表示部门数量,范围为 1 到 20。
第三行输入 N 个整数,表示 N 个部门各自的最小主机需求数。
输出描述:
若分配可行,输出 N 个 CIDR 字符串,按升序排列,字符串之间用英文逗号分隔。
若输入网段非法、主机位不足 2 位,或无法同时满足所有部门的主机数需求和子网数量需求,则输出空内容。
所有生成的子网掩码长度必须完全一致,子网必须从主网段起始地址开始连续分配,子网地址不能重叠。可用主机数公式为 2 的 h 次方减 2,其中 h 等于 32 减掩码位数。
样例 1
输入:
192.168.1.0/24 3 10 20 50输出:
192.168.1.0/26,192.168.1.64/26,192.168.1.128/26说明:
最大需求为 50,需要 6 位主机位,子网掩码为 26;原网段可划分 4 个等长子网,足够分配 3 个部门。
样例 2
输入:
192.168.1.0/24 20 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100 100输出:
说明:
每个部门至少需要 100 个主机,子网需要 7 位主机位,原网段只能划分 2 个等长子网,不足以分配 20 个部门。
样例 3
输入:
192.168.1.0/24 2 10 10输出:
192.168.1.0/28,192.168.1.16/28说明:
最大需求为 10,需要 4 位主机位,子网掩码为 28。
思路
整体思路:所有部门使用等长子网,因此先由最大主机需求决定每个子网至少要保留多少主机位,再判断主网段能切出多少个这样的子网。
第一步:解析并校验 CIDR。IPv4 地址需要四段,每段在 0 到 255 之间;掩码需要留出至少 2 位主机位;主网段起始地址需要和掩码对齐。
第二步:计算最大需求 maxNeed。找到最小的主机位 h,使得 2 的 h 次方减 2 大于等于 maxNeed,则等长子网掩码为 32 - h。
第三步:如果子网掩码小于主网段掩码,说明单个子网比主网段还大,无法分配。否则可划分子网数为 2 的 子网掩码减主网段掩码 次方,若小于部门数量则无法分配。
第四步:可行时从主网段起始地址开始,每次增加一个子网大小,输出连续的 N 个 CIDR。
复杂度分析:解析和生成只与 N 成正比,时间复杂度 O(N),空间复杂度 O(N)。
Code
import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class Main { static Long parseIp(String text) { String[] parts = text.split("\\."); if (parts.length != 4) { return null; } long value = 0; for (String part : parts) { // 每段都必须是纯数字,避免 parseInt 接受题目不允许的格式。 if (!part.matches("\\d+")) { return null; } int octet = Integer.parseInt(part); // IPv4 每段只能落在 0 到 255,越界就不是合法地址。 if (octet < 0 || octet > 255) { return null; } value = value * 256 + octet; } return value; } static String formatIp(long value) { // 按 8 位一段拆回点分十进制,输出才能符合 CIDR 格式。 return ((value >> 24) & 255) + "." + ((value >> 16) & 255) + "." + ((value >> 8) & 255) + "." + (value & 255); } static int hostBitsFor(int hostNeed) { int bits = 0; // bits 是子网中留给主机编号的位数,共能组合出 2^bits 个地址。 // 全 0 的网络地址和全 1 的广播地址不能分给主机,所以可用数是 2^bits - 2。 while ((1L << bits) - 2 < hostNeed) { bits++; } return bits; } static List<String> solve(String cidr, int subnetCount, int[] hostNeeds) { List<String> answer = new ArrayList<>(); // CIDR 必须包含斜杠,斜杠前是起始 IP,斜杠后是原始掩码长度。 int slash = cidr.indexOf('/'); if (slash < 0) { return answer; } String maskText = cidr.substring(slash + 1); if (!maskText.matches("\\d+")) { return answer; } int originalMask = Integer.parseInt(maskText); // /24 表示 32 位地址的前 24 位是固定网络号,后 8 位用于网内主机。 if (originalMask <= 0 || originalMask > 30) { return answer; } Long baseObject = parseIp(cidr.substring(0, slash)); if (baseObject == null) { return answer; } long base = baseObject; int originalHostBits = 32 - originalMask; // networkMask 的网络位为 1、主机位为 0。按位与会把 base 的主机位全部清零。 // 清零后仍等于 base,才说明输入 IP 正好是网段起点,而不是网段里的普通主机。 long networkMask = 0xffffffffL ^ ((1L << originalHostBits) - 1); // 输入的起始 IP 必须正好落在原网段边界上,否则不能从它开始划分。 if ((base & networkMask) != base) { return answer; } int maxHostNeed = 0; for (int need : hostNeeds) { maxHostNeed = Math.max(maxHostNeed, need); } // 等长子网要满足所有需求,所以统一子网大小由最大主机需求决定。 int subnetHostBits = hostBitsFor(maxHostNeed); // 子网掩码 = 32 - 主机位数;主机位越多,单个子网容量越大。 int subnetMask = 32 - subnetHostBits; if (subnetMask < originalMask) { return answer; } // 新掩码每比原掩码多 1 位,原网段就能多切一倍子网。 // 因而可切数量是 2^(新掩码-原掩码),不足 subnetCount 时无法分配。 if ((1L << (subnetMask - originalMask)) < subnetCount) { return answer; } long subnetSize = 1L << subnetHostBits; for (int i = 0; i < subnetCount; i++) { // 等长划分下,每个子网起点只需要按子网大小依次递增。 answer.add(formatIp(base + i * subnetSize) + "/" + subnetMask); } // answer 为空表示输入非法或大网段容量不足,调用方会自然输出空字符串。 return answer; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String cidr = scanner.nextLine().trim(); int subnetCount = Integer.parseInt(scanner.nextLine().trim()); int[] hostNeeds = new int[subnetCount]; for (int i = 0; i < subnetCount; i++) { // 每个数字表示对应子网至少需要容纳的主机数量。 hostNeeds[i] = scanner.nextInt(); } System.out.print(String.join(",", solve(cidr, subnetCount, hostNeeds))); } }Go
package main import ( "bufio" "fmt" "os" "strconv" "strings" ) func ipToNum(ip string) (uint64, bool) { parts := strings.Split(ip, ".") if len(parts) != 4 { return 0, false } // IPv4 可以看成 4 个 8 位数字拼在一起。 // 转成 32 位整数后,网络地址对齐、子网块大小和起点递增都可以直接算。 var value uint64 for _, part := range parts { segment, err := strconv.Atoi(part) if err != nil || segment < 0 || segment > 255 { // IPv4 每段必须是 0 到 255 的数字,非法段直接让整个 CIDR 无效。 return 0, false } // 折算成 32 位整数后,网络对齐和子网起点递增都能用整数完成。 value = value*256 + uint64(segment) } return value, true } func numToIP(value uint64) string { // 子网起点在程序里是整数,输出给题目时要拆回点分十进制地址。 // 每一段只取 8 位,对应 IPv4 的一个 0~255 字段。 return fmt.Sprintf( "%d.%d.%d.%d", (value>>24)&255, (value>>16)&255, (value>>8)&255, value&255, ) } func requiredHostBits(hosts int) int { bits := 0 // bits 是子网内用于主机编号的位数,共能组合出 2^bits 个地址。 // 全 0 的网络地址和全 1 的广播地址不能分配,所以普通主机容量要减 2。 capacity := (1 << bits) - 2 for capacity < hosts { // 每个子网要扣除网络地址和广播地址,所以可用容量按 2^bits - 2 计算。 bits++ capacity = (1 << bits) - 2 } return bits } func parseCIDR(cidr string) (uint64, int, bool) { slash := strings.Index(cidr, "/") if slash < 0 { return 0, 0, false } mask, err := strconv.Atoi(cidr[slash+1:]) // CIDR 的 /24 表示前 24 位是固定网络号,剩余 8 位用于网内地址。 if err != nil || mask <= 0 || mask > 30 { // 原始 /31 和 /32 没有普通可分配主机地址,本题规则下直接判无效。 return 0, 0, false } base, ok := ipToNum(cidr[:slash]) if !ok { return 0, 0, false } hostBits := 32 - mask // networkMask 的网络位全为 1、主机位全为 0;按位与会把 base 的主机位清空。 // 清空后仍等于 base,才说明输入 IP 正好是网段起点。 networkMask := uint64(0xffffffff) ^ ((uint64(1) << hostBits) - 1) if base&networkMask != base { // CIDR 的 IP 部分必须正好是网络地址,否则后续子网划分整体偏移。 return 0, 0, false } return base, mask, true } func main() { in := bufio.NewReader(os.Stdin) var cidr string var subnetCount int fmt.Fscan(in, &cidr, &subnetCount) needs := make([]int, subnetCount) maxNeed := 0 for i := 0; i < subnetCount; i++ { fmt.Fscan(in, &needs[i]) if needs[i] > maxNeed { maxNeed = needs[i] } } base, originalMask, ok := parseCIDR(cidr) if !ok { return } // 等长子网的意思是每个子网大小相同,所以容量要按照最大主机需求统一决定。 // 这样小需求子网会有余量,大需求子网也不会放不下。 subnetHostBits := requiredHostBits(maxNeed) // 子网掩码等于 32 减主机位数;主机位越多,单个子网容量越大。 subnetMask := 32 - subnetHostBits if subnetMask < originalMask { // 等长子网如果比原始网段还大,就无法在给定网段内分配。 return } // 新掩码每比原掩码多 1 位,原网段就能多切一倍子网。 available := uint64(1) << (subnetMask - originalMask) if available < uint64(subnetCount) { // 原始网段能切出的等长子网数量不足,按题意输出空字符串。 return } subnetSize := uint64(1) << subnetHostBits answer := make([]string, 0, subnetCount) for i := 0; i < subnetCount; i++ { // 第 i 个子网起点按统一块大小递增,最后再转回点分十进制 CIDR。 start := base + uint64(i)*subnetSize answer = append(answer, numToIP(start)+"/"+strconv.Itoa(subnetMask)) } fmt.Print(strings.Join(answer, ",")) }C
#include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int parse_ip_address(const char *text, unsigned long long *value) { const char *cursor = text; unsigned long long result = 0; // IPv4 由 4 个点分十进制字段组成,这里逐段校验格式和值域。 // 同时把 a.b.c.d 折算成一个 32 位整数,方便后面用掩码判断网络地址是否对齐。 for (int part_index = 0; part_index < 4; part_index++) { if (!isdigit((unsigned char)*cursor)) { return 0; } int part = 0; while (isdigit((unsigned char)*cursor)) { part = part * 10 + (*cursor - '0'); if (part > 255) { // IPv4 每段超过 255 就不是合法地址,不能继续做掩码和子网计算。 return 0; } cursor++; } result = result * 256 + part; if (part_index < 3) { if (*cursor != '.') { return 0; } cursor++; } else if (*cursor != '\0') { return 0; } } *value = result; return 1; } int parse_positive_int(const char *text, int *value) { if (*text == '\0') { return 0; } int result = 0; for (int i = 0; text[i] != '\0'; i++) { if (!isdigit((unsigned char)text[i])) { return 0; } result = result * 10 + (text[i] - '0'); } *value = result; return 1; } int required_host_bits(int host_count) { int host_bits = 0; // host_bits 是子网中用于主机编号的位数,共有 2^host_bits 种地址组合。 // 全 0 是网络地址、全 1 是广播地址,不能分配给主机,所以可用数要减 2。 while (((1LL << host_bits) - 2) < host_count) { host_bits++; } return host_bits; } void print_ip_address(unsigned long long value) { // 内部用整数计算子网起点,输出时必须拆回普通读者熟悉的 a.b.c.d 格式。 // 每个右移结果都只保留 8 位,因为 IPv4 的每段地址范围正好是 0 到 255。 printf( "%llu.%llu.%llu.%llu", (value >> 24) & 255, (value >> 16) & 255, (value >> 8) & 255, value & 255 ); } int parse_cidr(char cidr[], unsigned long long *base, int *mask) { char *slash = strchr(cidr, '/'); if (slash == NULL) { return 0; } *slash = '\0'; if (!parse_positive_int(slash + 1, mask)) { return 0; } // CIDR 的 /24 表示前 24 位是固定网络号,剩余 8 位属于网内地址。 if (*mask <= 0 || *mask > 30) { // 原始网段至少要能容纳普通主机地址,/31 和 /32 在本题分配规则下无效。 return 0; } if (!parse_ip_address(cidr, base)) { return 0; } int host_bits = 32 - *mask; // network_mask 的网络位全为 1、主机位全为 0;按位与会清空 base 的主机位。 // 清空后仍等于 base,才能证明输入 IP 正好是网段起点。 unsigned long long network_mask = 0xffffffffULL ^ ((1ULL << host_bits) - 1); if ((*base & network_mask) != *base) { // CIDR 的 IP 部分必须是网络地址;如果不是对齐起点,后续子网起点都会错。 return 0; } return 1; } int main(void) { char cidr[128]; int subnet_count = 0; int needs[10000]; if (scanf("%127s", cidr) != 1) { return 0; } if (scanf("%d", &subnet_count) != 1) { return 0; } for (int i = 0; i < subnet_count; i++) { scanf("%d", &needs[i]); } unsigned long long base = 0; int original_mask = 0; if (!parse_cidr(cidr, &base, &original_mask)) { return 0; } // 等长划分意味着每个子网容量都一样,所以统一容量必须能容纳最大的主机需求。 // max_need 用来决定子网主机位数,不能按平均需求或第一个需求来算。 int max_need = 0; for (int i = 0; i < subnet_count; i++) { if (needs[i] > max_need) { max_need = needs[i]; } } int subnet_host_bits = required_host_bits(max_need); // 子网掩码等于 32 减主机位数;主机位越多,单个等长子网越大。 int subnet_mask = 32 - subnet_host_bits; if (subnet_mask < original_mask) { // 单个等长子网如果比原网段还大,就不可能在原网段内完成分配。 return 0; } // 新掩码每比原掩码多 1 位,就多切一倍子网,所以数量是 2 的差值次方。 unsigned long long available = 1ULL << (subnet_mask - original_mask); if (available < (unsigned long long)subnet_count) { // 原网段切出的等长子网数量不足时,题目要求不给出任何分配结果。 return 0; } unsigned long long subnet_size = 1ULL << subnet_host_bits; for (int i = 0; i < subnet_count; i++) { if (i > 0) { printf(","); } // 子网按起始地址递增输出,第 i 个起点是 base 加 i 个统一块大小。 print_ip_address(base + (unsigned long long)i * subnet_size); printf("/%d", subnet_mask); } return 0; }【华为od机试真题Python+JS+Java+Go合集】【超值优惠】:Py/JS/Java/Go合集
【华为od机试真题Python】:Python真题题库
【华为od机试真题JavaScript】:JavaScript真题题库
【华为od机试真题Java&Go】:Java&Go真题题库
【华为od机试真题C++】:C++真题题库
【华为od机试真题C语言】:C语言真题题库
【华为od面试手撕代码题库】:面试手撕代码题库
【华为od机试面试交流群】【文章底部有二维码链接,可扫码加交流群】
华为OD机试:二本院校有机会吗? 有机会,但不大,大神除外!机考分数越高越好,所以需要提前刷题。机考通过后,如果没有收到面试邀请,也不要着急,非目标院校面试邀请发的时间比较晚。非目标院校今年有点难,机试至少要考到350分,所以需要疯狂刷题,华为OD机考是有题库的,最好在考前完所有题库题目。华为OD机试:跨专业可以参加华为OD可以,但是如果你的本科院校比较差,上岸概率不大。华为OD机试:华为OD简历被锁定机试通过,性格测试也通过,但是没人联系面试,发现简历被锁定。此时需要主动去联系HR。让他帮助你查询原因。