记录字符次数

最近看到一道挺好玩的题目

写一个函数用来统计字符串中各字母出现的次数。(编程语言不限)
示例:
s1输入:X2Y3XZ,输出:X3Y3Z1;
s2输入:Z3X(XY)2,输出:X3Y2Z3;
s3输入:Z4(Y2(XZ2)3)2X2,输出:X8Y4Z16;

给的题目就上面这两行,再说一下这个题目

1.字符串中出现的字母只能是单字母

2.字符串中出现的括号符合乘法结合律

首先,当然是先去网上找程序啦,不要重复造轮子。网上找到的程序如下:

    public static String countLetters(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }

        Map<Character, Integer> map = new HashMap<>();
        Stack<Integer> stack = new Stack<>();

        int num = 1;

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (Character.isDigit(c)) { // 数字,入栈
                num = c - '0';
                while (i + 1 < s.length() && Character.isDigit(s.charAt(i + 1))) { // 处理多位数字的情况
                    num = num * 10 + (s.charAt(++i) - '0');
                }
                stack.push(num);
            } else if (c == ')') { // 右括号,出栈并计算
                int multiply = stack.pop();
                num *= multiply;
                char prevChar = s.charAt(i - 1);
                map.put(prevChar, map.getOrDefault(prevChar, 0) + num);
                num = 1;
            } else { // 字母或左括号,直接计数
                if (!stack.isEmpty()) {
                    num *= stack.peek();
                }
                map.put(c, map.getOrDefault(c, 0) + num);
                num = 1;
            }
        }
        StringBuilder sb = new StringBuilder();
        for (char c : map.keySet()) {
            sb.append(c).append(map.get(c));
        }
        return sb.toString();
    }

可惜的是,这个程序没法正常计算,不管了,那我们自己写。

可以看到上面的程序使用了栈和Map,栈用来处理字符串中的括号(乘法结合律),Map用来做统计。这个思路很不错。

算法是对数据结构的处理,因此将字符串的结构做些调整。

1.初始化字符串:有些字母后没有数字(默认为1),要给这些默字母后面补齐数字

2.处理括号:使用栈去解决乘法结合律,将字符串恢复成一个标准字符串,字符串中只有字符和数字,每个字符后都有一个表示数量的数字

3.使用Map遍历字符串,进行统计

以下程序是上述过程的实现:

 public static void counts(String str) {

        System.out.println("原始字符串:" + str);

//        初始化字符串
        str = into(str);
//        处理字符串括号
        str = kuohao(str);

        System.out.println("处理之后的字符串:" + str);

        HashMap<String, Integer> map = new HashMap<String, Integer>();

//        转为数组好处理,数组中偶数位是字母,奇数位是字母对应的数值
        String[] strArray = str.split("");

        for (int i = 0; i < str.length(); i += 2) {
//            如果已经存在健
            if (map.containsKey(strArray[i])) {

//                获取键值,之后累加重新存入
                Integer integer = map.get(strArray[i]);

                map.put(strArray[i], integer + Integer.valueOf(strArray[i + 1]));

            } else {
//                没有存在健,直接存入
                map.put(strArray[i], Integer.valueOf(strArray[i + 1]));
            }
        }

//        遍历map输出
        map.entrySet().stream().forEach(entry -> System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue()));

    }

    //        初始化,将有些字母之后省略的数字给补上,也就是被省略的数字1
    public static String into(String str) {

        String newStr = new String();

//        初始化字符串
        for (int i = 0; i < str.length(); i++) {

            char c = str.charAt(i);

//            如果是数字直接跳
            if (Character.isDigit(c) || c == '(' || c == ')') {
                newStr = newStr + c;
                continue;
            }

//            如果是字母,判断字母之后是否有数字
            if (i + 1 < str.length() && Character.isDigit(str.charAt(i + 1))) {
                newStr = newStr + c;
            } else {
                newStr = newStr + c + "1";
            }

        }

        System.out.println("初始化之后的字符串:" + newStr);
        return newStr;
    }

    //    处理括号
    public static String kuohao(String str) {

        Stack<Character> stack = new Stack<>();
        Stack<Character> stackTemp = new Stack<>();

        String NewStr = "";

        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);


