438.找到字符串中所有字母异位词
总结:
1、什么情况下会想到滑动窗口法:
任何题目如果没有思路其实都可以想一下暴力解法。这道题暴力解法思路简单:
遍历任意i,j,使得i和j之间的子串长度,等于p串的长度。该子串称之为x。该步复杂度为O(n)。
判断x是否与p是异位词。是的话,则把i加入答案中。该步复杂度为O(n)。
暴力法的复杂度为O(n^2)。显然不高效。
可以发现第二步其实做了很多不必要的操作,例如[i, j]和[i+1, j+1]两个子串在暴力法第二步中,需要各遍历一次,完全没必要。其实[i+1, j+1]完全可以在[i, j]的基础上做判断,也就是去掉头部的字符(i位置),加上尾部的字符(j+1位置)。这样第一步的复杂度可以降到O(1)。整体复杂度降到O(n)。已经得到信息不重复使用就浪费了,没必要重新搜集近乎相同的信息。这就是滑动窗口法。
滑动窗口法的特点是,一连串元素的信息,可以用常数时间推断出,该串整体移位后,新串信息。
所有滑动窗口问题,如果能从暴力法优化的角度思考,都不难想到。
2、是先把左边第一字母弹出,在右边那个窗口加1。
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
int slen = s.size();
int plen = p.size();
if(plen>slen) return{};
vector<int> mpp(26,0);
vector<int> mps(26,0);
vector<int> ans;
for(int i = 0 ; i < plen;i++)
{
mpp[p[i]-'a']++;
mps[s[i]-'a']++;
}
if(mpp==mps) {ans.push_back(0);}
for(int i = plen;i<slen;i++) //固定窗口的大小,
{
mps[s[i-plen]-'a']--;
mps[s[i]-'a']++;
if(mps==mpp) ans.push_back(i-plen+1);
}
return ans;
}
};
349. 两个数组的交集
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
此时就要使用另一种结构体了,set ,关于set,C++ 给提供了如下三种可用的数据结构:
- std::set
- std::multiset
- std::unordered_set
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。
总结:C++的语法挺难的 num : nums2
首先,nums是一个数组,里面放的是int类型的数据,然后定义了一个int类型的变量num,每循环一次,就从nums数组中取出一个数据来打印。
int :表示你要遍历的集合的类型
nums:表示你要遍历的集合的名
num:表示你每遍历集合中一个元素 便存储到该变量中,
然后在foreach语句的{}使用num变量
作用就是迭代容器中所有的元素,每一个元素的临时名字就是x,等同于下边代码
for (vector::iterator iter = nums.begin(); iter != nums.end(); iter++)
总结:一个小小的单词错误,注意看上下文是不是那个单词错误。
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> result_set; // 存放结果
unordered_set<int> nums_set(nums1.begin(), nums1.end());
for (int num : nums2) {
// 发现nums2的元素 在nums_set里又出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num);
}
}
return vector<int>(result_set.begin(), result_set.end());
}
};
202.快乐数
总结:
1、缺点间复杂度:O(\log n)O(logn)。与时间复杂度密切相关的是衡量我们放入哈希集合中的数字以及它们有多大的指标。 哈希集合中需要 O(1)O(1) 的时间,
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
2、循环用链表循环指针。
哈希表+循环检测、
class Solution {
public:
// 取数值各个位上的单数之和
int getSum(int n) {
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;
while(1) {
int sum = getSum(n);
if (sum == 1) {
return true;
}
//发现不是在末尾出现,而是之前就有了,末尾就插入。
// 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
if (set.find(sum) != set.end()) {
return false;
} else {
set.insert(sum);
}
n = sum;
}
}
};
class Solution {
public:
bool isHappy(int n) {
set<int> hash;
while (n != 1) {
hash.insert(n); // 每次记录n(sum)的值
int sum = 0;
// 计算中间结果sum (n的每个数位上的数的平方的和)
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
if (hash.find(sum) != hash.end()) // 若之前计算得到的sum与当前sum相同,说明出现循环,因此返回false
return false;
n = sum; // sum做为n继续进行计算
}
return true; // 若最终n=1退出循环,说明起初的n为快乐数,因此返回true
}
};
// 快慢指针
class Solution {
private:
int caculate(int n);
public:
bool isHappy(int n) {
int slow = n, fast = n;
do {
slow = caculate(slow); // 对于slow每次运算一次
fast = caculate(caculate(fast)); // 对于fast每次运算两次
}while (slow != fast); // (1)如果死循环,则 slow==fast 而退出循环,此时slow!=1; (2)如果是正常退出循环,则slow==fast==1,只不过fast更早等于1(caculate(1)=1)
return (1 == slow) ? true : false; // 最终只要根据slow或fast的值,就可判断结果
}
};
int Solution::caculate(int n) { // 计算n的"每个数位上的数的平方的和"
int sum = 0;
while (n) {
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
350.两个数组的交集2
总结:
1、使用哈希表,小的为哈希表,字母出现的次数,然后大的对比
复杂度分析
时间复杂度:O(m+n)O(m+n),其中 mm 和 nn 分别是两个数组的长度。需要遍历两个数组并对哈希表进行操作,哈希表操作的时间复杂度是 O(1)O(1),因此总时间复杂度与两个数组的长度和呈线性关系。
空间复杂度:O(\min(m,n))O(min(m,n)),其中 mm 和 nn 分别是两个数组的长度。对较短的数组进行哈希表的操作,哈希表的大小不会超过较短的数组的长度。为返回值创建一个数组 intersection,其长度为较短的数组的长度。
注意:unordered_map, 写完要写;
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) {
return intersect(nums2, nums1);
}
unordered_map <int, int> m;
for (int num : nums1) {
++m[num];
}
vector<int> intersection;
for (int num : nums2) {
if (m.count(num)) {
intersection.push_back(num);
--m[num];
if (m[num] == 0) {
m.erase(num);
}
}
}
return intersection;
}
};
方法二:排序 + 双指针
如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。
首先对两个数组进行排序,然后使用两个指针遍历两个数组。
初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
时间复杂度:O(m \log m+n \log n)O(mlogm+nlogn),其中 mm 和 nn 分别是两个数组的长度。对两个数组进行排序的时间复杂度是 O(m \log m+n \log n)O(mlogm+nlogn),遍历两个数组的时间复杂度是 O(m+n)O(m+n),因此总时间复杂度是 O(m \log m+n \log n)O(mlogm+nlogn)。
空间复杂度:O(\min(m,n))O(min(m,n)),其中 mm 和 nn 分别是两个数组的长度。为返回值创建一个数组 intersection,其长度为较短的数组的长度。不过在 C++ 中,我们可以直接创建一个 vector,不需要把答案临时存放在一个额外的数组中,所以这种实现的空间复杂度为 O(1)O(1)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
int length1 = nums1.size(), length2 = nums2.size();
vector<int> intersection;
int index1 = 0, index2 = 0;
while (index1 < length1 && index2 < length2) {
if (nums1[index1] < nums2[index2]) {
index1++;
} else if (nums1[index1] > nums2[index2]) {
index2++;
} else {
intersection.push_back(nums1[index1]);
index1++;
index2++;
}
}
return intersection;
}
};
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
sort(begin(nums1),end(nums1));
sort(begin(nums2),end(nums2));
nums1.erase(set_intersection(begin(nums1),end(nums1),begin(nums2),end(nums2),
begin(nums1)),end(nums1));
return nums1;
}
};