文章目录
- [409. 最长回文串](https://leetcode-cn.com/problems/longest-palindrome/)
- [290. 单词规律](https://leetcode-cn.com/problems/word-pattern/)
- [205. 同构字符串](https://leetcode-cn.com/problems/isomorphic-strings/)
- [49. 字母异位词分组](https://leetcode-cn.com/problems/group-anagrams/)
- [3. 无重复字符的最长子串](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/)
- [187. 重复的DNA序列](https://leetcode-cn.com/problems/repeated-dna-sequences/)
- [76. 最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/)
409. 最长回文串
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 "Aa" 不能当做一个回文字符串。
注意:
假设字符串的长度不会超过 1010。
示例 1:
输入:
"abccccdd"
输出: 7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
分析:给定一个字符串,找可以形成的最长的回文串,回文串的特点**:如果回文串是偶数个字符,则每个字符应该出现偶数遍,如果回文串是奇数个字符,则可以视为最中间的一个字符出现奇数遍,其他每个字符出现偶数遍**。因此,这个问题就简化为:出现次数为偶数的字符共到底有多少个(求的是和)。我们可以设置一个哈希来进行计数。具体求和时,奇淫巧计颇多,在此可以考虑其中之一:如果某个字符出现的次数为奇数,则计数为奇数-1,如果为偶数则全计数。因此,如果最终的和小于给定字符串的长度,则说明还可以加一个放中间,如果等于说明这是偶数个。 判断偶数可以用 a&1==0的方式判断
class Solution {
public:
int longestPalindrome(string s)
{
int c[128];
for(int i=0;i<128;i++)
c[i]=0;
int result=0;
for(int i=0;i<s.length();i++)
c[s[i]]++; //将对应的HASH值++
for(int i=0;i<128;i++) //统计数量
result+=((c[i]&1)==0?c[i]:(c[i]-1)); //偶数则取自身,奇数则取奇数-1
return result==s.length()?result:result+1; //字符串全用的话则返回result,否则再在中间加一个字符
}
};
290. 单词规律
给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。
示例1:
输入: pattern = "abba", str = "dog cat cat dog"
输出: true
示例 2:
输入:pattern = "abba", str = "dog cat cat fish"
输出: false
示例 3:
输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false
示例 4:
输入: pattern = "abba", str = "dog dog dog dog"
输出: false
分析:这个规律实际就是pattern中的字符和 str 中的单词的一一对应关系。因此可以用HASH表示二者的关系,思路如下:
- 设置单词到pattern字符的HASH映射,同时使用一个used数组表示pattern是否被使用
- 遍历str,通过空格拆分单词,每拆分一个单词,pattern字符也向前移动一位
- 如果单词没有出现在HASH表
- 如果当前的pattern已经被使用,则返回false
- 否则的话新建立一个从此单词到该pattern的映射,将pattern对应的used标记为使用
- 如果单词出现在HASH表
- 如果当前的单词在HASH表中对应的字符不是当前的字符,返回false
- 如果单词没有出现在HASH表
- 如果单词个数和pattern字符个数不匹配,则返回false
class Solution {
public:
bool wordPattern(string pattern, string str)
{
map<string,char> MAP; //pattern到char的一个映射
bool used[128];
for(int i=0;i<128;i++)
used[i]=false; //初始化used
int pos=0; //存储当前的位置
string word; //存储当前的单词
str.push_back(' ');//加一个空格便于分割
for(int i=0;i<str.length();i++)
{
if(str[i]!=' ')
word+=str[i]; //记录
else //说明已经分割出一个单词
{
if(MAP.find(word)==MAP.end()) //没找到
{
if(used[pattern[pos]]) return false; //该字符已经被使用
else
{ //建立映射,将其标记为已经使用
MAP[word]=pattern[pos];
used[pattern[pos]]=true;
}
}
else //找到了该映射
{
if(MAP[word]!=pattern[pos]) return false;
}
word=""; //每判断完一个单词记得将word和pos更新
pos++;
}
}
if(pos!=pattern.length()) return false;
return true;
}
};
205. 同构字符串
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。
示例 1:
输入: s = "egg", t = "add"
输出: true
示例 2:
输入: s = "foo", t = "bar"
输出: false
示例 3:
输入: s = "paper", t = "title"
输出: true
方法一:既然都是字符,则可以充分利用STL中的优质函数,思路:遍历s和t时候,每个位置出现的字符,必须首次出现的位置相同,否则就不同构。
class Solution
{
public:
bool isIsomorphic(string s, string t)
{
for(int i=0;i<s.size();i++)
if(s.find(s[i])!=t.find(t[i]))
return false;
return true;
}
};
方法二:与上个题思路一致
class Solution {
public:
bool isIsomorphic(string s, string t)
{
map<char,char> MAP; //从s到t的映射
if(s.length()!=t.length()) return false;
bool used[128]; //标记t中字符是否被使用
for(int i=0;i<128;i++)
used[i]=false; //初始化used
int pos=0; //存储当前的位置
for(int i=0;i<s.length();i++)
{
if(MAP.find(s[i])==MAP.end()) //没找到
{
if(used[t[pos]]) return false; //该字符已经被使用
else
{ //建立映射,将其标记为已经使用
MAP[s[i]]=t[pos];
used[t[pos]]=true;
}
}
else //找到了该映射
{
if(MAP[s[i]]!=t[pos]) return false;
}
pos++;
}
return true;
}
};
49. 字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
分析:该题关键,也就是如何建立一个HASH函数,使得字符相同的字符串映射到一起。设置一个 string->[string]的映射。
每次判断字符时利用临时变量将其排序后的结果存储,如果排序后的字符存在于HASH中,则将其加入到对应的[string]中,否则创建新的映射,将其加入。
class Solution {
public:
vector<vector<string> > groupAnagrams(vector<string>& strs)
{
map<string,vector<string> > MAP;//映射
vector< vector<string> > result;
for(int i=0;i<strs.size();i++)
{
string str=strs[i];
sort(str.begin(),str.end());
if(MAP.find(str)==MAP.end()) //没找到
{
vector<string> temp;
MAP[str]=temp;
}
MAP[str].push_back(strs[i]); //将其加入到对应的str[i]
}
map<string,vector<string> >::iterator it;
for(it=MAP.begin();it!=MAP.end();it++)
result.push_back(it->second);
return result;
}
};
补充:由于是根据一个字符串中出现字符的各种数量进行的聚类,因此可以**制定一个向量映射 string->一个26长度的向量,26个位置分别存储string中每个字符出现的次数。**因此,将映射相同的添加到同一个[string],实际上,可以视为 **string->向量->[string]**的映射关系。具体代码不做详述,与上述类似,可以建立一个向量到[string]的映射,每次将字符串变为向量(上述方法是变为排序后的字符串),之后步骤相同。
3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
分析:通常涉及到字串的问题,常用滑动窗口法
大体思路如下:
- 设置一个记录字符数量的HASH表,简单的用 char[128] 表示即可,用于判断是否已经有重复
- 设置两个指针(实际上就是两个标记位置),一个作为begin,一个作为end,指向当前所扫描的字串头和尾
- 每次固定begin,end向后扫描,扫描过程中更新HASH表,如果扫描到某个end时中发现出现过该字符,说明以当前begin为头的字串至多这么长,更新result为max(result,当前字串长度)
- 需要移动begin位置(此时用到了滑动窗口),begin向后移动,同时更新HASH(每过一个字符对应减1),直至end所指的字符不在HASH中,则重新固定了begin,重复上个步骤。
class Solution {
public:
int lengthOfLongestSubstring(string s)
{
int begin=0,end=0;
int MAP[128]={0}; //作为HASH表
int result=0;
for(end=0;end<s.length();end++) //移动end
{
MAP[s[end]]++; //遍历到了end
if(MAP[s[end]]==1) //说明字串中没有出现过该字符
{
result=max(end-begin+1,result);
}
else //有重复就需要滑动窗口
{
while(MAP[s[end]]>1) //begin右移直至将end处相同字符跳过
{
MAP[s[begin]]--; //将对应的MAP减1
begin++;
}
}
}
return result;
}
};
187. 重复的DNA序列
所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。
编写一个函数来查找 DNA 分子中所有出现超过一次的 10 个字母长的序列(子串)。
示例:
输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
输出:["AAAAACCCCC", "CCCCCAAAAA"]
分析:暴力HASH解法。每次取一个长度为10的序列,通过HASH记录其数量,如果其之前已经出现过一次,即对应的HASH等于1,则将其加入(加入后将HASH更新为2,这样之后遍历到相同的字符时候,我们可以知道已经将其加入到答案序列了,跳过即可,省去了判断是否在答案序列中的步骤),如果该字符没有出现,则建立HASH,映射的值为1,表示出现一次。遍历至字串字符串尾部到达原字符串尾部停止。
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s)
{
map<string,short> MAP; //记录string次数的映射
vector<string> result; //存储答案
for(int i=0;i<s.length();i++)
{
string word=s.substr(i,10); //获取s的以i起始长度为10的序列
if(MAP[word]==1)
result.push_back(word); //添加
MAP[word]++;
}
return result;
}
};
该题具体的优化策略,更多的不是在算法层面,而是将四种字符映射为 00 01 10 11,将问题转化到位运算,从而使程序运行时间变得更快。
76. 最小覆盖子串
给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。
说明: 如果 S 中不存这样的子串,则返回空字符串 “”。 如果 S 中存在这样的子串,我们保证它是唯一的答案。
示例:
输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
分析:滑动窗口法
大致思路:
- 设置一个窗口,可以用left 和 end标记
- 不断增大right,直至窗口中的字符串符合要求
- 缩小窗口,即向右滑动left,直至窗口不符合要求,此时的字符串即从最初left到现在right的符合条件的最短字符串。调整begin,重复操作。
- 期间一直判断窗口是否减小,如果小于之前保留的最小值,则存储新的最小覆盖字串。
通常滑动窗口问题,都是完成目标后就开始调整窗口大小,直到尽可能达到目前最优。然后继续调整窗口。
class Solution {
public:
string minWindow(string s, string t) {
unordered_map<char, short> map; //映射
for (auto c : t) map[c]++; //建立关于t的映射
int left = 0, cnt = 0, maxlen = s.size() + 1, start = left;
for (int i = 0; i < s.size(); ++i)
{
if (--map[s[i]] >= 0) ++cnt;
while(cnt == t.size())
{
if (maxlen > i - left + 1)
{
maxlen = i - left + 1;
start = left;
}
if (++map[s[left]] > 0) cnt--;
left++;
}
}
return maxlen == s.size() + 1 ? "" : s.substr(start, maxlen);
}
};
3807




被折叠的 条评论
为什么被折叠?



