題目地址:
https://leetcode.com/problems/camelcase-matching/
給定一個字符串數組,再給定一個字符串,問數組裏哪些字符串可以由只添加小寫字母得到。以boolean列表的形式返回答案。
思路是雙指針。對於字符串可以這麼考慮:
先開兩個指針和分別從和的開頭開始遍歷。如果已經走到了的末尾,那麼只需要看剩餘字符裏有沒有大寫字母即可,一旦發現就返回false;如果還沒走到了的末尾,則需要進一步比較:如果則兩個指針分別各自向後走一步即可;否則的話,如果發現是大寫字母,那麼就無法只添加小寫字母成爲了,則返回false,如果是小寫字母則向後走,相當於可以在裏添加這個字母。最後遍歷完後,還需要看是否在末尾,如果在,則返回true,否則的話說明裏有個字母在裏沒有,這樣也不可能成立。
代碼如下:
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;
}
}
時間複雜度,爲字符串個數,爲字符串最大長度,空間。
算法正確性證明:
只需證明match方法正確即可。設兩個字符串分別是和,先看第一個while循環,注意到循環不變量,和的字符串一直可以match(可以match的意思是後者可以只添加小寫字母變成前者)。可以用數學歸納法證明。當剛進while循環時,顯然滿足;假設若干次循環後仍然滿足,接着如果發現則顯然和可以match,結論成立;如果,並且是大寫字母,那麼只能通過添加大寫字母的方式纔有可能變成,所以可以返回false;否則可以添加小寫字母,此時和是match的。當while循環退出時可以知道和仍然是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;
}
}
時空複雜度。