题目描述

✅ 补充题 22. IP 地址与整数的转换

实现 IPv4 地址与无符号 32 位整数之间的相互转换。

对于地址 a.b.c.d,将四段分别视为一个 8 位无符号数,并按从高位到低位的顺序拼接,得到对应整数。反向转换时,按相同顺序拆分四个字节,返回点分十进制地址。

示例 1:

输入:ip = "10.0.3.193"
输出:167773121

示例 2:

输入:value = 167773121
输出:"10.0.3.193"

提示:

  • 地址输入采用合法规范的 IPv4 点分十进制格式,四段均在 [0,255] 范围内。
  • 整数输入范围为 0 <= value <= 2^32 - 1。
  • 第一段对应最高 8 位,最后一段对应最低 8 位;反向结果不包含多余前导零。

题意分析

IPv4 地址由四个十进制段组成,每段范围为 0..255,恰好占 8 位。把第一段放在最高 8 位、最后一段放在最低 8 位,就能将地址表示为一个无符号 32 位整数,再按相同顺序拆回地址。

代码约定 IP 输入已是规范合法的四段地址,整数输入位于 [0, 2^32-1]。Java 用非负 long 保存这一范围,Go 直接使用 uint32。

解法:按 8 位字节移位转换

核心思路

[!blue]

设四段为 a.b.c.d,对应数值为 $a\times256^3+b\times256^2+c\times256+d$。可以像读取十进制整数那样从左到右累积,只是每次乘的进制是 256:value = value * 256 + 当前段。

左移 8 位就是乘 256,同时将最低 8 位清零。当前段不超过 255,只占最低 8 位,因此按位或与加法等价,代码使用 (value << 8) | 当前段。每处理一段,value 都恰好表示已经读过的地址前缀;处理四段后就得到完整数值。

反向转换时,对移位量 24、16、8、0 依次右移,把目标字节移到最低 8 位,再用 & 0xFF 去掉它上方的所有位。四次提取分别恢复 a、b、c、d,按十进制输出并用点连接,因此两种转换互为逆操作。

解题步骤

  1. IP 转整数:初始化 value = 0,按点拆分地址,从左到右解析四个十进制段。
  2. 每读一段,先将已有结果左移 8 位,再按位或入当前段;全部处理后返回 value。
  3. 整数转 IP:让 shift 从 24 开始,每次减 8,直到处理完 0。
  4. Java 用 (value >>> shift) & 0xFF,Go 用 (value >> shift) & 0xFF 提取字节,并依次拼接成四段地址。

反向转换固定输出四段,不能在剩余数值为 0 时提前停止,否则会丢掉地址中的零段。最小数值 0 对应四个 0,最大数值 2^32-1 对应四个 255,都使用相同流程。

代码实现

class Solution {
    public long ipToInt(String ip) {
        long value = 0;

        for (String part : ip.split("\\.")) {
            // 旧前缀提升一个字节,第一段最终位于最高八位。
            value = (value << 8) | Integer.parseInt(part);
        }

        return value;
    }

    public String intToIP(long value) {
        StringBuilder result = new StringBuilder();

        for (int shift = 24; shift >= 0; shift -= 8) {
            if (result.length() > 0) {
                result.append('.');
            }

            // 右移后还需掩码,只保留当前一个字节。
            result.append((value >>> shift) & 0xFF);
        }

        return result.toString();
    }
}
import (
    "strconv"
    "strings"
)

func ipToInt(ip string) uint32 {
    var value uint32
    for _, part := range strings.Split(ip, ".") {
        num, _ := strconv.Atoi(part)
        // 旧前缀提升一个字节,第一段最终位于最高八位。
        value = value<<8 | uint32(num)
    }
    return value
}

func intToIP(value uint32) string {
    parts := make([]string, 0, 4)
    for shift := 24; shift >= 0; shift -= 8 {
        // 右移后还需掩码,只保留当前一个字节。
        part := value >> shift & 0xFF
        parts = append(parts, strconv.Itoa(int(part)))
    }
    return strings.Join(parts, ".")
}

复杂度分析

  • 时间复杂度:$O(1)$。IPv4 固定为 4 段,字符串长度也有固定上限。
  • 空间复杂度:$O(1)$。切分数组和输出缓冲区都只保存固定数量的字符与分段。

关键点总结

[!green]

  • 正向每次腾出一个低位字节,再填入新段;先读入的段会被后续左移推到高位。
  • 反向先移动目标字节,再屏蔽其他字节,不能只做右移。
  • Java 的 long 与 Go 的 uint32 都能完整保存 0..4294967295,拼接过程中不会丢失最高位。

易错点总结

[!yellow]

  • 字节顺序反了:第一段应放在最高 8 位;从右向左累加会得到颠倒的地址。
  • Java 使用 int:255.255.255.255 会溢出成 -1,正确的无符号值是 4294967295。
  • Java 写成 split("."):点号会按正则中的任意字符匹配,无法得到四段。
  • 反向只右移、不掩码:中间两段会混入更高字节,得到远大于 255 的数。
  • 移位漏掉 0:只处理 24、16、8 会缺少最后一段。
  • 直接用于非法输入:段数和段值范围是上述推导的前提;Go 中忽略解析错误也依赖输入已经合法的约定。

相似题目

题目 难度 关联与区别
751. IP 到 CIDR 中等 IPv4转整数后可继续按二进制边界划分CIDR地址块,本题只做双向转换。
468. 验证IP地址 中等 原题负责格式合法性判断,本题约定规范合法IPv4输入,转换不等于同时实现IPv6校验。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/70714196
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!