校招期间刷题做的一些笔记,主要配合leetcode, acwing平台使用。
这些题目是20年疫情期间一个一个做的,一开始啥也不会,acwing的一些视频对我算法学习帮助很大。
最近在回顾很多东西,发的日记,印象笔记里面的笔记,这些算法题现在没啥兴趣做了,但是我觉得以后有一天总还是要用得上的。
3.无重复字符的最长子串
主要思想:滑动窗口,双指针
数据结构:set,map 可以通过map优化性能,更加快速更新左边界
使用set 通过insert(),erease()来更新集合元素
方法1.set
1 | int lengthOfLongestSubstring(string s) { |
方法2.map
参考acwing 提供了一种类似写法, 用哈希表存字符出现次数,当字符个数大于2即出现重复字符
1 | unordered_map<char,int> ma; |
CountandSay
是一道老题目了
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:
- 1
- 11
- 21
- 1211
- 111221
1 被读作 “one 1” (“一个一”) , 即 11。
11 被读作 “two 1s” (“两个一”), 即 21。
21 被读作 “one 2”, “one 1” (”一个二” , “一个一”) , 即 1211。
给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。
解题思路:
- 外层按照给定层次n 迭代
- 内层对当前层次处理每一个字符,统计其个数
要点:
- 数字转化为字符 通过加‘0’ 或者 to_string
- 边界处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19string countAndSay(int n) {
if(n==1) return string("1");
string res="1";
for(int i=1;i<n;i++){
string buf;
int str_len=res.size();
for(int j=0;j<str_len;j++){
int count=1;
while(res[j]==res[j+1]&&j+1<str_len){
j++;
count++;
}
buf.push_back(count+'0');
buf.push_back(res[j]);
}
res=buf;
}
return res;
}
49. 字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
考点:
无序字符串排序+哈希表
1
2
3
4
5
6
7
8
9
10
11
12
13vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string,vector<string>> hash_map;
for(auto str: strs){
string key=str;
sort(key.begin(),key.end());
hash_map[key].push_back(str);
}
vector<vector<string>> res;
for(auto c: hash_map){
res.push_back(c.second);
}
return res;
}
151. 翻转字符串中的单词 时间O(n) 空间O(1)
给定一个字符串,逐个翻转字符串中的每个单词。
比如说:
输入: “the sky is blue”
输出: “blue is sky the”
如果是翻转整个的字符串呢?
解题思路:
- 先翻转字符串中的每一个单词
- 在翻转整个字符串
技能点
- 遍历去除字符串开始空格
- 从字符串提取单个单词,处理,注意index的边界判定
- 思想in-place 替代字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16string reverseWords(string s) {
int index=0;
for (int i = 0; i < s.size(); i++)
{
while(s[i]==' ') i++; //去除连续空格
if(s.size()==i) break;
int j=i;
while(s[j]!=' '&&j<s.size())j++;
reverse(s.begin()+i,s.begin()+j);
if(index) s[index++]=' ';
while(i<j) s[index++]=s[i++]; // 这个很巧妙 in-place
}
s.erase(s.begin()+index,s.end()); // 可能有多余空格情况
reverse(s.begin(),s.end());
return s;
}
联想
这个题目起始也可以用来去除字符串中的空格,面试时候经常考察。
要求
- 去除字符串开始所有空格,中间的连续空格只保存一个,末尾的空格全部去除
1
2
3
4
5
6
7
8
9
10string s;
int n=s.size();
int index=0,i=0;
while(i<n){
while(s[i]==' ') i++;
if(i==s.size()) break; //为了防止已经到末尾加上空格
if(index) s[index++]=' ';
while(s[i]!=' ')s[index++]=s[i++];
}
s.erase(k);
165. 版本号比较
难点:
- 前导0的处理
提取符合特定要求的子字符串模式:
1 | int j=i; |
atoi() 函数用法
substring()用法
完整参考代码:
1 | int compareVersion(string s1, string s2) { |
两个字符串,两个数组通过while(s1.size()||s2.size())
来控制循环流
929. 独特的电子邮件地址
思路:
- 分离本地和域名
- 按照+,.规则来处理本地
- 链接成新的邮件地址 存入到unordered_set小技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int numUniqueEmails(vector<string>& emails) {
unordered_set<string> hash_set;
for(auto email:emails){
int i=0;
while(email[i]!='@') i++;
string local =email.substr(0,i);
string prefix=email.substr(i+1,email.size()-i);
string newlocal;
for(auto c: local){
if(c=='+') break;
if(c!='.') newlocal+=c;
}
string s=newlocal+'@'+prefix;
hash_set.insert(s);
}
return hash_set.size();
} - substr() 默认位置到 end() 因此在提取domin 的时候可以简写为
email.sub(i+1);
- 在寻找‘@’ 可以通过 string.find() http://www.cplusplus.com/reference/string/string/find/
1 | if(c=='+') break; |
这个是有顺序的,第二次写的时候给疏忽了。
5.最长回文子串
思路: 暴力枚举 时间复杂度 O(n*n)
- 以中心点 分为奇偶向两边扩展
1
2
3
4
5
6
7
8
9
10
11
12string longestPalindrome(string s) {
string res;
for (int i = 0; i < s.size(); i++)
{
// 对奇数长度
for( int j=i,k=i;j>=0&&k<s.size()&&s[k]==s[j];j--,k++)
if(res.size()<k-j+1) res=s.substr(j,k-j+1);
for(int j=i,k=i+1;j>=0&&k<s.size()&&s[k]==s[j];j--,k++)
if(res.size()<k-j+1) res=s.substr(j,k-j+1);
}
return res;
}在这里总结下判断回文字符串的几种常见方法
- 双指针从首尾向中间靠拢
- 从中间向两边,但是需要注意考虑奇偶情况,如果仅仅只是判断单个的字符串是否满足回文
这样来做是划不来的,调用reverse()
直接将字符串翻转比较
2020年2月11日
6. Z字形变换
这道题目描述有点长,属于一找规律的题目
关键规律:行数 n
自己画一下 Z图发现 首尾行为等差数列 2(n-1)
其他行为 两个交错的等差数列 第二个等差数列的首相需要注意下
以及对于边界条件的判断 n==1
1 | string convert(string s, int n) { |
208.前缀树(trie)
这个概念之前没有接触过
基本数据结构
- 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符都不相同。
以该题为例 都是小写字母 则有26个子节点
每个字符串需要有一个标志结尾,考虑上述因素定义如下数据结构
1 | struct node{ |
开始写的时候对面向对象的知识都丢了,不记得怎么来 new 一个对象。。。
trie 基础
目的:高效存储和查找字符串集合
特点:要么都是小写,大写,或者数字
使用数组来实现trie基本操作
1 | int son[N][26], cnt[N], idx; |
trie 不仅能用来存储字符串,也能够存储0-1bit,如下面例子
421. 数组中两个数的最大异或值
不要觉得构建字典树麻烦,能对时间性能有很大提升。
思路
按整数从高位到低位异或比较,在字典树中查找最大的异或对
这里看起来比较复杂的点在于trie的构建,需要自己定义数据结构
1 | struct node{ |
时间复杂度分析,树的查找时间复杂度为logn, 循环n次,所以整个的时间复杂度为nlogn, 但是题目里面的要求是O(n), 如何进一步优化
1371. 每个元音包含偶数次的最长子字符串
给你一个字符串 s,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 ‘a’,’e’,’i’,’o’,’u’,在子字符串中都恰好出现了偶数次。
解题思路: 哈希表+状态压缩+前缀和
将5个元音字母出现次数的奇偶视为一种状态,一共有32种状态,不妨使用一个整数代表状态,第0位为1表示a出现奇数次,第一位为1表示e出现奇数次……以此类推。仅有状态0符合题意。
而如果子串[0,i]与字串[0,j]状态相同,那么字串[i+1,j]的状态一定是0,因此可以记录每个状态第一次出现的位置,此后再出现该状态时相减即可。
1 | int findTheLongestSubstring(string s) { |
227. 基本计算器 II(加减乘除表达式)
1 | int calculate(string s) { |