//            如果是左括号,之后括号中的内容入栈,遇到右括号停止
            if (c == '(') {
//                    标识变量退出变量
                int sign = 0;

//                这里有括号嵌套问题,可以使用标识符来解决
                for (int j = i; j < str.length(); j++) {

                    char c1 = str.charAt(j);
                    stack.push(str.charAt(j));

                    if (c1 == '(') {
                        sign++;
                    }

//                    处理括号内容
                    if (c1 == ')') {
                        sign--;

//                        将右括号弹出
                        stack.pop();
//                        获得括号的乘数
                        int chengshu = str.charAt(j + 1) - '0';

                        Character pop;
//                            出栈,并对该括号中的数字乘上乘数,之后再入栈
                        do {
                            pop = stack.pop();

//                            如果出栈的是数字
                            if (Character.isDigit(pop)) {
                                Integer i1 = (pop - '0') * chengshu;
                                stackTemp.push(i1.toString().charAt(0));

                            } else if (Character.isLetter(pop)) {
                                stackTemp.push(pop);
                            }

                        } while (pop != '(');
//                        将左括号弹出
//                        stack.pop();
//                        重新入栈
                        while (!stackTemp.empty()) {
                            Character temp = stackTemp.pop();
                            stack.push(temp);
                        }
//                        省略括号后面的乘数
                        j = j + 1;

//                    退出条件
                        if (sign == 0) {
                            i = j;
                            break;
                        }
                    }
                }
                StringBuilder tempStr = new StringBuilder();

                while (!stack.empty()) {
                    String temp = stack.pop().toString();
                    tempStr = tempStr.append(temp);
                }
//                反转字符串并拼接
                NewStr = NewStr + tempStr.reverse();
            } else {
                NewStr = NewStr + c;
            }
        }
        return NewStr;
    }

这个程序可以完美处理s1的字符串,s2也可以处理,但在s3时,会出现问题。


目前是将一个string字符串的每一位切割,判定是啥,字母则记录,字母之后的数字就是该字母的值。但这样带来一个问题是,当字母后的数值为一位以上时(如10),需要手动去字符串中截取这个完整的数值。目前来说还可以实现

说一下上面这个字符串中截取数值,通过for遍历字符串,转为char判断是否为数字,如果是,继续探测下一个位置,这样继续向后推进,直到遇到不是数值的,那么上面这么多就可以构成一个完整的数字

但这样做有两个问题,
第一是,一个多位数如果牵扯括号运算,将会多次进行这种 遍历,转换,检测,构成数字 这会使程序的可读性大幅下降,并且不太优雅。
第二个问题是,括号运算涉及栈,栈中只能存储char,无法存储多位的数值(只能存1-9),如果非要存储,有两种方法,
        1.将数值分割,每一位作为一个char,存入栈,出栈时又要 遍历,判断,构成,因为是栈,还需要反转数值
        2.将数值的值,所对应的ascll码值的字符存入栈,这个又带来的问题是,栈中的值,无法判断那个是真正的字符,那个是对应的数值,这里可以强制默认一个字符出来后,下一个字符就是该字符对应的值。但这样程序并不强健

方式1太过繁琐,尝试方式2

程序实现如下:

    //        初始化,将有些字母之后省略的数字给补上,也就是被省略的数字1,
