• 10. Regular Expression Matching


    题目:

    Implement regular expression matching with support for '.' and '*'.

    '.' Matches any single character.
    '*' Matches zero or more of the preceding element.
    
    The matching should cover the entire input string (not partial).
    
    The function prototype should be:
    bool isMatch(const char *s, const char *p)
    
    Some examples:
    isMatch("aa","a") → false
    isMatch("aa","aa") → true
    isMatch("aaa","aa") → false
    isMatch("aa", "a*") → true
    isMatch("aa", ".*") → true
    isMatch("ab", ".*") → true
    isMatch("aab", "c*a*b") → true

    链接:http://leetcode.com/problems/regular-expression-matching/

    题解:

    这道题也卡了很久,一开始就在考虑很多边界条件。后来才理解到s是普通字符串,p是含有'.'或者'*'的字符串。有几种方法可以解题。

    一开始是recursive的普通解法,分别考虑p的长度为0, 为1,以及大于1的情况。

    public class Solution {
        public boolean isMatch(String s, String p) {
            if(p == null || s == null)
                return false;
            
            if (p.length() == 0) 
                return s.length() == 0;
            
            if (p.length() == 1) 
                return s.length() == 1 && (p.charAt(0) == '.' || p.charAt(0) == s.charAt(0)) ;
            
            if (p.charAt(1) == '*') {
                if (isMatch(s, p.substring(2))) 
                    return true;
                return s.length() > 0 && (p.charAt(0) == '.' || s.charAt(0) == p.charAt(0)) && isMatch(s.substring(1), p);
            } else {
                return s.length() > 0 && (p.charAt(0) == '.' || s.charAt(0) == p.charAt(0)) && isMatch(s.substring(1), p.substring(1));
            }
        }    
    }

    下面是dp解法,先对s构建一个数组, 从p的尾部向前遍历,当p的字符为'*'时,从s尾部向前遍历。否则从s头部向后遍历。

    Time Complexity - O(m * n), Space Complexity - O(n)。 

    public class Solution {
        public boolean isMatch(String s, String p) {            //dp
            boolean[] match = new boolean[s.length() + 1];
            match[s.length()] = true;
            
            for(int i = p.length() - 1; i >= 0; i--) {
                if(p.charAt(i) == '*') {
                    for(int j = s.length() - 1; j >= 0; j--) 
                        match[j] = match[j] || (match[j + 1] && (p.charAt(i - 1) == '.' || s.charAt(j) == p.charAt(i - 1)));
                    i--;
                } else {
                    for(int j = 0; j < s.length(); j++) 
                        match[j] = match[j + 1] && (p.charAt(i) == '.' || p.charAt(i) == s.charAt(j));
                    match[s.length()] = false;
                }
            }
            
            return match[0];
        }
    }
    

    更好的方法是使用regular expression的本质,利用NFA和有向图来计算,代码会写很长,先放在reference里。

    二刷:

    又做到了这一题,又被卡,实在不想背答案。也不想每道类似的题去找一个特别的recursive或者dp解。决定还是好好学习学习自动机Automata。下面是学了Sedgewick关于Regular Expression这一章后的一些想法。编写边学习,主要目的是理顺思路,有错误的话看官也别生气。

    Regular expression is a notation to specify a set of strings, 这个set有可能是infinite。基本操作一般有以下,我们先不管操作的优先级, concatenation - 就是ABC match ABC; or - '|',  AA | BAAB match  AA或者 BAAB;closure - '*',满足0个或者多个之前的字符, 这道题目里面就有'*', 比如 AB*A可以match AA,也可以match ABBBBA;最后就是括号 parentheses '('')'了,比如 (AB)*A可以match A或者 ABABABABA。 还有其他的操作符,比如 wildcard - '.', 可以match任意字符,这个这道题目里面有,我们在wildcard matching里也会遇到;character class - [A-Z] match word Capitailized; at least 1 - A(BC)+DE - matches ABCDE或者 ABCBCDE一类的; exact K [0-9]{5}  -  match 12345。

    自动机里常用的有DFA (Deterministic Finite Automata)和NFA(Non-deterministic Finite Automata),DFA和NFA可以互相转换。主要可以使用在String searching,Pattern Matching之类的。像KMP String searching就可以做一个DFA来完成matching的过程。 DFA: Machine to recognize whether a given string is in a given set。 Kleene's theorem说明对于任何DFA, 我们都可以招待一个RE来描述同样的same set of strings,对于任何RE,我们也可以找到一个DFA来识别same set of strings.

    一般构造RE识别有至少三种方法,第一种是把所有的NFA转换为一个DFA,然后用DFA来进行识别,这种建立DFA的时间会较长O(2m),但识别时间为O(n)。第二种是直接模拟NFA过程,这样建立NFA大约是O(m),但识别时间为O(mn)。第三种是用回溯backtracking来识别,但运行时常最坏情况下可能是O(2n)

    Ken Thompson用来识别的方法就是模拟NFA,也是我打算主要学习的方法,在塞神的video里,RE matching NFA主要有几个特征:

    1. RE enclosed in parentheses (我们可以不用做)
    2. One state per RE character(start = 0, accept = M)
    3. epsilon-transition (change state, but don't scan text): 这个epsilon-transition是代表在这一步我们有多少种可能转移的状态,比如 A*B,我们可以走回A,可以停留在*,也可以进一步走到B,这里我们有三种可能的下一步,该怎么选择呢? 在此我们就要转化这个小问题成为一个在有向图中单源的reachability的问题,要构建一个有向图,用DFS来计算到底这一步我们能够到达多少个状态,然后把这些状态放入一个临时的结果集里面,再进行下一步计算。 这个转移矩阵就代表NFA和DFA的本质区别, non-determinism,不可确定性。  在DFA里我们每一步都有一个明确的下一步,但在NFA的这一步里,我们不能确定下一步究竟怎么走。
    4. Match transition (change state and scan to next text char):  这里就是说我们找到了一个match,要进行下一步状态转换,同时扫描输入text的下一个字符, 比如 s = "ABC", p = "ABC",当i = 0时,因为s(0)='A',p(0)也等于'A',所以他们match,我们可以继续转移状态进行下一步来对比'B'和'B''
    5. After scanning all text characters, accept if any sequence of transitions ends in accept state。在扫描完毕所有text之后,假如结果集合中,有任何一个state = accept state,那么match成功。

    NFA representation:

    State names: Integers from 0 to M

    Match-transitions:  Keep regular expression in array re[]

    Epsilon-transitions:  Store in digraph G

    How to efficiently simulate an NFA? :  Maintain set of all possible states that NFA could be in after reading in the first i text characters. 在读取了i个字符后,保存下来所有NFA可能到达的state。

    如何从step i 扩展到step i+1呢?

    1. 这里首先保存all states reachable after reading i symbols
    2. 然后尝试读取 (i + 1)st symbol c, 根据matching transitions来获取当前NFA可能到达的state,这里一般我们可以重新建立一个Bag或者Set来保存新的state。这一步结束后我们已经读取了第i+1个字符
    3. 这时我们就可以看一看possible null transitions,就是在上一步的基础上,不读取下一个输入text字符的情况下,根据Epsilon-transitions我们能够到达的state,把这些state加入我们的Bag/Set里
    4. 到这里我们就完成了从step i 扩展到step i+1, 这时Bag/Set里保存的就是我们的NFA可以到达的所有state

    Digraph reachability: Find all vertices reachable from a given source or set of vertices:   Run DFS from each source without unmarking vertices

    上面是谈了如何simulate NFA matching process,那么如何根据RE构建NFA?

    Concatenation:   Add match-transition edge from state corresponding to characters in the alphabet to next state

    Parentheses: Add epsilon-transition edge to next state

    Closure: '*', add three eplison transition edges for each * operator,  比如  state 2 = 'A', state 3 = '*', state 4 = 'B',那么我们可以在eplison-transitions edges里面假如 2 -> 3, 3 -> 2以及 3 -> 4。这个操作也对closure expression成立

    Or: '|' addd two epsilon transition edges.  -  G.addEdge(lp, or + 1);  G.addEdge(or, i); 这里假设所有的RE都在Parentheses中。对于这种包含Parentheses的RE,我们需要维护一个栈来保存上一个left Parenthese lp的位置。比较复杂,先不考虑。

    Java:

    为什么Time Complexity是O(mn)呢?  假定输入是长为n的String s,  Pattern是长为m的pattern,那么我们构建NFA的时候最多会有3 * M条边 (全部都是3条边的Epsilon transitions),而遍历用dfs计算reachability的时候复杂度是O(V + E)。那么对于s中的每一个字符,我们们都要计算一遍dfs,最后结果就是  n * O(V + E) = n * O(m) = O(mn)。  空间复杂度没仔细算,也就是对n的每一个元素都进行dfs计算,n次dfs,乘以我们每次dfs的开销O(bm),b是branching factor,这里忽略,总的来说也是O(mn)。

    Time Complexity - O(mn), Space Complexity - O(mn)

    public class Solution {
        private Digraph graph;   // Digraph to record epsilon transitions
        
        public boolean isMatch(String s, String p) {
            buildNFA(p);
            DirectedDFS dfs = new DirectedDFS(graph, 0);
            Set<Integer> reachableState = new HashSet<>();
            for (int v = 0; v < graph.v(); v++) {
                if (dfs.marked(v)) {
                    reachableState.add(v);
                }
            }
            
            for (int i = 0; i < s.length(); i++) {
                Set<Integer> match = new HashSet<>();
                for (int v : reachableState) {      // calculate each possible match transition
                    if (v == p.length()) {      // accept state
                        continue;
                    }
                    if (p.charAt(v) == s.charAt(i) || p.charAt(v) == '.') { // match transition
                        match.add(v + 1);
                    }
                }
                dfs = new DirectedDFS(graph, match);
                reachableState = new HashSet<>();
                for (int v = 0; v < graph.v(); v++) {   // from each match transition, expand to epsilon transitions
                    if (dfs.marked(v)) {
                        reachableState.add(v);
                    }
                }
                if (reachableState.size() == 0) {
                    return false;
                }
            }
            
            for (int v : reachableState) {      // after scaned all s characters, if any NFA sequence leads to accept state
                if (v == p.length()) {
                    return true;
                }
            }
            return false;
        }
        
        private void buildNFA(String p) {
            this.graph = new Digraph(p.length() + 1);   // each char a state, plus one accept state
            for (int i = 0; i < p.length(); i++) {
                char c = p.charAt(i);
                if (c == '*'){
                    if (i > 0) {
                        graph.addEdge(i, i - 1);
                        graph.addEdge(i - 1, i);
                    }
                    graph.addEdge(i, i + 1);
                }
            }
        }
        
        private class DirectedDFS {
            private boolean[] marked;
            
            public DirectedDFS(Digraph graph, int start) {  // G and start state s
                marked = new boolean[graph.v()];
                dfs(graph, start);
            }
            
            public DirectedDFS(Digraph graph, Iterable<Integer> sources) {
                marked = new boolean[graph.v()];
                for (int v : sources) {
                    if (!marked[v]) {
                        dfs(graph, v);
                    }
                }
            }
            
            private void dfs(Digraph graph, int v) {
                marked[v] = true;
                for (int w : graph.adj[v]) {
                    if (!marked[w]) {
                        dfs(graph, w);
                    }
                }
            }
            
            public boolean marked(int v) {
                return marked[v];
            }
        }
        
        private class Digraph {
            private int V;    // number of vertices
            private Set<Integer>[] adj;     // adjacency list representation of Digraph
            
            public Digraph(int v) {
                this.V = v;
                adj = (Set<Integer>[])new Set[v];        //type unsafe
                for (int i = 0; i < v; i++) {
                    adj[i] = new HashSet<Integer>();
                }
            } 
            
            public void addEdge(int v, int w) {
                adj[v].add(w);
            }
            
            public Iterable<Integer> adj(int v) {   // verticies pointing from v
                return adj[v];
            }
            
            public int v() {
                return V;
            }
            
        }
    }

    或者

    public class Solution {
        private Digraph G;
        
        public boolean isMatch(String s, String p) {
            buildNFA(p);
            DirectedDFS dfs = new DirectedDFS(G, 0);
            Set<Integer> reachableState = new HashSet<>();
            for (int v = 0; v < G.V; v++) {
                if (dfs.marked[v]) {
                    reachableState.add(v);
                }
            }
            
            for (int i = 0; i < s.length(); i++) {
                Set<Integer> match = new HashSet<>();
                for (int v : reachableState) {
                    if (v == p.length()) {
                        continue;
                    }
                    if (p.charAt(v) == s.charAt(i) || p.charAt(v) == '.') {
                        match.add(v + 1);
                    }
                }
                dfs = new DirectedDFS(G, match);
                reachableState = new HashSet<>();
                for (int v = 0; v < G.V; v++) {
                    if (dfs.marked[v]) {
                        reachableState.add(v);
                    }
                }
                if (reachableState.size() == 0) {
                    return false;
                }
            }
            for (int v : reachableState) {
                if (v == p.length()) {
                    return true;
                }
            }
            return false;
        }
        
        private void buildNFA(String p) {       // use p build NFA
            this.G = new Digraph(p.length() + 1); // each char a state, plus one accept state
            for (int i = 0; i < p.length(); i++) {
                char c = p.charAt(i);
                if (c == '*') {
                    if (i > 0) {
                        G.addEdge(i - 1, i);
                        G.addEdge(i, i - 1);
                    }
                    G.addEdge(i, i + 1);
                }
            }
        }
        
        private class DirectedDFS {
            public boolean[] marked;
            
            public DirectedDFS(Digraph G, int num) {
                marked = new boolean[G.V];
                dfs(G, 0);
            }
            
            public DirectedDFS(Digraph G, Iterable<Integer> nums) {
                marked = new boolean[G.V];
                for (int v : nums) {
                    if(!marked[v]) {
                        dfs(G, v);    
                    }
                }
            }
            
            private void dfs(Digraph G, int v) {
                marked[v] = true;
                for (int w : G.adj.get(v)) {
                    if (!marked[w]) {
                        dfs(G, w);
                    }
                }
            }
        }
        
        private class Digraph {
            public int V;    // num of vertices
            public Map<Integer, Set<Integer>> adj;   // adjacency list representation of Digraph
            
            public Digraph(int num) {
                this.V = num;
                adj = new HashMap<>();
                for (int i = 0; i < num; i++) {
                    adj.put(i, new HashSet<>());
                }
            }
            
            private void addEdge(int v, int w) {
                adj.get(v).add(w);
            }
        }
    }

    假如不考虑比较复杂的 | 和 ()的话。假设我们有下面两个变型题:

    1.  *号代表之前字符出现一次或者0次(其实相当于"+"), 那么在建立Epsilon transitions的时候我们就没有G.addEdge(i - 1, i)这条代表0次matching的边, 只有 G.addEdge(i, i - 1)表示一次多次,以及通向下一state的G.addEdge(i, i + 1)

    2.  LeetCode no.44  Wildcard Matching:  用构建NFA的方法来做的话,在最后一个例子会超时,强行跳过才可以。我其实每想透该如何构建 Epsilon transition以及Recoganize,因为这里既有Epsilon transition也有matching transition。所以我打算先加入一个预处理步骤 -  在那道题目里 ?可以带表任意字符, * 代表 0个或者多个任意字符, 那么其实联系到这道 Regular Expression matching的话,可以把Wildcard Matching里的 '*' 看成是这里的 “.*”两个字符的组合, 意思是用 '.'来代表任意字符,然后用'*'来代表前面的字符出现一次或者多次, 这样就可以同时包括matching transition以及 epsilon transition。所以实现的话,在预处理里面把'*'替换为"?*", 之后就套用这道题目的code, 再跳过最后一个case就可以了。

    总结是:

    Simulate NFA的方法,对于OJ这两道题目虽然不是最快的,甚至不是较快的解法, 但却是一种通用的解,深度优化的话时间复杂度是O(mn),符合理论,虽然不能达到 DFA的 O(n)速度,但构建比较容易,理解起来也比较简单。我打算再加深对其他RE operator的理解,比如 |,  (), +,[-],等等。

    使用DP的方法: jianchao.li.fighter和xiaohui7解释得非常清楚。

    1. 首先建立dp矩阵res[][] = new boolean[m + 1][n + 1]
    2. res[0][0] = true表示s和p都为""的时候match成功
    3. 接下来对pattern p的首行'*'号的0 match情况进行初始化,res[0][j] = res[0][j - 2] && p.charAt(j - 1) == '*'
    4. 之后从1, 1开始对res矩阵进行dp,主要分为两种情况
      1. p.charAt(i - 1)不等于'*': 这里表示matching transition,假如s和p的上一个字符match, 即res[i - 1][j - 1] == true,同时新的字符s.charAt(i - 1) == p.charAt(j - 1),或者p.charAt(j - 1) == '.', 那么我们可以设定res[i][j] = true,这表示到 s 和 p 到 i - 1和j - 1的位置是match的
      2. 假如p.charAt(i - 1) == '*': 这里表示epsilon transition,系统可能处于不同的状态,我们要分多种情况进行考虑,只要有一个状态为true,在这个位置的结果就为true,是一个“或”的关系:
        1. res[i][j - 2] == true,即s.charAt(i - 1) match p.charAt(j - 3),这里'*'号和其之前的字符可以当作"",表示0 matching,这种情况下,我们可以认为状态合理,res[i][j] = true。 例 "C" match "CA*"
        2. 系统也可能处于另外一个状态,一个或者多个字符的matching。这里我们判断res[i - 1][j],代表s.charAt(i - 2)和 p.charAt(j - 1),假如这个状态成立,我们再继续判断。例“AA” match "A*",我们先反回去看"A"是否match "A*"。假如上面状态成立,我们再看p.charAt(j - 2)是否和s.charAt(i - 1)能match,这里根matching transition一样,需要判断p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(i - 1) == '.'。例子还是"AA" match "A*",当第一个A match了之后,我们判断s中第二个A和p中第一个"A"是否match。
    5. 最后我们返回res[m][n] 

    Time Complexity - O(mn), Space Complexity - O(mn)。

    public class Solution {
        public boolean isMatch(String s, String p) {
            if (s == null || p == null) {
                return false;
            }
            if (p.length() == 0) {
                return s.length() == 0;
            }
            int m = s.length(), n = p.length();
            boolean res[][] = new boolean[m + 1][n + 1];
            res[0][0] = true;
            for (int j = 2; j < res[0].length; j += 2) {        // 0 matching
                res[0][j] = res[0][j - 2] && p.charAt(j - 1) == '*';
            }
            
            for (int i = 1; i < res.length; i++) {
                for (int j = 1; j < res[0].length; j++) {
                    if (p.charAt(j - 1) == '*') {   // epsilon transition
                        if (j > 1 && res[i][j - 2]) {   // 0 matching
                            res[i][j] = true;
                        }
                        if (j > 1 && res[i - 1][j]          // one or more matching
                            && (p.charAt(j - 2) == '.' || p.charAt(j - 2) == s.charAt(i - 1))) {
                            res[i][j] = true;
                        }
                    } else {                    // matching transition
                        if (res[i - 1][j - 1] && (p.charAt(j - 1) == s.charAt(i - 1) || p.charAt(j - 1) == '.')) {
                            res[i][j] = true;
                        }
                    }
                }
            }
            
            return res[m][n];
        }
    }

    Python: 

    class Solution(object):
        def isMatch(self, s, p):
            """
            :type s: str
            :type p: str
            :rtype: bool
            """
            if len(p) == 0:
                return len(s) == 0
            m = len(s)
            n = len(p)
            res = []
            for i in range(0, m + 1):
                tmp = []
                for j in range(0, n + 1):
                    tmp.append(False)
                res.append(tmp)
            res[0][0] = True
            for j in range(2, n + 1):
                res[0][j] = res[0][j - 2] and p[j - 1] == '*'
            for i in range(1, m + 1):
                for j in range(1, n + 1):
                    if p[j - 1] == '*':
                        if j > 1 and res[i][j - 2]:
                            res[i][j] = True
                        if j > 1 and res[i - 1][j] and (s[i - 1] == p[j - 2] or p[j - 2] == '.'):
                            res[i][j] = True
                    else:
                        if res[i - 1][j - 1] and (s[i - 1] == p[j - 1] or p[j - 1] == '.'):
                            res[i][j] = True
            return res[m][n]            

    题外话:

    以前思考过怎样学习最快。总是觉得要看书,看经典书,然后动手做,动手练习。今天觉得看书有的时候真不如看视频。可能对于某些特别聪明的人,看书一眼就可以理解意义,然后就可以应用到实践中。但比如这Regular Expression,算法第四版这本书里只有11页纸,只看书对我来说的话可能怎么也参不透。而视频内容超过1小时,有各个关键点的Demo以及为什么要这么做,配合lecture notes看起来就好吸收不少。各种资源整合起来可能更适合我这种普通算法爱好者来进行学习。 是不是我发现得太晚了。之后试了一下用NFA去解Wildcard Matching, 发现并没有什么用...还是不能ac  -_____-!! 等深入学习了DP以后再去尝试吧。

    三刷:

    nfa的方法,代码比较长,面试时也许写不及,代码一长就容易写错。已经理解了dp,这道题我们要注意阅读清楚题意, '.'是可以match任何单字符,'*'必须连同其前面的字符才能表示0个或者1个该字符。所以使用二维dp的话我们可以按照以下步骤来做:

    1. 找出base状态。我们先去掉一些边界条件,比如s和p为null,p长度为0,以及p开头为'*',这些情况都是不合理的。接下来我们分析得出,当s和p都为空字符串情况下, 两串是match的,应该返回true。
    2. 初始化。 这时候我们要创建2维DP矩阵。 矩阵里面的 dp[i][j]的意思是, 到串s的第i - 1个字符和串p的第j - 1个字符是否match。我们也要对i = 0时,即s为空串时的dp数组进行初始化。
    3. 分析转移方程。
      1. 当 p.charAt(j - 1) = '*'时,这时候我们要考虑系统可能处于的两种状态
        1. 当dp[i][j - 2] 为true时,这时候代表epsilon transition : '*'之前的字母出现0次这种情况,我们可以设置dp[i][j] = true
        2. 或者,当dp[i - 1][j]为true时,这时候说明 s.charAt(i - 2)可以match到p.charAt( j - 1),我们要从s串继续向下扩展一个字符,看s.charAt(i - 1)是否能匹配p.charAt(j - 1)。这里我们就可以比较s.charAt(i - 1)是否等于p.charAt(j - 2),即s.charAt(i - 1)是否和'*'之前的字母相等,或者p.charAt(j - 2)等于通配符'.'。这时候代表epislon transition : '*'之前的字母出现1次。我们可以设置dp[i][j] = true。
      2. 否则p.charAt(j - 1) != '*'。 这时候我们只需要考虑在之前两个字母匹配的情况下,即 dp[i - 1][j - 1] = true时,  现在s中的字符s.charAt(i - 1)是否等于现在p中的字符p.charAt(j - 1),或者现在p的字符为通配符'.'
    4. 返回结果

    Java:

    Time Complexity - O(mn), Space Complexity - O(mn)。

    public class Solution {
        public boolean isMatch(String s, String p) {
            if (s == null || p == null) return s == p;
            if (p.length() == 0) return s.length() == 0;
            if (p.charAt(0) == '*') return false;
            int sLen = s.length(), pLen = p.length();
            boolean[][] dp = new boolean[sLen + 1][pLen + 1];
            dp[0][0] = true;
            for (int j = 2; j <= pLen; j++) {
                if (p.charAt(j - 1) == '*' && dp[0][j - 2]) dp[0][j] = true;
            }
            
            for (int i = 1; i <= sLen; i++) {
                for (int j = 1; j <= pLen; j++) {
                    if (p.charAt(j - 1) == '*') {
                        if (dp[i][j - 2] || (dp[i - 1][j] && ((s.charAt(i - 1) == p.charAt(j - 2)) || (p.charAt(j - 2) == '.')))) {
                            dp[i][j] = true;
                        }
                    } else {
                        if (dp[i - 1][j - 1] && (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.')) {
                            dp[i][j] = true;
                        }    
                    }
                }
            }
            
            return dp[sLen][pLen];
        }
    }

    Reference:

    http://algs4.cs.princeton.edu/54regexp/NFA.java.html              

    https://en.wikipedia.org/wiki/Regular_expression 

    https://en.wikipedia.org/wiki/Thompson%27s_construction

    https://www.cs.ubc.ca/~kevinlb/teaching/cs322%20-%202008-9/Lectures/Search3.pdf

    http://blog.csdn.net/linhuanmars/article/details/21145563

    http://blog.csdn.net/linhuanmars/article/details/21198049

    https://leetcode.com/discuss/18970/concise-recursive-and-dp-solutions-with-full-explanation-in

    https://leetcode.com/discuss/9405/the-shortest-ac-code

    https://leetcode.com/discuss/32424/clean-java-solution

    https://leetcode.com/discuss/43860/9-lines-16ms-c-dp-solutions-with-explanations

    https://leetcode.com/discuss/55253/my-dp-approach-in-python-with-comments-and-unittest

    https://leetcode.com/discuss/8648/my-ac-dp-solution-for-this-problem-asking-for-improvements

    https://leetcode.com/discuss/20470/fast-python-solution-with-backtracking-and-caching-solution

    https://leetcode.com/discuss/26809/dp-java-solution-detail-explanation-from-2d-space-to-1d-space

    https://leetcode.com/discuss/48436/accepted-solution-based-nondeterministic-finite-automata

    https://leetcode.com/discuss/31950/my-dfa-deterministic-finite-automata-java-codes

    https://leetcode.com/discuss/57880/solution-based-nfa-without-backtracking-only-4ms-from-russ

    https://leetcode.com/discuss/75098/java-4ms-dp-solution-with-o-n-2-time-and-o-n-space-beats-95%25

    
    
  • 相关阅读:
    python 单例模式
    socketserver 多进程、多线程应用实例
    socket 编程的一些应用例子
    模拟一个http 请求的json格式报文,带 rsa 签名操作
    python excel基本操作
    多线程 进程间共享变量等
    多线程 multiprocessing 的几个小例子
    mysql 数据库的相关操作
    正则表达式匹配IP地址
    32-服务的容量规划:怎样才能做到有备无患
  • 原文地址:https://www.cnblogs.com/yrbbest/p/4430699.html
Copyright © 2020-2023  润新知