字符串题目总结
5 最长回文子串
解法1. 中心扩展法,每个字符作为中枢,向外扩展寻找最长回文串。为了能正确处理偶数的情况,需要在每个间隔增加特殊字符 如”#”
复杂度O(n^2)
解法2: Manacher算法🌟
基于中心扩展法,但记录每个中心的臂长以及右臂最右的位置,减少重复比较。
复杂度O(n)
20 有效的括号
题目:给一个包含大、中、小括号的字符串,判断括号是否合法,即左右括号能否正确对应
思路:用一个栈来保存左括号,遇到右括号则出栈并判断是否匹配,最终栈正好清空则说明所有括号都能正确匹配
3 无重复字符的最长字串
题目:求不包含重复字符的字串的最大长度
思路:滑动窗口。右指针扫过的字符都加入集合,遇到重复的停止,并记录最大长度;左指针扫过的字符从集合中删除,直到不再重复,右指针可以继续前进。
22 括号生成🌟
题目:输入正整数n,输出n对括号组成的所有可能的有效括号字符串。
思路:递归,n=1时只有一种可能即”()”,n>1时,先找到n-1的所有字符串,对于每种可能,在其中每个间隙中插入”()”,然后去重即可。
其实这道题换一下问题,要求所有出现这种可能的字符串数量,会更有意思。对于这个变种问题,当然也可以用上面的方法,返回最后结果的长度即可,但由于中间会有重复字符串,所以必须在中间过程中保存字符串用于去重。而有一种更好的思路:
对于任何一个有效括号字符串,第一个括号一定是左括号,与它对应的右括号则把整个字符串分割成左右两部分,左边有i对括号,右边有n-i-1对括号。同样是将问题转化为比n更小的问题,但与上面的思路不同的是,这种分割方式是不会有重复的。因为只要右括号的位置不同,两个字符串就一定不同。
这样就把求f(n)的问题转换为求
边界条件是:f(0) = f(1) = 1
这样一来,我们就可以套公式递归求解,或者从0到n用一个数组记录每个值来求解。
1249. 移除无效的括号
题目:从一组括号字符串中,删除最少数量的括号,使其成为有效括号。
思路:记住一个前提:如果需要删除一个”(”,那么删除最右边的那个一定没问题;同理,从最左边删除”)”一定没错。
解法很简单,应该很容易想到用栈或者计数的方式来做,中途遇到多余的右括号就直接删除。不要忘了末尾可能会有多余的左括号,需要记下多出来的数量并从右侧删除。
当然也可以只修改一次字符串,遍历一次,记下左右括号的总数量和需要删除的右括号数量,只需一次遍历就确定了需要删除的左右括号的数量,只需从左边删除右括号,从右边删除左括号即可。
这种处理字符串的题目,要注意字符串拼接的时间复杂度,不要从中间插入字符串,这样都可能提高时间复杂度。
关于字符串追加的效率,python可以用list然后join,java用stringbuilder,c++追加字符串可以直接相加,或用string.append(), 追加字符可以用string.push_back(),stringstream效率反而低。
415 字符串相加
也叫大数加法,非常经典的一道纯工程题目,没有写过的同学一定要写一次。
这道题思路都知道,就是从个位开始一位一位地加,注意进位。第一次写可能代码很长,实际上这道题代码可以很精简,即使用c++也只需十几行。
其实还有更复杂一些的大数乘法,需要先会写大数加法。
76. 最小覆盖子串
题目:给定两个字符串s和t,返回s 中包含t中所有字符的最短子串。如果t中有字符重复n次,s的子串中也要包含至少n个该字符。
这道题虽然是hard难度,但实际做起来也就中等难度。思路也比较容易想到,只要用一个滑动窗口即可。指针i、j表示窗口左右边界,首先右指针j右移,直到满足条件,然后左指针i右移,直到不满足条件,在i最后一次移动前,i、j形成的窗口是一个可能的解,记录。重复上面的过程,直到遍历到字符串末尾。
17. 电话号码的字母组合 回溯法
题目:按九宫格打字法,输入一串数字,返回所有可能的字母组成的字符串
所有的可能组成一棵树,按深度优先或广度优先进行遍历即可。
如果按深度优先,则可以用递归的方式;广度优先可以用一个list存储每一层的中间结果
91. 解码方法🌟
题目:英文字母分别编码为1~26,给一串数字,解码为英文字母,求可能的结果数量
思路:典型的动态规划,因为字母对应的数字可能是一位或者两位,所以,前n位数字可能的解码结果数量f(n),依赖于f(n-1)(第n位单独一个字母)和f(n-2)(第n位与第n-1位组合成一个字母)。
93. 复原 IP 地址 回溯法
题目:给出一串数字组成的字符串,向其中加入3个点,可能组成合法的ip地址。求所有可能的ip地址。
这也是一道用回溯法解的题目,其实就是用递归的方式深度优先遍历。
227. 基本计算器 II
题目:输入一个字符串,包含正整数的加减乘除运算,不包含括号。求计算结果。
这道题更偏工程,关键点在于乘除法优先级高于加减法,可以分两次遍历,第一次先计算所有的乘除法,将需要加减法的数入栈,第二次再计算加减法即可。
680. 验证回文串 II
题目:给一个字符串s,从中最多删除一个字符,判断是否能成为回文串。
双指针判断回文串,遇到不匹配的,分别尝试跳过其中一个数指针即可
767. 重构字符串🌟
题目:给定一个字符串 s ,重新排布其中的字母,使得两相邻的字符不同。
思路:直观的想法可能是,对所有字符计数,每次尽量取最大的那一个。但每次找最大的操作需要建堆,实现起来也比较复杂。而且这并不是时间复杂度最右的方法。
有一种更巧妙的思路。先分配好数组的空间,把相同的字符跳格放入似乎就可以了。不过这里面也有一些细节需要注意:
- 首先必须有解,而有解的条件其实也简单,即每一个字符都不超过(n+1)/2
- 如果总长度是奇数且有一个字符正好是(n+1)/2个,那这个字符最终一定在下标为偶数的位置
- 先把字符分配到奇数下标位置,原因有二:
- 如果是奇数长度,遇到某个字符数量正好等于(n+1)/2个,则最后再处理,恰好可以把这个字符分配在所有偶数下标位置。
- 如果是偶数长度,有某个字符数量正好是n/2个,先分配奇数下标,再回头分配偶数下标,可以保证不会相邻;如果先分配偶数下标,则可能会有相邻的情况发生。
12. 整数转罗马数字 13. 罗马数字转整数
正反两种转换,其实和进制转换类似。两种首先都需要写一个转换表,然后循环处理即可。
罗马转整数,只要从左到右把罗马字符转成对应整数然后相加就行。对于IV=4这种情况,可以判断一下如果右边的罗马字符代表的数字比当前的要大,则当前字符不要加而是要减去对应的值。即先减去1,下次循环再加上5。
整数转罗马,思路也很简单,对应表需要有顺序,从大到小去转换即可。