//    并将数字转化为对应的ascll码值对应的字符
    public static String intoPro(String str) {

        String newStr = new String();
        String newStr1 = new String();

//        初始化字符串
        for (int i = 0; i < str.length(); i++) {

            char c = str.charAt(i);

//            如果是括号直接跳
            if (Character.isDigit(c) || c == '(' || c == ')') {
                newStr = newStr + c;
                continue;
            }

//            如果是字母,判断字母之后是否有数字
            if (i + 1 < str.length() && Character.isDigit(str.charAt(i + 1))) {
                newStr = newStr + c;
            } else {
                char temp = 1;
                newStr = newStr + c + temp;
            }
        }

//          初始化之后,对字符中的数字处理位char
        for (int i = 0; i < newStr.length(); i++) {

            char c = newStr.charAt(i);

//            如果是数字,首先判断是几位数,之后转为ascll码值
            if (Character.isDigit(c)) {

                String tempInt = String.valueOf(c - '0');

                int j = i + 1;
                for (; j < newStr.length(); j++) {
                    char c1 = newStr.charAt(j);

                    if (Character.isDigit(c1)) {
                        tempInt += c1;
                        i = j;
                    } else {
                        break;
                    }
                }

                int i1 = Integer.parseInt(tempInt);

                char charTemp = (char) i1;

                newStr1 = newStr1 + charTemp;
            } else {
                newStr1 += c;
            }
        }

        System.out.println("初始化之后的字符串(Pro):" + newStr1);
        return newStr1;
    }

    //    处理括号
    public static String kuohaoPro(String str) {

        Stack<Character> stack = new Stack<>();
        Stack<Character> stackTemp = new Stack<>();

        String NewStr = "";

        for (int i = 0; i < str.length(); i++) {

            char c = str.charAt(i);
//            如果是左括号,之后括号中的内容入栈,遇到右括号停止
            if (c == '(') {
//                    标识变量退出变量
                int sign = 0;

//                这里有括号嵌套问题,可以使用标识符来解决
                for (int j = i; j < str.length(); j++) {

                    char c1 = str.charAt(j);
                    stack.push(str.charAt(j));

                    if (c1 == '(') sign++;

//                    处理括号内容
                    if (c1 == ')') {
                        sign--;
//                        将右括号弹出
                        stack.pop();
//                        获得括号的乘数
                        int chengshu = str.charAt(j + 1);

                        Character pop;

                        do {
                            pop = stack.pop();

                            if (pop != '(') {
//                            数字处理
                                int i1 = pop * chengshu;
                                stackTemp.push((char) i1);

//                            数字对应的字母处理
                                pop = stack.pop();
                                stackTemp.push(pop);
                            }
                        } while (pop != '(');
//                        重新入栈
                        while (!stackTemp.empty()) {
                            Character temp = stackTemp.pop();
                            stack.push(temp);
                        }
//                        省略括号后面的乘数
                        j = j + 1;

//                    退出条件
                        if (sign == 0) {
                            i = j;
                            break;
                        }
                    }
                }

                StringBuilder tempStr = new StringBuilder();

                while (!stack.empty()) {

                    String temp = stack.pop().toString();
                    tempStr = tempStr.append(temp);
                }
//                反转字符串并拼接
                NewStr = NewStr + tempStr.reverse();
            } else {
                NewStr = NewStr + c;
            }
        }
        return NewStr;
    }


    public static void countsPro(String str) {

        System.out.println("原始字符串(Pro):" + str);

//        初始化字符串
        str = intoPro(str);
//        处理字符串括号
        str = kuohaoPro(str);

        System.out.println("处理之后的字符串(Pro):" + str);

        HashMap<String, Integer> map = new HashMap<String, Integer>();

//        转为数组好处理,数组中偶数位是字母,奇数位是字母对应的数值
        String[] strArray = str.split("");

        for (int i = 0; i < str.length(); i += 2) {

//            如果已经存在健
            if (map.containsKey(strArray[i])) {

//                获取键值,之后累加重新存入
                Integer integer = map.get(strArray[i]);

                map.put(strArray[i], integer + (int) str.charAt(i + 1));

            } else {
//                没有存在健,直接存入
                map.put(strArray[i], (int) str.charAt(i + 1));
            }
        }

//        遍历map输出
        map.entrySet().stream().forEach(entry -> System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue()));

    }

