算法-哈希表、集合、映射
1、两数之和(简单)
题目地址:https://leetcode.cn/problems/two-sum/description/
给定一个整数数组 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 int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap();
int[] ans = new int[2];
for(int i = 0; i<nums.length; i++){
if(map.containsKey(nums[i])){
ans[0] = map.get(nums[i]);
ans[1] = i;
break;
}else{
map.put(target-nums[i], i);
}
}
return ans;
}
}
2、模拟行走机器人(中等)
题目地址:https://leetcode.cn/problems/walking-robot-simulation/
机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0)
处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands
:
-2
:向左转90
度-1
:向右转90
度1 <= x <= 9
:向前移动x
个单位长度
在网格上有一些格子被视为障碍物 obstacles
。第 i
个障碍物位于网格点 obstacles[i] = (xi, yi)
。
机器人无法走到障碍物上,它将会停留在障碍物的前一个网格方块上,但仍然可以继续尝试进行该路线的其余部分。
返回从原点到机器人所有经过的路径点(坐标为整数)的最大欧式距离的平方。(即,如果距离为 5
,则返回 25
)
注意:
- 北表示
+Y
方向。 - 东表示
+X
方向。 - 南表示
-Y
方向。 - 西表示
-X
方向。
示例 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
提示:
1 <= commands.length <= 104
commands[i]
is one of the values in the list[-2,-1,1,2,3,4,5,6,7,8,9]
.0 <= obstacles.length <= 104
-3 * 104 <= xi, yi <= 3 * 104
- 答案保证小于
231
解答
class Solution {
public int robotSim(int[] commands, int[][] obstacles) {
Map<String, String> map = new HashMap();
String connector = "-";
int ans = 0;
for(int[] obstacle : obstacles){
String key = obstacle[0] + connector + obstacle[1];
if(!map.containsKey(key)){
map.put(key, key);
}
}
int[][] arr = {{0,1},{1,0},{0,-1},{-1,0}};
int x = 0, y = 0;
int tempX = 0, tempY = 0;
int index = 0;
for(int command : commands){
if(command == -1){
index = (index + 1) % 4;
continue;
}
if(command == -2){
index = (index + 3) % 4;
continue;
}
for(int i = 0; i < command; i++){
tempX = x + arr[index][0];
tempY = y + arr[index][1];
String key = tempX + connector + tempY;
if(map.containsKey(key)){
break;
}
x = tempX;
y = tempY;
ans = Math.max(ans, x*x + y*y);
}
}
return ans;
}
}
3、字母异位词分组(中等)
题目地址:https://leetcode.cn/problems/group-anagrams/
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
示例 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 List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap();
for(String str :strs){
String key = geneKey(str);
if(map.containsKey(key)){
map.get(key).add(str);
}else{
List<String> list = new ArrayList();
list.add(str);
map.put(key, list);
}
}
List<List<String>> ans = new ArrayList();
for(List<String> list : map.values()){
ans.add(list);
}
return ans;
}
private String geneKey(String str){
char[] ch = str.toCharArray();
Arrays.sort(ch);
String key = "";
for(char c : ch){
key += c;
}
return key;
}
}
4、串联所有单词(困难)
题目地址:https://leetcode.cn/problems/substring-with-concatenation-of-all-words/
给定一个字符串 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
由小写英文字母组成
解答
先化繁为简,首先 words 中单词的长度是相等的,那么 words 中字符的长度是一定的,假设words中字符的长度为wordsCountLength,那么只需要从s的第一个字符开始向后循环,每次取出长度为wordsCountLength的字符串,去判断该字符串是否与words匹配即可,如果匹配,把下标放入集合,最终返回即可。
那么就剩余怎么判断一个字符串和words匹配了,由于words中单词长度一样,那么就可以将字符串按照单词的长度也分成一个个单词,为了判断方便,就可以用哈希表,将单词作为key,单词数量作为value,只需要判断两个map里面的key是一样的,且key中的value相等即可,如果判断成功,则为匹配,不成功,则为不匹配。
易错点:这里有一个易错点,使用集合时,使用的都是包装类型,例如 Integer,判断值相等,要使用原子类型int,否则有可能判断错误,为什么说有可能呢,因为在java中,-128到127是放在缓存中的,因此就算是使用Integer包装,也是同一个Integer,值也相等,但是超过这个范围,使用Integer包装的值就不等了。
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> list = new ArrayList();
int wordsCountLength = words.length * words[0].length();
Map<String, Integer> wordsMap = new HashMap();
for(String word : words){
int count = wordsMap.containsKey(word) ? wordsMap.get(word) + 1 : 1;
wordsMap.put(word, count);
}
for(int i=0; i+wordsCountLength <= s.length(); i++){
String str = s.substring(i, i+wordsCountLength);
if(containsWords(str, wordsMap, words[0].length())){
list.add(i);
}
}
return list;
}
private boolean containsWords(String str, Map<String, Integer> wordsMap, int wordLength){
Map<String, Integer> map = new HashMap();
for(int i=0; i< str.length(); i=i+wordLength){
String key = str.substring(i, i+wordLength);
int count = map.containsKey(key) ? map.get(key) + 1 : 1;
map.put(key, count);
}
return containsWords(wordsMap, map);
}
private boolean containsWords(Map<String, Integer> wordsMap, Map<String, Integer> strWordsMap){
if(wordsMap.size() != strWordsMap.size()){
return false;
}
for(String key : wordsMap.keySet()){
if(!strWordsMap.containsKey(key) || strWordsMap.get(key).intValue() != wordsMap.get(key).intValue()){
return false;
}
}
return true;
}
}
5、LRU 缓存(中等)
题目地址:https://leetcode.cn/problems/lru-cache/description/
请你设计并实现一个满足 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
解答
class LRUCache {
ListNode headNode;
ListNode endNode;
int capacity;
int count;
Map<Integer, ListNode> map;
public LRUCache(int capacity) {
headNode = new ListNode();
endNode = new ListNode();
headNode.next = endNode;
endNode.pre = headNode;
this.capacity = capacity;
count = 0;
map = new HashMap();
}
public int get(int key) {
if(map.containsKey(key)){
moveNode(map.get(key));
return map.get(key).val;
}
return -1;
}
public void put(int key, int value) {
if(map.containsKey(key)){
ListNode listNode = map.get(key);
listNode.val = value;
moveNode(listNode);
}else{
if(count == capacity){
map.remove(endNode.pre.key);
delNode(endNode.pre);
}else{
count++;
}
ListNode listNode = new ListNode(key, value);
map.put(key, listNode);
addHeadNode(listNode);
}
}
private void moveNode(ListNode listNode){
delNode(listNode);
addHeadNode(listNode);
}
private void delNode(ListNode listNode){
listNode.pre.next = listNode.next;
listNode.next.pre = listNode.pre;
}
private void addHeadNode(ListNode listNode){
headNode.next.pre = listNode;
listNode.next = headNode.next;
headNode.next = listNode;
listNode.pre = headNode;
}
}
class ListNode{
public ListNode next;
public ListNode pre;
public int key;
public int val;
public ListNode(int key, int val){
this.key = key;
this.val = val;
}
public ListNode(){
}
}
/**
* 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);
*/
6、子域名访问统计(中等)
题目地址:https://leetcode.cn/problems/subdomain-visit-count/description/
网站域名 "discuss.leetcode.com"
由多个子域名组成。顶级域名为 "com"
,二级域名为 "leetcode.com"
,最低一级为 "discuss.leetcode.com"
。当访问域名 "discuss.leetcode.com"
时,同时也会隐式访问其父域名 "leetcode.com"
以及 "com"
。
计数配对域名 是遵循 "rep d1.d2.d3"
或 "rep d1.d2"
格式的一个域名表示,其中 rep
表示访问域名的次数,d1.d2.d3
为域名本身。
- 例如,
"9001 discuss.leetcode.com"
就是一个 计数配对域名 ,表示discuss.leetcode.com
被访问了9001
次。
给你一个 计数配对域名 组成的数组 cpdomains
,解析得到输入中每个子域名对应的 计数配对域名 ,并以数组形式返回。可以按 任意顺序 返回答案。
示例 1:
输入:cpdomains = ["9001 discuss.leetcode.com"]
输出:["9001 leetcode.com","9001 discuss.leetcode.com","9001 com"]
解释:例子中仅包含一个网站域名:"discuss.leetcode.com"。
按照前文描述,子域名 "leetcode.com" 和 "com" 都会被访问,所以它们都被访问了 9001 次。
示例 2:
输入:cpdomains = ["900 google.mail.com", "50 yahoo.com", "1 intel.mail.com", "5 wiki.org"]
输出:["901 mail.com","50 yahoo.com","900 google.mail.com","5 wiki.org","5 org","1 intel.mail.com","951 com"]
解释:按照前文描述,会访问 "google.mail.com" 900 次,"yahoo.com" 50 次,"intel.mail.com" 1 次,"wiki.org" 5 次。
而对于父域名,会访问 "mail.com" 900 + 1 = 901 次,"com" 900 + 50 + 1 = 951 次,和 "org" 5 次。
提示:
1 <= cpdomain.length <= 100
1 <= cpdomain[i].length <= 100
cpdomain[i]
会遵循"repi d1i.d2i.d3i"
或"repi d1i.d2i"
格式repi
是范围[1, 104]
内的一个整数d1i
、d2i
和d3i
由小写英文字母组成
解答
class Solution {
private Map<String, Integer> map;
public List<String> subdomainVisits(String[] cpdomains) {
map = new HashMap();
String numberSplit = " ";
for(String cpdomain : cpdomains){
String[] arr = cpdomain.split(numberSplit);
int number = Integer.parseInt(arr[0]);
setNumber(arr[1], number);
}
List<String> list = new ArrayList();
for(String key : map.keySet()){
list.add(map.get(key) + " " + key);
}
return list;
}
private void setNumber(String key, int number){
int value = map.containsKey(key) ? map.get(key) + number : number;
map.put(key, value);
if(key.indexOf(".") > -1){
setNumber(key.substring(key.indexOf(".")+1), number);
}
}
}
7、数组的度(简单)
题目地址:https://leetcode.cn/problems/degree-of-an-array/
给定一个非空且只包含非负数的整数数组 nums
,数组的 度 的定义是指数组里任一元素出现频数的最大值。
你的任务是在 nums
中找到与 nums
拥有相同大小的度的最短连续子数组,返回其长度。
示例 1:
输入:nums = [1,2,2,3,1]
输出:2
解释:
输入数组的度是 2 ,因为元素 1 和 2 的出现频数最大,均为 2 。
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组 [2, 2] 的长度为 2 ,所以返回 2 。
示例 2:
输入:nums = [1,2,2,3,1,4,2]
输出:6
解释:
数组的度是 3 ,因为元素 2 重复出现 3 次。
所以 [2,2,3,1,4,2] 是最短子数组,因此返回 6 。
提示:
nums.length
在1
到50,000
范围内。nums[i]
是一个在0
到49,999
范围内的整数。
解答
class Solution {
public int findShortestSubArray(int[] nums) {
Map<Integer, Integer> indexMap = new HashMap();
Map<Integer, Integer> countMap = new HashMap();
int endIndex = 0;
int numberCount = 0;
for(int i=0; i<nums.length; i++){
// 计算当前值的count
int count = countMap.containsKey(nums[i]) ? countMap.get(nums[i]) + 1 : 1;
// 获取当前值第一次出现的下标
int firstIndex = indexMap.containsKey(nums[i]) ? indexMap.get(nums[i]) : i;
indexMap.put(nums[i], firstIndex);
countMap.put(nums[i], count);
// 如果当前值的数量最大 或者 与之前最大值相等,且最短,替换最后出现的下标以及总数
if(numberCount < count || numberCount == count && i-indexMap.get(nums[i]) < endIndex-indexMap.get(nums[endIndex])){
numberCount = count;
endIndex = i;
}
}
return endIndex-indexMap.get(nums[endIndex]) + 1;
}
}
-----------------------------------------------------------
---------------------------------------------
朦胧的夜 留笔~~