一、字符串,数据(包含DP、STL)专题
3. 无重复字符的最长子串(双指针)
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
厦大计算机系机试第一题(只拿了一半好像,回过头来想到是ascii-‘0’)出现的负数造成的 ,哎菜的难受(如果ascii的值比’0’小就异常了)。
本题可采用滑动窗口的方法,重点是2个指针,i和j,分别表示最长子串的首端和末端。
9.9更新: 第一种方法效率更多,可以不适用map,也较好理解,其实双指针对于此题,我认为重点就是如何理解子串开头的慢指针,我的逻辑思维不好,因此举了一个稍微特殊的例子理解了慢指针的妙用。
(结合代码)双指针演示:
i(慢指针)
j(快指针)
a b c d b e (uniquestr = a)
i j
a b c d b e(uniquestr = ab)
i j
a b c d b e(uniquestr = abc)
i j
a b c d b e(uniquestr = abcd)
i j
a b c d b e(此时不执行if的第一个分支,i移动)
i j
a b c d b e(此时不执行if的第一个分支,i移动)
i j(此时,j才继续移动,所以i是会移动到不重复的位置上,(uniquestr = cdb))
a b c d b e
// 无论i怎么移动,判断子串是否变大的依据是看j,而不是看num[s[i]]
// 如果s[j] 出现了重复,其实并不需要找到s[j]相同的字符,只需要将i依次
//向前移动,并还原num[s[i]]的值,切忌不要直接去想怎么马上移动到第一个重复
//的字符,因为只要这样子移动,而判断ans的变化与否还是看的是j,只要没移动
//到重复的字符,那么j的位置就不变改变,就不用担心出现错误。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
// i j 双指针
int len = s.size();
int num[256] = {
0};
int i = 0, j = 0;
int ans = 0;
while(j < len){
if(num[s[j]] == 0){
ans = max(ans, j - i + 1);
num[s[j]]++;
j++;
}else{
num[s[i]] = 0;
i++;
}
}
return ans;
}
};
5. 最长回文子串(中心扩散法和动态规划)
给定一个字符 串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
(1)中心扩散法
需要两次判断,用来检测以x为中心的回文是奇数还是偶数的情况。巩固了C语言欠缺的知识(太差了,一脸泪,字符指针的返回值无法使用局部变量 ,必须动态分配内存给字符指针malloc函数,或者使用static作为全局变量,并且在使用malloc的时候,先必须申请的内存空间为返回的字符串的长度len+1,切一定要加上 ans[len] = '\0’结束符,否则无法编译通过。)
char * longestPalindrome(char * s){
int maxlen = 0;
int start = 0;
int len = strlen(s);
if(len < 2){
return s;
}
for(int h = 0; h < len; h++){
int i = h;
int j = i;
while(i >= 0 && j <= len && s[i] == s[j] ){
i--;
j++;
}
if(maxlen < j - i -1){
maxlen = j - i - 1;
start = i + 1;
}
i = h;
j = h + 1;
while(i >= 0 && j <= len && s[i] == s[j] ){
i--;
j++;
}
if(maxlen < j - i -1){
maxlen = j - i - 1;
start = i + 1;
}
}
char * ans =(char *) malloc(sizeof(char) * (maxlen + 1 ));
strncpy(ans, s + start, maxlen);
ans[maxlen] = '\0';
// strcpy(s,ans);
return ans;
}```
(2)动态规划法
//动态规划
//C语言版本,不过这个leetcode通过不了,“cbbd” 本地这个这个回文串是“bb”,在服务器上却是“cbb”,
//无语了,不过对比了底下的C++版本可以通过,我觉得思路应该是没问题的,恳请批评指正
//dp[i][j] 表示 从尾i 到 头j组成的字符串是否为回文字符串,分别用1与0表示。
char * longestPalindrome(char * s){
int dp[1001][1001];
int len = strlen(s);
int maxlen = 1;
int start = 0;
int end = 0;
//首先,初始化,自己本身就是个长度为1的回文串。
for(int i = 0; i < len; i++){
dp[i][i] = 1;
}
//两层for循环,首先i从头遍历,j是一个重点,她是从i的位置向前遍历,
//重点判断dp[i - 1][j + 1],而不是dp[i + 1][j - 1]]。
for(int i = 0; i < len; i++){
for(int j = i; j >= 0; j--){
if((s[i] == s[j]) && (i - j < 2)){
//这种是特殊情况,表示i与j相邻的情况。
dp[i][j] = 1;
}
else if((s[i] == s[j]) && (dp[i - 1][j + 1] == 1)){
//核心
dp[i][j] = 1;
}
if((dp[i][j] == 1) && (i - j + 1 > maxlen)){
//判断长度是否超过最大
maxlen = i - j + 1;
start = j ;
end = i;
}
}
}
char * ans =(char *) malloc(sizeof(char) * (maxlen + 1 ));
strncpy(ans, s + start, maxlen);
ans[maxlen] = '\0';
return ans;
}
//C++ 版本
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
if (len == 0)
return s;
bool dp[len][len];
int start = 0, end = 0;
for (int i = 0; i <len; i++)
dp[i][i] = true;
for (int r = 0; r < len; r++)
for (int l = 0; l < r; l++)
if (s[r]==s[l] && (r-l==1 || dp[l+1][r-1])) {
dp[l][r] = true;
if (r-l > end-start) {
start = l;
end = r;
}
continue;
}else{
dp[l][r] = false;
}
string ans = s.substr(start, end-start+1);
return ans;
}
};
10. 正则表达式匹配(动态规划)
思路:借鉴leetcode大佬的评论:@袁雷洋 今天提交完答案之后,又在网上看了几个解答的方法,还是感觉这个递归的方法逻辑最清晰,但是思路很容易就迷糊了.在这里稍微梳理一下。
首先建立了一个全部为False的二维矩阵保存递归的结果,行数是(字符串长度+1),列数是(p的长度+1).这里为什么要多建一行一列呢?因为需要考虑到p和s为空的情况.
其中dp[i][j]表明字符串s[:i]与p[:j]的匹配结果.(其中s[:i]最后一个元素为s[i-1],p[:j]最后一个元素为p[j-1],搞懂这一点才看得懂代码的)
首先处理i为0(s==’’)或者j为0(p==’’)的情况.当p==’‘时,只有s==’‘时二者才能匹配,所以首列中,只有首个元素为0,即dp[0][0]=0. 当s==’‘时,只有p==’‘或者p==‘x*y*’(或类似形式)时,二者才能匹配. 所以有
dp[0][j]= =( j >= 2) and (p[j-1] = =’*’) and (dp[0][j-2]).
有必要对上面这个式子解释: 首先j>=2,因为j=1时,p不为空,也不存在首个元素就为*的情况,所以此时p与s肯定无法匹配.dp[0][j]对应p的子串,最后一个元素为p[j-1],只有p[j-1]= =’*'时才有可能匹配成功.同时需要dp[0][j-2]同样为True时,dp[0][j]才能为True.(这个地方好好理解一下,对下面的总代码理解很重要)
下面就使用双重循环开始递归了.dp[i][j]对应s的子串,最后一个元素为s[i-1],对应p的子串,最后一个元素为p[j-1].
(1)首先判断p[j-1]是否为’*’.
如果p[j-1] == '’,那么’‘前面肯定是有一个字母的(比如说b吧),那么需要同时考虑’b*’,此时分两种情况:
①’b*'是无用的:
比如s=‘aaaa’,p=‘a*b*’.此时s与p的配对结果与s与’a*'配对的结果是一样的.所以有 dp[i][j] = dp[i][j-2].
②’b*'是有用的:
比如s=‘aabb’,p=‘a*b*’.之前s=‘aab’,p='a*b’时二者已经配对过了,所以s=‘aab’,p=‘a*b*‘时,二者同样能配对,同样需要判断s[i-1]是否与p[j-2]相同,或者p[j-2]==’.’(即可以代替任何字符)。
所以有 dp[i][j] = dp[i-1][j] and (p[j-2] = = s[i-1] or p[j-2] = = ‘.’)
1与2两种情况有一种成立就行,即取或.
if p[j-1] == ‘*’: dp[i][j] = dp[i][j-2] or (dp[i-1][j] and (p[j-2] = = s[i-1] or p[j-2] == ‘.’))
(2)如果p[j-1]不为’*’.
判断起来就相对简单了.首先要考虑之前的子串是否匹配成功了,即dp[i-1][j-1]的值,同时要考虑dp[i][j]对应的s的子串最后一位s[i-1],p的子串p[j-1]是否相等, p[j-1] == '.‘时同样满足情况,毕竟’.'是万能匹配符. 所以就有 else: dp[i][j] = dp[i-1][j-1] and(p[j-1] == s[i-1] or p[j-1] == ‘.’)
希望能对大家有所帮助。
#include <stdio.h>
bool isMatch(char * s, char * p) {
int sl = strlen(s);
int pl = strlen(p);
//首先建立了一个全部为False的二维矩阵保存递归的结果,行数是(字符串长度+1),列数是(p的长度+1).这里为什么要多建一行一列呢?因为需要考虑到p和s为空的情况. 其中dp[i][j]表明字符串s[:i]与p[:j]的匹配结果.(其中s[:i]最后一个元素为s[i-1],p[:j]最后一个元素为p[j-1],搞懂这一点才看得懂代码的)
bool dp[sl + 1][pl + 1];
memset(dp, false, sizeof(dp));
//初始化 三步
dp[0][0] = true;
for(int i = 1; i < strlen(s) + 1; i++){
dp[i][0] = false;
}
for(int j = 1; j < strlen(p) + 1; j++){
dp[0][j] = j > 1 && dp[0][j - 2] && p[j - 1] == '*';
}
// 有必要对上面这个式子解释: 首先j>=2,因为j=1时,p不为空,也不存在首个元素就为*的情况,所以此时p与s肯定无法匹配.dp[0][j]对应p的子串,最后一个元素为p[j-1],只有p[j-1]=='*'时才有可能匹配成功.同时需要dp[0][j-2]同样为True时,dp[0][j]才能为True.(这个地方好好理解一下,对下面的总代码理解很重要)
for(int i = 1; i < strlen(s) + 1; i++){
for(int j = 1; j < strlen(p) + 1; j++){
if(p[j - 1] == '*'){
dp[i][j] = dp[i][j - 2] ||( (p[j - 2] == s[i - 1] || p[j - 2] =='.') && dp[i - 1][j]);
}else{
dp[i][j] = dp[i - 1][j - 1] &&( (s[i - 1] == p[j - 1]) || p[j - 1] == '.');
}
}
}
return dp[strlen(s)][strlen(p)];
}
11. 盛最多水的容器
给定 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
示例:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
//双指针 i, j
class Solution {
public:
int maxArea(vector<int>& height) {
if(height.size() <= 1) return -1;
int i = 0, j = height.size() - 1, res = 0;
while(i < j){
int h = min(height[i], height[j]);
res = max(res, h * (j - i));
if(height[i] < height[j]) ++i;
else --j;
}
return res;
}
};
15. 三数之和(双指针)
双指针的运用,减少时间复杂度。我之前的方法不够好,多次运用vector和set去重,下方为大神写的,简洁易懂。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int len = nums.size();
vector<vector<int>> ans;
sort(nums.begin(),nums.end());
set<vector<int>> s;
if(len < 3){
return ans;
}
int i, j, k;
for(i = 0; i < len; i++){
if(nums[i] > 0)
break;
vector<int> res = nums;
res.erase(res.begin() + i);
j = 0;
k = res.size() - 1;
while(j < k &&(i == 0 || nums[i] > nums[i - 1])){
if(res[j] + res[k] + nums[i] == 0)