代码随想录03
哈希表
优势 ?
- 快速判断一个数是否在哈希表内
如何选择数组, set, map ?
- 数组: 数值限定大小
- set: 没有限定数值大小
- map: 不仅要判断n是否存在而且还要记录n的下标位置
242.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1: 输入: s = “anagram”, t = “nagaram” 输出: true
示例 2: 输入: s = “rat”, t = “car” 输出: false
说明: 你可以假设字符串只包含小写字母。
注: 字母异位词, 两个字符串含有的字母的种类和个数都相同.
**思路: ** 哈希表(数组), 根据ascii码找下标
**代码: **
class Solution {
public boolean isAnagram(String s, String t) {
int[] arr = new int[26];
// 1. 统计s的字符
for(int i = 0;i < s.length();i++) arr[s.charAt(i) - 'a']++;
// 2. 减去t的字符
for(int i = 0;i < t.length();i++) arr[t.charAt(i) - 'a']--;
// 3. 判断arr是否全为0
for(int i = 0;i < arr.length;i++) {
if(arr[i] != 0) return false;
}
return true;
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(1)
383.赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)
注意:
你可以假设两个字符串均只含有小写字母。
canConstruct(“a”, “b”) -> false
canConstruct(“aa”, “ab”) -> false
canConstruct(“aa”, “aab”) -> true
思路:
- 只需要判断magazine是否可以组成ransom, 即maga中是否含有ran的所有字母, 最终数组中的每个 值不小于0即可
代码:
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
int[] letter = new int[26];
// 1. 判断长度是否合格
if(ransomNote.length() > magazine.length()) return false;
// 2. 统计magazine的字母
for(int i = 0;i < magazine.length();i++) {
letter[magazine.charAt(i) - 'a']++;
}
// 3. 统计ransomNote的字母
for(int i = 0;i < ransomNote.length();i++) {
letter[ransomNote.charAt(i) - 'a']--;
}
// 4. 判断letter是否合格
for(int i = 0;i < letter.length;i++) {
if(letter[i] < 0) return false;
}
return true;
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(1)
49.字母异位词分组
思路:
- 将每个字符串重新从大到小排列, 排序后相同的为一组收集到map中, 返回values
代码:
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
// 1. 建立容器
Map<String,List<String>> m = new HashMap<>();
// 2. 遍历, 排序
for(String str : strs) {
char[] s = str.toCharArray();
Arrays.sort(s);
// 3. 收集
m.computeIfAbsent(new String(s),k -> new ArrayList<String>()).add(str);
}
// 4. 返回
return new ArrayList<>(m.values());
}
}
438.找到字符串中所有字母异位词
给定两个字符串 s
和 p
,找到 s
中所有 p
的异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
法一:
- 思路: 定长滑窗
- 代码:
class Solution {
public List<Integer> findAnagrams(String s, String p) {
// 1. 定义容器, ans, 统计s, 统计p
List<Integer> ans = new ArrayList<>();
int[] cntS = new int[26];
int[] cntP = new int[26];
// 2. 统计p
for(int i = 0;i < p.length();i++) {
cntP[p.charAt(i) - 'a']++;
}
// 3. 统计s
for(int r = 0;r < s.length();r++) {
// 3.1 移入右端点
cntS[s.charAt(r) - 'a']++;
int l = r - p.length() + 1;
if(l < 0) continue;
// 3.2 更新
if(Arrays.equals(cntP,cntS)) {
ans.add(l);
}
// 3.3 移除左端点
cntS[s.charAt(l) - 'a']--;
}
// 4. 返回
return ans;
}
}
- 时间复杂度:O(∣Σ∣_m_+_n_),其中 _m_ 是 _s_ 的长度,_n_ 是 _p_ 的长度,∣Σ∣=26 是字符集合的大小。
- 空间复杂度:O(∣Σ∣)。返回值不计入。
法二:
- 思路: 不定长滑窗, 滑动时每次都保持s中r位置的字母数 <= p中该字母的数量, 如果某次循环中窗口中的长度等于p的长度, 则该窗口内为异位词, 记录l
- 代码
class Solution {
public List<Integer> findAnagrams(String s, String p) {
// 1. 定义容器
List<Integer> ans = new ArrayList<>();
int[] cntP = new int[26];
for(int i = 0;i < p.length();i++) cntP[p.charAt(i) - 'a']++;
// 2. 处理s
int l = 0;
for(int r = 0;r < s.length();r++) {
// 2.1 进右
cntP[s.charAt(r) - 'a']--;
// 2.2 判断该字母是否多进, 若是, 从左侧出
while(cntP[s.charAt(r) - 'a'] < 0) {
cntP[s.charAt(l++) - 'a']++;
}
// 2.3 判断循环是否找到字母异位词
if(r - l + 1 == p.length()) ans.add(l);
}
// 3. 返回
return ans;
}
}
349.两个数组的交集
法一:
- 思路: set做hash表
- 代码:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 1. 定义容器: 统计nums1的set, 记录ans的set
Set<Integer> set = new HashSet<>();
Set<Integer> equ = new HashSet<>();
// 2. 统计nums1
for(int n : nums1) {
set.add(n);
}
// 3. 统计nums2, 记录equ
for(int n : nums2) {
if(set.contains(n)) {
equ.add(n);
}
}
// 4. 将equ转为数组, 返回
int[] ans = new int[equ.size()];
int i = 0;
for(int n : equ) {
ans[i++] = n;
}
return ans;
}
}
法二:
- 思路: 数组做hash表
- 代码:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 1. 定义容器, 处理nums1的arr1, 处理nums2的arr2, 收集交集的set
int[] arr1 = new int[1001];
int[] arr2 = new int[1001];
Set<Integer> equ = new HashSet<>();
// 2. 处理nums1, nums2
for(int i : nums1) {
arr1[i]++;
}
for(int i : nums2) {
arr2[i]++;
}
// 3. 收集交集
for(int i = 0; i < 1001;i++) {
if(arr1[i] > 0 && arr2[i] > 0) {
equ.add(i);
}
}
// 4. 返回
int[] ans = new int[equ.size()];
int i = 0;
for(int n : equ) {
ans[i++] = n;
}
return ans;
}
}
350.两个数组的交集 II
法一:
- 思路: 数组哈希表
- 代码:
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
// 1. 统计nums1的arr1,统计nums2的arr2,记录答案的ans
int[] arr1 = new int[1001];
int[] arr2 = new int[1001];
int[] ans = new int[1001];
// 2. 遍历nums1, nums2
for(int i : nums1) {
arr1[i]++;
}
for(int i : nums2) {
arr2[i]++;
}
// 3. 比较, 记录ans
int j = 0;
for(int i = 0;i < 1001;i++) {
while(arr1[i] > 0 && arr2[i] > 0) {
arr1[i]--;
arr2[i]--;
ans[j++] = i;
}
}
// 4. 返回
return Arrays.copyOfRange(ans,0,j);// [0,j)
}
}
- 时间复杂度:O(m+n)
- 空间复杂度:O(m+n)
法二:
- 思路: 排序 + 双指针, 找到两数组中相同的数, 收集
- 代码
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int length1 = nums1.length, length2 = nums2.length;
int[] intersection = new int[Math.min(length1, length2)];
int index1 = 0, index2 = 0, index = 0;
while (index1 < length1 && index2 < length2) {
if (nums1[index1] < nums2[index2]) {
index1++;
} else if (nums1[index1] > nums2[index2]) {
index2++;
} else {
intersection[index] = nums1[index1];
index1++;
index2++;
index++;
}
}
return Arrays.copyOfRange(intersection, 0, index);
}
}
- 时间复杂度:O(mlogm+nlogn),其中 m 和 n 分别是两个数组的长度。对两个数组进行排序的时间复杂度是 O(mlogm+nlogn),遍历两个数组的时间复杂度是 O(m+n),因此总时间复杂度是 O(mlogm+nlogn)。
- 空间复杂度:O(min(m,n)),其中 m 和 n 分别是两个数组的长度。为返回值创建一个数组 equ,其长度为较短的数组的长度。
202.快乐数
**法一: **
- 思路: set哈希表, 用set记录每次的平方和后的值, 判断是否重复, 若重复则不是快乐数, 否则是
- 代码:
class Solution {
// 计算每位平方和的函数
public static int compute(int n) {
int res = 0;
while (n > 0) {
int i = n % 10;
n = n / 10;
res += i * i;
}
return res;
}
public boolean isHappy(int n) {
// 1. 容器, 收集每次计算的结果
Set<Integer> set = new HashSet<>();
// 2. 循环判断该数是不是快乐数
while(n != 1 && !set.contains(n)) {
set.add(n);
n = compute(n);
}
// 3. 返回
return n == 1;
}
}
1.两数之和
思路: map哈希表, key为nums中的数, value为数的下标 (找Key的速度快)
**代码: **
class Solution {
public int[] twoSum(int[] nums, int target) {
// 1. 建立容器, 遍历nums的map
Map<Integer,Integer> map = new HashMap<>();
// 2. 遍历nums, 收集map
for(int i = 0;i < nums.length;i++) {
// 2.1 判断该位置的补数是否在map中
// 2.1.1 在, 返回
if(map.containsKey(target - nums[i])) {
return new int[] {map.get(target - nums[i]), i};
}
// 2.2.2 不在, 本次的数值加入map
map.put(nums[i],i);
}
// 3. 返回
return null;
}
}
454.四数相加 II
**思路: ** map哈希表, 四个数组两两组合, 变为两个数组的判断, 思路同1
**代码: **
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
// 1. 定义容器, 收集1+2的map, 返回的答案ans
int ans = 0;
Map<Integer,Integer> map = new HashMap<>();
// 2. 计算1 2
for(int i : nums1) {
for(int j : nums2) {
map.put(i + j,map.getOrDefault(i + j,0) + 1);
}
}
// 3. 遍历3 4, 找补
for(int i : nums3) {
for(int j : nums4) {
if(map.containsKey(0 - i - j)) {
ans += map.get(0 - i - j);
}
}
}
// 4. 返回
return ans;
}
}
15.三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
**思路: ** 双指针, 数组有序后, left指向i + 1, right指向nums.length - 1
代码:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 1. 定义容器, ans
List<List<Integer>> ans = new ArrayList<>();
// 2. 对原数组进行排序
Arrays.sort(nums);
// 3. 遍历查找
for (int i = 0; i < nums.length; i++) {
int left = i + 1;
int right = nums.length - 1;
// 3.1 对最外层进行剪枝,去重
if (nums[i] > 0) // 剪枝
return ans;// 从此之后不能再组成三元组
if (i > 0 && nums[i] == nums[i - 1])
continue;// 去重nums[i]
// 3.2 移动left right
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];// 每次循环更新sum
if (sum < 0) {
left++;
} else if (sum > 0) {
right--;
} else {
ans.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 找到一个三元组后, 对nums[left],nums[right] 去重
while (left < right && nums[left] == nums[left + 1])
left++;
while (left < right && nums[right] == nums[right - 1])
right--;
left++;
right--;
}
}
}
// 4. 返回
return ans;
}
}
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
18.四数之和
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
**思路: ** 三层循环, 第一层控制i, 第二层控制j, 第三层控制left right
**代码: **
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
// 1. 定义容器, 返回ans
List<List<Integer>> ans = new ArrayList<>();
// 2. 对原数组排序
Arrays.sort(nums);
// 3. 一级剪枝, 去重
for(int i = 0;i < nums.length;i++) {
// 3.1 剪枝
if(nums[i] > target && nums[i] >= 0) return ans;
// 3.2 去重
if(i > 0 && nums[i] == nums[i - 1]) continue;
// 4. 二级剪枝, 去重
for(int j = i + 1;j < nums.length;j++) {
// 4.1 剪枝
if(nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) break;
// 不能return, 因为此时可能nums[j]走到了一个较大值, 但是i++后可能有新的解
// nums[i] + nums[j] > target不代表nums[++i] + nums[?](? < j) > target
// 4.2 去重
if(j > i + 1 && nums[j] == nums[j - 1]) continue;
// 5. 移动left right
int left = j + 1;
int right = nums.length - 1;
while(left < right) {
long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
if(sum < target) {
left++;
}else if(sum > target) {
right--;
}else {
ans.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
// 5.1 对l f去重
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
}
return ans;
}
}
- 时间复杂度: O(n^3)
- 空间复杂度: O(1)