《算法系列》之字符串

简介

  字符串是若干字符组成的有限序列,也可以理解为是一个字符数组。在做字符串类的算法题时,API方法对我们的诱惑特别的大,因为主流语言都会有很多处理字符串的方法,我们的题目有时两三行API就能直接被解决了,这显然不是我们刷题的目标,就算只为了过面试也不能直接调API,这样只能回去等通知了。API也不是不能用,当我们觉得,我们的API不影响题目考察的重点时,就可以放心使用!

理论基础

  字符串在存储上类似字符数组,所以它每一位的单个元素都是可以提取的,如s=“abcdefghij”,则s[1]=“b”,s[9]=“j”,这可以给我们提供很多方便,如高精度运算时每一位都可以转化为数字存入数组。觉见的高级语言都有很多处理字符串的方法如:连接求子串删除子串插入子串求长度搜索子串位置大写转换等。我们平时编程处理最多也是字符串,这里就不过多介绍了。最后做字符串类的题目,有时候会用到KMP算法,不了解的同学,可以参考这篇文章:一文读懂 KMP 字符串查找算法

解题心得

  • 有时可把字符串类的题看做char类型的数组题,很多问题就迎刃而解了。
  • 解题时常会用到递归双指针滑动窗口反转等技巧。
  • 有时字符串算法题可能会用到KMP算法,我们需要重点了解该算法。
  • 做字符串算法题时,不可太过依赖API方法。
  • 字符串类的题目,往往想法很简单,实现起来很考验对代码的掌控能力。

算法题目

6. Z 字形变换

在这里插入图片描述
题目解析:根据Z字形生成规律,直接构造结果字符串。
代码如下:

/**
  * 字符串
  */
class Solution {
    public String convert(String s, int numRows) {

        if (numRows == 1) {
            return s;
        }

        StringBuilder ret = new StringBuilder();
        int n = s.length();
        int cycleLen = 2 * numRows - 2;

        for (int i = 0; i < numRows; i++) {
            // 每次加一个周期
            for (int j = 0; j + i < n; j += cycleLen) {
                ret.append(s.charAt(j + i));
                // 除去第 0 行和最后一行
                if (i != 0 && i != numRows - 1 && j + cycleLen - i < n) {
                    ret.append(s.charAt(j + cycleLen - i));
                }
            }
        }
        return ret.toString();
    }
}
8. 字符串转换整数 (atoi)

在这里插入图片描述
题目解析:从左往右依次处理字符串,去掉无用字符,判断正负,确定数字,最后大小值控制在Integer.MAX_VALUE内即可。
代码如下:

/**
  * 字符串
  */
class Solution {
    public int myAtoi(String s) {
        int sign = 1;
        int ans = 0, pop = 0;
        //代表是否开始转换数字
        boolean hasSign = false;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '-' && !hasSign) {
                sign = -1;
                hasSign = true;
                continue;
            }
            if (s.charAt(i) == '+' && !hasSign) {
                sign = 1;
                hasSign = true;
                continue;
            }
            if (s.charAt(i) == ' ' && !hasSign) {
                continue;
            }

            if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
                hasSign = true;
                pop = s.charAt(i) - '0';
                if (ans * sign > Integer.MAX_VALUE / 10 || (ans * sign == Integer.MAX_VALUE / 10 && pop * sign > 7)) {
                    return 2147483647;
                }
                if (ans * sign < Integer.MIN_VALUE / 10 || (ans * sign == Integer.MIN_VALUE / 10 && pop * sign < -8)) {
                    return -2147483648;
                }
                ans = ans * 10 + pop;
            } else {
                return ans * sign;
            }
        }
        return ans * sign;
    }
}
14. 最长公共前缀

在这里插入图片描述
题目解析:所有字符串从左往右开始比较即可。
代码如下:

/**
  * 字符串
  */
