总结:字符串其实可以看做是字符数组,所以处理字符串实质上就是处理数组问题,在这个模块中可以看到仍有很多题目运用到了数组的经典办法:双指针法,例如题目1,题目2,题目5;还有比较重要的一点是当字符串和数字进行转化的时候要注意整型越界的问题,例如题目2,题目6。
题目1:反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 1:
输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
思路:利用双指针,point1指向字串开头,point2指向字串末尾,将两个指针指向的字符互换;之后point1向后移一位,point2向前移一位,重复上述过程直到双指针相遇,此时字符串已经全部遍历完毕。
void reverseString(vector<char>& s)
{
int len = s.size();
if(len<=1)
return;
int point1 = 0;
int point2 = len-1;
while(point1<point2)
{
char temp = s[point1];
s[point1] = s[point2];
s[point2] = temp;
point1++;
point2--;
}
}
题目2:整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
思路1:将数字转化为字符串之后,对字符串进行倒置处理,最后再转化为数字并返回。
int reverse(int x)
{
if(x>-10&&x<10)
return x;
string s = to_string(x);
int point1 = x>0?0:1;
int point2 = s.size()-1;
while(point1<point2)
{
char temp = s[point1];
s[point1] = s[point2];
s[point2] = temp;
point1++;
point2--;
}
long result = stol(s);
if(result<INT_MIN||result>INT_MAX)
return 0;
return (int)result;
}
思路2:不从字符串的角度考虑,将x从低位到高位逐位分解,每分解出一位数字,先用result乘10再将分解出来的数字加上去,在对result处理时要对result进行判断,如果超出整型范围则直接返回0。
int reverse(int x)
{
if(x>-10&&x<10)
return x;
if(x==INT_MAX||x==INT_MIN)
return 0;
int num = abs(x);
int result = 0;
while(num!=0)
{
int temp = num%10;
if((result>(INT_MAX/10))||(result==(INT_MAX/10)&&temp>7))
return 0;
result = result*10+temp;
num = num/10;
}
if(x<0)
result *= -1;
return result;
}
题目3:字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
案例:
s = "leetcode"
返回 0.
s = "loveleetcode",
返回 2.
思路:声明两个长度为26的数组count和flag,分别记录字串中26个字母出现的次数以及最早出现的下标。依次遍历字串中每个字符,并完成对两个数组的记录。之后遍历count数组找到只出现了一次的字符,最后比较其下标大小,返回下标最小字符。
int firstUniqChar(string s)
{
int result = -1;
int count[26] = {0};
int flag[26] = {0};
for(int i=0;i<s.size();i++)
{
char c = s[i];
int index = c-'a';
if(count[index]==0)
flag[index] = i;
count[index]++;
}
for(int i=0;i<26;i++)
{
if(count[i]==1)
{
if(result==-1||flag[i]<result)
result = flag[i];
}
}
return result;
}
题目4:有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
思路:先比较两字串长度,若长度不同则直接返回false,若相同则用两个长度为26数组count1,count2来分别记录两个字串中各字母出现次数,最后比较两字串中各字母出现次数,若全都相同则返回true,两字串为字母异位词。
bool isAnagram(string s,string t)
{
int slen = s.size();
int tlen = t.size();
if(slen!=tlen)
return false;
int count1[26] = {0};
int count2[26] = {0};
//记录各字母出现次数
for(int i=0;i<slen;i++)
{
count1[s[i]-'a']++;
count2[t[i]-'a']++;
}
//比较次数
for(int i=0;i<26;i++)
{
if(count1[i]!=count2[i])
return false;
}
return true;
}
题目5:验证回文字符串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false
思路:使用双指针,首先在字符串的第一个字母(数字)字符和最后一个字母(数字)字符放置两个指针left,right;比较指针指向字符是否相同(如果两个字符全为字母字符则转化为小写后比较),若不同则返回false,若相同则将left后移至下一个字符处,将right前移至前一个字符处,重复上述过程直到left大于等于right。
ps:还有另外一种思路,将字串中的字母字符和数字字符按顺序放入新字串str中,之后逆置str,和原来的str比较,如果相等则说明是回文字串,反之则不是,没有对这种思路进行实现,但这是处理回文字符串的一种简单方法。
bool isPalindrome(string s)
{
int len = s.size();
if(len==0)
return true;
int left = 0;
int right = len-1;
while(left<right)
{
while((left<right)&&(!isalpha(s[left]))&&(s[left]<'0'||s[left]>'9'))
left++;
while((left<right)&&(!isalpha(s[right]))&&(s[right]<'0'||s[right]>'9'))
right--;
//将两个字母字符转化为小写
if(isalpha(s[left])&&isalpha(s[right]))
{
s[left] = tolower(s[left]);
s[right] = tolower(s[right]);
}
if(s[left]!=s[right])
return false;
//必须进行这次移动否则两指针将原地不动,无限循环
left++;
right--;
}
return true;
}
题目6:字符串转换整数 (atoi)
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−2^31, 2^31 − 1]。如果数值超过这个范围,qing返回 INT_MAX (2^31 − 1) 或 INT_MIN (−2^31) 。
示例 1:
输入: "42"
输出: 42
示例 2:
输入: " -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
示例 3:
输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
示例 4:
输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。因此无法执行有效的转换。
示例 5:
输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。因此返回 INT_MIN (−2^31) 。
思路:遍历数组寻找第一个非空格字符,若第一个非空格字符不是数字字符也不是'-'或'+',则不可以转化;若第一个第一个非空格字符是'-'或'+',则看其后面字符是否为数字字符,若是则对其后数字进行转化,并在最后对结果添加符号; 若第一个第一个非空格字符是数字字符,则直接对其进行转化。要注意一点在转化过程中结果可能会超过int的最大值,所以在转化每一位之前要进行判断是否会超过int最大值,如果会超过则直接返回INT_MAX或INT_MIN。
int myAtoi(string str)
{
int len = str.size();
if(len==0)
return 0;
int result = 0;
//第一个数字字符下标,设置为len是为了防止字符为全空字符
int index = len;
bool flag = false;
for(int i=0;i<len;i++)
{
if(str[i]==' ')
continue;
if((str[i]=='-'||str[i]=='+')&&(i<len-1)&&(str[i+1]>='0'&&str[i+1]<='9'))
{
if(str[i]=='-')
flag = true;
index = i+1;
break;
}
if(str[i]<'0'||str[i]>'9')
return 0;
if(str[i]>='0'&&str[i]<='9')
{
index = i;
break;
}
}
//转化字符为字母
for(int i=index;i<len;i++)
{
if(str[i]>='0'&&str[i]<='9')
{
if((result>(INT_MAX/10))||(result==(INT_MAX/10)&&str[i]-'0'>7))
{
result = flag?INT_MIN:INT_MAX;
return result;
}
result = result*10+(str[i]-'0');
}
else
break;
}
if(flag)
result *= -1;
return result;
}
题目7:实现strStr()
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba"
输出: -1
思路:遍历haystack,寻找与needle开头字符相同的字符,若无相同字符则返回-1,若找到相同字符则比较后续几个字符,若后续几个字符全部和needle中字符一一对应则返回该字符的下标,若后续字符部分和needle中字符不对应则继续遍历haystack并重复上述过程,直到haystack剩余长度小于needle长度。
int strStr(string haystack,string needle)
{
int len = needle.size();
if(len==0)
return 0;
int limit = haystack.size()-len;
for(int i=0;i<=limit;i++)
{
if(haystack[i]==needle[0])
{
int point1 = i;
int point2 = 0;
while(point2<len&&haystack[point1]==needle[point2])
{
point1++;
point2++;
}
if(point2==len)
return i;
}
}
return -1;
}
题目8:报数
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
1 被读作 "one 1" ("一个一") , 即 11。
11 被读作 "two 1s" ("两个一"), 即 21。
21 被读作 "one 2", "one 1" ("一个二" , "一个一") , 即 1211。
给定一个正整数 n(1 ≤ n ≤ 30),输出报数序列的第 n 项。
思路:题目的意思是每一个报数序列是对上一个报数序列的描述。例如题目中第一个报数序列是"1",那么第二个报数序列则是对第一个的描述:1个1,故第二个报数序列为"11";第三个报数序列则是对第二个的描述:2个1,故第三个报数序列为"21";第四个报数序列则是对第三个的描述:1个2,1个1 ,故第四个报数序列为"1211";第五个报数序列则是对第四个的描述:1个1,1个2,2个1,故第五个报数序列为"111221"。所以要得到第n个报数序列需要从第一个序列推导过去。每次都遍历前一个序列,依次将每个数字连续出现的次数和数字本身插入新的序列中,从而产生了新的序列。
string countAndSay(int n)
{
string s = "1";
string temp = "1";
//推导的过程
for(int i=2;i<=n;i++)
{
temp = s;
s = "";
int len = temp.size();
//遍历上一个序列
for(int j=0;j<len;j++)
{
int count = 1;
while(j+1<len&&temp[j+1]==temp[j])
{
count++;
j++;
}
s += count+'0';
s += temp[j];
}
}
return s;
}
题目9:最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
思路:先遍历数组找到数组中字串的最短长度min,也就是公共前缀的最大长度。之后同时比较所有字符串的第i(0到min)个字符,若均相同则将第i位字符加入结果字符串并且i加一,若有不同则返回结果字符串。
string longestCommonPrefix(vector<string>& strs)
{
string result = "";
int len = strs.size();
if(len==0)
return result;
int min = strs[0].size();
//找数组中最短长度
for(int i=0;i<len;i++)
if(strs[i].size()<min)
min = strs[i].size();
//进行比较
for(int i=0;i<min;i++)
{
char c = strs[0][i];
for(int j=1;j<len;j++)
if(strs[j][i]!=c)
return result;
result += c;
}
return result;
}