2020/8/16 15:00 开始
争取刷30-40道
预计2020/8/26 完成
套路
字符串常用的方法
- 简单的通过指针进行遍历
递归+回溯的方法
进行暴力的查找动态规划进行处理
(求子串,子序列,回文串的时候)双指针
(尤其是前后指针的处理方法,有点类似于链表的双指针方法
),或者滑动窗口借助数据结构
:(1)借助栈对于字符串中的一些数据进行对消的操作;(2)借助hashMap
统计字符串中每个元素出现的次数进行分析回文串
:则通过首尾指针子串,子序列
:滑动窗口,双指针或者动态规划的问题
技巧
- 对于字符串的操作往往需要采取
s.toCharry()
的方式将其转换为数组,这样对于数组中元素的删除,插入都比较方便 - 可以采用
String
和StringBuilder
或者StringBuffer
的方式进行增删盖茶
对字符串中元素的遍历/查找/判定格式
- 字符串的遍历往往采用指针对其进行遍历
- 和字符串次数有关采用
hash
,Map<Character,Integer> 其中key存储遍历过程中的字符,value存储在字符串中的每个字符出现的次数
遍历字符串对每个字符出现的次数进行统计并进行分析
-
LeetCode383:一个字符串中是否包含另外一个字符串中所有字符:暴力的方法,对need字符串中的每个字符出现的次数进行统计,然后在maganzie给出的字符串中的字符进行统计(采用hashMap)进行统计
-
LeetCode520检测大写字母是否出现在合法位置:使用map对字符串中的每个字符出现的次数进行统计,分别按照大写字母与小写字母进行统计,最后对大写字母与小写字母的个数进行分析(按照题目的要求判断是否符合)
-
LeetCode387字符串中的第一个唯一字符:对于字符串中字符的次数问题,可以采用hashMap对每个字符出现的次数进行统计,先遍历以下字符串,然后再取出字符个数为1的字符
-
LeetCode459 重复的子字符串
采用暴力的解法,对字符串中截取子字符串subStrung(0,i),对这个字符串重读i,i看看是否能够成 -
学生是否迟到:字符串是否满足某种要求:对字符串进行遍历即可:遍历字符串,对某种特性进行统计,若A则直接进行加1,若是出现L则在
-
LeetCode567机器人是否能返回原点:
对字符串中字符的个数进行统计,UD为一组,RL为一组,若最终统计的结果为0则可以回到原点 *
-
LeeetCode49字母的异位词分组:这题比较巧妙,将字符串中的每个字符赋以hash质数对字符串中的每个值可以乘以对应的质数,若发现最终的乘积结果相等,则字符串为对应的易伟慈分组,利用到hashMap的变相处理方法
-
LeetCode58最后一个单词的长度:根据字符串中的空格,区分单词(很常见)
-
LeetCode38外观数列:对字符串进行统计,进行遍历,若发现是相等的字符则将相等字符的个数加1
-
字符串的求和:注意进位以及若两个字符串的长度不相等如何进行简化运算
双指针 /滑动窗口
往往第一步需要将字符串转换为字符数组
- LeetCode434字符串中单词的个数:双指针,这里对字符串中每个单词的判定很简单就是以空格为界限,因此这里只需要根据空格来区分单词即可。
- LeetCode443压缩字符串,对字符串中的每个字符的个数进行统计:双指针,需要left和right对字符串中的连续单词进行遍历,则right-left为连续重复的次数,同时因为在元数组上进行修改,所以需要记录一个对数组中进行修改的尾指针位置,在修改的时候不断将新的值赋值给尾指针之后的位置
- LeetCode344翻转字符串:
双指针
:遍历真个字符串,进行就交换 - 验证回文串
- 翻转字符串中原因字母
- 实现indexOf函数:通过一个指针指向haystack,一个指针指向needle,不断移动指针向前走,若发现needle指针指到字符串的尾部则说明相等此时结束;若发现遍历到的某个字符不相等则需要回溯haystack,并将needle指针重置
/* 滑动窗口算法框架 */
void slidingWindow(string s, string t) {
Map<char, int> need, window;
// 将待比较的字符串中的字符以及字符出现的次数
for(int i =0;i<p.length();i++) {
need.put(p.charAt(i),need.getOrDefault(p.charAt(i),0)+1);
}
int left = 0, right = 0;
int valid = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s.charAt(right);
// 右移窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s.charAt(left);
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
- LeetCode76最小覆盖子串:(1)
将need字符串遍历将其存到needMap中,并记录valid
(2)我们在字符串S中使用双指针中的左右指针技巧,初始化left=right=0,把索引左闭右开区间[left,right)称为一个[窗口]
(3)我们先不断增加right指针扩大窗口[left,right),直到窗口中的字符串符合要求(包含了T中的所有字符)
(4)此时,我们停止增加right,转而不断增加left指针缩小窗口[left,right)直到窗口中的字符串不再符合要求(不包含T中的所有字符同时,每次增加left,我们都要更新一轮结果(两个字符串) - LeetCode28字符串的查找indexof:注意这个是子字符串的查找不是字符串序列的查找,因此窗口只能维持在
need字符串的大小
(两个字符串,注意这种球的是序列,不符合本文题意,主要是一种思路的借鉴) - LeetCode567字符串的排列,给定两个字符串s1和s2,写一个函数来判断s2是否包含s1的排列:套用模板注意是字符串的排列,(连续的字符串)(连个字符串)
- LeetCode438 找到字符串中所有字母异位词
:套用模板即可 - LeetCode3最长无重复子串:定义欢动窗口map,[left,right)不断右移动指针,若发现有重复的元素,则发现在窗口内有重复的元素,则不断将左指针左移动(重点)
- LeetCode239滑动窗口内的最大值:
滑动窗口:使用一个队列(队列中存放的是数组中元素的索引值),确保队首元素是滑动窗口内的最大值,如何保证,每添加一个元素要判断,这个元素是否大于队列中现有元素,若大于则不断移除队列中的元素,再判断队列中的元素是否不再此时滑动窗口范围内
使用栈解决问题
使用栈的情况一般是后面的元素会对前面的元素造成影响,因此将前面的元素加入栈中,后面的元素加入的时候和前面的元素进行比对,若发现某种特性则可以将栈中的元素进行弹出
- LeetCode20有效的括号,给定一个只包含(),{},[]的字符串,判断字符串是否有效:使用栈,遍历整个字符串进行扫描
- LeetCode71简化路径:一句话:站解决,把当前目录压入栈中,遇到…弹出栈顶,最后返回栈中的元素
- LeetCode844比较含退格的字符串,给定S和T连个字符串,当他们分别被输入都空白的文本编辑器后,判断二者是否相等,#代表退格字符:重构字符串,使用栈,使用build(S)和build(T)构造取出了退格和被删除字符后的字符串然后比较它们是否相等。
递归/回溯
- LeetCode17电话号码组合:这道题目和数组的组合很像,采用递归+回溯处理即可,关键是如何对数据进行前置处理(
多个集合进行组合,从集合1中挑选1个从集合2中挑选1个
) - LeetCode139单词拆分,给定一个非空字符串s和一个包含非空单词列表的字典wordDict,判定s是否可以被空格拆分为一个或对个在字典中出现的单词:采用递归+回溯的方法,对于每个位置都可以对单词进行拆分
- LeetCode140单词拆分II
- LeetCode22括号生成:
采用递归+回溯的方法,判断只有在左括号大于右括号的情况下才可以接着进行递归,
- LeetCode784字符串大小写的全排列:
采用递归加回溯的方法
动态规划解决
子串,子序列的问题
一般来说。这类问题都是让你求一个最长子序列,因为最短子序列就是一个字符,没啥可问的;一旦涉及到子序列和最值,那几乎就可以肯定,考察的是动态规划技巧,时间复杂度是O(N^2)
- LeetCode516最长回文子序列:(1)定义状态:dp[i][j]在子串s[i…j]中最长回文子序列的长度;(2)状态转移方程:dp[i][j]=dp[i+1][j-1]+2(若
s[i]==s[j]
);否则dp[i][j]=max(dp[i+1][j],dp[i][j-1])(主要还是正确定义 dp 数组的含义,遇到子序列问题,首先想到两种动态规划思路,然后根据实际问题看看哪种思路容易找到状态转移关系,另外,找到状态转移和baseCase之后,一定要观察DP table,看看怎么遍历才能保证通过已计算出来的结果解决新的问题 - LeetCode5最长回文子串:注意回文子串与回文子序列的区别,子串要求必须是连续的;(1)定义状态:dp[i][j]在子串s[i…j]中最长回文子串;(2)装太转移方程:dp[i][j] = s.charAt(i)+dp[i+1][j-1]+s.charAt(j);(若s[i…j]能构成回文串,
注意和上面的区别
)否则 dp[i][j]=dp[i][j-1],dp[i+1][j];
两个字符串/字符串的匹配
-
LeetCode72编辑距离:两个字符串进行增删改使得字符串一样最少次数:(1)dp[i][j]字符串s[0…i]转换为s2[0…j]所需要的最少的编辑次数(2)若s[i]==s2[j]则不需要编辑,
dp[i][j] = dp[i-1][j-1]
,否则需要进行修改增加,删除,修改 -
LeetCode583两个字符串的删除操作:每部都可以删除任意一个字符串中的一个字符使得字符串相等所需的最小步数:转换为两个字符串中的最长公共子序列;(1)dp[i][j]为两个字符串中前s1[0…i]与s2[0…j]最长公共子序列的长度,(2)若s1[i]==s2[j]则dp[i][j]=dp[i-1][j-1]+1否则dp[i][j]=min(dp[i-1][j],dp[i][j-1])
-
LeetCode139单词拆分,给定一个非空字符串s和一个包含非空单词列表的字典wordDict,判断s是否可被空格拆分为一个或多个字典中出现的单词:(1)dp[i]表示前i个元素可以拆分成(2)则dp[i]= dp[j]&&字典中包含(s.subString(j,i-j))
-
解码方法,给定一包含A-Z消息通过下列方式进行编码:(1)dp[i]表示前i个元素有dp[i]个拆分方法;(2)状态转移方程 dp[i] = dp[i-2] +dp[i-1]
-
LeetCode44通配符匹配:(1)
dp[i][j] 表示 p 的前 i 个字符和 s 的前 j 个字符是否匹配。处理一下匹配串 p 以若干个星号开头的情况。因为星号是可以匹配空串的:
(2)如果 p[i - 1] == s[j - 1] 或 p[i - 1] == ‘?’,表示当前的字符串是匹配的,dp[i][j] 可以从 dp[i - 1][j - 1] 转移而来。 //如果 p[i - 1] == '’,这个位置可以匹配 0 到 若干个字符。那么 dp[i][j] 可以从 dp[i - 1][j] 转移而来(表示当前星号没有匹配字符),例如 ab, ab
回文串
回文串的常见三种做法:
双指针
,栈
和reverse
LeetCode125验证回文串
:给定一个字符串,验证她是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写;采用双指针对字符串进行双向遍历,若发现当前字符串不符合要求则跳过
LeetCode680 验证回文串(最多删除一个字符)
:判断回文串显然是用双指针,i从前往后遍历,j从后往前遍历,难点即是怎么去判断删除一个元素后的字符串是不是回文串,以"abdda"这个串为例,此时i指向’b’,j指向’d’,发现不对了 但是有一次删除的机会,我们自己写几个case其实就能发现,此时子串范围为(i+1, j)或(i, j-1)的俩子串只要有任意一个是回文串,则结果就是回文串,否则就不是。
字符串的翻转
往往需要先对字符串转换为数组然后进行操作
- 一般可以采用双指针对数组进行遍历
- 若是对整个字符串进行翻转,则需要定义整个字符串的首尾指针
- 若是对字符串中的一个个读单词,则指针需要对字符串中的一个个单词进行,(关键是如何判别单词,根据空格对字符串中的一个个单词进行区分)
最后将处理完的数组转换为字符串
-
LeetCode344翻转字符串
:首尾指针,双向推进对字符串进行中的元素进行交换 -
LeetCode541翻转字符串II
:采用双指针的方式,对字符串转换为数组之后,再对其进行2k个位一组进行翻转 -
LeetCode557 翻转字符串中的单词
:首先识别字符串中的每个单词,通过双指针将字符串中的每个单词的边界进行界定,然后将每个单词进行翻转 -
LeetCode151 翻转字符串里的单词(单词不需要翻转)
:采用双指针的方式对数组依据空格对数组进行遍历,以此来区分单词 -
LeetCode345翻转字符串中的元音字母
:采用收尾指针,对字符串进行遍历,找到元音字母就进行交换