面试中的字符串算法

本文提到的题目部分来自《程序员算法面试指南》一书,这本书的题目可以在牛客网进行在线编程练习。部分来自LeetCode。

KMP算法

提到字符串问题,首先想到的就是KMP算法,它用于解决字符串匹配问题,在字符串str(长度为N)中查找子串match(长度为M)出现的位置。暴力的解法是从左到右遍历str的每一个字符,将当前字符作为第一个字符是否与match匹配。若与match匹配到一半发现不匹配了,则回到str的下一个字符重新出发,match也回到第一个字符重新匹配。时间复杂度为O(MN)。这是由于每次都从头开始检查,没有利用之前遍历检查得到的信息来优化下一次遍历。KMP算法利用了这一信息,使得时间复杂度降为O(N+M),空间复杂度为O(M)。其主要思想为计算子串match的信息保存下来,使得在匹配发生失败时,不必再去检查str串已经遍历过的部分,且子串match的位置也是一直向后滑动。

首先给出需要计算子串match什么样的信息、以及这个信息应该如何计算问题的解答。

计算子串match的next数组,next数组的长度与子串match长度一致。next[i]的含义是,在match[i]之前的字符串match[1..i-1]中以match[i-1]结尾的后缀子串,与match[0..i-2]中以match[0]开始的前缀子串的最大匹配长度。(注意,后缀子串和前缀子串掐头去尾)。举例说明,如match="aaaab",next[4]的值是多少。后缀子串match[1..3]="aaa",前缀子串match[0..2]=="aaa",即next[4]的值为这一匹配的长度3。有了这一数组后,当不匹配发生时,便知应将子串match向后移动多少来进行下一次匹配检查。

如上图在红叉处发生不匹配,我们已经计算了其之前字符串的前缀和后缀的匹配长度,即蓝圈部分。现在只需将match的下一个匹配位置改为蓝圈的后一个位置(也就是我们在next数组中存储的值),继续与str进行匹配即可。因为这一位置之前肯定是与str匹配的。

计算next数组,从左到右依次求解。将next[0]规定为-1,next[1]根据以上定义计算其值为0。在求解next[i]时,可以利用之前已经计算过的next[0..i-1]的值。如下图,若i-1处的字符和next[i-1]处的字符相同,即之前字符的匹配长度又增加1,则next[i]=next[i-1]+1。否则,继续比较i-1处字符与next[next[i-1]]处字符是否相同。当跳到最左位置,即next[0]=-1时,令next[i]=0。

public static int[] nextArray(StringBuilder match) {
        if (match.length() == 1) {
            return new int[] {-1};
        }
        int[] next = new int[match.length()];
        next[0] = -1;
        next[1] = 0;
        for (int i=2;i<match.length();i++) {
            int cn = next[i-1];
            while(cn != -1 && match.charAt(cn) != match.charAt(i)) {
                cn = next[cn];
            }
            if (cn == -1) {
                next[i] = 0;
            } else {
                next[i] = next[cn] + 1;
            }
        }
        return next;
    }

有了next数组,下面便可以进行字符串匹配。在str和match上分别定义一个指针,代表当前匹配位置,若两指针指向位置字符相同,则都向后移动一个位置。否则,将match的指针移到next[matchp]处,若match的指针已经移到0的位置,则将str的指针向后移动一个位置。

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        StringBuilder str = new StringBuilder(sc.nextLine());
        StringBuilder match = new StringBuilder(sc.nextLine());
        int strp = 0;
        int matchp = 0;
        int[] next = nextArray(match);
        while (strp < str.length() && matchp < match.length()) {
            if (str.charAt(strp) == match.charAt(matchp)) {
                strp++;
                matchp++;
            } else if (matchp == 0) {
                strp++;
            } else {
                matchp = next[matchp];
            }
        }
        if (matchp == match.length()) {
            System.out.println(strp - matchp);
        } else {
            System.out.println(-1);
        }
    }

字符串的调整

给定一个字符串chas[],其中只含有字母字符和“*”字符,现在想把所有“*”全部挪到chas的左边,字母字符移到chas的右边。完成调整函数。要求时间复杂度为O(N),空间复杂度为O(1)。

举例:输入"12**345",输出"**12345"。

利用倒着复制的技巧,遇到字母字符就复制,遇到*则不复制,遍历完整个字符串后,将左半部分全部用*填充。

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        StringBuilder sb = new StringBuilder(sc.nextLine());
        int index = sb.length() - 1;
        for (int i=sb.length()-1;i>=0;i--) {
            if (sb.charAt(i) != '*') {
                sb.setCharAt(index,sb.charAt(i));
                index--;
            }
        }
        for (index=index;index>=0;index--) {
            sb.setCharAt(index,'*');
        }
        System.out.println(sb.toString());
    }
}

125.验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

说明:本题中,我们将空字符串定义为有效的回文串。

输入: "A man, a plan, a canal: Panama"
输出: true

输入: "race a car"
输出: false

验证回文串是回文串问题中最简单的。使用两个指针分别从两端向中间移动,遇到非字母数字字符跳过,否则判断对应位是否相等,不相等则返回false,相等继续向中间遍历。

class Solution {
    public boolean isPalindrome(String s) {
        int left = 0;
        int right = s.length() - 1;
        s = s.toLowerCase();
        while (left < right) {
            if ( !(s.charAt(left) >= 'a' && s.charAt(left) <= 'z' || s.charAt(left) >= '0' && s.charAt(left) <= '9')) {
                left ++;
                continue;
            }
            if ( !(s.charAt(right) >= 'a' && s.charAt(right) <= 'z' || s.charAt(right) >= '0' && s.charAt(right) <= '9')) {
                right --;
                continue;
            }
            if (s.charAt(left) == s.charAt(right)) {
                left++;
                right--;
            } else {
                return false;
            }
        }
        return true;
    }
}

 

5.最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

输入: "babad"
输出: "bab"

输入: "cbbd"
输出: "bb"

给出两个解法。(1) 选取每一个字符和字符间隔一共n+n-1个位置作为中心,向两边扩展,判断是否为回文串;(2) 将原字符串倒转,查询这两个字符串的最长公共子串,将这个问题转化成了一个动态规划问题。

class Solution {
    public String longestPalindrome(String s) {
        if (s.length() == 0) {
            return s;
        }
        String ret = s.substring(0,1);
        for(int i=0;i<s.length();i++) {
            // 字符为中心
            int left = i - 1;
            int right = i + 1;
            // 越界
            while (left>=0 && right<s.length()) {
                if (s.charAt(left) == s.charAt(right)) {
                    left--;
                    right++;
        
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值