原文作者:aircraft
原文链接:https://www.cnblogs.com/DOMLX/p/11089327.html
已经刷了很多篇leetcode题了,不过最近在找c++的实习工作(大佬们有推荐的吗QAQ),现在慢慢补上吧
虽然有点烦,但是还是要提一句,刷leetcode题目的时候,最好还是自己先思考然后写出一个通过的代码,再去看其他人的代码参考比较好,当然了,写不出来就当我没说。尽力而为即可。
一套面试题的目录在此,还在继续完善中。。。。。。
c/c++ 2019面试题目录
一、反转字符串
class Solution {
public:
void reverseString(vector<char>& s) {
int len = s.size();
int i = 0,j = len - 1;
while(i < j){
s[i] = s[i]^s[j];
s[j] = s[i]^s[j];
s[i] = s[i]^s[j];
i++;
j--;
}
}
};
我的思路就是很简单的用两个i,j 一个由前向后移动,另外一个反之。这样就可以不断的把头尾字符串交换了,当i<j时就说明已经交换完毕了结束循环。
看看大佬们的代码:44ms左右
class Solution
{
public:
void reverseString(vector<char>& s)
{
for (int i = 0; i < s.size() / 2; i++)
swap(s[i], s[s.size() - i - 1]);
}
};
好吧,看来跟我的差别不是很大,就是调用了库函数交换,以及省略些判断过程。
二、整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
我的代码:8ms左右
class Solution {
public:
int reverse(int x) {
int tem = 0;
if(x > 0){
while(x != 0){
if(tem > (INT_MAX - (x % 10)) / 10 )return 0;
tem = tem * 10 + x % 10;
x /= 10;
}
}
else{
while(x != 0){
if(tem < (INT_MIN - (x % 10)) / 10)return 0;
tem = tem * 10 + x % 10;
x /= 10;
}
}
return tem;
}
};
这道题的思路很简单,在大一学c语言的时候就遇到过将数字反转的测试题目,只要对X%10就能将其个位数字保留下来,然后tem = tem * 10 + x % 10; 就相当于将原来的末尾数保留下来,x /= 10; 已经拿到个位数了,那么就将原来的数除以10,将原来的十位数字移动到个位,这样组合起来就是每加入一个数就将上一个数值乘10加上后来的个位数字即可,
比如21----%10,就保留1,然后将21/10,并且因为是整型数据所以剩下2,然后2%10,还是2,tem = tem * 10 + x % 10;这样就是把1乘10+2,就将数字反转了。
不过这个跟大一的题目比较多了一个环境只能存储32 位的有符号整数,则其数值范围为 [−231, 231 − 1]。这其实就是有符号的Int型的存储范围,最大可以用内置的INT_MAX 表示,最小则是INT_MIN。
也就是说我们的环境是不予许出现超出这两个范围的数字的,那么我们一开始想的肯定是加一个判断--比如:tem * 10 + x % 10>INT_MAX就结束循环,或者tem * 10 + x % 10<INT_MIN,这个思路是没有问题的,不过在最后如果因为判断条件成立要结束这个循环的话,tem * 10 + x % 10 有些值必然会超过环境允许的范围,而报错,那么我们就换一个写法,数学的在大于小于符号左右移动数值的方法大家都学过吧。
我们只要将tem * 10 + x % 10>INT_MAX 变成 tem < (INT_MIN - (x % 10)) / 10 就不会在某一次判断的时候出现超过环境允许范围的数值了。
看看大佬们的代码:8ms左右
class Solution {
public:
int reverse(int x) {
int sum = 0;
for (; x != 0; x /= 10) {
int yu = x % 10;
if (sum > (INT_MAX - yu) / 10) return 0;
if (sum < (INT_MAX - yu) / 10) return 0;
sum = sum * 10 + yu;
}
return sum;
}
};
这个思路跟我一样,不过我一开始写的时候觉得需要对于正数负数需要分开判断,其实不需要,这样写就简化美观了很多。
大佬的代码:1ms左右
class Solution {
public:
int reverse(int x) {
if(x == 0) return 0;
else
{
int flag = 1;
if(x > 0) flag = 1;
else flag = -1;
long long l = abs((long long)(x));
long long res = 0;
int ans;
while(l > 0)
{
int temp = l % 10;
res = res*10 + temp;
l = l / 10;
}
if(flag == 1) ans = res > INT_MAX ? 0:res;
else ans = res*flag < INT_MIN ? 0:res*flag;
return ans;
}
}
};
这个代码就是直接用64位的环境变量来存储,最后加一个判断,不过题目好像说只能在32的环境下吧,这个应该是为了刷速度写的。
三、字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
案例:
s = "leetcode"
返回 0.
s = "loveleetcode",
返回 2.
注意事项:您可以假定该字符串只包含小写字母。
我的代码:80ms
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char,int> umap;
int len = s.size();
if(len == 1) return 0;
for(auto s1:s){
umap[s1]++;
}
for(int i = 0; i < len; i++){
if(umap[s[i]] == 1) return i;
}
return -1;
}
};
我的思路就是将每个数字都存储起来然后计数,计数为1就是那一个字符了,不过呢,这样简单的东西我用了需要做很多特殊处理的unorder_map容器就比较花时间了,这个思路的还是用数组来存储会快上很多,比如下面的代码
20ms左右
class Solution {
public:
int firstUniqChar(string s) {
static const auto io_sync = []{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
int times[26]={0};
for(int i=0;i<s.size();i++){
times[s[i]-'a']++;
}
for(int i=0;i<s.size();i++){
if(times[s[i]-'a']==1)
return i;
}
return -1;
}
};
还有12ms大佬的代码:
class Solution {
public:
int firstUniqChar(string s) {
static const auto io_sync = []{
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
}();
int num = 0, len = s.length();
int a[26] = {0}, pos[26] = {0};
char c[26];
for(int i = 0; i < len; i++){
if(a[s[i] - 'a'] == 0){
c[num++] = s[i];
pos[s[i] - 'a'] = i;
}
a[s[i] - 'a']++;
}
for(int i = 0; i < num; i++){
if(a[c[i] - 'a'] == 1){
return pos[c[i] - 'a'];
}
}
return -1;
}
};
这个就是将前面那个20ms的代码优化了一下,我们从string类型中拿数据的话要比从一个char数组里拿数据要做的工作会多很多。
四、有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
我的代码:16ms左右
class Solution {
public:
bool isAnagram(string s, string t) {
unordered_map<char,int> umap_s,umap_t;
int len_s = s.size();
int len_t = t.size();
if(len_s != len_t) return false;
for(int i = 0; i < len_s; i++){
umap_s[s[i]]++;
umap_t[t[i]]++;
}
for(auto ch:umap_s){
if(umap_s[ch.first] != umap_t[ch.first]) return false;
}
return true;
}
};
就是像unordered_map以及map类的容器真的是非常的好用,在一些局部代码或者是小型项目,对速度的需要没有确切需求的地方都可以很好的使用。
我的思路就是用两个容器分别存储,然后计数,如果双方某一个字符的计数值不同,那么肯定不是异位字符串了。
但是我们其实用char或者int类型的数组来操作就可以做到了,并且速度优化很多。
大佬们的代码:1ms左右
class Solution {
public:
bool isAnagram(string s, string t) {
//字母异位词,指的是字母相同,排列不同的单词
if (s.size() != t.size()) return false;
int m[26] = {0};
for (int i = 0; i < s.size(); ++i) ++m[s[i] - 'a'];
for (int i = 0; i < t.size(); ++i){
if (--m[t[i]-'a'] < 0) return false;
}
return true;
}
};
这个代码就简化了很多,并且提高的速度,直接用一个int型的数组存储第一个字符串的-‘a'之后的ASCII的值,其他没出现的都置为0,然后第二个for循环就是把第二个字符串的数值都拿去跟第一个通过相减的方式比对,因为你有一个,我也有 一个那么减后就是为0,而如果我有你没有,那么减后就会小于0,这样判断是有大前提的s.size() != t.size(),两个的size是相同的。
五、验证回文字符串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false
我的代码:20ms
class Solution {
public:
bool isPalindrome(string s) {
int len = s.size();
if(len == 1) return true;
int i = 0,j = len - 1;
while(i < j){
if(s[i] >= '0' && s[i] <= '9' || s[i] >= 'A' && s[i] <= 'Z' || s[i] >= 'a' && s[i] <= 'z');
else{
i++;
continue;
}
if(s[j] >= '0' && s[j] <= '9' || s[j] >= 'A' && s[j] <= 'Z' || s[j] >= 'a' && s[j] <= 'z');
else{
j--;
continue;
}
if(s[i] >= 'a' || s[j] >= 'a'){
if(abs(s[i] - s[j]) == 0 || abs(s[i] - s[j]) == 32){
i++;
j--;
}
else return false;
}
else if(abs(s[i] - s[j]) == 0){
i++;
j--;
}
else return false;
}
return true;
}
};
我的思路就是忽略掉大小写的影响,以及不属于数字或者字符的影响,剩下的用两个指针来移动对比,一个头指针,一个尾,如果有一个比对不成立,那么就不是回文字符串,这个代码有很多可以优化的地方,比如判断是字符或者数字,都可以直接使用库函数,然后题目说忽略大小写的影响,那么干脆把所有字符串都换成大写或者小写在来比对好了。。。。反正当时我是没有想到用库函数,都是自己写了。
大佬们的代码:1ms左右
class Solution {
public:
bool isPalindrome(string s) {
int lefts=0;
int rights=s.size()-1;
while(lefts<rights)
{
while(!isalnum(s[lefts]))
{
lefts++;
if(lefts>=rights)
return true;
}
while(!isalnum(s[rights]))
{
rights--;
if(lefts>rights)
return false;
}
if(tolower(s[lefts])!=tolower(s[rights]))
return false;
lefts++;
rights--;
}
return true;
}
};
这个代码就是一个外循环来遍历,而里面的两个while则是用来分别跳过左边不是数字或者字符的数,然后在将左边的值与右边的值都转为小写比对,若是不相同那么就不是回文字符串了。
isalnum()用来判断字符或者数字,tolower()转为小写。
六、字符串转整数
请你来实现一个 atoi
函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,qing返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 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
) 。
我的代码:8ms左右
class Solution {
public:
int myAtoi(string str) {
int sign = 0,res = 0,i = -1,len = str.size();
if(len < 1) return 0;
while(i < len && str[++i] == ' ');
if(i >= len) return 0;
if(str[i] == '-'){
sign = -1;
i++;
}
else if(str[i] == '+') i++;
if(sign != -1){
while(str[i] >= '0' && str[i] <= '9'){
if(res > ((INT_MAX - (str[i] - '0')) / 10)) return INT_MAX;
res = res * 10 + (str[i]-'0');
i++;
}
return res;
}
while(str[i] >= '0' && str[i] <= '9'){
if(-res < ((INT_MIN + (str[i] - '0') ) / 10) ) return INT_MIN;
if(res * 10 > (INT_MAX -(str[i] - '0')) ){
return -res * 10 - (str[i]-'0');
}
res = res * 10 + (str[i]-'0');
i++;
}
return -res;
}
};
字符串转数字这个代码应该初学c语言的时候大家都学过,而这个题目就是加一个限制,环境只能容纳32位有符号整型的值。所以要多加点判断。
字符串转数字无非就是res * 10 + (str[i]-'0'); 将字符的值-‘0’的值,然后如果刚转的这个数前面已经有数字存在了,那么就把前面的那个高位数乘10提高一个位数加上去就行了。
而要加判断的话,大于0的值比较,一般是这样的想法:res * 10 + (str[i]-'0');>INT_MAX就不成立,然而环境不允许存在比INT_MAX大的值,那么我们就移动一下比较符号左右的运算,变成这样:
res > ((INT_MAX - (str[i] - '0')) / 10);这样的预判式子就不会突破环境变量的限制了。
小于0的话就这样改:-res < ((INT_MIN + (str[i] - '0') ) / 10);
大佬们的代码:1ms左右
class Solution {
public:
int myAtoi(string str) {
int cur = 0;
int len = str.length();
int flag = 1;
int ret = 0;
while(cur < len && str[cur]==' ')
{
cur ++;
}
if(cur ==len)
return 0;
else if(str[cur]=='-')
flag = -1;
else if(str[cur]=='+')
flag = 1;
else if(isdigit(str[cur]))
ret = str[cur] -'0';
else
return 0;
cur +=1;
while(cur < len && isdigit(str[cur]))
{
int temp = (str[cur] -'0') * flag;
if (ret > INT_MAX / 10 || ret == INT_MAX / 10 && temp > 7)
return INT_MAX;
if (ret < INT_MIN / 10 || ret == INT_MIN / 10 && temp < -8)
return INT_MIN;
ret =ret *10 + temp;
cur ++;
}
return ret;
}
};
这个代码就比我的优化很多了,我的分别把正负值分开算,多了很多代码和判断,而这里直接放在一个循环里,通过flag来确定符号就挺好的。
七、实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba"
输出: -1
说明:
当 needle
是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle
是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
我的代码:4ms
class Solution {
public:
int strStr(string haystack, string needle) {
int ht_len = haystack.size();
int nl_len = needle.size();
int len = ht_len - nl_len;
int i, j = 0;
if(nl_len == 0) return 0;
for(i = 0; i <=len; i++){
if(haystack[i] == needle[j]){
while(j < nl_len && haystack[i + j] == needle[j]) j++;
if(j == nl_len) return i;
j = 0;
}
}
return -1;
}
};
我的思路就是:从第一个字符串中找第二个子串出现的位置,那么只要从第一个串里面找到第二个子串相匹配的字符,然后记住位置,遍历一个子串长度的数据对比,都相同的话就代表找到了。
同样的,假如第二个字符串长度为3,第一个为6,那么第一个字符串到第四个位置之后就没有必要再去比较判断了,所以真正可以比较长度就是len1 - len2的长度。
大佬们的代码:1ms左右
class Solution {
int issame(string haystack, string needle,int k)
{
if(haystack.length()-k<needle.length())
return -1;
for(int i=0;i<needle.length();i++)
{
if(haystack[i+k]!=needle[i])
return issame(haystack,needle,k+1);
}
return k;
}
public:
int strStr(string haystack, string needle) {
if(needle=="")
return 0;
return issame(haystack,needle,0);
}
};
这个大佬用递归的方法不断的递归寻找到第二串的第一个字符在第一个串出现的位置,找到后就遍历比对,若不成立则继续递归并且增加k值,直到k值不满足haystack.length()-k<needle.length()这个条件就说明不存在,返回-1.利用了空间换时间的方法,增加了速度,而且编程之美里面说过,“迭代是人,递归为神”,能用递归的方法会使得代码更加简洁和快速,不过要注意堆栈的溢出问题就是了。
八、报数
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:
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"
示例 2:
输入: 4
输出: "1211"
我的代码:8ms左右
class Solution {
public:
string countAndSay(int n) {
vector<string> list;
int len;
int j = 0;
int count = 1;
string temp = "";
string ch = "";
list.emplace_back("1");
for (int i = 1; i < n; i++) {
len = list[i - 1].size();
while (j < len) {
ch = list[i - 1][j];
if (j == (len - 1)) {
temp += "1" + ch;
break;
}
while (j < len - 1 && list[i - 1][j] == list[i - 1][j + 1]) {
count += 1;
j++;
}
temp += to_string(count) + ch;
count = 1;
j++;
}
list.emplace_back(temp);
temp = "";
j = 0;
}
return list[n - 1];
}
};
这题理解题目的意思就比较重要了,一开始我没理解写了很多都通过不了,自己也找不到原因。后来是删除了所有代码重新思考了一遍才写出来。
反正就是下一个数就是把上一个数字按照一定语法来解读,比如1211后面的111221,因为1211是先一个1 所以为11,然后是一个2 所以为 12,然后两个1 为21,全部组合起来就是111221,是这么个意思,所以我们就用上一个不断的去推出下一个,这就是一个迭代过程。
那我们只需要纪录下第一个字符,然后计算有几个一样的写上就好,遇到不一样的在替换纪录的字符继续计数,比如1112,三个1=31,替换为2,有一个2=12 组合起来就是3112
看看大佬们的代码:1ms左右
class Solution {
public:
string countAndSay(int n) {
string str = "1";
if(n == 1) return str;
for(int i = 2; i <= n; ++i)
{
string tmp = "";
char cur = str[0];
int cnt = 1;
for(int j = 1; j < str.length(); ++j)
{
if(str[j] != cur)
{
tmp.push_back(char(cnt+'0'));
tmp.push_back(cur);
cur = str[j];
cnt = 1;
}
else ++cnt;
}
tmp.push_back(char(cnt+'0'));
tmp.push_back(cur);
str = tmp;
}
return str;
}
};
大佬的思路跟我差不多,不过写法比我优化很多。
九、最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z
。
我的代码:20ms
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty()) return "";
int len = INT_MAX;
string temp;
for(auto str:strs){
if(str.size() < len) len = str.size();
}
for(int i = 0; i < len; i++){
temp = strs[0].substr(0,i + 1);
for(auto str:strs){
if(str.substr(0,i + 1).find(temp) == str.npos) return strs[0].substr(0,i);
}
}
return strs[0].substr(0,len);
}
};
题目的意思很清楚,也不需要什么思路就是比较就是了,我的是一次把多个字符切割好去比较,直到最后一次比较不成功,之前的就是最长公共前缀,这样看起来代码很清楚,不过效率特别慢,因为中间做了很多很多事。
大佬们的代码:1ms左右
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string res;
int n=strs.size();
if(n==0)
return res;
else if(n==1)
return strs[0];
char c;
for(int i=0;;i++){
c=strs[0][i];
for(int j=1;j<n;j++){
if(c=='