【Leetcode】1023. Camelcase Matching

題目地址:

https://leetcode.com/problems/camelcase-matching/

給定一個字符串數組,再給定一個字符串pp,問數組裏哪些字符串可以由pp只添加小寫字母得到。以boolean列表的形式返回答案。

思路是雙指針。對於字符串ss可以這麼考慮:
先開兩個指針iijj分別從sspp的開頭開始遍歷。如果jj已經走到了pp的末尾,那麼只需要看ss剩餘字符裏有沒有大寫字母即可,一旦發現就返回false;如果jj還沒走到了pp的末尾,則需要進一步比較:如果s[i]=p[j]s[i]=p[j]則兩個指針分別各自向後走一步即可;否則的話,如果發現s[i]s[i]是大寫字母,那麼pp就無法只添加小寫字母成爲ss了,則返回false,如果s[i]s[i]是小寫字母則ii向後走,相當於可以在pp裏添加s[i]s[i]這個字母。最後遍歷完ss後,還需要看jj是否在pp末尾,如果在,則返回true,否則的話說明pp裏有個字母在ss裏沒有,這樣也不可能成立。

代碼如下:

import java.util.ArrayList;
import java.util.List;

public class Solution {
    public List<Boolean> camelMatch(String[] queries, String pattern) {
        List<Boolean> res = new ArrayList<>();
        for (String query : queries) {
            res.add(match(query, pattern));
        }
        
        return res;
    }
    
    private boolean match(String word, String pattern) {
        int i = 0, j = 0;
        while (i < word.length() && j < pattern.length()) {
            char w = word.charAt(i), p = pattern.charAt(j);
            // 如果字母相同則兩指針同時移動;
            // 如果不同,並且w是大寫字母,說明pattern至少必須得添加大寫字母才能變成word,返回false;
            // 否則移動i,相當於pattern裏添加了一個小寫字母
            if (w == p) {
                i++;
                j++;
            } else if (Character.isUpperCase(w)) {
                return false;
            } else {
                i++;
            }
        }
        // 如果pattern沒有遍歷完,說明pattern裏有word裏不存在的字符,返回false
        if (j < pattern.length()) {
            return false;
        }
        // 如果word沒遍歷完,只需要看一下word後面有沒有大寫字母即可
        if (i < word.length()) {
            while (i < word.length()) {
                if (Character.isUpperCase(word.charAt(i))) {
                    return false;
                }
                i++;
            }
        }
        
        return true;
    }
}

時間複雜度O(nl)O(nl)nn爲字符串個數,ll爲字符串最大長度,空間O(1)O(1)

算法正確性證明:
只需證明match方法正確即可。設兩個字符串分別是sspp,先看第一個while循環,注意到循環不變量,s[0,i)s[0,i)p[0,j)p[0,j)的字符串一直可以match(可以match的意思是後者可以只添加小寫字母變成前者)。可以用數學歸納法證明。當剛進while循環時,i=j=0i=j=0顯然滿足;假設若干次循環後仍然滿足,接着如果發現s[i]=p[j]s[i]=p[j]則顯然s[0,i+1)s[0,i+1)p[0,j+1)p[0,j+1)可以match,結論成立;如果s[i]p[j]s[i]\ne p[j],並且s[i]s[i]是大寫字母,那麼pp只能通過添加大寫字母的方式纔有可能變成ss,所以可以返回false;否則pp可以添加小寫字母,此時s[0,i+1)s[0,i+1)p[0,j)p[0,j)是match的。當while循環退出時可以知道s[0,i)s[0,i)p[0,j)p[0,j)仍然是match的,剩下的邏輯就很簡單了。

法2:Trie + DFS。可以將queries數組裏的字符串全存進一個Trie,然後用遍歷樹的方式尋找滿足條件的字符串,最後返回。代碼如下:

import java.util.*;

public class Solution {
    
    class Trie {
        class Node {
            boolean isWord;
            Map<Character, Node> next;
            
            Node() {
                next = new HashMap<>();
            }
        }
        
        Node root;
        
        Trie(String[] words) {
            root = new Node();
            for (String word : words) {
                add(word);
            }
        }
        
        private void add(String word) {
            Node cur = root;
            for (int i = 0; i < word.length(); i++) {
                char c = word.charAt(i);
                if (!cur.next.containsKey(c)) {
                    cur.next.put(c, new Node());
                }
                cur = cur.next.get(c);
            }
            
            cur.isWord = true;
        }
        
        // 尋找滿足和pattern配對的字符串並返回
        public Set<String> search(String pattern) {
            Set<String> set = new HashSet<>();
            dfs(root, new StringBuilder(), pattern, 0, set);
            return set;
        }
        
        // 從node爲根的子樹遍歷,找到和pattern[start:]配對的字符串;
        // sb是遍歷到當前節點時,路徑上字符組成的子串
        private void dfs(Node node, StringBuilder sb, String pattern, int start, Set<String> set) {
        	// 如果pattern遍歷完了,並且找到一個單詞,則加入結果
            if (start == pattern.length() && node.isWord) {
                set.add(sb.toString());
            }
            // 如果pattern沒遍歷完,則取出當前字符
            char ch = ' ';
            if (start < pattern.length()) {
                ch = pattern.charAt(start);
            }
            
            for (Map.Entry<Character, Node> entry : node.next.entrySet()) {
            	// 如果字符相同,則繼續進行深搜
                if (entry.getKey() == ch) {
                    sb.append(ch);
                    dfs(node.next.get(ch), sb, pattern, start + 1, set);
                    sb.setLength(sb.length() - 1);
                } else {
                	// 如果字符不同,或者pattern已經遍歷完了,都只繼續遍歷小寫字母的分叉
                    char key = entry.getKey();
                    if (Character.isLowerCase(key)) {
                        sb.append(key);
                        dfs(node.next.get(key), sb, pattern, start, set);
                        sb.setLength(sb.length() - 1);
                    }
                }
            }
        }
    }
    
    public List<Boolean> camelMatch(String[] queries, String pattern) {
        List<Boolean> res = new ArrayList<>();
        Trie trie = new Trie(queries);
        
        Set<String> set = trie.search(pattern);
        for (int i = 0; i < queries.length; i++) {
            res.add(set.contains(queries[i]));
        }
        
        return res;
    }
}

時空複雜度O(nl)O(nl)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章