class Solution {
    public String longestCommonPrefix(String[] strs) {

        if (strs == null || strs.length == 0)
            return "";

        StringBuilder res = new StringBuilder();
        for (int i = 0; i < strs[0].length(); i++) {
            Character tmp = strs[0].charAt(i);
            for (int j = 0; j < strs.length; j++) {
                // 如果字符串长度不足或字符不相等,立即中止比较
                if (strs[j].length() - 1 >= i && tmp == strs[j].charAt(i)) {
                    // 比较完一轮后,再添加字符
                    if (j == strs.length - 1) {
                        res.append(tmp);
                    }
                } else {
                    break;
                }

            }
            if (res.length() - 1 != i) {
                break;
            }
        }
        return res.toString();
    }
}
28. 实现 strStr()

在这里插入图片描述
题目解析:这题看上去是道简单题,其实要用KMP算法,再这里骗,偷袭我们这些老师傅。用KMP算法可以提高效率到O(n + m)。不了解KMP算法,可以参考这篇文章:一文读懂 KMP 字符串查找算法
代码如下:

/**
  * 字符串
  */
class Solution {
	/**
 	*  获取当前模式串的 next[] (回退表)
 	*  这里没有右移一位,如果需要右移,所有值减1即可
 	*/
    public void getNext(int[] next, String s){
		int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.length(); i++) {
        	// j要保证大于0,因为下面有取j-1作为数组下标的操作
            while (j > 0 && s.charAt(i) != s.charAt(j)) { 
                // 注意这里,是要找前一位的对应的回退位置了
                j = next[j - 1]; 
            }
            if (s.charAt(i) == s.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
    }
    
    public int strStr(String haystack, String needle) {
        if(needle.length()==0){
            return 0;
        }

        int[] next = new int[needle.length()];
        getNext(next, needle);
        int j = 0;
        for(int i = 0; i < haystack.length(); i++){
            while(j>0 && haystack.charAt(i) != needle.charAt(j)){
                j = next[j - 1];
            }
            if(haystack.charAt(i) == needle.charAt(j)){
                j++;
            }
            if(j == needle.length() ){
                return (i - needle.length() + 1);
            }
        }

        return -1;
    }
}
38. 外观数列

在这里插入图片描述
在这里插入图片描述
题目解析:采用递归,描述的时候注意多个相同数字要合并描述即可。
代码如下:

/**
 * 递归
 */
class Solution {
    public String countAndSay(int n) {
        // 递归出口
        if (n == 1) return "1";
        // 递归调用
        String result = countAndSay(n - 1);
        StringBuilder temp = new StringBuilder();
        for (int j = 0; j < result.length(); j++) {
            int count = 1;
            char c = result.charAt(j);
            while (j < result.length() - 1 && c == result.charAt(j + 1)) {
                // 计算该字符有多少个
                count++;
                // 指针向后移
                j++;
                c = result.charAt(j);
            }
            // 拼接
            temp.append(count);
            temp.append(c);
        }
        return temp.toString();
    }
}
58. 最后一个单词的长度

在这里插入图片描述
题目解析:从后往前循环查找空格即可,这里需要注意字符串最末尾的空格需要排除。
代码如下:

/**
 * 字符串
 */
class Solution {
    public int lengthOfLastWord(String s) {
        int res = 0;
        // 标志是否已开始统计单词长度
        boolean flag = false;
        for (int i = s.length() - 1; i >= 0; i--) {
            // 开始统计单词后,遇到的第一个空格即为单词分隔
            if (flag && s.charAt(i) == ' ') {
                break;
            }
            // 计数共有多少字母
            if (s.charAt(i) != ' ') {
                flag = true;
                res++;
            }
        }
        return res;
    }
}
65. 有效数字

在这里插入图片描述
题目解析:确定有限状态自动机,太难了,直接看官方答案吧。
代码如下:

/**
 * 确定有限状态自动机
 */
class Solution {
    public boolean isNumber(String s) {
        Map<State, Map<CharType, State>> transfer = new HashMap<State, Map<CharType, State>>();
        Map<CharType, State> initialMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_INTEGER);
            put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT);
            put(CharType.CHAR_SIGN, State.STATE_INT_SIGN);
        }};
        transfer.put(State.STATE_INITIAL, initialMap);
        Map<CharType, State> intSignMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_INTEGER);
            put(CharType.CHAR_POINT, State.STATE_POINT_WITHOUT_INT);
        }};
        transfer.put(State.STATE_INT_SIGN, intSignMap);
        Map<CharType, State> integerMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_INTEGER);
            put(CharType.CHAR_EXP, State.STATE_EXP);
            put(CharType.CHAR_POINT, State.STATE_POINT);
        }};
        transfer.put(State.STATE_INTEGER, integerMap);
        Map<CharType, State> pointMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_FRACTION);
            put(CharType.CHAR_EXP, State.STATE_EXP);
        }};
        transfer.put(State.STATE_POINT, pointMap);
        Map<CharType, State> pointWithoutIntMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_FRACTION);
        }};
        transfer.put(State.STATE_POINT_WITHOUT_INT, pointWithoutIntMap);
        Map<CharType, State> fractionMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_FRACTION);
            put(CharType.CHAR_EXP, State.STATE_EXP);
        }};
        transfer.put(State.STATE_FRACTION, fractionMap);
        Map<CharType, State> expMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER);
            put(CharType.CHAR_SIGN, State.STATE_EXP_SIGN);
        }};
        transfer.put(State.STATE_EXP, expMap);
        Map<CharType, State> expSignMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER);
        }};
        transfer.put(State.STATE_EXP_SIGN, expSignMap);
        Map<CharType, State> expNumberMap = new HashMap<CharType, State>() {{
            put(CharType.CHAR_NUMBER, State.STATE_EXP_NUMBER);
        }};
        transfer.put(State.STATE_EXP_NUMBER, expNumberMap);

        int length = s.length();
        State state = State.STATE_INITIAL;

        for (int i = 0; i < length; i++) {
            CharType type = toCharType(s.charAt(i));
            if (!transfer.get(state).containsKey(type)) {
                return false;
            } else {
                state = transfer.get(state).get(type);
            }
        }
        return state == State.STATE_INTEGER || state == State.STATE_POINT || state == State.STATE_FRACTION || state == State.STATE_EXP_NUMBER || state == State.STATE_END;
    }

    public CharType toCharType(char ch) {
        if (ch >= '0' && ch <= '9') {
            return CharType.CHAR_NUMBER;
        } else if (ch == 'e' || ch == 'E') {
            return CharType.CHAR_EXP;
        } else if (ch == '.') {
            return CharType.CHAR_POINT;
        } else if (ch == '+' || ch == '-') {
            return CharType.CHAR_SIGN;
        } else {
            return CharType.CHAR_ILLEGAL;
        }
    }

    enum State {
        STATE_INITIAL,
        STATE_INT_SIGN,
        STATE_INTEGER,
        STATE_POINT,
        STATE_POINT_WITHOUT_INT,
        STATE_FRACTION,
        STATE_EXP,
        STATE_EXP_SIGN,
        STATE_EXP_NUMBER,
        STATE_END
    }

    enum CharType {
        CHAR_NUMBER,
        CHAR_EXP,
        CHAR_POINT,
        CHAR_SIGN,
        CHAR_ILLEGAL
    }
}
127. 单词接龙

