目录
4.Longest Common Prefix(最长公共前缀)
2.Longest Palindromic Subsequence(最长回文序列(可不连续))
3.Longest Palindromic Substring
6.Find All Anagrams in a String
1.Regular Expression Matching 正则表达式匹配
一、easy
1.Valid Palindrome(回文)
Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignoring cases.
Note: For the purpose of this problem, we define empty string as valid palindrome.
Example 1:
Input: "A man, a plan, a canal: Panama"
Output: true
Example 2:
Input: "race a car"
Output: false
并不难,注意越过标点符号,以及双向查找即可
class Solution {
public:
bool isPalindrome(string s) {
if (s.size() == 0)
return true;
int st = 0;
int ed = s.size()-1;
while (st<ed){
if (isalnum(s[st])==false){st++; continue;}
if (isalnum(s[ed])==false){ed--; continue;}
if (tolower(s[ed])!=tolower(s[st])){
return false;
}else{
st++;
ed--;
}
}
return true;
}
};
2.Implement strStr() 子串问题
Implement strStr().
Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
Example 1:
Input: haystack = "hello", needle = "ll"
Output: 2
Example 2:
Input: haystack = "aaaaa", needle = "bba"
Output: -1
Clarification:
What should we return when needle
is an empty string? This is a great question to ask during an interview.
For the purpose of this problem, we will return 0 when needle
is an empty string. This is consistent to C's strstr() and Java's indexOf().
class Solution {
public:
int strStr(string haystack, string needle) {
// 注意不要遗漏
if (needle==""){return 0;}
int i = 0, j = 0, pre = i;
while (i < haystack.size()){ //小于即可,等于会拖慢时间
if (j == 0){pre = i;}
if (haystack[i] == needle[j]){
if (j == needle.size()-1){return pre;} //注意当needle==1的时候,j=0如果放到这个if外面会有问题
++i;++j;
}
else{
i = pre + 1;
j = 0;
}
}
return -1;
}
};
3.add binary
Given two binary strings, return their sum (also a binary string).
The input strings are both non-empty and contains only characters 1
or 0
.
Example 1:
Input: a = "11", b = "1"
Output: "100"
Example 2:
Input: a = "1010", b = "1011"
Output: "10101"
// 我的版本,先逆置两个字符串,然后开始从后往前加
class Solution {
public:
string addBinary(string a, string b) {
string res;
int i=0,add_num=0,sum;
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
while(i < a.size() && i < b.size()){
sum = add_num + a[i]-'0' + b[i]-'0';
add_num = sum/2;
res += to_string(sum % 2);
++i;
}
while(i<a.size()){
sum = add_num + a[i++]-'0';
add_num = sum/2;
res += to_string(sum % 2);
}
while(i < b.size()){
sum = add_num + b[i++]-'0';
add_num = sum/2;
res += to_string(sum % 2);
}
if (add_num == 1){
res += to_string(add_num);
}
reverse(res.begin(), res.end());
return res;
}
};
网上看到别人更加简洁的版本,思想一致,这种写法又巧妙又简洁,用了两个指针分别指向a和b的末尾,然后每次取出一个字符,转为数字,若无法取出字符则按0处理,然后定义进位carry,初始化为0,将三者加起来,对2取余即为当前位的数字,对2取商即为当前进位的值,记得最后还要判断下carry,如果为1的话,要在结果最前面加上一个1:
class Solution {
public:
string addBinary(string a, string b) {
string res;
int p,q,add_num=0,sum = 0;
int i = a.size()-1,j = b.size()-1;
while(i >= 0 || j >= 0){
p = i >= 0? a[i--]-'0':0;
q = j >= 0? b[j--]-'0':0;
sum = add_num + p + q;
// 此处注意顺序,新生成的加在前面
res = to_string(sum % 2)+res;
add_num = sum/2;
}
return add_num == 0? res:'1'+res;
}
};
4.Longest Common Prefix(最长公共前缀)
Write a function to find the longest common prefix string amongst an array of strings.
If there is no common prefix, return an empty string ""
.
Example 1:
Input: ["flower","flow","flight"]
Output: "fl"
Example 2:
Input: ["dog","racecar","car"]
Output: ""
Explanation: There is no common prefix among the input strings.
确实很简单
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string res;
if(strs.size()==0 || strs[0].empty()) return res;
for(int i = 0;i < strs[0].size();i++){
for(int j = 1;j < strs.size();j++){
if(strs[j][i] != strs[0][i]){
return res;
}
}
res += strs[0][i];
}
return res;
}
};
5.Roman to Integer
Roman numerals are represented by seven different symbols: I
, V
, X
, L
, C
, D
and M
.
Symbol Value
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
For example, two is written as II
in Roman numeral, just two one's added together. Twelve is written as, XII
, which is simply X
+ II
. The number twenty seven is written as XXVII
, which is XX
+ V
+ II
.
Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII
. Instead, the number four is written as IV
. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX
. There are six instances where subtraction is used:
I
can be placed beforeV
(5) andX
(10) to make 4 and 9.X
can be placed beforeL
(50) andC
(100) to make 40 and 90.C
can be placed beforeD
(500) andM
(1000) to make 400 and 900.
Given a roman numeral, convert it to an integer. Input is guaranteed to be within the range from 1 to 3999.
思路:罗马数字转数字的关键就是判断一位数的后面一位是否大于它,大于就减去这位数,否则加上这位数
class Solution {
public:
int romanToInt(string s) {
map<char,int> roman2int = {{'I',1},
{'V',5},
{'X',10},
{'L',50},
{'C',100},
{'D',500},
{'M',1000}};
int res = 0;
for(int i = 0;i<s.size();i++){
// 判断s[i]后面的数是否大于它或者是否为最后一位数,大于则为减
if(i < s.size()-1 && roman2int[s[i+1]] > roman2int[s[i]]){
res -= roman2int[s[i]];
}
else{
res += roman2int[s[i]];
}
}
return res;
}
};
6.Integer to Roman
Roman numerals are represented by seven different symbols: I
, V
, X
, L
, C
, D
and M
.
Symbol Value
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
For example, two is written as II
in Roman numeral, just two one's added together. Twelve is written as, XII
, which is simply X
+ II
. The number twenty seven is written as XXVII
, which is XX
+ V
+ II
.
Roman numerals are usually written largest to smallest from left to right. However, the numeral for four is not IIII
. Instead, the number four is written as IV
. Because the one is before the five we subtract it making four. The same principle applies to the number nine, which is written as IX
. There are six instances where subtraction is used:
I
can be placed beforeV
(5) andX
(10) to make 4 and 9.X
can be placed beforeL
(50) andC
(100) to make 40 and 90.C
can be placed beforeD
(500) andM
(1000) to make 400 and 900.
Given an integer, convert it to a roman numeral. Input is guaranteed to be within the range from 1 to 3999.
我的思路:让输入的数一次去除以1000,100,10,1,分别判断商为9,5,4的情况,和6-8,1-3的情况,循环迭代即可。
class Solution {
public:
string intToRoman(int num) {
string roman[7] = {"M","D","C","L","X","V","I"},res;
int value[7] = {1000,500,100,50,10,5,1};
int it;
for(int i = 0;i < 7;i += 2){
it = num / value[i];
num = num % value[i];
if(it == 9) {res += roman[i];res += roman[i-2];}
else if(it == 5) res += roman[i-1];
else if(it == 4) {res += roman[i];res += roman[i-1];}
else{
if(it > 5) {res += roman[i-1];it -= 5;}
while(it) {res += roman[i];it--;}
}
}
return res;
}
};
7.Count and Say
The count-and-say sequence is the sequence of integers with the first five terms as following:
1. 1
2. 11
3. 21
4. 1211
5. 111221
1
is read off as "one 1"
or 11
.11
is read off as "two 1s"
or 21
.21
is read off as "one 2
, then one 1"
or 1211
.
Given an integer n where 1 ≤ n ≤ 30, generate the nth term of the count-and-say sequence.
Note: Each term of the sequence of integers will be represented as a string.
思路:注意这里的1234只是标号,不代表真实数字,所以只需要迭代循环下去,碰到一个数字只有1次,则记为1+该数字,多次则为次数+该数字。
其实我们可以发现字符串中永远只会出现1,2,3这三个字符,假设第k个字符串中出现了4,那么第k-1个字符串必定有四个相同的字符连续出现,假设这个字符为1,则第k-1个字符串为x1111y。第k-1个字符串是第k-2个字符串的读法,即第k-2个字符串可以读为“x个1,1个1,1个y” 或者“*个x,1个1,1个1,y个*”,这两种读法分别可以合并成“x+1个1,1个y” 和 “*个x,2个1,y个*”,代表的字符串分别是“(x+1)11y” 和 "x21y",即k-1个字符串为“(x+1)11y” 或 "x21y",不可能为“x1111y”.
class Solution {
public:
string countAndSay(int n) {
string pre = "1",res = "1";
int cnt;
for(int i = 1;i < n;i++){
res = "";
for(int j = 0;j < pre.size();j++){
cnt = 1;
while(j+1 < pre.size() && pre[j] == pre[j+1]) {cnt++;j++;}
if(cnt > 1) {res += to_string(cnt);res += pre[j];}
else {res += "1";res += pre[j];}
}
pre = res;
}
return res;
}
};
8.Length of Last Word
Given a string s consists of upper/lower-case alphabets and empty space characters ' '
, return the length of last word in the string.
If the last word does not exist, return 0.
Note: A word is defined as a character sequence consists of non-space characters only.
Example:
Input: "Hello World"
Output: 5
思路:确实简单,反向查找第一个单词即可
class Solution {
public:
int lengthOfLastWord(string s) {
int i = s.size()-1,len = 0;
while(isspace(s[i]) && i > 0) i--;
while(!isspace(s[i]) && i >= 0) {i--;len++;}
return len;
}
};
9. Isomorphic Strings
Given two strings s and t, determine if they are isomorphic.
Two strings are isomorphic if the characters in s can be replaced to get t.
All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character but a character may map to itself.
Example 1:
Input: s = "egg", t = "add"
Output: true
Example 2:
Input: s = "foo", t = "bar"
Output: false
Example 3:
Input: s = "paper", t = "title"
Output: true
思路:维护两个字典,如果有对应不上,则返回false,注意这里的对应是双向的,不要维护一个字典,会导致ab和aa这种情况出现问题。
class Solution {
public:
bool isIsomorphic(string s, string t) {
map<char,char> m_s;
map<char,char> m_t;
for(int i = 0;i < s.size();i++){
if(m_s.count(s[i]) == 0) m_s[s[i]] = t[i];
if(m_t.count(t[i]) == 0) m_t[t[i]] = s[i];
if(m_s[s[i]] != t[i] || m_t[t[i]] != s[i]) return false;
}
return true;
}
};
10.Word Pattern
Given a pattern
and a string str
, find if str
follows the same pattern.
Here follow means a full match, such that there is a bijection between a letter in pattern
and a non-empty word in str
.
Example 1:
Input: pattern ="abba"
, str ="dog cat cat dog"
Output: true
Example 2:
Input:pattern ="abba"
, str ="dog cat cat fish"
Output: false
Example 3:
Input: pattern ="aaaa"
, str ="dog cat cat dog"
Output: false
Example 4:
Input: pattern ="abba"
, str ="dog dog dog dog"
Output: false
思路:本题基本与上题相同,这里使用istringsteam去做空格的切分,注意最后对于切分后长度与pattern长度的比较。这是使用了两个map,属于用空间换时间,也可以使用一个字典。
class Solution {
public:
bool wordPattern(string pattern, string str) {
map<char,string> m_p;
map<string,char> m_s;
istringstream in(str);
int i = 0;
for(string word;in >> word;i++){
if(m_p.count(pattern[i]) == 0) m_p[pattern[i]] = word;
if(m_s.count(word) == 0) m_s[word] = pattern[i];
if(m_s[word] != pattern[i] || m_p[pattern[i]] != word) return false;
}
// 注意判断i与pattern长度是否相同,这是与上题不一样的地方
return i == pattern.size();
}
};
使用一个字典的写法:
class Solution {
public:
bool wordPattern(string pattern, string str) {
map<char,string> m;
istringstream in(str);
int i = 0;
for(string word;in >> word;i++){
if(m.count(pattern[i]) == 0) m[pattern[i]] = word;
else if(m[pattern[i]] != word) return false;
// 注意这个检查每次都要做,不放在else里面
for(auto it = m.begin();it != m.end();it++){
if(it->second == word){
if(it->first == pattern[i]) break;
else return false;
}
}
}
return i == pattern.size();
}
};
二、medium
1. String to Integer (atoi)
题意:非常好理解的一个题目,字符串转化成整形,要求从第一个不是空格的字符开始算,最长的连续的一串可以转化成int类型的子串,这个子串如果能转化成int类型可以表示的数字就返回该数字,如过不可以就返回0。比如:“ 2132141dasfas123123”返回2132141,“dasas12412412”返回0,当然如果子串是以“-”“+”开头的话就要看后面的字符情况,以及注意题目中要求的int溢出则返回最大最小int值问题。
Input: "-91283472332"
Output: -2147483648
Explanation: The number "-91283472332" is out of the range of a 32-bit signed integer.
Thefore INT_MIN (−231) is returned.
class Solution {
public:
int myAtoi(string str) {
int i=0;
bool flag = true;
int temp,res = 0;
# 去掉空格
while (str[i]==' ' && i<str.size()) ++i;
# 判断符号,注意[i++]的使用,简洁代码
if (str[i] == '-' || str[i] == '+'){
if (str[i++] == '-') flag = false;
}
# 去掉无意义的0
while(str[i]=='0') ++i;
if (i == str.size() or !(isdigit(str[i]))) {return 0;}
while(isdigit(str[i])){
# 对于char转int的转法,注意
temp = res*10 + str[i++]-'0';
# 对于溢出的判断
if(temp/10 != res ){
return flag? INT_MAX:INT_MIN;
}
res = temp;
}
return flag? res:-1*res;
}
};
2.Longest Palindromic Subsequence(最长回文序列(可不连续))
Given a string s, find the longest palindromic subsequence's length in s. You may assume that the maximum length of s is 1000.
Example 1:
Input:"bbbab"
Output:4
One possible longest palindromic subsequence is "bbbb".
Example 2:
Input:"cbbd"
Output:2
One possible longest palindromic subsequence is "bb".
思路:
这道题给了我们一个字符串,让我们求最大的回文子序列,子序列和子字符串不同,不需要连续。而关于回文串的题之前也做了不少,处理方法上就是老老实实的两两比较吧。像这种有关极值的问题,最应该优先考虑的就是贪婪算法和动态规划,这道题显然使用DP更加合适。我们建立一个二维的DP数组,其中dp[i][j]表示[i,j]区间内的字符串的最长回文子序列,那么对于递推公式我们分析一下,如果s[i]==s[j],那么i和j就可以增加2个回文串的长度,我们知道中间dp[i + 1][j - 1]的值,那么其加上2就是dp[i][j]的值。如果s[i] != s[j],那么我们可以去掉i或j其中的一个字符,然后比较两种情况下所剩的字符串谁dp值大,就赋给dp[i][j],那么递推公式如下:
/ dp[i + 1][j - 1] + 2 if (s[i] == s[j])
dp[i][j] =
\ max(dp[i + 1][j], dp[i][j - 1]) if (s[i] != s[j])
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
vector<vector<int>> d(n,vector<int>(n));
// 注意i和j的起始和方向,保证d[i+1][j-1]已被访问过有结果
for(int i = n-1;i >= 0;i--){
d[i][i] = 1;
for(int j = i+1;j < n;j++){
if(s[i] == s[j]){
d[i][j] = d[i+1][j-1]+2;
}
else{
d[i][j] = max(d[i+1][j],d[i][j-1]);
}
}
}
return d[0][n-1];
}
};
另一种节省空间的方式,时间也会有所提高
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size(),res = 0,pre;
vector<int> d(n,1);
for(int i = n-1;i >= 0;i--){
int maxlen = 0;
for(int j = i+1;j < n;j++){
// 注意此处存之前的结果,作为下一个点的递归项,即上一种方法的d[i-1][j+1]
pre = d[j];
if(s[i] == s[j]){
d[j] = maxlen+2;
}
maxlen = max(maxlen,pre);
}
}
for (int num : d){
res = max(res,num);
}
return res;
}
};
3.Longest Palindromic Substring
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example 2:
Input: "cbbd"
Output: "bb"
思路:
用动态规划Dynamic Programming来解,根Palindrome Partitioning II 拆分回文串之二的解法很类似,我们维护一个二维数组dp,其中dp[i][j]表示字符串区间[i, j]是否为回文串,当i = j时,只有一个字符,肯定是回文串,如果i = j + 1,说明是相邻字符,此时需要判断s[i]是否等于s[j],如果i和j不相邻,即i - j >= 2时,除了判断s[i]和s[j]相等之外,dp[j + 1][i - 1]若为真,就是回文串,通过以上分析,可以写出递推式如下:
dp[i, j] = 1 if i == j
= s[i] == s[j] if j = i + 1
= s[i] == s[j] && dp[i + 1][j - 1] if j > i + 1
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size(),maxlen = 0,left=0,right=0;
vector<vector<int>> dp(n,vector<int>(n,0));
for(int i=n-1;i>=0;i--){
dp[i][i] = 1;
for(int j=i+1;j<n;j++){
// 注意增加j-i<2这种情况,即两个点相邻相同,比如abbc中的bb
// 这个与上一题不同,上一题只需要考虑个数问题,这里有个检验连续的过程
if(s[i]==s[j] && (j - i < 2 || dp[i+1][j-1])){
dp[i][j] = 1;
if(j-i+1 > maxlen){
maxlen = abs(i-j)+1;
left = i;
right = j;
}
}
}
}
return s.substr(left, right - left + 1);
}
};
O(n)复杂度解法参考:https://blog.youkuaiyun.com/dyx404514/article/details/42061017
4.Valid Anagram
Given two strings s and t , write a function to determine if t is an anagram of s.
Example 1:
Input: s = "anagram", t = "nagaram"
Output: true
Example 2:
Input: s = "rat", t = "car"
Output: false
思路:用一个大小为26的int数组来统计每个单词中字符出现的次数,然后将int数组转为一个唯一的字符串,跟字符串数组进行映射,这样我们就不用给字符串排序了
class Solution {
public:
bool isAnagram(string s, string t) {
vector<int> s_cnt(26,0),t_cnt(26,0);
for(char c:s) ++s_cnt[c-'a'];
for(char c:t){
++t_cnt[c-'a'];
if(t_cnt[c-'a'] > s_cnt[c-'a']) return false;
}
return t_cnt==s_cnt;
}
};
5.Group Anagrams
Given an array of strings, group anagrams together.
Example:
Input: ["eat", "tea", "tan", "ate", "nat", "bat"]
,
Output:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
思路:直接把每一个anagram排成内部有序,这样只需要对照一次即可(这个我一开始没想到,想着直接暴力破解)
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
//我的解法,由于不熟悉c++,所以我手写了冒泡排序,真的打扰了,解法本身没有问题,将每个string排序后,作为map的key值
string now_str,order_str;
bool flag;
int end;
char temp;
map<string,vector<string>> order_idx;
vector<vector<string>> res;
for(int i = 0;i < strs.size();i++){
now_str = strs[i];
order_str = strs[i];
end = now_str.size()-1;
flag = true;
while(flag){
flag = false;
for(int k = 0;k < end;k++){
if(order_str[k] > order_str[k+1]){
temp = order_str[k];
order_str[k] = order_str[k+1];
order_str[k+1] = temp;
flag = true;
}
}
end--;
}
order_idx[order_str].push_back(now_str);
}
for(auto it = order_idx.begin();it != order_idx.end();it++) {res.push_back((*it).second);}
return res;
}
};
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
// 别人的优雅解法
map<string,vector<string>> m;
vector<vector<string>> res;
for(string str: strs){
string t = str;
sort(t.begin(),t.end());
m[t].push_back(str);
}
for(auto a: m) {res.push_back(a.second);}
return res;
}
};
下面这种解法没有用到排序,提高了运算效率,我们用一个大小为26的int数组来统计每个单词中字符出现的次数,然后将int数组转为一个唯一的字符串,跟字符串数组进行映射,这样我们就不用给字符串排序了,代码如下:
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<string,vector<string>> m;
vector<vector<string>> res;
for(string str: strs){
string t;
vector<int> cnt(26,0);
for(char c:str) ++cnt[c-'a'];
for(int n:cnt) t += to_string(n);
m[t].push_back(str);
}
for(auto a: m) res.push_back(a.second);
return res;
}
};
6.Find All Anagrams in a String
Given a string s and a non-empty string p, find all the start indices of p's anagrams in s.
Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.
The order of output does not matter.
Example 1:
Input:
s: "cbaebabacd" p: "abc"
Output:
[0, 6]
Explanation:
The substring with start index = 0 is "cba", which is an anagram of "abc".
The substring with start index = 6 is "bac", which is an anagram of "abc".
Example 2:
Input:
s: "abab" p: "ab"
Output:
[0, 1, 2]
Explanation:
The substring with start index = 0 is "ab", which is an anagram of "ab".
The substring with start index = 1 is "ba", which is an anagram of "ab".
The substring with start index = 2 is "ab", which is an anagram of "ab".
思路:参考上一题,使用一个vector去保存出现过的数字个数。我的思路是每次都去p长度的子串与p进行比较,这样速度会慢,网上大神的做法是,维护一个s的vector表,每次移动时左边的字符数减去1,右边的字符数加上1,直接比较即可
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> p_cnt(26,0),sub_cnt(26,0),res;
if(p.size()>s.size()) return res;
for(int i = 0;i < p.size();i++){
++p_cnt[p[i]-'a'];
++sub_cnt[s[i]-'a'];
}
if(p_cnt==sub_cnt) res.push_back(0);
for(int i = p.size();i<s.size();i++){
--sub_cnt[s[i-p.size()]-'a'];
++sub_cnt[s[i]-'a'];
if(p_cnt==sub_cnt) res.push_back(i-p.size()+1);
}
return res;
}
};
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
// 我的垃圾做法...速度会比上面的慢
vector<int> p_cnt(26,0),res;
string pt;
if(p.size()>s.size()) return res;
for(char c:p) ++p_cnt[c-'a'];
for(int i:p_cnt) pt += to_string(i);
for(int i = 0;i < s.size()-p.size()+1;i++){
vector<int> sub_cnt(26,0);
bool flag = true;
for(char c:s.substr(i,p.size())){
if(p_cnt[c-'a'] == 0) {flag = false;break;}
else ++sub_cnt[c-'a'];
}
if(flag){
string sub_t;
for(int i:sub_cnt) sub_t += to_string(i);
if(sub_t == pt){
res.push_back(i);
bool flag_true = true;
while(flag_true){
if(s[i] == s[i+p.size()]){
i++;
res.push_back(i);
}
else flag_true = false;
}
}
}
}
return res;
}
};
7.Simplify Path
Given an absolute path for a file (Unix-style), simplify it. Or in other words, convert it to the canonical path.
In a UNIX-style file system, a period .
refers to the current directory. Furthermore, a double period ..
moves the directory up a level. For more information, see: Absolute path vs relative path in Linux/Unix
Note that the returned canonical path must always begin with a slash /
, and there must be only a single slash /
between two directory names. The last directory name (if it exists) must not end with a trailing /
. Also, the canonical path must be the shortest string representing the absolute path.
Example 1:
Input: "/home/"
Output: "/home"
Explanation: Note that there is no trailing slash after the last directory name.
Example 2:
Input: "/../"
Output: "/"
Explanation: Going one level up from the root directory is a no-op, as the root level is the highest level you can go.
Example 3:
Input: "/home//foo/"
Output: "/home/foo"
Explanation: In the canonical path, multiple consecutive slashes are replaced by a single one.
Example 4:
Input: "/a/./b/../../c/"
Output: "/c"
Example 5:
Input: "/a/../../b/../c//.//"
Output: "/c"
Example 6:
Input: "/a//b////c/d//././/.."
Output: "/a/b/c"
思路:找规律,遇到..则回退上一级目录,遇到.则去掉.
我原本直接处理字符串从左往右,导致写法非常复杂而且出错了,参考网上的写法,取出/之间的部分,送入队列中,若遇上..,在队列非空情况下出队列。(向量容器的成员函数pop_back()可以删除最后一个元素)
class Solution {
public:
string simplifyPath(string path) {
vector<string> v;
int i=0,start,end;
string res,sub;
while(i < path.size()){
while(path[i]=='/') i++;
if(i == path.size()) break;
start = i;
while(i < path.size() && path[i] != '/') i++;
end = i - start;
sub = path.substr(start,end);
if(sub == "..") {
// 注意此处if(!v.empty())得写到内部而不是写在上面,不然v.empty()且sub==“..”的情况会到else里面
if(!v.empty()) v.pop_back();
}
else if(sub != ".") v.push_back(sub);
}
for(int i = 0;i < v.size();i++) {res += '/';res += v[i];}
if(res.size()==0) return "/";
return res;
}
};
三、hard
1.Regular Expression Matching 正则表达式匹配
Given an input string (s
) and a pattern (p
), implement regular expression matching with support for '.'
and '*'
.
'.' Matches any single character.
'*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Note:
s
could be empty and contains only lowercase lettersa-z
.p
could be empty and contains only lowercase lettersa-z
, and characters like.
or*
.
Example 1:
Input:
s = "aa"
p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".
Example 2:
Input:
s = "aa"
p = "a*"
Output: true
Explanation: '*' means zero or more of the precedeng element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".
Example 3:
Input:
s = "ab"
p = ".*"(即.重复无数次,故可以匹配任意字符串)
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".
Example 4:
Input:
s = "aab"
p = "c*a*b"
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches "aab".
Example 5:
Input:
s = "mississippi"
p = "mis*is*p*."
Output: false
思路:'.*'的匹配情况完全没办法通过打补丁来解决。解法基本上就是递归和dp两种思想
注意考虑p各种情况,进行递归。
边界条件:p为空,p为1
递归:分为p[1]等不等于*的情况
class Solution {
public:
bool isMatch(string s, string p) {
// p为空的时候,s必须为空,否则为false
if(p.empty()) return s.empty();
// p为1的时候,s必须为1,且要么两者相同,或者p为.,否则为false
if(p.size()==1) return ((p[0] == '.' || s[0] == p[0]) && s.size()==1);
// p[1]不为*时,s必须不为空,或者s[0]==p[0]或p[0]=='.',递归往后
if(p[1] != '*'){
if (s.empty()) return false;
return (s[0] == p[0] || p[0] == '.') && isMatch(s.substr(1), p.substr(1));
}
// p[1]为*时,首先去掉p两个字符,进行递归匹配
// 若匹配成功,则为true(即前面字符出现0次的情况)
// 若匹配不成功,去掉s一个字符,递归进行匹配(即前面字符多次重复情况,s*s与s)
while(!s.empty() && (s[0] == p[0] || p[0] == '.')){
if(isMatch(s,p.substr(2))) return true;
s = s.substr(1);
}
// 加上p[0]直接与s[0]不匹配情况,直接去掉两个字符递归(即前面字符出现0次的情况,s*a与a)
return isMatch(s,p.substr(2));
}
};
动态规划解法:定义一个二维的DP数组,其中dp[i][j]表示s[0,i)和p[0,j)是否match,让s从空开始,与p的子串进行匹配
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
// 都为空时为true
dp[0][0] = true;
// s可以为空,p不可以为空(s空p空情况上面已经讨论过)
for (int i = 0; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
// 当前为*时
// 1.*代表0,即dp[i][j] = dp[i][j - 2]
// 2.*代表个数,要求i>0,即非空,且*前面字符相同或者p为.,则其等于dp[i - 1][j]
if (j > 1 && p[j - 1] == '*') {
dp[i][j] = dp[i][j - 2] || (i > 0 && (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j]);
} else {
// 当前非*时
dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.');
}
}
}
return dp[m][n];
}
};
2. Wildcard Matching
Given an input string (s
) and a pattern (p
), implement wildcard pattern matching with support for '?'
and '*'
.
'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).
The matching should cover the entire input string (not partial).
Note:
s
could be empty and contains only lowercase lettersa-z
.p
could be empty and contains only lowercase lettersa-z
, and characters like?
or*
.
我写的递归版本,代码应该没有问题,主要是时间不够。注意s为空,p不为*的情况,此时应该是false,不要遗漏
class Solution {
public:
bool isMatch(string s, string p) {
if(p.empty()) return s.empty();
if(p.size()==1){
if(p=="*") return true;
if(s.size()==1 && (p=="?" || p==s)) return true;
return false;
}
while(!p.empty()){
if(p[0] == '*'){
while(p.size()>1 && p[1] == '*') p = p.substr(1);
if (isMatch(s,p.substr(1))) return true;
// 此处注意,要是s为空了,则直接返回false
else return !s.empty() && isMatch(s.substr(1),p);
}
if(!s.empty() && (p[0]=='?' || p[0] == s[0])) return isMatch(s.substr(1),p.substr(1));
return false;
}
return false;
}
};
我写的动态规划版本
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size(),n = p.size();
vector<vector<bool>> dp(m+1,vector<bool>(n+1,false));
dp[0][0] = true;
for(int i = 0; i <= m; i++){
for(int j = 1; j <= n; j++){
if(i > 0 && (p[j-1] == '?' || p[j-1] == s[i-1])){
dp[i][j] = dp[i-1][j-1];
}
else if(p[j-1] == '*'){
dp[i][j] = dp[i][j-1] || (i > 0 && dp[i-1][j]);
}
}
}
return dp[m][n];
}
};
3.Valid Number(判断是否为数字)
Validate if a given string can be interpreted as a decimal number(十进制数).
Some examples:"0"
=> true
" 0.1 "
=> true
"abc"
=> false
"1 a"
=> false
"2e10"
=> true
" -90e3 "
=> true
" 1e"
=> false
"e3"
=> false
" 6e-1"
=> true
" 99e2.5 "
=> false
"53.5e93"
=> true
" --6 "
=> false
"-+3"
=> false
"95a54e53"
=> false
Note: It is intended for the problem statement to be ambiguous. You should gather all requirements up front before implementing one. However, here is a list of characters that can be in a valid decimal number:
- Numbers 0-9
- Exponent - "e"
- Positive/negative sign - "+"/"-"
- Decimal point - "."
这题真的太无聊了!如果只是穷举做的话
穷举规则:
空格:可以出现在首尾,但是不能出现在中间
+/-:可以出现在首,或者e之后
e:前后必须有数字(重点)
. : 前后必须有一个数字,注意只有一个点的情况(重点)
class Solution {
public:
bool isNumber(string s) {
// 直接为空
if(s.size()==0) return false;
int start = 0,end = s.size()-1;
// flag_e:e是否出现过,flag_point:点是否已出现,flag_num:是否已出现数字
// flag: 是否为正负点e,behind_e_num:e后面是否出现数字,用于最后的判断
bool flag_e = false,flag_point = false,flag_num=false,flag = false,behind_e_num = false;
while(isspace(s[start])) start++;
while(isspace(s[end])) end--;
// 去掉首尾空格后为空,注意不要遗漏
if(start == s.size()) return false;
if(s[start] == '-' || s[start] == '+') start++;
for(int i = start;i <= end;i++){
if(s[i] == 'e'){
flag = true;
if(flag_e || !flag_num) return false;
flag_e = true;
}
if(s[i] == '.'){
flag = true;
// (i == end && !flag_num)即单独一个.的情况,注意不要遗漏
if(flag_e || flag_point || (i == end && !flag_num)){
return false;
}
flag_point = true;
}
if(s[i] == '+' || s[i] == '-'){
flag = true;
if(i == start || s[i-1] != 'e') return false;
}
if(isdigit(s[i])){
flag_num = true;
if(flag_e){
behind_e_num = true;
}
}
else if (!flag) return false;
flag = false;
}
if(flag_e && !behind_e_num) return false;
return true;
}
};
解法2:有限状态自动机
状态0:起始状态
状态1:只有+-号
状态2:只有数字
状态3:只有.
状态4:有.,有数字(前后均可)
状态5:有e,e后没有任何东西
状态6:有e,e后有数字
状态7:有e,e后有+/-
状态8:终止
定好状态后,开始画转化表,注意考虑清楚非法合法的转换
class Solution {
public:
bool isNumber(string s) {
enum input_type {Invalid,
Sign,
Num,
Space,
Dot,
Exponent};
int trans_table[9][6] = {-1,1,2,0,3,-1,
-1,-1,2,-1,3,-1,
-1,-1,2,8,4,5,
-1,-1,4,-1,-1,-1,
-1,-1,4,8,-1,5,
-1,7,6,-1,-1,-1,
-1,-1,6,8,-1,-1,
-1,-1,6,-1,-1,-1,
-1,-1,-1,8,-1,-1};
int state = 0,type;
for(auto it = s.begin();it != s.end();it++){
if(*it == 'e') type = Exponent;
else if(*it == '.') type = Dot;
else if(*it == '+' || *it == '-') type = Sign;
else if(isspace(*it)) type = Space;
else if(isdigit(*it)) type = Num;
else type = Invalid;
state = trans_table[state][type];
if(state == -1) return false;
}
if(state == 2||state == 4||state == 6||state == 8) return true;
else return false;
}
};
Minimum Window Substring(面经头条面试题)
Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).
Example:
Input: S = "ADOBECODEBANC", T = "ABC" Output: "BANC"
Note:
- If there is no such window in S that covers all characters in T, return the empty string
""
. - If there is such window, you are guaranteed that there will always be only one unique minimum window in S.
因为要统计T串中字母的个数,而不是仅仅看某个字母是否在T串中出现。
统计好T串中字母的个数了之后,我们开始遍历S串,对于S中的每个遍历到的字母,都在 HashMap 中的映射值减1,如果减1后的映射值仍大于等于0,说明当前遍历到的字母是T串中的字母,我们使用一个计数器 cnt,使其自增1。
当 cnt 和T串字母个数相等时,说明此时的窗口已经包含了T串中的所有字母,此时更新一个 minLen 和结果 res,这里的 minLen 是我们维护的一个全局变量,用来记录出现过的包含T串所有字母的最短的子串的长度,结果 res 就是这个最短的子串。
然后我们要开始收缩左边界,由于我们遍历的时候,对映射值减了1,所以此时去除字母的时候,就要把减去的1加回来,此时如果加1后的值大于0了,说明此时我们少了一个T中的字母,那么 cnt 值就要减1了,然后移动左边界 left。(然后就去探索右边界,直到少了的T中字母被加回来,cnt又变成T大小)
那么你可能会疑问,对于不在T串中的字母的映射值也这么加呀减呀的,真的大丈夫(带胶布)吗?其实没啥事,因为对于不在T串中的字母,我们减1后,变-1,cnt 不会增加,之后收缩左边界的时候,映射值加1后为0,cnt也不会减少,所以并没有什么影响
class Solution {
public:
string minWindow(string s, string t) {
map<char,int> m;
int left = 0,cnt = 0,min_len = INT_MAX;
string min_win;
for(char &c:t) ++m[c];
for(int i = 0;i < s.size();i++){
// 扩大右边界
if(--m[s[i]] >= 0) cnt++;
while(cnt == t.size()){
if(min_len > i-left+1){
min_len = i-left+1;
min_win = s.substr(left,min_len);
}
// 收缩左边界,由于我们遍历的时候,对映射值减了1
// 所以此时去除字母的时候,就要把减去的1加回来
// 此时如果加1后的值大于0了,说明此时我们少了一个T中的字母,那么 cnt 值就要减1
// 对于不在T串中的字母的映射值,扩大右边界时有减,所以这里加,仍然小于0
if(++m[s[left]] > 0) cnt--;
// 收缩左边界
++left;
}
}
return min_win;
}
};