今天题目
题目 | 难度 | 备注 |
---|---|---|
242. 有效的字母异位词 - 力扣(LeetCode) | 简单 | ♥♥ |
349. 两个数组的交集 - 力扣(LeetCode) | 简单 | |
202. 快乐数 - 力扣(LeetCode) | 简单 | 此题考查分析能力 |
454. 四数相加 II - 力扣(LeetCode) | 中等 | 此题考查分析能力 |
383. 赎金信 - 力扣(LeetCode) | 简单 | 掌握这个思想 |
15. 三数之和 - 力扣(LeetCode) | 中等 | 动态规划题 |
18. 四数之和 - 力扣(LeetCode) | 中等 | 动态规划题 |
哈希表篇
题目:242. 有效的字母异位词
一、源代码
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
vector<int> hasNum(30,0);
for (int i = 0; i < s.size(); ++i) { //存入s 用++
hasNum[s[i] - 'a']++;
}
for (int i = 0; i < t.size(); ++i) { //判断与 t 是否相等,则--
hasNum[t[i] - 'a']--;
if (hasNum[t[i] - 'a'] < 0) { //不相等必会 < 0
return false;
}
}
return true;
}
二、代码思路
一开始想着字符要和出现次数绑定,想着用 map<char,int>,但这样得先用一个for循环存入,在存入中还得不断用find()方法判断map中是否已经存在该字符,然后还得判等…太麻烦
其实要实现字符和出现次数的绑定,最好用 int 数组,map更适合用在字符串和数字的绑定。因为单个字符也可以看作数字嘛,两者可以转换的:如字符 ’5’ - ‘0‘ = 数字 5,字符 ‘b’ - ‘a’ = 数字 1
那要实现两个字符串判断各字符出现次数是否相等就简单了,先定义一个int 数组hasNum,初始化各值为 0。再遍历字符串还是,直接用 hasNum[s[i] - ‘a’]++ 就可以得到字符和出现次数的绑定了,hasNum的下标可指代 字符,其值为出现次数。 存入 s 用 +,那判断 s 与 t 每个字符出现的次数是否相同当然用 -,若完全相同遍历 t 后 hasNum数组的值为全0
三、优化思路
其实若 s 和 t 中每个字符出现的次数都相同,那 s 与 t 的长度必相等,用sort排序后,sort(s) 必等于 sort(t),利用性质解题更分别。
四、优化代码
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
sort(s.begin(), s.end());
sort(t.begin(), t.end());
return s == t;
}
题目:349. 两个数组的交集
一、源代码
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> ans;
bool hasVis[1000] = {0}; //用hasVis判断是否已经存在
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int i = 0, j = 0;
while (i < nums1.size() && j < nums2.size()){ //双指针法
if (nums1[i] > nums2[j]) ++j;
else if (nums1[i] < nums2[j]) ++i;
else {
if (hasVis[nums1[i]] == 0) { //hasVis[nums1[i]] == 1,表示已存在,无需存入
ans.push_back(nums1[i]);
hasVis[nums1[i]] = 1;
}
++i; ++j;
}
}
return ans;
}
二、优化思路
因为使用 sort() 后,nums1 和nums2 中元素都从小到大排列,所以用双指针遍历时 push_back到 ans 的数也是按从小到大的顺序,即下一个要 push_back 到 ans 的数,要么比ans中所有的数都大,要么等于ans.back() 。那么就无需定义 hasVis 数组来判断 nums1[i]] 是否已经存在。只要判断 nums1[i] 是否等于 ans.back() 就行了,等于就不存入。
if (hasVis[nums1[i]] == 0) ==> if (nums[i] != ans.back())
题目:202. 快乐数
没有思路
一、答案思路
任何一个数,循环把它各个位置平方相加,可能出现的情况无非就三种:
① 最终会得到 1;
② 最终会进入循环;
③ 值越来越大,最终接近无穷
分析 ③,什么情况值会越来越大?假如n为三位数,则n <= 999,999的各个位置平方和 == 243 < 999。所以三位数的 n 其平方和再大也不会大于 999 (三位数中999的平方和最大,所以三位数中最大的平方和仍然为三位数,仍然小于999,再计算平方和肯定任小于999)
所以值不会越来越大的,那就只有 ①、② 两种情况,那这题就简单了,循环计算各个位置平方和,若得到的值出现过,则说明进入循环了,break;若得1 则为快乐数
二、答案代码
private:
int getNext (int n) {
int sum = 0;
while (n > 0) {
int d = n % 10;
n = n / 10;
sum += d * d;
}
return sum;
}
public:
bool isHappy(int n) {
unordered_set<int> nums;
while (n != 1 && !nums.count(n)) {
nums.insert(n);
n = getNext(n);
}
return n == 1;
}
三、优化思路
既然要么 ① 最终会得到 1,要么② 最终会进入循环;那题目不就变成了类似判断链表是否有环了吗?那当然是 fast 和 slow 双指针法,和前几天的题目很像,所以本体出现的情况可以变成以下:
如果 n
是一个快乐数,即没有循环,那么快跑者最终会比慢跑者先到达数字 1。
如果 n
不是一个快乐的数字,那么最终快跑者和慢跑者将在同一个数字上相遇。
四、优化代码
private:
int getNext (int n) {
int sum = 0;
while (n > 0) {
int d = n % 10;
n = n / 10;
sum += d * d;
}
return sum;
}
public:
bool isHappy(int n) {
int slowRunner = n;
int fastRunner = getNext(n);
while (fastRunner != 1 && slowRunner != fastRunner) {
slowRunner = getNext(slowRunner);
fastRunner = getNext(getNext(fastRunner));
}
return fastRunner == 1;
}
题目:454. 四数相加 II
一、源代码
private:
int cnt = 0;
void DFS (vector<int>& nums1, vector<int>& nums2, vector<int>& nums3,
vector<int>& nums4, int i, int j, int k, int h) { //dfs实现递归探索
int n = nums1.size();
if (i>=n || j>=n || k>=n || h>=n) return;
if (nums1[i] + nums2[j] + nums3[k] + nums4[h] == 0) { //找到符合条件的则++cnt
++cnt;
return;
}
DFS(nums1, nums2, nums3, nums4, i+1, j, k, h);
DFS(nums1, nums2, nums3, nums4, i, j+1, k, h);
DFS(nums1, nums2, nums3, nums4, i, j, k+1, h);
DFS(nums1, nums2, nums3, nums4, i, j, k, h+1);
}
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
DFS(nums1, nums2, nums3, nums4, 0, 0, 0, 0);
return cnt;
}
二、代码思路
用DFS实现递归探索,一次改变一个量,这样能遍历所有情况了,遇到符合情况的则++cnt
三、错误原因
① 实际输出与答案不符
因为遍历过程中,会有一个元组多次出现,并且每次都能++cnt 的情况,如对以下输入
遍历过程中会出现 元组为0100 -> 1100 时 cnt++,元组2为1000 ->1100时,cnt还++,这样cnt数就会大于答案了
也就是说会出现一个元组占两个坑计数两次的情况,那遇到这种情况怎么处理呢?当然是用 set,在set中一个元素只能有一个坑,所以全部导入set中,再返回 set 的 size 试试
四、修改代码(答案错误)
private:
int p = 200; //n最多为200,为了把元组<i,j,k,h> 转换为数,所以定义p为200
set<long long> ans;
void DFS (vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4, int i, int j, int k, int h){
int n = nums1.size();
if (i>=n || j>=n || k>=n || h>=n) return;
if (nums1[i] + nums2[j] + nums3[k] + nums4[h] == 0) {
long long sum = h + k*p + j*p*p + i*p*p*p; //为了把元组<i,j,k,h> 转换为数
ans.insert(sum); //存入set,避免一个元组占多个坑的情况
return;
}
DFS(nums1, nums2, nums3, nums4, i+1, j, k, h);
DFS(nums1, nums2, nums3, nums4, i, j+1, k, h);
DFS(nums1, nums2, nums3, nums4, i, j, k+1, h);
DFS(nums1, nums2, nums3, nums4, i, j, k, h+1);
}
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
DFS(nums1, nums2, nums3, nums4, 0, 0, 0, 0);
return ans.size();
}
本以为这个代码虽然很烂,时间复杂度很高。好像也能做出来题目,先求做出再求优化。
结果测试用例对了,一提交还是错的。看了题解,这个题目我是真不喜欢。
五、题解思路
分成两组,一组存入一组拿来对比。这能理解,比较类的题目大都要用这个思路
注意这个分治思想,为什么得出要两两一组,这个分析过程很重要!要学会从时间复杂角度分析
六、题解代码
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
unordered_map<int, int> countAB;
for (int u: A) {
for (int v: B) {
++countAB[u + v];
}
}
int ans = 0;
for (int u: C) {
for (int v: D) {
if (countAB.count(-u - v)) {
ans += countAB[-u - v];
}
}
}
return ans;
}
};
题目:383. 赎金信
一、源代码
bool canConstruct(string ransomNote, string magazine) {
bool falg = true;
for (int i = 0; i < ransomNote.size(); i++) { //遍历ransomNote
int n = magazine.find(ransomNote[i]) //在magazine中查找ransomNote的元素,find返回下标
if (n!= magazine.npos) { //如果找到则magazine中下标元素
magazine.erase(n,1);
}else{
falg = false;
break;
}
}
return falg;
}
二、优化思路
其实这也是两个string 相比较,也可以用一组存入,一组对比的思想来解题。时间复杂度会小
三、优化代码
bool canConstruct(string ransomNote, string magazine) {
if (ransomNote.size() > magazine.size()) {
return false;
}
vector<int> cnt(26);
for (auto & c : magazine) {
cnt[c - 'a']++;
}
for (auto & c : ransomNote) {
cnt[c - 'a']--;
if (cnt[c - 'a'] < 0) {
return false;
}
}
return true;
题目:15. 三数之和
一、源代码
vector<vector<int>> threeSum(vector<int>& nums) {
unordered_map<int,int> numIndex;
vector<int> ans;
vector<vector<int> > ansTotal;
for(int i = 0; i < nums.size(); i++) {
numIndex[nums[i]] = i;
}
for(int i = 1; i < nums.size(); i++) {
for(int j = 1; j < nums.size(); j++) {
unordered_map<int,int>::iterator it = numIndex.find(-nums[i]-nums[j]);
if (it != numIndex.end() && it->second != i && it->second != j && i != j) {
ans.push_back(it->second); ans.push_back(i); ans.push_back(j);
ansTotal.push_back(ans);
}
}
}
return ansTotal;
}
二、错误原因
本想继续用分两组存一组,另一组拿来比较的方法。用 map 存入数组值和下标,再在map中查找值。但是这会出现两个问题:
① map 不能出现两个相同的键,nums中却有重复的值,这就不能用map;
② 答案要求不重复,用map 得去重,很麻烦
所以这题其实不是用哈希表法,而是用动态规划的知识,动态规划以后再来探讨吧,先学基础的
三、题解代码
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
// 枚举 a
for (int first = 0; first < n; ++first) {
// 需要和上一次枚举的数不相同
if (first > 0 && nums[first] == nums[first - 1]) {
continue;
}
// c 对应的指针初始指向数组的最右端
int third = n - 1;
int target = -nums[first];
// 枚举 b
for (int second = first + 1; second < n; ++second) {
// 需要和上一次枚举的数不相同
if (second > first + 1 && nums[second] == nums[second - 1]) {
continue;
}
// 需要保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target) {
--third;
}
// 如果指针重合,随着 b 后续的增加
// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
if (second == third) {
break;
}
if (nums[second] + nums[third] == target) {
ans.push_back({nums[first], nums[second], nums[third]});
}
}
}
return ans;
}
};
题目:18. 四数之和
同样动态规划的题,暂时不考虑,以后再来做