这个写起来还是十分折磨的,因为在处理括号部分要反复处理索引问题  

上述程序可以完美解决多位数问题,当然,还是有问题的,在测试过程中,遇到两个问题

1.如果在处理字符串时,出现40,41这两个值,程序会出错,因为这两个值在Ascll中表示左右括号

2.单个字符的值,总计不能超过7W,可能是因为Ascll的限制
 

要如何继续改进呢,要怎么样解决多位数问题,

首先,从字符串中截取数值是必须做的,否则无法分离字母和数值。那么后续需要面对多位数的存储问题,如何很好的存储,方便之后的乘法,存取。我能想到的一种方法是,将字符串初始化之后,都存储到map中。但如果所有字母数字对都用Map存储,那么该如何做括号运算呢。因为多个括号嵌套需要记录多个左括号,Map中不能记录相同的键。


解决上述问题,可以尝试在初始化字符串之后,将字符串中的每个字符和对应的值存在map中,这样可以完全解决上述问题但这样又会带来问题,如何去处理字符串中的括号呢,目前的想法是,每个对应括号中的值,放在一个map中,也就是说,最里面的括号中的值,在一个map中,去做完乘法之后,再将这个map的值去复制到外面一层的map中。这个动作,要如何实现呢。

两种破局方式,
1.如果继续上面char模式,可以去记录括号数量,也就是去匹配左右括号,没有被匹配的括号就是char值被转换的,这样其实也有问题
如果恰好出现,数值转换时,同时出现左右括号,这种情况真是没法子了。并且会造成运算混乱
2.继续map模式,如果出现左括号,做一个String数组,将之后的所有东西进入数组(在数字进入数组时,进行多位数判断),之后遇到第一个右括号时,开始将数组索引向前,并且数组东西出来进入map中,直到数组索引遇到第一个左括号,停止。然后继续,重复上述过程,直到数组为空。


public class CountLetter {
    //    初始化
    public static String[] intoProMax(String str) {

        String newStr = new String();
//        初始化字符串
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);

//            如果是括号直接跳
            if (Character.isDigit(c) || c == '(' || c == ')') {
                newStr = newStr + c;
                continue;
            }

//            如果是字母,判断字母之后是否有数字
            if (i + 1 < str.length() && Character.isDigit(str.charAt(i + 1))) {
                newStr = newStr + c;
            } else {
                newStr = newStr + c + "1";
            }
        }

        System.out.println("初始化之后的字符串(ProMax):" + newStr);

//        将初始化之后的字符串切割到一个数组中
        String[] strArray = new String[newStr.length()];

