String算法题的思考
String 算法思考
String 类型的题目算是leetcode里第二多的题型,其实这个和array的使用方法差不太多,只不过其中有一些特定的使用场景。我在这里会将string分为几类问题,可能不能包括所有的类别,尽量使用较为统一的思路解释遇到的题目。
数据类型
首先要说清楚一点,在java中String是immutable的,也就是当你创建一个string后是不能修改的,如果你需要给他加内容,java会重新创一个新的实例。这样的问题就是如果使用s+=a
,这样时间复杂度是O(n),在java中会重新复制一遍string再给你创建。
解决办法就是使用StringBuilder,这个的时间复杂度对于增加就是O(1).其中内部实现就是一个char[]
- sb.append(String str)
- sb.toString()
组合问题
这类问题就是需要得到某个字符串中包含的所有子集,或者是permutation.这类题日非常相似,需要找出符合条件的所有组合。基本思路是样的,可以使用DFS与backtrack实现。例如 Combination Sum,可以把这题变成一个子问题选择了数字n后,求解sum-n的子问题的解答。所以可以先排序选择第一个元素,然后在函数中记录以下这些:
- 结果列表
- 当前路径存入的值,
- 所有可能的值
- 剩余的Sum
- 当前选择的位置,深度
在执行递归前将数字存入,在执行后从数组中删去。如果是计算permutation, 在参种增加一个数组用来判断当前数字是否访过。类似的题目有以下这些:
- 17. Letter Combinations of a Phone Number
- 282. Expression Add Operators
- 39. Combination Sum
- 40. Combination Sum II
- 78. Subsets
- 79. Word Search
- 46. Permutations
- 47. Permutations II
- 266. Palindrome Permutation
- 267. Palindrome Permutation II
- 494. Target Sum
- 679. 24 Game
- 491. Increasing Subsequences
对于subset的题目,可以用以下的思路.原因是只有这样才可能遍历所有的,第一dfs可以一直到底,之后再一个个增加。
dfs(){
if(index ==length){
rs.add(tmp);
return;
}
dfs(index+1,tmp);
newtmp = new ArrayList<>(tmp);
newtmp.add(arr[index]);
dfs(index+1,tmp);
}
一个注意点是:如果是用list的话,当加入结果的时候,每次需要重新构造一个新的:
rs.add(new ArrayList<>(cur));
如果题目中存在重复的数字该如何处理呢?首先需要sort,然后就可以在遍历的时候首先看是不是前面两个一致,如果一致的话如果前面的没visited过就跳过。例如1,1,2
, 第一个1遍历完了之后,第二个1就不需要作为首位了,所以这时就可以跳过第二个1作为首位的遍历。对于Palindrome的要求可以选定好中间,然后再从两边扩展。
还有一类题目就是和数学搭配,比如使用列表中的数计算24,可以使用各类运算.基本的思路是相似的,需要我们穷举所有可能的组合,唯一需要注意的就是乘法和除法,需要记录上一个数值的结果,把乘法的结果看作一个整体,一起计算。而算24点的话,首先选两个数,然后进行四则运算,把结果和剩下的数(3个)放到一个数组里,之后就可以递归对这3个数算24点。
括号问题
这也是string类的题目中一大类型,题目有判断是否是valid,可以使用stack的思想,只要是出现"(“之后的反方向括号一定是”)"。生成所有括号的思路和上面的dfs差不多,有两个参数记录剩下的括号数量,同时保证左括号比右括号少就可以了。
找出最长合理括号则可以使用DP来解决,可以把这个问题分成两大类:
- ()()
- (())
if(s.charAt(i) == ')') {
if(s.charAt(i - 1) == '(') {
dp[i] = 2 + (i >= 2 ? dp[i-2] : 0);
}
// s.charAt(i - 1) == )
else if(i - dp[i-1] > 0 && s.charAt(i-dp[i-1]-1) == '(') {
dp[i] = 2 + dp[i-1] + ((i - dp[i-1]) >= 2 ? dp[i-dp[i-1]-2] : 0);
}
max = Math.max(max, dp[i]);
}
在数字中加括号的题目则是分治算法的典型代表,可以将所有的运算符的地方作为分界点,然后分别计算左右两边所有可能的值,之后再合并,得到最后所有的答案。括号是一个将数学表达式分开最合适的方法。
301题最简单的思路就是尝试所有的可能性,当出现不合理的情况时就直接舍弃。在尝试的时候测试使用括号和不使用括号两种可能性。
if (c == '(') {
dfs(s, i + 1, res, sb, rmL - 1, rmR, open); // not use (
dfs(s, i + 1, res, sb.append(c), rmL, rmR, open + 1); // use (
} else if (c == ')') {
dfs(s, i + 1, res, sb, rmL, rmR - 1, open); // not use )
dfs(s, i + 1, res, sb.append(c), rmL, rmR, open - 1); // use )
} else {
dfs(s, i + 1, res, sb.append(c), rmL, rmR, open);
}
对于678,则可以使用贪心的思想,计算最小开放的括号以及最多开放的括号,如果不等于")“的个数小于”)“的话,那么就不可能是合理的。思路想清楚了不难,出现*这种就思考最多和最少的情况。
- 20. Valid Parentheses
- 22. Generate Parentheses
- 32. Longest Valid Parentheses
- 241. Different Ways to Add Parentheses
- 301. Remove Invalid Parentheses
- 678. Valid Parenthesis String
- 856. Score of Parentheses
总体来说,stack的题目比较经典,因为这个模型非常简单易懂,也容易和其他的东西结合起来。
transform
这类题目指的是如何将一类的string改成其他的,比如罗马数字,英文,数字等等。这类题目就是看你的细心程度,根据规则一步一步推即可得到正确的答案。这类题目的难点一般在于有很多奇怪的输入需要思考,要不然很容易系统出错。
大部分都比较easy,比较有意思的就是find and replace 那道题,因为可能输入是没有序的,所以可以先用一个数组存在下需要match的index,然后再换就可以避免无序的问题。
- 8. String to Integer (atoi)
- 273. Integer to English Words
- 12. Integer to Roman
- 13. Roman to Integer
- 722. Remove Comments
- 758. Bold Words in String
- 824. Goat Latin
- 833. Find And Replace in String
- 168. Excel Sheet Column Title
Substring
这类题目一般就是双指针,以及使用一个数组记录所有出现过的值,比较要求。比如说第三题,要求求出最长没有重复字符的题目,我们可以用一个数组记录出现的字符,如果出现重复了,那么起始位置就增加,直到到上次出现的这个字符。
判断是否重复的题目只需要取可以整除长度的内容的substring,然后重复几遍,然后比较是否一致即可。对于回文数就是确定中心,往两边扫这种简单的思路。中心可能是一个字符也有可能是两个相同的字符。还有的题目只需一步一步来就好。
- 3. Longest Substring Without Repeating Characters
- 5. Longest Palindromic Substring
- 76. Minimum Window Substring
- 159. Longest Substring with At Most Two Distinct Characters
- 340. Longest Substring with At Most K Distinct Characters
- 459. Repeated Substring Pattern
- 647. Palindromic Substrings
- 696. Count Binary Substrings
Trie
Trie是一种特别的数据类型,主要是使用一个树来代表string的前缀。一般的构造如下:
static class TrieNode
{
TrieNode[] children = new TrieNode[ALPHABET_SIZE];
// isEndOfWord is true if the node represents
// end of a word
boolean isEndOfWord;
TrieNode(){
isEndOfWord = false;
for (int i = 0; i < ALPHABET_SIZE; i++)
children[i] = null;
}
};
在插入的时候,只需要在数组里新建下一个的TreeNode.在string的搜索上很有帮助。在每个节点都有一个变量来判断是不是一个单词的结束。
TODO:
- 14. Longest Common Prefix
- 208. Implement Trie (Prefix Tree)
- 211. Add and Search Word - Data structure design
- 212. Word Search II
- 336. Palindrome Pairs
- 421. Maximum XOR of Two Numbers in an Array
- 425. Word Squares
- 472. Concatenated Words
- 642. Design Search Autocomplete System
- 648. Replace Words
- 676. Implement Magic Dictionary
- 677. Map Sum Pairs
- 692. Top K Frequent Words
- 720. Longest Word in Dictionary
- 745. Prefix and Suffix Search
Compare
还有一些题目就是需要来比较一些单词的,相似程度,比如说比较两个单词的距离,以及用一些东西来匹配这些字符串等。判断修改距离的题目。我们可以分成增加,删除以及修改三个层面进行讨论。判断那个字符之后其他字符的情况。而判断匹配的题目则可以使用动态规划的思路来做。也分成几个情况,一是完全匹配,还有就是匹配零个,三是匹配多个。
这类题目不是很好归纳,总之在比较字符串的时候需要用equals
- 161. One Edit Distance
- 直接比较在edit后两个是不是相同
- 49. Group Anagrams
- 44. Wildcard Matching
- 10. Regular Expression Matching
- 686. Repeated String Match
模糊匹配的题目可以用一个map存这个单词最基本的情况,比如所有可以改变都变成a,新来的单词也把所有可以改变的变成a就可以了。
other
-
- 注意只有一个单词和最后一行的处理