在这里插入图片描述
题目解析:从 beginWord 和 endWord 两边同时开始广度优先搜索,同时一层一层扩展,当发现某一时刻两边都访问过同一顶点时就停止搜索。
代码如下:

/**
 * 双向广度优先搜索
 */
class Solution {
    Map<String, Integer> wordId = new HashMap<String, Integer>();
    List<List<Integer>> edge = new ArrayList<List<Integer>>();
    int nodeNum = 0;

    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        for (String word : wordList) {
            addEdge(word);
        }
        addEdge(beginWord);
        if (!wordId.containsKey(endWord)) {
            return 0;
        }

        int[] disBegin = new int[nodeNum];
        Arrays.fill(disBegin, Integer.MAX_VALUE);
        int beginId = wordId.get(beginWord);
        disBegin[beginId] = 0;
        Queue<Integer> queBegin = new LinkedList<Integer>();
        queBegin.offer(beginId);
        
        int[] disEnd = new int[nodeNum];
        Arrays.fill(disEnd, Integer.MAX_VALUE);
        int endId = wordId.get(endWord);
        disEnd[endId] = 0;
        Queue<Integer> queEnd = new LinkedList<Integer>();
        queEnd.offer(endId);

        while (!queBegin.isEmpty() && !queEnd.isEmpty()) {
            int queBeginSize = queBegin.size();
            for (int i = 0; i < queBeginSize; ++i) {
                int nodeBegin = queBegin.poll();
                if (disEnd[nodeBegin] != Integer.MAX_VALUE) {
                    return (disBegin[nodeBegin] + disEnd[nodeBegin]) / 2 + 1;
                }
                for (int it : edge.get(nodeBegin)) {
                    if (disBegin[it] == Integer.MAX_VALUE) {
                        disBegin[it] = disBegin[nodeBegin] + 1;
                        queBegin.offer(it);
                    }
                }
            }

            int queEndSize = queEnd.size();
            for (int i = 0; i < queEndSize; ++i) {
                int nodeEnd = queEnd.poll();
                if (disBegin[nodeEnd] != Integer.MAX_VALUE) {
                    return (disBegin[nodeEnd] + disEnd[nodeEnd]) / 2 + 1;
                }
                for (int it : edge.get(nodeEnd)) {
                    if (disEnd[it] == Integer.MAX_VALUE) {
                        disEnd[it] = disEnd[nodeEnd] + 1;
                        queEnd.offer(it);
                    }
                }
            }
        }
        return 0;
    }

    public void addEdge(String word) {
        addWord(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for (int i = 0; i < length; ++i) {
            char tmp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = tmp;
        }
    }

    public void addWord(String word) {
        if (!wordId.containsKey(word)) {
            wordId.put(word, nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }
}
165. 比较版本号

在这里插入图片描述
题目解析:字符串分割,然后分区比较,即可得到对比结果。
代码如下:

/**
 * 字符串分割
 */
class Solution {
    public int compareVersion(String version1, String version2) {
        String[] v1 = version1.split("\\.");
        String[] v2 = version2.split("\\.");
        for (int i = 0; i < v1.length || i < v2.length; ++i) {
            int x = 0, y = 0;
            if (i < v1.length) {
                x = Integer.parseInt(v1[i]);
            }
            if (i < v2.length) {
                y = Integer.parseInt(v2[i]);
            }
            if (x > y) {
                return 1;
            }
            if (x < y) {
                return -1;
            }
        }
        return 0;
    }
}
214. 最短回文串

在这里插入图片描述
题目解析:此题也是用KMP算法,可以参考这篇文章:一文读懂 KMP 字符串查找算法
代码如下:

/**
 * KMP 算法
 */
class Solution {
    public String shortestPalindrome(String s) {
        int n = s.length();
        int[] fail = new int[n];
        Arrays.fill(fail, -1);
        for (int i = 1; i < n; ++i) {
            int j = fail[i - 1];
            while (j != -1 && s.charAt(j + 1) != s.charAt(i)) {
                j = fail[j];
            }
            if (s.charAt(j + 1) == s.charAt(i)) {
                fail[i] = j + 1;
            }
        }
        int best = -1;
        for (int i = n - 1; i >= 0; --i) {
            while (best != -1 && s.charAt(best + 1) != s.charAt(i)) {
                best = fail[best];
            }
            if (s.charAt(best + 1) == s.charAt(i)) {
                ++best;
            }
        }
        String add = (best == n - 1 ? "" : s.substring(best + 1));
        StringBuffer ans = new StringBuffer(add).reverse();
        ans.append(s);
        return ans.toString();
    }
}
回到首页

刷 leetcode 500+ 题的一些感受

下一篇

《算法系列》之双指针

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夏陌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值