两数之和
暴力枚举
cpp
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for(int i=0; i<nums.size()-1; i++){
for(int j=i+1; j<nums.size(); j++){
if(target == nums[i] + nums[j]){
return {i,j};
}
}
}
return {};
}
};
哈希表
方法一的时间复杂度较高的原因是寻找target-x的时间复杂度过高。
因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找到它的索引。
使用哈希表,可以将寻找target-x的时间复杂度从O(N)降到O(1)。
cpp
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 {];
}
};
字母异位词分组
给一个字符串数组,请你将字母异位词组合在一起。
排序
由于互为字母异位词的两个字符串包含的字母相同。
cpp
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> strMap;
for(string& str:strs){
string key = str;
sort(key.begin(),key.end());
mp[key].emplace_back(str);
}
vector<vector<string>> ans;
for(auto it=mp.begin(); it!=mp.end();it++){
ans.empalce_back(it->second);
}
return ans;
}
};
最长连续序列
给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请设计并实现时间复杂度为O(n)的算法解决此问题。
哈希表
我们考虑枚举数组中的每个数x,考虑以其为起点,不断尝试匹配x+1,x+2,...是否存在,假设最长匹配到了x+y,那么以x为起点的最长连续序列即为x,x+1,...,x+y,其长度为y+1,我们不断枚举并更新答案即可。
对于匹配的过程,暴力的方法是O(n)遍历数组去看是否存在这个数,但其实更高效的方法是使用一个哈希表存储数组中的数,这样查看一个数是否存在即能优化至O(1)的时间复杂度。
仅仅是这样,我们的算法时间复杂度最坏情况下还是会达到O(n²)(外存需要枚举O(n)个数,内存需要暴力匹配O(n)次),无法满足题目的要求。
仔细分析这个过程,会发现其中执行了很多不必要的枚举,如果已知有一个从x开始的连续序列,而我们却从x+1开始,那么得到的结果肯定不会优于枚举x为起点的答案,因此我们在外层循环的时候碰到这种情况跳过即可。
cpp
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> hashTable;
for(const int&num:nums){
hashTable.insert(num);
}
int maxLength = 0;
for(const int&num:hashTable){
if(!hashTable.count(num-1)){
int currentNum = num;
int currentLen = 1;
while(hashTable.count(currentNum+1)){
currentNum++;
currentLen++;
}
maxLength = max(maxLength, currentLen);
}
}
return maxLength;
}
};
移动零
给定一个数组nums,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。
双指针
使用双指针,左指针指向当前已处理好的序列的尾部,右指针指向待处理序列的头部。
右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
无重复字符的最长子串
cpp
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int n = s.size();
if(n <= 1){
return n;
}
int l = 0;
int r = 1;
unordered_set<char> charMap;
charMap.insert(s[l]);
int maxLen = 1;
while(r < n){
if(charMap.count(s[r])){
maxLen = max(maxLen, r-l);
charMap.remove(s[l]);
l++;
}else{
charMap.insert(s[r]);
r++;
}
}
maxLen = max(maxLen,r-l);
return maxLen;
}
};
和为k的子数组
枚举
cpp
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int count = 0;
for(int i=0; i<nums.size(); i++){
int sum = 0;
for(int j=i; j>=0; j--){
sum += nums[j];
if(sum == k){
count++;
}
}
}
return count;
}
};
前缀和+哈希表优化
方法一的瓶颈在于对每个i,我们需要枚举所有的j来判断是否符合条件。
定义pre[i]为0,...,i的和
cpp
pre[i] = pre[i-1] + nums[i]
j,...i\]子数组和为k:
```cpp
pre[i] - pre[j-1] == k
pre[j-1] = pre[i] - k;
```
### 最大子数组和
找出一个具有最大和的连续子数组,返回最大和(至少包含一个元素)。
**动态规划**
用f(i)代表以第i个数结尾的连续子数组的最大和。
可以考虑nums\[i\]单独成为一段还是加入f(i-1)对应的一段。
f(i) = max{nums\[i\],f(i-1)+nums\[i\]}
不难给出一个时间复杂度、空间复杂度均为O(n)的实现,即用一个f数组来保存f(i)的值,用一个循环求出所有f(i)。
f(i)只和f(i-1)有关,所以可以用一个变量维护即可,类似于滚动数组。
```cpp
class Solution {
public:
int maxSubArray(vector
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> res(n,1);
for(int i=1; i<n; i++){
res[i] *= res[i-1]*nums[i-1];
}
int R = 1;
for(int i=n-1; i>=0; i--){
res[i] = res[i]*R;
R *= nums[i];
}
return res;
}
};