剑指offer-字符替换

265 阅读4分钟

题目

将字符串中每个空格替换成 "%20"

网络编程中,如果URL参数中含有特殊字符,如空格,"#"等,可能导致服务器端无法获得正确的参数。需要将这些特殊符号转换成服务器可以识别的字符串,转换规则:'%'后面跟上ACSII码的两位十六进制的表示
空格 ACSII码是32,十六进制 0x20,转换为 '%20'
#   ACSII码是35,十六进制 0x23,转换为 '%23'

方案一:使用字符串拼接

原理

逐个复制字符串的字符信息,遇空格替换

步骤

  1. 定义一个新的字符串变量信息
  2. 循环遍历目标字符串,遇到空格替换
    private static String replaceBlank(String originStr) {
        if (Objects.isNull(originStr) || originStr.length() == 0) {
            return null;
        }

        StringBuilder builder = new StringBuilder();
        int length = originStr.length();
        for (int index = 0; index < length; index++) {
            Character character = originStr.charAt(index);
            if (!Objects.equals(character,' ')){
                builder.append(character);

            }else {
                builder.append("%").append("20");
            }
        }
        return builder.toString();
    }

优缺点

优点:方案设计简单,时间复杂度为O(n) 缺点: StringBuilder 内部是一个可变长度数组,在拼接过程中多次扩容,对原有字节数据信息复制,对内存扩容,释放,效率降低

方案二:定义固定大小字符数组转换

原理

根据需要替换的空格数量,定义一个长度恰好的字节数组,按照原字符串进行字符复制,遇空格替换

步骤

  1. 根据原有字符串查询空格数量 n
  2. 定义新的字节数组 长度 = 原有字符串长度 + 2*n
  3. 循环原始字符串字节信息,遇空格替换


    private static String replaceBlankUseArray(String originStr) {
        if (Objects.isNull(originStr) || originStr.length() == 0) {
            return null;
        }

        int originLength = originStr.length();
        int blankCount = 0;
        for (int index = 0; index < originLength; index++) {
            Character indexCharacter = originStr.charAt(index);
            if (Objects.equals(indexCharacter, ' ')) {
                blankCount++;
            }
        }

        int newLength = originLength + blankCount * 2;
        char[] newStrArray = new char[newLength];
        int newStrStartIndex = 0;
        for (int index = 0; index < originLength; index++) {
            char indexCharacter = originStr.charAt(index);
            if (indexCharacter != ' ') {
                newStrArray[newStrStartIndex] = indexCharacter;
            } else {
                newStrArray[newStrStartIndex] = '%';
                newStrArray[++newStrStartIndex] = '2';
                newStrArray[++newStrStartIndex] = '0';
            }
            //新数组复制完之后,指向下一个需要处理的位置
            newStrStartIndex++;
        }
        return new String(newStrArray);
    }

优缺点

优点:提前定义好需要的字节数组长度,避免后续扩容

题目变形

现有字符数组,从头开始存储字符,遇到NULL时结束。要求:在原有数组上操作,将空格替换成 "%20"

题目分析

w h o a r e y o u NULL NULL
  1. 数组中存储连续字符信息
  2. 连续字符下一位是NULL时,表示连续字符终结
  3. 要在原有的字符数组进行替换操作,不能新定义存储对象
  4. 需要考虑无NULL对象情况,即连续字符的最后一位数据就是数组的最后一位数据

方案一:从始到终替换

原理

定义原有数组开始角标P,由P往后遍历,遇非空格字符不处理,遇空字符时,其后所有字符信息往后移动两位,填充'%20'

步骤

  1. 从字符数组头部开始,逐个查询每个字节信息
  2. 遇到空字串时,将空字串后面的数据往后移动两位
  3. 对空字串进行替换
  4. 循环角标为数组最大长度-1或角标对应数据为NULL时结束

注意 需要判断数组长度是否能够容纳替换后的数据

  

    private static Character[] replaceArrayBlank(Character[] targetCharacters) {
        if (Objects.isNull(targetCharacters) || targetCharacters.length == 0) {
            return null;
        }
        int originStrLength = 0;

        //统计空格数量
        int blankCount = 0;
        for (Character targetCharacter : targetCharacters) {
            if (Objects.isNull(targetCharacter)) {
                break;
            }
            originStrLength++;
            if (Objects.equals(targetCharacter, ' ')) {
                blankCount++;
            }
        }

        int targetLength = originStrLength + 2 * blankCount;
        //说明无空格
        if (targetLength == originStrLength) {
            return targetCharacters;
        }

        //原始数组不能进行完成替换
        if (targetLength > targetCharacters.length) {
            return null;
        }

        //已经处理的空格数量
        int handleCount = 0;
        for (int index = 0; index < targetLength; index++) {

            //如果已经处理的空格数量与统计的空格数量一致,已经不需要进行处理
            if (handleCount == blankCount) {
                break;
            }

            Character character = targetCharacters[index];
            if (Objects.isNull(character)) {
                break;
            }

            if (!Objects.equals(character, ' ')) {
                continue;
            }

            //不为空字符最后位置
            int originEndIndex = originStrLength + handleCount * 2 - 1;

            //向后移动两位
            for (int i = originEndIndex; i > index; i--) {
                targetCharacters[i + 2] = targetCharacters[i];
            }

            targetCharacters[index] = '%';
            targetCharacters[++index] = '2';
            targetCharacters[++index] = '0';

            handleCount ++ ;
        }
        return targetCharacters;
    }



缺点

  1. 如果有多个空格,部分字符需要进行多次移动

由终到始进行替换

原理

对数组不为空数据进行由终到始循环,遇到不为空格数据,拷贝到相应位置,遇到空格数据,相应位置填充"%20"

步骤

  1. 定义两个指向角标信息,一个指向原数组最后一个字节位置P1,一个指向转换后最后一个角标位置P2
  2. 对P1循环,遇字符,复制到P2指向位置,遇空格,P2填充 '%20'信息
  3. P1,P2重合,表示空格已经替换完毕

    private static Character[] endToStartRepalce(Character[] targetCharacters){
        if (Objects.isNull(targetCharacters) || targetCharacters.length == 0) {
            return null;
        }
        int originStrLength = 0;

        //统计空格数量
        int blankCount = 0;
        for (Character targetCharacter : targetCharacters) {
            if (Objects.isNull(targetCharacter)) {
                break;
            }
            originStrLength++;
            if (Objects.equals(targetCharacter, ' ')) {
                blankCount++;
            }
        }

        int targetLength = originStrLength + 2 * blankCount;
        //说明无空格
        if (targetLength == originStrLength) {
            return targetCharacters;
        }

        int targetEndIndex = targetLength - 1;

        for (int index = originStrLength - 1; index >= 0 && index < targetEndIndex ; index--) {
            Character character = targetCharacters[index];
            if (!Objects.equals(character,' ')){
                targetCharacters[targetEndIndex] = character;
            }else {
                targetCharacters[targetEndIndex] = '0';
                targetCharacters[--targetEndIndex] = '2';
                targetCharacters[--targetEndIndex] = '%';
            }
            targetEndIndex --;

        }
        return targetCharacters;
    }

优点

  1. 避免字符重复复制