//        数组索引,不用i是因为字符串索引在多位数判断时,会跳跃
        int ArrayIndex = 0;

        for (int i = 0; i < newStr.length(); i++) {
            char c = newStr.charAt(i);

//            如果是字母,左右括号,直接进入数组
            if (Character.isLetter(c) || c == '(' || c == ')') {
                strArray[ArrayIndex] = String.valueOf(c);
                ArrayIndex++;
                continue;
            }

//            如果是数字,截取完整数字之后进入数组
            if (Character.isDigit(c)) {

                String Num = String.valueOf(c);
//                如果已经时最后一个数字了,就不用判断是否是多位数
                if (i == newStr.length() - 1) {
                    strArray[ArrayIndex] = Num;
                    break;
                }

//                从当前数字向后遍历
                for (int j = i + 1; j < newStr.length(); j++) {
                    char c1 = newStr.charAt(j);

//                    如果是数字,添加到临时变量上,并改变外层索引值
                    if (Character.isDigit(c1)) {

                        Num += c1;
                        i = j;
                    } else {
//                        否则将已有数值添加到数组,改变数组索引,结束
                        strArray[ArrayIndex] = Num;
                        ArrayIndex++;
                        break;
                    }

//                    如果字符串已经到了结尾,要将已经截取好的字符串存入数组
                    if(j==newStr.length()-1){
                        strArray[ArrayIndex] = Num;
                    }
                }
            }
        }
        return strArray;
    }

    //    处理括号,并返回处理好的Map
    public static void kuohaoProMax(String str) {

        String[] strArray = intoProMax(str);
        HashMap<Character, Integer> map = new HashMap<>();
        Stack<String> stack = new Stack<>();

        for (int i = 0; i < strArray.length; i += 2) {

            String s = strArray[i];

//            退出条件
            if (s == null) {
                break;
            }

            char c = s.charAt(0);
//            如果是左括号,之后括号中的内容入栈,遇到右括号停止
            if (c == '(') {
//                将左括号压入栈
                stack.push(s);
                HashMap<Character, Integer> mapStack = new HashMap<>();

                for (int j = i + 1; j < strArray.length; j++) {
                    String s2 = strArray[j];

                    if (")".equals(s2)) {
                        String peek = "";
                        do {

                            String value = stack.pop();
                            String key = stack.pop();

                            if (!mapStack.containsKey(key)) {
                                mapStack.put(key.charAt(0), Integer.valueOf(value));
                            } else {
                                mapStack.put(key.charAt(0), mapStack.get(key) + Integer.valueOf(value));
                            }

                            peek = stack.peek();

                        } while (!"(".equals(peek));
//                        将左括号弹出
                        stack.pop();


                        //                        获取乘数
                        Integer NumValue = Integer.valueOf(strArray[j + 1]);
                        j = j + 1;
//                        给每个map值承以乘数
                        mapStack.entrySet().stream().forEach(entry -> {
                            mapStack.put(entry.getKey(), entry.getValue() * NumValue);
                        });

                        if (stack.empty()) {
                            System.out.println("栈Map:");

                            mapStack.entrySet().stream().forEach(entry -> System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue()));

//                            改变i的值,因为j之前的都在栈中处理了,包括j+1的乘数也处理完毕,因此下一次的字符串遍历可以从j+1位置开始
//                            但是设置的for自增规则是每次+2,因为每次会处理一个键值对,因此这里还要减去2
                            i = j + 1 - 2;

//                            将栈Map与统计Map合并
                            mapStack.entrySet().stream().forEach((entry) -> {
                                Character key = entry.getKey();
                                Integer value = entry.getValue();

//                                如果健已经存在,则值相加之后存入
                                if (map.containsKey(key)) {
                                    map.put(key, map.get(key) + value);
                                } else {
                                    map.put(key, value);
                                }
                            });
                            break;
                        }
                    } else {
                        stack.push(s2);
                    }
                }
            } else {
//            如果没有括号,就直接在Map中存储
                Integer value = 1;

//            如果是数字,首先判断是几位数
                value = Integer.valueOf(strArray[i + 1]);

//                判断是否已经存在键之后再存入
                if (map.containsKey(c)) {
                    map.put(c, map.get(c) + value);
                } else {
                    map.put(c, value);
                }
            }
        }
        System.out.println("统计Map:");
        map.entrySet().
                stream().
                forEach(entry -> System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue()));
    }
}

上述程序就是我最后的结果了,可以完美解决括号问题,数字多位数问题,乘积上限问题。

其实还可以继续优化,就像开头说的,这个程序没法计数多个字符情况,比如:

输入字符:XYXUIXY

这个字符串中,X,XY,YX,XYX等字符出现的次数

这种是没法统计的,并且让我个人不满意的是,这个程序比较复杂,不优雅。如果可以简化,提高可读性会更好。

就这样。

后言

大地的另一面是梦中的世界,而我们则在那个世界的梦中

——《尺波》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值