
手撕不难,直接秒了
cpp
class Solution {
public:
bool checkInclusion(string s1, string s2) {
vector <int> cnt1(26, 0), cnt2(26, 0);
for(auto i : s1) cnt1[i - 'a']++;
int left = 0;
bool flag = false;
for(int right = 0; right < s2.size(); right++) {
cnt2[s2[right] - 'a']++;
while(right - left + 1 > s1.size()) {
cnt2[s2[left] - 'a']--;
left++;
}
if(cnt1 == cnt2) {
flag = true;
break;
}
}
return flag;
}
};
手撕到是不难,记录一下没答出来的几个问题:
-
快速排序如何确保选择的哨兵节点比较优
策略一:固定选择(最差劲的策略)
- 方法:总是选择第一个元素或最后一个元素作为哨兵。
- 问题 :如果输入数组已经有序或接近有序,这种策略每次都会选到最小或最大值,导致分区极度不平衡,算法退化为 O(n²)。这是在实践中绝对要避免的。
策略二:随机选择(简单而有效)
- 方法 :在待排序的数组中,随机选择一个元素作为哨兵。
- 优点 :
- 实现简单,只需要一个随机数生成器。
- 它不存在固定的最坏情况。即使输入数组是完全有序的,因为我们是随机选择,选到最差哨兵的概率变得非常低。
- 从概率期望上看,随机化快速排序的平均时间复杂度为 O(n log n),并且这个期望与输入数据的分布无关。
- 结论 :这是实践中最常用、性价比最高的方法之一。它用极小的代价,就几乎杜绝了因输入数据特性而导致的最坏情况。
策略三:三数取中法(Median-of-Three)
- 方法 :取出当前数组的第一个元素、最后一个元素和中间元素 ,然后取这三个元素的中位数作为哨兵。
- 过程 :
- 比较首、中、尾三个元素的大小。
- 将这三个元素中大小排在中间的那个,交换到数组的头部(或尾部,根据实现方式),然后将其作为哨兵。
- 优点 :
- 它有效地避免了在数组已经"基本有序"或"完全逆序"时选到极值的情况。例如,如果数组是
[1, 2, 3, 4, 5]
,三个数是1, 3, 5
,中位数是3
,这是一个很好的分割。 - 它是对随机选择的一种有效补充和优化,能进一步减少出现较差分区的概率。
- 计算中位数的开销很小。
- 它有效地避免了在数组已经"基本有序"或"完全逆序"时选到极值的情况。例如,如果数组是
- 结论 :这是另一个在实践中非常流行和高效的优化策略。很多标准库(如某些版本的C++
qsort
)都采用了此策略或其变种。
-
static修饰类的成员方法有什么用
-
隐藏的作用,static函数、static变量均可
当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
-
存储在静态数据去的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是可用来隐藏的。
-
C++类中的类成员声明static
- 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时任然维持上次的值
- 在模块内的static全局变量只可被这一模块内所有函数访问,但不能被模块外其他函数访问
- 在模块内的static函数只可被这一模块内的其他函数调用,这个函数的使用范围被限制在声明它的模块内
- 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝
- 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
类内:
- static类对象必须在类外进行初始化,static修饰的变量先于对象存在,所以static修饰的变量要在类外初始化;
- 由于static修饰的类成员属于类,不属于对象,因此static类成员函数是没有this指针的,this指针是指向本对象的指针。正因为没有this指针,所以static类成员函数不能访问非static的类成员,只能访问static修饰的类成员
- static成员函数不能被virtual修饰,static成员不属于任何对象或实例,所以加上virtual没有任何实际意义;静态成员函数没有this指针,虚函数的实现是为每一个对象分配一个vptr指针,而vptr是通过this指针调用的,所以不能为virtual;虚函数调用关系:this->vptr->ctable->virtual function
-
-
为啥构造函数可以不申明为虚函数,析构函数一定要
由于类的多态性,基类指针可以指向派生类对象,如果删除该基类指针,就会调用该指针指向的派生类析构函数,但是派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。
如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类的析构函数,这样就会造成派生类对象析构不完全,造成内存泄漏。
所以将析构函数声明为虚函数是十分必要的。在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生,要将基类的析构函数声明为虚函数。
析构函数可以是纯虚函数,含有纯虚函数的类是抽象类,此时不能被实例化。
构造函数不能被声明为虚函数,因为虚函数对应一个虚函数表,类中存储一个vptr指向这个虚函数表。如果构造函数是虚函数,就需要通过虚函数表调用,可是对象没有初始化就没有vptr,无法找到虚函数表,所以构造函数不能是虚函数。
-
vector扩容机制需要开辟一段新的内存扩容时,此时是移动语义还是拷贝语义
在C++11及以后的标准中,vector扩容时会优先使用移动语义,如果移动语义不可用,则回退到拷贝语义。
-
unique_ptr不允许拷贝底层是如何实现的
unique_ptr
不允许拷贝的特性是通过删除拷贝构造函数和拷贝赋值运算符来实现的,而不是简单的运算符重载。cpptemplate<typename T> class unique_ptr { private: T* ptr; // 管理的原始指针 public: // 构造函数 explicit unique_ptr(T* p = nullptr) : ptr(p) {} // 析构函数:释放资源 ~unique_ptr() { delete ptr; } // 1. 禁止拷贝构造:用 = delete 显式删除 unique_ptr(const unique_ptr& other) = delete; // 2. 禁止拷贝赋值:用 = delete 显式删除 unique_ptr& operator=(const unique_ptr& other) = delete; // 允许移动构造(转移所有权) unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; // 原指针置空,避免重复释放 } // 允许移动赋值(转移所有权) unique_ptr& operator=(unique_ptr&& other) noexcept { if (this != &other) { delete ptr; // 释放当前资源 ptr = other.ptr; // 接收对方的资源 other.ptr = nullptr; // 原指针置空 } return *this; } // 其他成员函数(如 operator*、operator-> 等) T& operator*() const { return *ptr; } T* operator->() const { return ptr; } };