目录
1. 每日一题
给你一个字符串 s
和一个字符 c
,且 c
是 s
中出现过的字符。
返回一个整数数组 answer
,其中 answer.length == s.length
且 answer[i]
是 s
中从下标 i
到离它 最近 的字符 c
的 距离 。
两个下标 i
和 j
之间的 距离 为 abs(i - j)
,其中 abs
是绝对值函数。
示例 1:
输入:s = "loveleetcode", c = "e" 输出:[3,2,1,0,1,0,0,1,2,2,1,0] 解释:字符 'e' 出现在下标 3、5、6 和 11 处(下标从 0 开始计数)。 距下标 0 最近的 'e' 出现在下标 3 ,所以距离为 abs(0 - 3) = 3 。 距下标 1 最近的 'e' 出现在下标 3 ,所以距离为 abs(1 - 3) = 2 。 对于下标 4 ,出现在下标 3 和下标 5 处的 'e' 都离它最近,但距离是一样的 abs(4 - 3) == abs(4 - 5) = 1 。 距下标 8 最近的 'e' 出现在下标 6 ,所以距离为 abs(8 - 6) = 2 。
示例 2:
输入:s = "aaab", c = "b" 输出:[3,2,1,0]
提示:
1 <= s.length <= 104
s[i]
和c
均为小写英文字母- 题目数据保证
c
在s
中至少出现一次
流下不学无术的眼泪,先是暴力模拟了一通,大致思路是记录每个c所在的下标,然后计算各个c之间字符的最短距离:
class Solution {
public:
vector<int> shortestToChar(string s, char c) {
vector<int> ret(s.size());
vector<int> indices;
for(int i = 0; i < s.size(); i++){
if(s[i] == c){
indices.push_back(i);//顺序记录c的所有下标
}
}
for(int i = 0; i < s.size(); i++){
if(s[i] == c){
ret[i] = 0;
}
if(i < indices[0]){
ret[i] = abs(indices[0] - i);
}
if(i > indices.back()){
ret[i] = abs(indices.back() - i);
}
if(indices.size() > 1){
for(int j = 0; j < indices.size() - 1; j++){
if(i > indices[j] && i < indices[j + 1]){//s[i]在两个c中间
ret[i] = min(abs(i - indices[j]),abs(i - indices[j + 1]));
}
}
}
}
return ret;
}
};
但肯定还有更好的解法,看到评论区说可以两次遍历,分别得到每个字符到它右边和左边c的最短距离,二者取最小就是每个字符的最终结果,于是又实现了一下:
class Solution {
public:
vector<int> shortestToChar(string s, char c) {
vector<int> ret(s.size(),INT_MAX);//后面要取min
int beg = 0;
for(int i = 0; i < s.size(); i++){//每个c左边字符的最短距离
if(s[i] == c){
ret[i] = 0;
for(int j = beg; j < i; j++){
ret[j] = i - j;
}
if(i < s.size() - 1){
beg = i + 1;
}
}
}
beg = s.size() - 1;
for(int i = s.size() - 1; i >= 0; i--){//每个c右边字符的最短距离
if(s[i] == c){
ret[i] = 0;
for(int j = beg; j > i; j--){
ret[j] = min(ret[j], j - i);//取左右的最小值
}
if(i > 0){
beg = i - 1;
}
}
}
return ret;
}
};
继续刷剑指:
2. 58-I.翻转单词
难度简单199
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
示例 1:
输入: "the sky is blue
" 输出: "blue is sky the
"
示例 2:
输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
说明:
- 无空格字符构成一个单词。
- 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
- 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
一看这题不都做烂了吗!即刻双指针+两次反转,然而被卡了好几个用例emmm,被自己菜哭了,主要是没考虑到字符串全是空格的情况。
class Solution {
public:
string reverseWords(string s) {
if(s.empty()) return s;//空
int left = 0, right = s.size() - 1;
while(s[left] == ' '){
if(left == s.size() - 1){
return "";//" "//" "全是空格
}
left++;
}
while(s[right] == ' ') right--;//去掉右边空格
s = s.substr(left,right - left + 1);
left = 0,right = 1;
reverse(s.begin(),s.end());
while(right < s.size()){
if(s[right] == ' '){
while(s[right + 1] == ' '){//连续多个空格
s.erase(s.begin() + right);
}
reverse(s.begin() + left,s.begin() + right);//reverse左闭右开
left = right + 1;
}
right++;
}
reverse(s.begin() + left,s.end());//反转最后一个单词
return s;
}
};
3. 左旋字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
示例 1:
输入: s = "abcdefg", k = 2 输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6 输出: "umghlrlose"
限制:
1 <= k < s.length <= 10000
整体反转+局部反转,重拳出击:
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(),s.end());
reverse(s.begin(),s.begin() + s.size() - n);
reverse(s.begin() + s.size() - n, s.end());
return s;
}
};
4. 构造队列
请定义一个队列并实现函数 max_value
得到队列里的最大值,要求函数max_value
、push_back
和 pop_front
的均摊时间复杂度都是O(1)。
若队列为空,pop_front
和 max_value
需要返回 -1
示例 1:
输入: ["MaxQueue","push_back","push_back","max_value","pop_front","max_value"] [[],[1],[2],[],[],[]] 输出: [null,null,null,2,1,2]
示例 2:
输入: ["MaxQueue","pop_front","max_value"] [[],[],[]] 输出: [null,-1,-1]
限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5
一个构造题,凭我非常有限的经验,这类题目往往需要一个辅助结构。本题用deque存放队列中大于等于当前元素的值,头部是当前的最大值,如果要放入的元素大于等于尾部元素,则删除尾部元素,直到要放入的元素小于尾部元素或deque为空。
class MaxQueue {
public:
queue<int> que;
deque<int> maxVal;
MaxQueue() {
}
int max_value() {
return que.empty() ? -1 : maxVal.front();
}
void push_back(int value) {
que.push(value);
while(!maxVal.empty() && value > maxVal.back()){//只保存>=当前的元素
maxVal.pop_back();
}
maxVal.push_back(value);
}
int pop_front() {
if(que.empty()){
return -1;
}
if(que.front() == maxVal.front()){//弹出的是当前队列的最大值
maxVal.pop_front();
}
int ret = que.front();
que.pop();
return ret;
}
};
5. 项目
assert()断言
用法:如果()内的表达式为真,程序继续执行,否则程序停止执行。
优点:自带错误信息输出,相比ifelse可以更方便地检测变量的值是否在合法范围
注意:最好每次只检测一个条件,否则不容易判断是哪个条件出了问题
内存池
***最近看项目需要自己实现一个内存池,第一次接触这个概念:
通用new/malloc申请内存的缺点:1)申请时系统根据最先匹配/最优匹配等算法分配,free/delete时可能需要合并空闲内存块,产生开销2)频繁free/delete会产生内存碎片,造成内存资源浪费3)可能出现new/delete不匹配,造成内存泄漏
定义:一种内存分配方式,在使用内存之前先向系统申请一定数量和大小的内存块,申请内存空间时直接从内存池中查找合适的内存块,如果不够用再继续向系统申请,实现了自己对内存资源的管理
优点:1)内存碎片少2)申请和释放更快3)可以检查任何一个指针是否在内存池中4)如果没有释放分配的内存,内存池会抛出一个断言,可以实现内存泄漏检测
***我看的这个项目中,设计了五个固定大小缓冲池,分别分配给需要0-64、65-128、129-256、257-512、513-1024字节内存的请求,超过1024字节的则直接向操作系统申请内存。
6. C++基础
const 和 *
以下关于指针的说法,正确的是()
正确答案: C 你的答案: C (正确)
int *const p与int const *p等价
const int *p与int *const p等价
const int *p与int const *p等价
int *p[10]与int (*p)[10]等价
这个看着头疼,其实根据靠近原则来看就可以,const修饰指针p则指针本身不能改变,修饰*p则指针指向的值不能改变:
int *const p:const修饰p,指针本身不能改变
int const *p:const修饰*p,指针指向的值不能改变
const int *p:const修饰*p,指针指向的值不能改变
int const *p:const修饰*p,指针指向的值不能改变
int *p[10]:长度为10的指针数组,每个元素是指向int的指针
int (*p)[10]:数组指针,p指向一个长度为10的int数组