2025年4月6日 星期日 乙巳(蛇)年 正月初七 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > 数据结构与算法

程序员的算法趣题Q40: 优雅的IP地址

时间:12-30来源:作者:点击数:44

1. 问题描述

IPv4 中的 IP 地址是二进制的 32 位数值。不过,这样的数值对我们人类而言可读性比较差,所以我们通常会以 8 位为 1 组分割,用类似 192.168.1.2 这种十进制数来表示它(图 1)。

图 1 IP 地址(IPv4)

这里,我们思考一下十进制数 0~9 这 10 个数字各出现 1 次的 IP 地址(像正常情况一样,省略每组数字首位的 0。也就是说,不能像 192.168.001.002 这样表示,而要像 192.168.1.2 这样来表示)。

问题:求用二进制数表示上述形式的 IP 地址时,能使二进制数左右对称的 IP 地址的个数(用二进制数表示时不省略 0,用完整的 32 位数表示)。

2. 解题分析

2.1 笨办法

10个数共有10!种排列。IPv4的第一个字段允许为0吗?如果不允许的话,则应该是9*9!。注意,题中的要求是省略每组数字首位的 0,但是单独的0至少对于后面几个字段是允许的。这里先假定第一个字段也允许为0,反正只是定性的分析,差异不大。

将每种排列分割成4段,相当于在9个位置种插入3个分割点。如果第一个数是0,则0后面必须放一个分割点,则总共有C(8,2)种分割点放置方式;如果第一个数不是0,则分割必须不出现在0的前面,因此总共有C(8,3)种分割点放置方式。综合考虑有C(8,2)+9*C(8,3)种分割点放置方式。

然后再进一步去判断这每一种分割方式所生成的IP是否符合题设的要求(每个字段必须在[0,255]之间,且转换成32比特二进制表示后左右对称)。

因此总共需要检查(10!)*( C(8,2)+9*C(8,3))= 1930521600种可能的IP。这是一个巨大的数字,有没有办法削减搜索范围?

2.2 逆向思考

IPv4的每个字段为8比特,十进制表示范围是从0~255。以A、B、C、D分别表示第1、2、3、4个字段的十进制表示的话,如果使得整体用32比特表示的二进制为对称的话,则必然A和D、B和C作为8比特的二进制表示分别构成对称对(顺便说一下,原书的提示不知道是不是翻译的问题,说的非常含糊容易引起误导)。因此,事实上只要对两个8比特数的组合进行扫描,对每一种组合在进一步判断十进制表示是不是刚好包含0~9各1个的10个数字。共有256*256=65536种可能的组合,这个组合数远远地小于上一节的方案。

以下代码按照这种思路来实现。其中有一些小巧的细节,说明如下:

(1) 十进制数转换成二进制数用bin(),但是bin()的输出前面包含’0b’前缀。所以后跟[2:]去除这个前缀。如下所示:

  • Abin = bin(A)[2:]

(2) 求对称的二进制序列。由于bin()的输出可能不满8比特长。但是在判断二进制序列是否相互对称时是考虑双方都是8比特(不足8比特的先头补0),因此在求与A互补的D的二进制表示时首先用Abin[::-1]取Abin的reverse,然后再在尾部根据需要补0(因为A是头部补0)。如下所示:

  • Dbin = Abin[::-1] + (8-len(Abin))*'0'

(3) 将二进制转换成十进制,int()函数有参数只是原字符串的进制数,二进制的话则设为2.如下所示:

  • D    = int(Dbin,2)

(4) 判断是否是刚好0~9各包含一个的10个数。将A、B、C、D变成字符串然后串联起来,然后再传换成set(因为set的元素是unique的,自动剔除重复的元素),因此最后只要判断该set的长度是不是10即可。如下所示:

  • len(set(combinedStr)) == 10

3. 代码及测试

  • # -*- coding: utf-8 -*-
  • """
  • Created on Fri Sep 3 07:25:27 2021
  • @author: chenxy
  • """
  • import sys
  • import time
  • import datetime
  • import math
  • # import random
  • from typing import List
  • # from queue import Queue
  • # from collections import deque
  • class Solution:
  • def elegantIP(self) -> int:
  • """
  • :ret: The total number of IP satisfying the requirement
  • """
  • nums = 0
  • rsltLst = []
  • for A in range(256):
  • for B in range(256):
  • Abin = bin(A)[2:]
  • Bbin = bin(B)[2:]
  • Dbin = Abin[::-1] + (8-len(Abin))*'0'
  • Cbin = Bbin[::-1] + (8-len(Bbin))*'0'
  • D = int(Dbin,2)
  • C = int(Cbin,2)
  • combinedStr = str(A)+str(B)+str(C)+str(D)
  • if len(set(combinedStr)) == 10:
  • nums = nums + 1
  • rsltLst.append((A,B,C,D))
  • return nums, rsltLst
  • if __name__ == '__main__':
  • sln = Solution()
  • tStart = time.time()
  • nums, rsltLst = sln.elegantIP()
  • tCost = time.time() - tStart
  • print('nums={0}, tCost = {1:6.3f}(sec)'.format(nums,tCost))
  • for item in rsltLst:
  • print('{0}'.format(item))

运行结果:

nums=8, tCost = 0.152(sec)

(34, 179, 205, 68)

(34, 205, 179, 68)

(68, 179, 205, 34)

(68, 205, 179, 34)

(179, 34, 68, 205)

(179, 68, 34, 205)

(205, 34, 68, 179)

(205, 68, 34, 179)

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门