1. 两数之和
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
- 只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2)
的算法吗?
class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int, int> hashtable;for (int i = 0; i < nums.size(); ++i) {auto it = hashtable.find(target - nums[i]);if (it != hashtable.end()) {return {it->second, i};}hashtable[nums[i]] = i;}return {};}
};
注意:如果不采用这种一般将数据压入hashtable一边判断而是采用先将所有数据全部压入hashtable后再一个给判断过不了样例2
49. 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i]
仅包含小写字母
方法一:排序
时间复杂度:$$O(nklogk)$$,其中 n 是 strs 中的字符串的数量,k是 strs 中的字符串的的最大长度。
class Solution {
public:vector<vector<string>> groupAnagrams(vector<string>& strs) {unordered_map<string,vector<string>> hashtable;vector<string> tmp(strs);for(int i=0;i<strs.size();i++) sort(tmp[i].begin(),tmp[i].end());for(int i=0;i<strs.size();i++)hashtable[tmp[i]].push_back(strs[i]);vector<vector<string>> res;for(auto it:hashtable) res.push_back(it.second);return res;}
};
注意:下面代码只会对tmp的副本排序,所以tmp[i]仍然没有被排序,需要将for(string s:tmp) sort(s.begin(),s.end());
改成for(string& s:tmp) sort(s.begin(),s.end());
class Solution {
public:vector<vector<string>> groupAnagrams(vector<string>& strs) {unordered_map<string,vector<string>> hashtable;vector<string> tmp(strs);for(string s:tmp) sort(s.begin(),s.end());for(int i=0;i<strs.size();i++)hashtable[tmp[i]].push_back(strs[i]);vector<vector<string>> res;for(auto it:hashtable) res.push_back(it.second);return res;}
};
他山之石
由于互为字母异位词的两个字符串包含的字母相同,因此两个字符串中的相同字母出现的次数一定是相同的,故可以将每个字母出现的次数使用字符串表示,作为哈希表的键。
由于字符串只包含小写字母,因此对于每个字符串,可以使用长度为 26 的数组记录每个字母出现的次数。需要注意的是,在使用数组作为哈希表的键时,不同语言的支持程度不同,因此不同语言的实现方式也不同。
class Solution {
public:vector<vector<string>> groupAnagrams(vector<string>& strs) {// 自定义对 array<int, 26> 类型的哈希函数auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t {return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {return (acc << 1) ^ fn(num);});};unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash);for (string& str: strs) {array<int, 26> counts{};int length = str.length();for (int i = 0; i < length; ++i) {counts[str[i] - 'a'] ++;}mp[counts].emplace_back(str);}vector<vector<string>> ans;for (auto it = mp.begin(); it != mp.end(); ++it) {ans.emplace_back(it->second);}return ans;}
};
原文链接:https://leetcode.cn/problems/group-anagrams/solutions/520469/zi-mu-yi-wei-ci-fen-zu-by-leetcode-solut-gyoc/
语法学习
std::array
是 C++ 标准库提供的静态数组,长度固定,但有更多功能,如边界检查。
在 C++11
之后,vector
容器中添加了新的方法:emplace_back()
,和 push_back()
一样的是都是在容器末尾添加一个新的元素进去,不同的是 emplace_back()
在效率上相比较于 push_back()
有了一定的提升。
#include <vector>
#include <iostream>
using namespace std;
int main() {vector<pair<int, string>> vec;// 使用 push_back 需要显式构造对象vec.push_back(make_pair(1, "Apple"));// 使用 emplace_back 直接构造对象vec.emplace_back(2, "Banana");for (const auto& p : vec) {cout << p.first << " - " << p.second << endl;}return 0;
}
C++中push_back和emplace_back的区别 - 知乎
自定义hash函数
在C++中如何对自定义类型做hash操作_c++自定义哈希函数-CSDN博客
128. 最长连续序列
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
示例 3:
输入:nums = [1,0,1,2]
输出:3
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
class Solution {
public:int longestConsecutive(vector<int>& nums) {set<int> s(nums.begin(),nums.end());nums.clear();for(int num:s) nums.push_back(num);int res=(nums.size()!=0);int l=0,r=1;int n=nums.size();while(r<n){while(r<n&&nums[r]-nums[r-1]==1){r++;}res=max(res,r-l);l=r;r=r+1;}return res;}
};
他山之石
本题不能排序,因为排序的时间复杂度是 $O(n\log n)$,不符合题目 $O(n)$ 的要求。
核心思路:
对于 nums
中的元素 x
,以 x
为起点,不断查找下一个数 x+1, x+2, ...
是否在 nums
中,并统计序列的长度。
关键优化(使时间复杂度达到 $O(n)$):
-
使用哈希集合
- 将
nums
中的数存入哈希集合,这样可以 $O(1)$ 判断数字是否存在。
- 将
-
避免重复计算
- 如果
x-1
存在于哈希集合中,则不以x
为起点。 - 这样可以保证我们只会从连续序列的第一个数开始计算,避免大量重复计算。
- 例如
nums = [3,2,4,5]
:- 从
3
开始,我们可以找到3,4,5
这个连续序列。 - 从
2
开始,我们可以找到2,3,4,5
,一定比从3
开始的更长,因此3
不是一个合适的起点。
- 从
- 如果
细节优化:
- 遍历哈希集合,而不是
nums
本身!- 例如
nums = [1,1,1,...,1,2,3,4,5,...]
(前一半都是1
)。 - 遍历
nums
会导致每个1
都执行 $O(n)$ 的循环,总复杂度变为 $O(n^2)$,从而超时。
- 例如
class Solution {
public:int longestConsecutive(vector<int>& nums) {int res=0;unordered_set<int> hashtable(nums.begin(),nums.end());for(int x:hashtable){if(hashtable.contains(x-1)) continue;//x是序列的起点int y=x+1;while(hashtable.contains(y)) y++;//循环结束后,y-1是最后一个在连续数res=max(res,y-x);}return res;}
};
语法学习
从C++11到C++23(六)C++20利用contains查询map是否存在某个键_c++ map contains-CSDN博客
C++20新增了std::map::contains
可以直接查找键是否存在,返回值类型为bool型
#include <iostream>
#include <string>
#include <map>int main()
{std::map<int, std::string> example = {{1, "One"}, {2, "Two"}, {3, "Three"}, {42, "Don\'t Panic!!!"}};if(example.contains(42)) {std::cout << "Found\n";} else {std::cout << "Not found\n";}
}
283. 移动零
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
进阶:你能尽量减少完成的操作次数吗?
class Solution {
public:void moveZeroes(vector<int>& nums) {int i=0,j=0;int n=nums.size();while(i<n&&j<n){while(n>i&&nums[i]) i++;j=i+1;while(n>j&&!nums[j]) j++;if(n>i,n>j) swap(nums[i],nums[j]);i++;}}
};
他山之石
📌 核心思路
我们将 0
视作 空位,把所有 非零元素 依次移动到数组左侧的空位,同时 保持原有顺序。
示例
cpp复制编辑输入: nums = [0, 1, 0, 3, 12]
输出: [1, 3, 12, 0, 0]
💡 关键点
- 维护一个指针
i0
,指向最左侧的 空位(即0
)。 - 遍历数组
- 遇到非零元素 → 交换
nums[i]
和nums[i0]
,然后i0++
。 - 遇到 0 → 不操作,继续遍历。
- 遇到非零元素 → 交换
📜 代码
class Solution {
public:void moveZeroes(vector<int>& nums) {int i0 = 0; // 记录最左侧空位for (int& x : nums) { // if (x) { // 只有非零元素才交换swap(x, nums[i0]);i0++; // 更新最左侧空位}}}
};
📊 复杂度分析
- 时间复杂度:O(n),遍历
nums
一次,每个元素最多交换一次。 - 空间复杂度:O(1),原地修改,没有额外空间消耗。
🧐 运行过程解析
示例
输入: nums = [0, 1, 0, 3, 12]
i |
i0 |
nums[i] |
交换后 nums |
---|---|---|---|
0 | 0 | 0 | 不操作 |
1 | 0 | 1 | [1, 0, 0, 3, 12] |
2 | 1 | 0 | 不操作 |
3 | 1 | 3 | [1, 3, 0, 0, 12] |
4 | 2 | 12 | [1, 3, 12, 0, 0] |
最终输出:
[1, 3, 12, 0, 0]
💬 可能的疑问
❓ Q2: 为什么可以i0=0
?
- 实际上
i0
会自动找到第一个为0的元素
-
例如
nums = [1, 2, 3, 4,0,12]
- 发现
1
,交换nums[0]
和nums[0]
→[1, 2, 3, 4,0,12]
,i0=1
- 发现
2
,交换nums[1]
和nums[1]
→[1, 2, 3, 4,0,12]
,i0=2
- 发现
3
,交换nums[2]
和nums[2]
→[1, 2, 3, 4,0,12]
,i0=3
- 发现
4
,交换nums[3]
和nums[3]
→[1, 2, 3, 4,0,12]
,i0=4
- 发现
0
,交换nums[4]
和nums[4]
→[1, 2, 3, 4,0,12]
,i0=4
- 发现
12
,交换nums[5]
和nums[4]
→[1, 2, 3, 4,12,0]
,i0=5
- 发现
原文链接