文章目录
第三课 哈希表、集合、映射
lc1.两数之和–简单
题目描述
给定一个整数数组 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
- 只会存在一个有效答案
代码展示
class Solution {
public: //HaspMap实现
vector<int> twoSum(vector<int>& numbers, int target) {
// j < i
// for i = 0 ~ n - 1:
// search if (target - numbers[i]) exists in numbers[0..i-1]
unordered_map<int, int> value_to_index; // 值到下标的映射
for (int i = 0; i < numbers.size(); i++) {
// 不等于尾部,就是找到了,存在
// Java: value_to_index.containsKey(target-numbers[i])
// Pythong: target-numbers[i] in value_to_index
if (value_to_index.find(target - numbers[i]) != value_to_index.end()) {
return {value_to_index[target - numbers[i]], i};
}
// 边循环i,边插入,维护的是对于numbers[0..i-1]的映射
// 本质上是在i之前查找,防止查找i本身
value_to_index[numbers[i]] = i;
}
return {};
}
};
lc30.串联所有单词的子串–困难
题目描述
给定一个字符串 s
和一个字符串数组 words
。 words
中所有字符串 长度相同。
s
中的 串联子串 是指一个包含 words
中所有字符串以任意顺序排列连接起来的子串。
- 例如,如果
words = ["ab","cd","ef"]
, 那么"abcdef"
,"abefcd"
,"cdabef"
,"cdefab"
,"efabcd"
, 和"efcdab"
都是串联子串。"acdbef"
不是串联子串,因为他不是任何words
排列的连接。
返回所有串联子串在 s
中的开始索引。你可以以 任意顺序 返回答案。
示例 1:
输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。
示例 2:
输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。
示例 3:
输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。
提示:
1 <= s.length <= 104
1 <= words.length <= 5000
1 <= words[i].length <= 30
words[i]
和s
由小写英文字母组成
代码展示
class Solution {
public: //测试用例通过了,但耗时太长。
vector<int> findSubstring(string s, vector<string>& words) {
wordsMap = countWords(words);
int tot = words.size() * words[0].size();
vector<int> ans;
// 枚举开始位置
for (int start = 0; start + tot <= s.length(); start++) {
if (isSame(s.substr(start, tot), words)) {
ans.push_back(start);
}
}
return ans;
}
private:
unordered_map<string, int> wordsMap;
// 想判断一个字符串t,是否由words拼成
// 把t分解成若干个单词,然后看跟words数组是否相同(顺序无关)
bool isSame(string t, vector<string>& words) {
// cout << "isSame: " << t << " and words" << endl;
int m = words[0].length(); // 每个单词的长度
unordered_map<string, int> tMap; // 单词出现次数
// 把t,每m个字符分解成一个单词
// foothe 分解为 foo, the
for (int i = 0; i < t.length(); i += m) {
// i开始的m个字符,组成一个单词
tMap[t.substr(i, m)]++;
}
return equals(tMap, wordsMap);
}
// 判断两个map是否一样的方法
// a.size() == b.size()
// size: key的个数
// a里面有的b里全有,且值一样
bool equals(unordered_map<string, int>& a, unordered_map<string, int>& b) {
if (a.size() != b.size()) return false;
for (auto& key_value_pair : a) {
auto& key = key_value_pair.first;
auto& value = key_value_pair.second;
// 如果b里面没有,或者value不相等(次数不一样)
if (b.find(key) == b.end() || value != b[key]) return false;
}
return true;
}
unordered_map<string, int> countWords(vector<string>& words) {
unordered_map<string, int> ans;
for (string& word : words) {
ans[word]++;
}
return ans;
}
};
//bar foo
//arf oot
//rfo oth
//...
优化方法:
class Solution {
public: //优化解法
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ans;
words_map = getMap(words);
int n = s.size();
int m = words[0].size();
int tot = m * words.size();
for (int first = 0; first < m; first++) {
if (first + tot > n) break;
unordered_map<string, int> s_map;
int curr = first;
for (int i = 0; i < words.size(); i++) {
s_map[s.substr(curr, m)]++;
curr += m;
}
for (int start = first, end = curr; start + tot <= n; start += m, end += m) {
if (isSame(s_map, words_map)) ans.push_back(start);
s_map[s.substr(end, m)]++;
s_map[s.substr(start, m)]--;
}
}
return ans;
}
private:
unordered_map<string, int> getMap(vector<string>& words) {
unordered_map<string, int> res;
for (string& word : words) {
res[word]++;
}
return res;
}
bool isSame(unordered_map<string, int>& a, unordered_map<string, int>& b) {
for (auto& pr : a) {
if (b[pr.first] != pr.second) return false;
}
for (auto& pr : b) {
if (a[pr.first] != pr.second) return false;
}
return true;
}
unordered_map<string, int> words_map;
};
lc49.字母异位分组–中等
题目描述
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 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]
仅包含小写字母
代码展示
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
// HashMap可用于分组
// 从排序以后的字符串(aet)到一个数组(["ate","eat","tea"])的map
unordered_map<string, vector<string>> group;
for (string& str : strs) {
string copy = str;
// 字符串里面的字符排序
// Java: toCharArray()
sort(copy.begin(), copy.end());
// 分组
group[copy].push_back(str);
// cout << "Added " << str << " into group " << copy << endl;
}
vector<vector<string>> ans;
// Java .keySet()
// Python for .. in ...
for (auto pr : group) {
// pr.first == key
// pr.second == value
ans.push_back(pr.second);
}
return ans;
}
// 字符串长度分别是n1, n2, n3,....,总和是n
// n1 log n1 + n2 log n2 + ....
// <= n1 log n + n2 log n + ....
// = (n1 + n2 + ...) log n
// = n log n
};
/*
["eat", "tea", "tan", "ate", "nat", "bat"]
["aet", "aet", "ant", "aet", "ant", "abt"]
*/
lc874.模拟行走机器人–中等
题目描述
机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0)
处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands
:
-2
:向左转90
度-1
:向右转90
度1 <= x <= 9
:向前移动x
个单位长度
在网格上有一些格子被视为障碍物 obstacles
。第 i
个障碍物位于网格点 obstacles[i] = (xi, yi)
。
机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,并继续执行下一个命令。
返回机器人距离原点的 最大欧式距离 的 平方 。(即,如果距离为 5
,则返回 25
)
注意:
- 北方表示 +Y 方向。
- 东方表示 +X 方向。
- 南方表示 -Y 方向。
- 西方表示 -X 方向。
- 原点 [0,0] 可能会有障碍物。
示例 1:
输入:commands = [4,-1,3], obstacles = []
输出:25
解释:
机器人开始位于 (0, 0):
1. 向北移动 4 个单位,到达 (0, 4)
2. 右转
3. 向东移动 3 个单位,到达 (3, 4)
距离原点最远的是 (3, 4) ,距离为 32 + 42 = 25
示例 2:
输入:commands = [4,-1,4,-2,4], obstacles = [[2,4]]
输出:65
解释:机器人开始位于 (0, 0):
1. 向北移动 4 个单位,到达 (0, 4)
2. 右转
3. 向东移动 1 个单位,然后被位于 (2, 4) 的障碍物阻挡,机器人停在 (1, 4)
4. 左转
5. 向北走 4 个单位,到达 (1, 8)
距离原点最远的是 (1, 8) ,距离为 12 + 82 = 65
示例 3:
输入:commands = [6,-1,-1,6], obstacles = []
输出:36
解释:机器人开始位于 (0, 0):
1. 向北移动 6 个单位,到达 (0, 6).
2. 右转
3. 右转
4. 向南移动 6 个单位,到达 (0, 0).
机器人距离原点最远的点是 (0, 6),其距离的平方是 62 = 36 个单位。
提示:
1 <= commands.length <= 104
commands[i]
的值可以取-2
、-1
或者是范围[1, 9]
内的一个整数。0 <= obstacles.length <= 104
-3 * 104 <= xi, yi <= 3 * 104
- 答案保证小于
231
代码展示
class Solution {
public:
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
// {(x1,y1), (x2,y2), ...}
// (-200, 30000)
// string: "-200,30000"
// long long: (-200, 300) --> (-200+30000, 300+30000) --> (-200+30000)*60000 + 300 + 30000
unordered_set<long long> blockers;
for (auto& obstacle : obstacles) {
blockers.insert(calcHash(obstacle[0], obstacle[1]));
}
// 方向数组技巧
// N, E, S, W
int dx[4] = {0, 1, 0, -1}; // 偏移量(单位向量)
int dy[4] = {1, 0, -1, 0};
int x = 0, y = 0;
int dir = 0; // N
int ans = 0;
for (int cmd : commands) {
if (cmd > 0) {
for (int i = 0; i < cmd; i++) {
// 尝试走到nextx, nexty
// 想向dir走一步,就加dir方向的偏移量
int nextx = x + dx[dir];
int nexty = y + dy[dir];
// 有障碍物
// calcHash(nextx, nexty) in blockers
if (blockers.find(calcHash(nextx, nexty)) != blockers.end()) {
break;
}
// 走一步
x = nextx;
y = nexty;
ans = max(ans, x * x + y * y);
}
} else if (cmd == -1) {
// 0->1->2->3->0
// N->E->S->W->N
// 右转(顺时针)
dir = (dir + 1) % 4;
} else {
// 左转,避免负数,加一个mod数
dir = (dir - 1 + 4) % 4;
}
}
return ans;
}
private:
/*string calcHash(int x, int y) {
return to_string(x) + "," + to_string(y);
}*/
long long calcHash(int x, int y) {
return (x + 30000) * 60000ll + y + 30000;
}
};
lc146.LRU缓存–中等
题目描述
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache
类:
LRUCache(int capacity)
以 正整数 作为容量capacity
初始化 LRU 缓存int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回-1
。void put(int key, int value)
如果关键字key
已经存在,则变更其数据值value
;如果不存在,则向缓存中插入该组key-value
。如果插入操作导致关键字数量超过capacity
,则应该 逐出 最久未使用的关键字。
函数 get
和 put
必须以 O(1)
的平均时间复杂度运行。
示例:
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
提示:
1 <= capacity <= 3000
0 <= key <= 10000
0 <= value <= 105
- 最多调用
2 * 105
次get
和put
相关补充
思路讲解
代码展示
public class LRUCache {
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {}
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
}
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int size;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/