笔试算法题
〇、设计模式
六大设计原则:
开闭原则(扩展开放,修改关闭),单一职责原则(每个类的职责单一)、迪米特原则(最少知道原则,模块相对独立)、里氏代换原则(父类能用的,子类都能用)、接口隔离原则(多个好于一个,解耦)、依赖倒置原则(依赖于抽象,但不依赖于具体)
二十三种设计模式:
创建型模式:
单例模式、建造者模式、工厂方法模式、简单工厂模式、抽象工厂模式、原型模式
结构性模式:
代理模式、组合模式、适配器模式、装饰者模式、享元模式、外观模式、桥接模式
行为型模式:
策略模式、状态模式、责任链模式、观察者模式、模板方法模式、迭代器模式、备忘录模式、访问者模式、中介者模式、解释器模式、命令模式
OO、数据结构
时间复杂度 T(n)=f(n),O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
f(n)=运行次数
12 O(1) 常数阶,不涉及循环,时间的消耗不会由某个变量增加而变长
log2n+3 O(logn) 对数阶
2n+3,for 0-n O(n) 线性阶
n+nlog2n+3 O(nlogn) nlogn阶
3n^2+2n O(n^2) 平方阶
n^3 O(n^3) 立方阶
n^k O(n^k) n方阶
2^n 0(2^n) 指数阶
1、数组
//常规使用
int[] arr = new int[3];
int[] arr = {1,2,3}
2、链表
//常规使用
LinkedList<String> list = new LinkedList<>();
list.add("b");
list.set(0,"a");
String a = list.get(0);
linkedList.contains("b");
linkedList.remove("b");
3、队列
//常规使用,FIFO
Queue<String> queue = new LinkedListed<>();
queue.offer("a");
queue.offer("b");
queue.offer("c");
queue.offer("d");
queue.poll();//a
//获取队首元素
queue.element();//b
queue.peek();//b
queue.poll();//b
queue.poll();//c
4、双向队列
//常规使用
Deque<Integer> deque=new LinkedList<>();
deque.offer(122);//尾部入队,区别在于如果失败了返回false
deque.poll(122);//尾删除,失败返回false
deque.peek();//获取队尾元素
deque.add(1);//头插入,失败抛异常
deque.remove();//头删除,失败抛异常
deque.element();//获取队首元素
5、栈
//常规使用
Deque<Integer> stack = new ArrayDeque<Integer>();
stack.push(12);//入栈
int tail = stack.pop();//出栈
tail = stack.peek();//获取栈顶元素
6、二叉树
OOO、算法
1、链表反转
public static Node reverseNode(Node node){
Node pre=null;
Node next=null;
while(node != null){
next = node.getNext();//先存储下一个节点
node.setNext(pre);//当前节点的下个节点为null
pre = node;//当前节点赋给pre
node = next;//当前节点的下一个赋给当前节点node
}
return pre;
}
2、链表指定区间反转
public static Node reverseNodeOfInterval(Node node,int m,int n){
Node nodeNew = new Node(0);
nodeNew.setNext(node);
Node curNode = nodeNew;
Node nextNode = node;
for (int i=0;i<m-1;i++){
curNode = nextNode;
nextNode = nextNode.getNext();
}
for(int i=0;i<n-m;i++){
Node temp = nextNode.getNext();//永远把下一节点的下一个节点作为temp节点
nextNode.setNext(temp.getNext());//下一节点的下一个为temp节点的下一个,跳过temp
temp.setNext(curNode.getNext());//temp的下一个是当前节点的下一个,即反转后第一个数
curNode.setNext(temp);//把temp当反转后的第一个数,并接在当前节点后面
}
return nodeNew.getNext();
}
一、单例模式
1、懒汉
//常规:懒汉,线程不安全的(不支持多线程)
public class Singleton {
//1构造函数私有化
private Singleton(){}
private static Singleton singleton = null;
//2构建实例化方法
public static Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
/**
*
* 懒汉式(懒加载),支持多线程
* 优点:第一次调用才初始化,避免内存浪费。
* 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
*/
public class Singleton {
private Singleton(){}
private static Singleton singleton= null;
public static synchronized Singleton getSingleton(){
if(singleton != null){
singleton = new Singleton();
}
return singleton;
}
}
2、饿汉
/**
*
* 饿汉式
* 这种方式比较常用,但容易产生垃圾对象。
* 优点:没有加锁,执行效率会提高。
* 缺点:类加载时就初始化,浪费内存
*/
public class Singleton {
private Singleton(){}
private static Singleton singleton = new Singleton();
public static Singleton getSingleton(){
return singleton;
}
}
二、排序算法
1、冒泡排序
/**
每一轮挑选最大的放最右边,固定
fori=0 length-1
forj=0 length-1-i
*/
public class bubbleSort {
/**
* 冒泡法排序
* @param array 数组
*/
public static void bubbleSort(int[] array) {
// 临时变量
int temp;
// 控制比较轮次,一共 n-1 趟
for (int i = 0; i < array.length - 1; i++) {
// 上次遍历之后倒数第 i 位开始 已经是有序的了,所以这次遍历的范围是 [0,array.length - 1 - i)
for (int j = 0; j < array.length - 1 - i; j++) {
// 如果前面的数比后面的数大,则交换
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
}
2、选择排序
/*
选择排序:第一个和所有的比最小,一轮下来固定最小
fori length-1
forj=i+1 length
*/
public class choiceSort {
/**
* 选择排序 从小到大排序
* 时间复杂度O(n^2)
* @param array array
*/
public static void selectSort(int[] array) {
for (int i = 0; i < array.length -1 ; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i] > array[j]) { // 说明假定的最小值并不是最小,交换
int temp=array[i];
array[i] = array[j]; // 重置最小值
array[j] = temp; // 重置最小值的索引
}
}
}
}
}
三、生产者和消费者
1、线程之间的通信问题:object对象的wait、notifyAll
//管程法实现生产者消费者
public class CompanyProducts {
Product[] goods = new Product[10];
int count = 0;//计数器
//生产者生产放入缓冲区
public synchronized void produce(Product good){
while(count == goods.length){//缓冲区满
try {
this.wait();//生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
goods[count] = good;
count++;
this.notifyAll();//唤醒等待的线程
}
//消费者消费产品
public synchronized Product consume(){
while(count == 0){//缓存区为空
try {
this.wait();//消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费者消费:没空,消费商品
count--;//长度是10,数组0-9
Product good = goods[count];
this.notifyAll();//唤醒等待的线程
return good;
}
}
测试:
public class TestPC {
public static void main(String[] args) {
CompanyProducts products = new CompanyProducts();
for (int i = 0; i < 40; i++) {
int finalI = i+1;
new Thread(()->{
products.produce(new Product(finalI));
System.out.println(Thread.currentThread().getName()+"->生产:第"+ finalI +"个商品");
},"商店").start();
}
for (int i = 0; i < 20; i++) {
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"->消费了:第"+ products.consume().getId()+"个商品");
},"顾客A").start();
}
for (int i = 0; i < 20; i++) {
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"->消费了:第"+ products.consume().getId()+"个商品");
},"顾客B").start();
}
}
}
/*
真正的多线程开发,降低耦合性
线程就是一个单独的资源类,没有任何附属的操作!!!
1、属性、方法
*/
public class SaleTicket {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入接口
Ticket_synchronized ticket = new Ticket_synchronized();
//jdk1.8 lamdba表达式 (参数)->{代码}
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类
class Ticket_synchronized{
private int number = 50;
/*
synchronized 本质就是排队
*/
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出第"+number--+"票,剩余:"+number);
}
}
}
2、线程之间的通信问题:condition对象的await、signalAll
/*
JUC Locked
线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
线程交替执行 A、B操作同一变量 num=0
A num++
B num--
*/
public class ProducterAndConsumer_JUC {
public static void main(String[] args) {
Data_JUC data = new Data_JUC();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
//C、D虚假唤醒问题:if->while
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待,业务,通知
class Data_JUC{
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//AC业务排序区分
public void increment() throws InterruptedException {
lock.lock();//不属于业务
try{
while(num !=0){//等于0干活
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,+1完毕
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();//不属于业务
try {
while(num == 0){//不等于0干活
//等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我-1完毕了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
四、死锁
public class DeadLock {
public static void main(String[] args) {
Makeup her1=new Makeup(0,"灰姑娘");
Makeup her2=new Makeup(1,"⽩雪公主");
new Thread(her1).start();
new Thread(her2).start();
}
}
class Lipstick{
}
class Mirror{
}
//资源类
class Makeup extends Thread{
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice;
String girlName;
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
public void run(){
try{
makeup();
}catch(InterruptedException e){
e.printStackTrace();
}
}
private void makeup()throws InterruptedException {
if(choice==0){
synchronized(lipstick){
System.out.println(this.girlName+"获得⼝红的锁");
Thread.sleep(1000);
synchronized(mirror){
System.out.println(this.girlName+"获得镜⼦的锁");
}
}
} else{
synchronized(mirror){
System.out.println(this.girlName+"获得镜⼦的锁");
Thread.sleep(2000);
synchronized(lipstick){
System.out.println(this.girlName+"获得⼝红的锁");
}
}
}
}
}
五、华为机试
ps:排序,字符串,数组,递归,循环,栈,滑动窗口最大值,成绩统计,日志按时间排序,数组整数对求最小值,机器人走迷宫,仿LISP运算,找城市(树和子树)
1、字符串最后一个单词的长度
描述:计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。(注:字符串末尾不以空格为结尾)
核心:字符串截取后获取最后一个字符串的长度
核心代码:
str.split(" ");
s[s.length-1].length();
扩展:字符串分割用正则
String[] sp = text.split("[^a-zA-Z]+");
import java.util.Scanner;
//思路:字符串截取后长度-1的数组位置
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
String[] s = str.split(" ");
int length = s[s.length - 1].length();
System.out.println(length);
}
}
考察:字符串的最后一个,字符串的截取
2、计算某字符出现次数
描述:接受一个由字母、数字和空格组成的字符串,和一个字符,然后输出输入字符串中该字符的出现次数。(不区分大小写字母)
举一反三:
十进制转二进制字符串后1的个数
Integer.toBinaryString(5);
十六进制转十进制
描述:0xAA -> 10*16^0+10*16^1=170
核心代码:Integer.decode("0xAA");
扩展:
十进制转二进制字符串:Integer.toBinaryString(5);
四舍五入:Math.round(4.4); Math.round(4.5);
数组排序:Arrays.sort(arr);
十进制转n进制:
核心:str.charAt(m%n); M/=N;
import java.util.*;
public class Solution {
public String solve (int M, int N) {
if(M == 0) return "0";
boolean flag = true;
if(M < 0){
M = - M;
flag = false;
}
StringBuffer res = new StringBuffer();
String hex = "0123456789ABCDEF";
while(M != 0){
res.append(hex.charAt(M % N));
M = M / N;
}
return flag == true ? res.reverse().toString() : "-"+res.reverse().toString();
}
}
import java.util.Scanner;
//思路:大字符串的长度-字符截取后字符串的长度
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String ss = sc.nextLine().toLowerCase();
String s =sc.nextLine().toLowerCase();
String str = ss.replaceAll(s,"");
System.out.println(ss.length() - str.length());
}
}
二进制加减:
add、substact、multiply、divide
BigInteger b1=new BigInteger("10",2);
BigInteger b2=new BigInteger("1",2);
BigInteger c=b1.add(b2);
System.out.println(Integer.toBinaryString(Integer.valueOf(c.toString())));
//加减乘除 最大最小
System.out.println(b1.add(b2)); //加法操作
System.out.println(b1.subtract(b2)); //减法操作
System.out.println(b1.multiply(b2)); //乘法操作
System.out.println(b1.divide(b2)); //除法操作
System.out.println(b1.max(b2)); //较大数
System.out.println(b1.min(b2)); //较小数
import java.util.Scanner;
import java.util.TreeSet;
import java.util.Set;
import java.util.Iterator;
public class Main{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
//每次输入
while(scan.hasNext()){
int n = scan.nextInt();
//去重排序
Set set = new TreeSet();
for(int i=0;i<n;i++){
set.add(scan.nextInt());
}
//遍历
Iterator it = set.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
}
4、字符串分割
描述:按长度为8拆分,不够用0补位
核心:不满足8的整数倍补8个0,再依次截取8的区间
核心代码:
s+="00000000";
s.substring(i*8,i*8+8);
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
String s = sc.nextLine();
if(s.length()%8 !=0 )
s = s + "00000000";
int n = s.length()/8;
for(int i=0;i<n;i++){
System.out.println(s.substring(i*8, i*8+8));
}
}
}
}
6、反向提取不重复的整数
描述:987673 >> 37689
核心:字符串反转;set去重;遍历
核心代码:
str.reverse();
if(set.add(str.substring(i,i+1)))
System.out.println(str.substring(i,i+1));
扩展:(1)数组反转
String ss = "i am a boy";
String[] arr = ss.split(" ");
for(int i=arr.length-1;i>=0;i--){
System.out.print(arr[i]+" ");
}
(2)字符全反
new StringBuilder(str).reverse().toString()
import java.util.Scanner;
import java.util.Set;
import java.util.HashSet;
public class Main{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
String str = scan.nextLine();
StringBuffer sb = new StringBuffer(str);
sb.reverse();
Set set = new HashSet();
for(int i = 0;i<sb.length();i++){
String s = sb.substring(i,i+1);
if(set.add(s)){
System.out.print(s);
}
}
}
}
7、质数因子
描述:输入一个数,输出这个数所有的因子
核心:因子范围【2,数的平方根】;取余没有余数即整除;最后判断数是否为1
核心代码:
for i=2;i<=Math.sqrt(data);
while(data % i == 0){
System.out.println(i+" ");
data /= i;
}
if(data != 1){
System.out.println(data);
}
扩展:此题不能用位运算代替,位运算代替时,除数一定要为2^N
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int data = sc.nextInt();
int ms = (int)(Math.sqrt(data)+1);
for (int i = 2; i < ms; i++) {
while (data % i == 0) {
System.out.print(i + " ");
data = data / i;
}
}
if(data != 1){
System.out.println(data+"");
}
}
}
8、递归求最小公倍数
核心:两数乘积÷最大公约数
核心代码:最大公约数(辗转相除法)
private static int gys(int a, int b) {
if (b == 0) return a;
return gys(b, a % b);
}
import java.util.Scanner;
public class Main {
/**
* 求最大公约数:辗转相除法
*/
private static int gys(int a, int b) {
if (b == 0) return a;
return gys(b, a % b);
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int a = in.nextInt();
int b = in.nextInt();
int gysNum = gys(a, b);
System.out.println(a*b/gysNum);
}
}
9、数组分组:递归求可能满足条件的情况
描述:被3整除分一组(不包括5的倍数),被5整除分一组,其余随意分配,两组数组和相同
核心:3、5的数组累加,然后和其余的数组分别求值
import java.util.Scanner;
public class Main {
public static void main(String args[]){
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int n = scanner.nextInt();
int[] nums = new int[n];
int index = 0,sum1 = 0,sum2 = 0;
for(int i=0;i<n;i++){
int tmp = scanner.nextInt();
if(tmp % 5 == 0) sum1 += tmp;
else if (tmp % 3 == 0) sum2 += tmp;
else nums[index++] = tmp;
}
System.out.println(isExists(sum1, sum2, nums, 0));
}
scanner.close();
}
public static boolean isExists(int sum1,int sum2,int[] nums,int index){
if(index == nums.length && sum1 != sum2) return false;
if(index == nums.length && sum1 == sum2) return true;
if(index < nums.length) return isExists(sum1+nums[index], sum2, nums, index+1) || isExists(sum1, sum2+nums[index], nums, index+1);
return false;
}
}
10、密码合格性:数组+字符串
要求:length>8;满足大小写、数字、其他字符任意3种;最大重复子串不超过2
核心: Pattern.compile("[a-z]").matcher(str).find(); //正则表达式
s.substring(i+3).contains(s.substring(i,i+3));//最大重复子串判断 contains
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
String str=sc.nextLine();
if(str.length()>8 && regex(str) && !repeat(str))
System.out.println("OK");
else
System.out.println("NG");
}
}
public static boolean regex(String s){
int count=0;
String[] str={"[a-z]","[A-Z]","[0-9]","[^a-zA-Z0-9]"};
for(int i=0;i<str.length;i++){
Pattern p=Pattern.compile(str[i]);
Matcher m=p.matcher(s);
if(m.find())
count++;
}
return count>=3;
}
public static boolean repeat(String s){
for(int i=0;i<s.length()-3;i++){
String str = s.substring(i+3);
String str2 = s.substring(i,i+3);
if(str.contains(str2))
return true;
}
return false;
}
}
11、字符串排序
规则:a-z不区分大小写;大小写按顺序来;非字母位置不变
核心:字符串append,遍历A-Z(+32a-z);for循环字符串把非字母元素补上
a-z 97,122 A-Z 65,90
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
String str = sc.nextLine();
char [] cha = str.toCharArray();
StringBuffer sb = new StringBuffer();
for (int i = 0; i<26; i++){
char c = (char)(i + 'A');
for (int j = 0; j<str.length(); j++){
//同一个大小写同时存在按照顺序插入
if (cha[j] == c || cha[j] == (char)(c + 32))
sb.append(cha[j]);
}
}
for (int k = 0; k<str.length(); k++){
if ((cha[k] < 'A' || cha[k] > 'Z') && (cha[k] < 'a' || cha[k] > 'z'))
sb.insert(k, cha[k]);
}
System.out.println(sb.toString());
}
}
}
1*、坐标移动
描述:输入字符串读取坐标移动,最后输出坐标(0,0)>> A10S20W10D30 >> (0,10)
开发一个坐标计算工具, A表示向左移动,D表示向右移动,W表示向上移动,S表示向下移动。从(0,0)点开始移动,从一些坐标,并将最终输入结果输出到输出文件里面。
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
//思路:方向,步长(合理校验)
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String str = in.nextLine();
int x=0,y=0;//x表横坐标,y代表纵坐标
String[] ss = str.split(";");
String direction;//方向
for(String s :ss){
if (s.equals("")||s==null){
continue;
}
direction = s.substring(0,1);//WSAD方向
String step = s.substring(1);//步数,需要校验
if(Pattern.compile(".*[a-zA-Z]+.*").matcher(step).matches()){
continue;
}
switch(direction){
case "A"://横坐标左移
x-=Integer.parseInt(step);
break;
case "D"://横坐标右移
x+=Integer.parseInt(step);
break;
case "S"://纵坐标下移
y-=Integer.parseInt(step);
break;
case "W"://纵坐标上移
y+=Integer.parseInt(step);
break;
default:
break;
}
}
System.out.println(x+","+y);
}
}
2*、识别有效的IP地址和掩码并进行分类统计:ABCDENI
请解析IP地址和对应的掩码,进行分类识别。要求按照A/B/C/D/E类地址归类,不合法的地址和掩码单独归类。
所有的IP地址划分为 A,B,C,D,E五类
A类地址从1.0.0.0到126.255.255.255;
B类地址从128.0.0.0到191.255.255.255;
C类地址从192.0.0.0到223.255.255.255;
D类地址从224.0.0.0到239.255.255.255;
E类地址从240.0.0.0到255.255.255.255
私网IP范围是:
从10.0.0.0到10.255.255.255
从172.16.0.0到172.31.255.255
从192.168.0.0到192.168.255.255
import java.util.*;
//思路:分别统计ABCDENI的数量,T存储临时变量
//要求:~拆分ip地址与子网掩码;校验子网掩码合法性;校验ip地址合法性;统计非法ip地址count
public class Main {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("A",0);//A类地址,网络地址最高位必须为0,由网络地址(1B)和主机地址(3B)组成。[1,126]
map.put("B",0);//B类地址,网络地址最高位必须为10,由网络地址(2B)和主机地址(2B)组成。[128,191]
map.put("C",0);//C类地址,网络地址最高位必须为110,由网络地址(3B)和主机地址(1B)组成。[192,223]
map.put("D",0);//D类地址,网络地址最高位必须为1110,不分网络地址和主机地址。[224,239]
map.put("E",0);//E类地址,网络地址最高位必须为11110,不分网络地址和主机地址。[240,255]
/*
私网地址:
10.0.0.0~10.255.255.255 A类私有IP
172.16.0.0~172.31.255.255 B类私有IP
192.168.0.0~192.168.255.255 C类私有IP
*/
map.put("N",0);//私有ip地址,ABC各一个
map.put("I",0);//不合法的ip地址。
map.put("T",0);//不合法的ip地址。
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
String str = sc.next();
String[] arr = str.split("~");
String ip = arr[0];
String subnet = arr[1];
if(!isLegalIP(ip) || !isLegalSubnet(subnet)){
if(!isException(ip)){//排除第一个网络字节是0或127的
map.put("I",map.get("I")+1);
}
}else{
if(isPrivateIp(ip)){
map.put("N",map.get("N")+1);
}
String type = getTypeABCDE(ip);
map.put(type,map.get(type)+1);
}
}
System.out.println(map.get("A")+" "+map.get("B")+" "+map.get("C")+" "+map.get("D")+" "+map.get("E")+" "
+
map.get("I")+" "+map.get("N"));
}
//是否是合法ip
public static boolean isLegalIP(String ip){
String[] arr = ip.split("\\.");
if(arr.length != 4){//4B
return false;
}
for(int i = 0; i < 4;i++){
int num = Integer.parseInt(arr[i]);
if(num < 0 || num > 255){//长度限制[1,255]
return false;
}
}
return true;
}
//是否是合法掩码
public static boolean isLegalSubnet(String subnet){
if(!isLegalIP(subnet)){
return false;
}
String[] arr = subnet.split("\\.");
String binarySunbet = "";
for(int i = 0; i < 4;i++){
int num = Integer.parseInt(arr[i]);
String str = Integer.toBinaryString(num);
while(str.length() < 8){
str = "0"+str;
}
binarySunbet += str;
}
if(!binarySunbet.contains("10")|| binarySunbet.contains("01")){
return false;
}else{
return true;
}
}
//是否是私网IP
public static boolean isPrivateIp(String ip){
String[] arr = ip.split("\\.");
if(Integer.parseInt(arr[0]) == 10){//ip第一个字节=10
return true;
}
//ip第1个字节172,ip第二个字节[16,31]
if(Integer.parseInt(arr[0]) == 172 && 16 <= Integer.parseInt(arr[1]) && Integer.parseInt(arr[1]) <=31){
return true;
}
//ip第1个字节192,ip第二个字节168
if(Integer.parseInt(arr[0]) == 192 && Integer.parseInt(arr[1]) == 168){
return true;
}
return false;
}
//判断属于哪一类IP
public static String getTypeABCDE(String ip){
String[] arr = ip.split("\\.");
if(1 <= Integer.parseInt(arr[0]) && Integer.parseInt(arr[0]) <=126){
return "A";
}
if(128 <= Integer.parseInt(arr[0]) && Integer.parseInt(arr[0]) <192){
return "B";
}
if(192 <= Integer.parseInt(arr[0]) && Integer.parseInt(arr[0]) <224){
return "C";
}
if(224 <= Integer.parseInt(arr[0]) && Integer.parseInt(arr[0]) <240){
return "D";
}if(240 <= Integer.parseInt(arr[0]) && Integer.parseInt(arr[0]) <=255){
return "E";
}
return "T";
}
//判断是否是例外,计数忽略
public static boolean isException(String ip){
if(!isLegalIP(ip)){
return false;
}
String[] arr = ip.split("\\.");
if(Integer.parseInt(arr[0]) == 0 || Integer.parseInt(arr[0]) == 127){
return true;
}else{
return false;
}
}
}