LeetCode题解+代码——Implement strStr

在我之前的一篇文章中详细解读了String Matching的算法。下面是对KMP算法实现的代码:

再来简要回顾一下算法的思路:

  1. 算每一个状态 i 的影子向状态 shadow[i]. (注意shadow[i] < i)
    其中,shadow[i] = σ \sigma σ(text), where text = P[0,…,i-1], pattern = P[0,…,i-2]. σ \sigma σ(text) 算的是 length of the longest prefix of pattern which has common suffix with text.

  2. 根据shadow表建立Finite State Machine
    简要来说,假设已经到达了状态s,看到了新字符a。如果a是第s+1个字符,那么顺利向前推进到状态s+1。否则,回退到s的影子状态shadow[s](shadow[s]<s)影子状态看到a之后会走到哪里,s看到a之后就会走到哪里。

  3. 拿着FSM把Text扫描一遍
    这一步就很简单了,扫描的时候每看到一个新字符(text[i]),就按照FSM上面走一步,记录走到的状态(table[i])。当走到最后一个状态时(table[i] = m),代表text[i-m+1,…,i] 是一个完全匹配。

整理完这题的思路复盘的时候发现其实每一步都是动态规划,都是根据当前状态和新看到的信息去获得新状态。

import java.util.*;
// KMP字符串匹配算法
public class Solution {
    public String strStr(String haystack, String needle) {
        if(needle.length()==0) {return haystack;}
        // 用于表示FSM的数据结构。ArrayList的index代表上一个state,
        // Character是看到的新字符,Integer是new state
        ArrayList<Map<Character, Integer>> table = new ArrayList(needle.length()+1);
        
        int[] shadow = new int[needle.length()+1];
        // 其实可能会想到最后一个状态的影子状态已经没用了,为什么这里还要给它算?因为shadow会预先填好0和1的值,所以shadow表的length至少是2.
        shadow[0] = 0; shadow[1] = 0;
        // 必须先填好shadow[1].
        // 因为如果放到循环里的话,第一个字符将会等于状态1的影子状态(0)的下一个字符,
        // 于是shadow[1]就会被写成1,随后每个shadow[i]都会被写成i。
        
        // Step1:建立shadow表
        for(int i=2; i<=needle.length(); ++i){
            // 不要弄混i和i-1。i代表状态i。needle的第i个字符的index是i-1。
            // 状态i的影子状态(index是shadow[i-1]-1)的下一个字符(index是shadow[i-1])
            if(needle.charAt(i-1)==needle.charAt(shadow[i-1])){
                shadow[i] = shadow[i-1]+1;
            }else{
                int X = shadow[i-1];
                while(true){
                    X = shadow[X];
                    if(needle.charAt(i-1)==needle.charAt(X)){
                        shadow[i] = X+1; break;
                    }
                    if(X==0) {
                        if(needle.charAt(i-1)==needle.charAt(0)){
                            shadow[i] = 1; break;}
                        else {shadow[i] = 0; break;}
                    }
                }
            }
        }
        
        // Step2:建状态转移图
        for(int state=0; state<needle.length(); state++){
            Map<Character, Integer> map = new HashMap();
            for(char c='a'; c<='z'; ++c){
            	// 当前state的index是state-1,当前状态的下一个字符index是state
                if(c==needle.charAt(state)){
                    map.put(c, state+1);
                }else{
                    if(state==0) {map.put(c, 0); continue;}
                    // state0要单独考虑,因为这个时候table还是空的,没有可以回退的点
                    int pre_state = shadow[state];
                    int new_state = table.get(pre_state).get(c);
                    map.put(c, new_state);
                }
            }
            table.add(map);
        }
        
        // Step3:遍历一遍haystack
        int state = 0;
        for(int i=0; i<haystack.length(); ++i){
            state = table.get(state).get(haystack.charAt(i));
            if(state==needle.length()) {
                return haystack.substring(i-needle.length()+1, haystack.length());
            }
        }
        return null;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值