title: leedcode 初级算法 字符串
tags: [leedcode,算法,字符串]
categories:
- 算法
- leedcode
date: 2021-06-17 00:04:43
leedcode 初级算法 字符串
反转字符转
题目说明
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
题目解析
懒得解析了。
class Solution
{
public:
void reverseString(vector<char> &s)
{
int i = 0;
int j = s.size() - 1;
while (i < j)
{
swap(s[i++], s[j--]);
}
}
};
整数反转
题目说明
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
题目解析
将整数的每一位存在一个vector中,倒序再存入一个数字中。或者就不用这样麻烦,直接从最后一位拿出来放在前面就行。
因为只能使用32位整数,所以需要使用下面的方法进行判断是否超界。
我们需要在「推入」数字之前,判断是否满足
INT_MIN=<ans*10+i<=INT_MAX
INT_MAX=2^31-1=2147483647=[INT_MAX]/10*10+7
所以可以变为
ans*10+i<=[INT_MAX]/10*10+7
所以:
[ans-[INT_MAX]/10]*10<=7-i
当
- ans>[INT_MAX]/10 左边大于10,绝对大于右边。所以不行。
- ans=[INT_MAX]/10 由于原数字符合条件,所以其最高位=1或者2,即7-i>0 左边为零,满足。
- ans<[INT_MAX]/10 显然满足
综上只需判断
ans<=[INT_MAX]/10即可
对于负数同理存在
ans>=[INT_MIN]/10
所以判别条件可变为
[INT_MIN]<=ans<=[INT_MAX]/10
class Solution
{
public:
int reverse(int x)
{
vector<int> v;
int a = x;
while (a != 0)
{
v.push_back(a % 10);
a /= 10;
}
long ans = 0;
for (int i : v)
{
if (ans < INT32_MIN / 10 || ans > INT32_MAX)
return 0;
ans *= 10;
ans += i;
}
if (ans > INT32_MAX)
return 0;
return ans;
}
};
字符串中的第一个唯一字符
题目说明
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
题目解析 方法一
依次存入unordered_multimap<char,int> 第一个是字符,第二个是索引.遍历字符串,设当前字符为,c,则count©==1?,相等则返回直至最后没有相等的返回-1;
class Solution
{
public:
int firstUniqChar(string s)
{
unordered_multimap<char, int> map;
for(int i=0;i<=s.size()-1;i++)
{
map.insert({s[i],i});
}
for (int i = 0; i <= s.size()-1; i++)
{
if (map.count(s[i]) == 1) return i;
}
return -1;
}
};
题目解析 方法二
或者放入unordered_map<char,int> 第一个是字符,第二个是该字符出现的次数。全部插入后,遍历string,寻找map[s[i]]==1;并返回i;
class Solution
{
public:
int firstUniqChar(string s)
{
//字符,出现的次数
unordered_map<char, int> map;
unordered_map<char, int>::iterator it;
for (int i = 0; i <= s.size() - 1; i++)
{
//若不存在的话默认为零
map[s[i]]++;
}
//遍历map
int min = -1;
for (int i = 0; i <= s.size(); i++)
{
if (map[s[i]] == 1)
return i;
}
return min;
}
};
题目解析 方法三
依次存入unordered_map<char,int> 第一个是字符,第二个是索引.遍历字符串插入,若存在则索引变为-1。遍历map,寻找索引不为-1的最小值。
class Solution
{
public:
int firstUniqChar(string s)
{
//字符,索引值
unordered_map<char, int> map;
unordered_map<char, int>::iterator it;
for (int i = 0; i <= s.size() - 1; i++)
{
it = map.find(s[i]);
if (it == map.end())
{
map.insert({s[i], i});
}
else
{
map[s[i]] = -1;
}
}
//遍历map
int min = s.size();
for (it = map.begin(); it != map.end(); it++)
{
if (it->second != -1)
{
if (it->second < min)
min = it->second;
}
}
if (min == s.size())
return -1;
return min;
}
};
题目解析 方法四
unordered_map<char,int> map; //字符和索引值
deque<pair<char,int>> dq; //字符和索引值
使用一个额外的队列来查找最前面的为一个那个字符。遍历字符串,若当前字符再map中未出现的话,就插入map并且从后面插入队列。反之,则令该key在map中的值为-1,将队列中从头开始的所有字符在map对应的value==-1的元素全部弹出。则遍历完字符串之后剩下的队列的front就是所求。
class Solution
{
public:
int firstUniqChar(string s)
{
unordered_map<char, int> map;
deque<pair<char, int>> dq;
for (int i = 0; i <= s.size() - 1; i++)
{
//没找到
if (map.count(s[i]) == 0)
{
map[s[i]] = i;
dq.push_back({s[i], i});
}
else
{
map[s[i]] = -1;
while (!dq.empty() && map[dq.front().first] == -1)
dq.pop_front();
}
}
return dq.empty() ? -1 : dq.front().second;
}
};
有效的字母异位词
题目说明
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。(两个字符串排序后相等)
题目解析 方法一
先判断两个字符串的长度是否相等,若不想等则返回false,使用两个哈希表<char,int>存储字符和出现的次数,在遍历一个哈希表,查找其对应的字符在两个哈希表中出现的次数是否一样?
class Solution
{
public:
bool isAnagram(string s, string t)
{
if (s.size() != t.size())
return false;
unordered_map<char, int> map1;
unordered_map<char, int> map2;
unordered_map<char, int>::iterator it;
for (int i = 0; i <= s.size() - 1; i++)
{
map1[s[i]]++;
map2[t[i]]++;
}
for (it = map1.begin(); it != map1.end(); it++)
{
if (it->second != map2[it->first])
return false;
}
return true;
}
};
但是这么简单的问题使用哈希表就有些小题大做了,直接使用一个数组 vector v(26,0),即可,如下:
class Solution
{
public:
bool isAnagram(string s, string t)
{
if (s.size() != t.size())
return false;
vector<int> v(26, 0);
for (char c : s)
v[c - 'a']++;
for (char c : t)
if (--v[c - 'a'] < 0)
return false;
return true;
}
};
题目解析 方法二
对两个字符串排序之后判断是否相等即可
class Solution
{
public:
bool isAnagram(string s, string t)
{
if (s.size() != t.size())
return false;
sort(s.begin(), s.end());
sort(t.begin(), t.end());
return (s == t);
}
};
验证回文串
题目说明
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
题目解析
先将串中的无关字符舍去并处理相关字符,再使用双指针从前后遍历判断即可。
直接使用双指针在遍历的时候处理也行。
class Solution
{
public:
bool isPalindrome(string s)
{
if (s.empty())
return true;
vector<char> v;
for (char c : s)
{
if (c <= 'Z' && c >= 'A')
v.push_back(c + 32);
else if (c <= 'z' && c >= 'a')
v.push_back(c);
else if (c <= '9' && c >= '0')
v.push_back(c);
}
int i = 0;
int j = v.size() - 1;
while (i < j)
if (v[i++] != v[j--])
return false;
return true;
}
};
字符串转换整数 (atoi)
题目说明
请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
函数 myAtoi(string s) 的算法如下:
读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−2^31, 2^31 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −2^31 的整数应该被固定为 −2^31 ,大于 2^31 − 1 的整数应该被固定为 2^31 − 1 。
返回整数作为最终结果。
注意:
本题中的空白字符只包括空格字符 ’ ’ 。
除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
题目解析
class Solution
{
public:
int myAtoi(string s)
{
/*
读入字符串并丢弃无用的前导空格
检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
如果整数数超过 32 位有符号整数范围 [−2^31, 2^31 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −2^31 的整数应该被固定为 −2^31 ,大于 2^31 − 1 的整数应该被固定为 2^31 − 1 。
返回整数作为最终结果。
*/
bool flag = 0; //正数
bool finish_0 = 0; //前导0是否已经处理完
long ans = 0;
int a = -1;
while (s[++a] == ' ')
;
//a指向第一个不是空格的地方
if (s[a] == '-')
{
flag = 1;
a++;
}
else if (s[a] == '+')
{
flag = 0;
a++;
}
//a指向符号位后一位,若没有符号位则指向第一个可能是数字的那一位。
int b = a;
for (b = a; b <= s.size() - 1; b++)
{
char c = s[b];
if (c <= '9' && c >= '0')
continue;
else
break;
}
b--;
cout << a << b << endl;
//b指向最后一个数字。
for (int i = a; i <= b; i++)
{
int digit = s[i] - '0';
ans = ans * 10 + digit;
cout << digit << " ";
if (ans > INT32_MAX)
{
ans = INT32_MAX;
if (flag == 1)
{
ans++;
}
}
}
cout << endl;
//负数
if (flag == 1)
{
ans = -ans;
}
if (ans > INT32_MAX)
{
ans = INT32_MAX;
}
if (ans < INT32_MIN)
{
ans = INT32_MIN;
}
cout << ans << endl;
return ans;
}
};
实现 strStr()
题目说明
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
题目解析 方法一
暴力判断即可,不过还是要节省一点时间的。
class Solution
{
public:
int strStr(string haystack, string needle)
{
int m = haystack.size();
int n = needle.size();
if (m == 0)
{
if (n == 0)
return 0;
else
return -1;
}
if (n == 0)
return 0;
int i = 0;
int j = 0; //mississippi sipp
while (1)
{
while (1)
{
cout << i << endl;
if (m - i < n)
return -1;
if (haystack[i] != needle[0])
{
i++;
continue;
}
else if (haystack[i + n - 1] != needle[n - 1])
{
i++;
continue;
}
else
break;
}
if (m - i < n)
return -1;
int a = i; //m
int b = 0; //n
while (1)
{
if (b == n)
return i;
if (haystack[a++] != needle[b++])
break;
}
//没找到
i++;
}
}
};
题目解析 方法二
KMP
kmp算法的简单介绍:
两个字符串s1,s2,指针t1,t2分别指向两个字符串中的字符。现在要从s1中寻找s2出现的最早的一次,没有出现的话就返回-1.
普通暴力算法是一个字符一个字符的去匹配,匹配失败了就从头开始并后移一位。
kmp的思想是可不可以在匹配失败之后向后多移动几位呢?
假设在第n位匹配失败,即s1[n]!=s2[n]则t2前移几位,前移的程度要最大,即移动过后t2之前的字符仍然要保持匹配,再比较t2当前和t1是否对应字符相等即可,重复t2==0仍然匹配失败的话说明此处t1指向的字符是不可能正确的,必须略过,所以t1++,之后又开始了新一轮的比较。知道最后匹配成功则返回 t1-s2.size(),失败的条件不在赘述。
前面提到了需要再字符匹配失败之后t2向前移动,但是需要移动几位合适呢?现在t2前面a个字符都是匹配成功的,现在看这a个字符组成字符串a,如果a存在两个真子串相等的话(设为a1,a2),显然a2已经是匹配成功的了,如果此时将a1移动到a2所在的地方那么自然也是匹配成功的了,所以上面提到的将t2前移几位的程度也呼之欲出,即t2要指向子串a1紧紧后面的那一位,这样就最大程度的利用了已知的信息。而这里提到的a1,a2就是a的最长相等真前缀和真后缀,所以问题的重点有落在了怎样求出s2的最长真前缀的长度呢???
我们用数组 kmp<int> 来表示s2每一位对应的最长真前缀的长度。怎么求呢?
明显kmp[0]=0,因为只有一个字符并没有真前缀和真后缀。我们这里使用两个指针now和x分别指向a1,a2要进行匹配的字符。
如果s2[x]==s2[now]则显然可以匹配的字符数量要加1,即kmp[x]==kmp[x-1]+1;
如果不相等的话,就需要查看前面的就要像上面s2和s1进行你匹配是那样指针前移,这里又要移动几位呢?和上面的分析一样,显然kmp[now-1]个字符已经匹配成功,所以需要将**now=kmp[now-1]**在进行s2[x]s2[now]是否相等的比较了,不成立的话再进行now=kmp[now-1];若now0,则判断s2[0]==s2[x],不等的话说明连开头和结尾的字符都不想等,则显然kmp[x]=0;好了,算出了kmp[x],x++,进行下一论循环即可。
哈哈哈,终于写明白了。
class Solution
{
public:
int strStr(string haystack, string needle)
{
if (needle.empty())
return 0;
if (haystack.empty())
return -1;
string s1(haystack);
string s2(needle);
cout << s1 << endl
<< s2 << endl;
//kmp:
vector<int> kmp(s2.size(), 0);
kmp[0] = 0;
for (int i = 1; i <= s2.size() - 1; i++)
{
if (s2[kmp[i - 1]] == s2[i])
{
kmp[i] = kmp[i - 1] + 1;
}
else
{
int x = i;
int now = kmp[i - 1];
while (1)
{
//aabaaac
cout << now << x << endl;
if (s2[now] == s2[x])
break;
if (now >= 1)
now = kmp[now - 1];
if (now <= 0)
{
now = -1;
if (s2[0] == s2[x])
now = 0;
break;
}
}
kmp[i] = now + 1;
}
}
for (int x : kmp)
cout << x << " ";
cout << endl;
cout << "匹配" << endl;
//匹配
int t1 = 0;
int t2 = 0;
while (1)
{
cout << "t1: " << t1 << " t2: " << t2 << endl;
//成功
if (t2 == s2.size())
{
return t1 - s2.size();
}
//失败
if (t1 == s1.size() && t2 != s2.size())
{
return -1;
}
//匹配该字符失败
if (s1[t1] != s2[t2])
{
if (t2 != 0)
{
t2 = kmp[t2 - 1];
}
else
{
t1++;
}
}
else //匹配该字符成功
{
t1++;
t2++;
}
}
}
};
题目解析 方法三
还有一个基于kmp算法的改进版。我们将两个字符串拼接起来为s,并使用一个两串中都不存在的字符’#‘作为分隔符(如 s1=“aaa” s2=“aa” 的情况,如果中间没有#的话就不行,因为这种情况无论如何算kmp的时候都会算上两个子串的公共部分)。s=s2+’#’+s1; 别加反了。。。
遍历s,根据上面的算法求解s的kmp数组,若kmp[n]==s2.size()且n>=2*s2.size(),这样保证是s1的子串和s2匹配,而不是s2的或者s1和s2中间共有的。结果返回i - 2 * s2.size()即可。
class Solution
{
public:
int strStr(string haystack, string needle)
{
if (needle.empty())
return 0;
if (haystack.empty())
return -1;
string s1(haystack);
string s2(needle);
string s = s2 + "#" + s1;
cout << s1 << endl
<< s2 << endl
<< s << endl;
//kmp:
vector<int> kmp(s.size(), 0);
kmp[0] = 0;
for (int i = 1; i <= s.size() - 1; i++)
{
if (s[kmp[i - 1]] == s[i])
{
kmp[i] = kmp[i - 1] + 1;
}
else
{
int x = i;
int now = kmp[i - 1];
while (1)
{
//aabaaac
//cout << now <<" "<< x <<s[now]<<" "<<s[x]<< endl;
if (s[now] == s[x])
break;
if (now >= 1)
now = kmp[now - 1];
if (now <= 0)
{
now = -1;
if (s[0] == s[x])
now = 0;
break;
}
}
kmp[i] = now + 1;
}
if (kmp[i] == s2.size())
{
//cout<<"相等"<<kmp[i]<<" "<<i<<endl;
if (i >= 2 * s2.size())
{
//cout<<"匹配"<<kmp[i]<<" "<<i<<endl;
return (i - 2 * s2.size());
}
}
}
return -1;
}
};
外观数列
题目说明
给定一个正整数 n ,输出外观数列的第 n 项。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
你可以将其视作是由递归公式定义的数字字符串序列:
countAndSay(1) = “1”
countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。
-
1
-
11
-
21
-
1211
-
111221
第一项是数字 1
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 “11”
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 “21”
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 “1211”
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 “111221”
题目解析
很好理解题意,就是遍历并且判断数量即可。
使用一个数组vector<vector> v来保存结果。
class Solution
{
public:
string countAndSay(int n)
{
string s = "1";
vector<vector<int>> v;
//cout<<"1"<<endl;
vector<int> temp;
v.push_back(temp);
v[0].push_back(1); //1
for (int i = 1; i <= n - 1; i++)
{
//每一轮循环pushback进去一个temp来申请内存空间。v[i]
v.push_back(temp);
//遍历v[i-1] 并得到结果
int j = 0;
while (j <= v[i - 1].size() - 1)
{
int count = 0;
int num = v[i - 1][j];
int k = j;
while (k <= v[i - 1].size() - 1)
{
if (v[i - 1][k] == num)
{
k++;
count++;
}
else
break;
}
j = k;
v[i].push_back(count);
v[i].push_back(num);
}
s.clear();
for (int j = 0; j <= v[i].size() - 1; j++)
{
char c = v[i][j] + '0';
s.push_back(c);
}
//cout << s<<endl;;
}
return s;
}
};
最长公共前缀
题目说明
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
题目解析 方法一
纵向扫描,若不符合条件则退出。
class Solution
{
public:
string longestCommonPrefix(vector<string> &strs)
{
string s = "";
int min = INT32_MAX; //记录最短的长度
int strnum = strs.size();
vector<string>::iterator it;
//就一个串 或者没有串
if (strs.size() <= 1)
return strs[0];
//判断是否存在空字符串
for (it = strs.begin(); it != strs.end(); it++)
{
min = (min < it->size()) ? min : it->size();
cout << min << endl;
}
if (min == 0)
return s;
cout << "至少两个字符串,而且没有空串" << endl;
int index = 0;
int v = 0;
for (index = 0; index <= min - 1; index++)
{
//记录当前要比较的字符
char c = strs[0][index];
int flag = 0; //不存在不相等的。
for (v = 0; v <= strnum - 1; v++)
{
if (strs[v][index] != c)
{
flag = 1;
}
}
//当前字符串通过考核
if (flag == 0)
{
s.push_back(c);
}
else
{
//未通过
break;
}
}
return s;
}
};
题目解析 方法二
横向扫描,遍历字符串数组,得到相邻字符串的公共前缀,在用这个公共前缀与下一个字符串进行比较。
class Solution
{
public:
string longestCommonPrefix(vector<string> &strs)
{
string s = strs[0];
int strnum = strs.size();
for (int i = 1; i <= strnum - 1; i++)
{
s = CommonPrefix(s, strs[i]);
cout << s << endl;
}
return s;
}
string CommonPrefix(string &s1, string &s2)
{
string s = "";
int len = (s1.size() > s2.size()) ? s2.size() : s1.size();
if (len == 0)
return s;
for (int i = 0; i <= len - 1; i++)
{
if (s1[i] == s2[i])
s.push_back(s1[i]);
else
break;
}
return s;
}
};
题目解析 方法三
分治
class Solution
{
public:
string longestCommonPrefix(vector<string> &strs)
{
string s = "";
if (strs.size() == 0)
return s;
if (strs.size() == 1)
return strs[0];
s = Prefix(strs, 0, strs.size()); //前闭后开
return s;
}
string Prefix(vector<string> strs, int begin, int end)
{
int mid = (begin + end) / 2;
//就一个字符串
if (end - begin == 1)
return strs[begin];
else if (end - begin == 2) //正好两个串
{
return CommonPrefix(strs[begin], strs[begin + 1]);
}
else
{
return CommonPrefix(Prefix(strs, begin, mid), Prefix(strs, mid, end));
}
}
string CommonPrefix(string s1, string s2)
{
string s = "";
int len = (s1.size() > s2.size()) ? s2.size() : s1.size();
if (len == 0)
return s;
for (int i = 0; i <= len - 1; i++)
{
if (s1[i] == s2[i])
s.push_back(s1[i]);
else
break;
}
return s;
}
};