java
class Solution {
// 1. 定义一个名为 twoSum 的公共方法,接收整数数组 nums 和整数目标值 target,返回一个整数数组(即两个索引)。
public int[] twoSum(int[] nums, int target) {
// 2. 创建一个 HashMap,名为 hashtable。
// 键 (Key) 用于存储数组中的元素值 (Integer),值 (Value) 用于存储该元素对应的数组下标 (Integer)。
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
// 3. 使用 for 循环遍历数组 nums,变量 i 表示当前元素的索引。
for (int i = 0; i < nums.length; ++i) {
// 4. 检查哈希表中是否存在一个键,其值等于 target - nums[i]。
// target - nums[i] 就是与当前元素 nums[i] 配对的目标值(补数)。
if (hashtable.containsKey(target - nums[i])) {
// 5. 如果哈希表中存在这个补数,说明找到了匹配的一对。
// 返回一个新的整数数组,包含两个索引:
// 第一个索引是哈希表中存储的补数对应的值(即补数在原数组中的下标),
// 第二个索引是当前元素的下标 i。
return new int[]{hashtable.get(target - nums[i]), i};
}
// 6. 如果哈希表中没有找到匹配的补数,就将当前元素 nums[i] 作为键,其索引 i 作为值,存入哈希表。
// 这样,当后续遍历到其他元素时,就可以在这个表中查询它。
hashtable.put(nums[i], i);
}
// 7. 如果循环结束仍未找到任何匹配对(根据题目描述,这种情况实际上不会发生,因为题目保证有解),
// 则返回一个空数组。
return new int[0];
}
}
核心思路与注意点
-
核心思路 :这是一次遍历+哈希表查找的解法。它牺牲了空间复杂度(使用哈希表)来换取时间复杂度(从O(n²)降到O(n)) 。在遍历数组时,对于每个元素
nums[i],只需以O(1)的时间复杂度检查其"补数"target - nums[i]是否已经存在于哈希表中。 -
为什么先查找再插入 :这是此代码最精妙之处。先检查补数是否存在,再将当前元素放入哈希表 ,这样可以保证不会出现自己与自己匹配的情况。例如,如果
target = 6, nums = [3],在检查时哈希表为空,不会误判3 + 3 = 6。 -
声明与方法 :代码中的
Map<Integer, Integer>是Java的接口,HashMap是其实现类。使用接口类型声明hashtable是良好的编程实践,便于日后更换实现。 -
返回新数组 :
return new int[]{...}是Java中创建并返回一个匿名整型数组的简洁写法。
java
class Solution {
// 1. 定义一个名为 groupAnagrams 的公共方法,接收字符串数组 strs,返回一个列表的列表。
// 每个内部列表包含一组字母异位词。
public List<List<String>> groupAnagrams(String[] strs) {
// 2. 创建一个 HashMap,名为 map。
// 键 (Key) 是排序后的字符串 (String),作为一组字母异位词的唯一标识。
// 值 (Value) 是一个字符串列表 (List<String>),用于存储所有拥有相同键的原始字符串。
Map<String, List<String>> map = new HashMap<String, List<String>>();
// 3. 使用增强 for 循环遍历字符串数组 strs 中的每一个字符串 str。
for (String str : strs) {
// 4. 将当前字符串 str 转换为字符数组 array。
// 例如 "tea" -> ['t', 'e', 'a']
char[] array = str.toCharArray();
// 5. 对字符数组 array 进行排序。
// 例如 ['t', 'e', 'a'] -> ['a', 'e', 't']
Arrays.sort(array);
// 6. 将排序后的字符数组转换回字符串,作为哈希表的键 key。
// 例如 ['a', 'e', 't'] -> "aet"。所有字母异位词(如 "tea", "eat")都会得到相同的 key。
String key = new String(array);
// 7. 从哈希表 map 中获取键 key 对应的值(字符串列表)。
// 使用 getOrDefault 方法:如果 key 存在,则返回对应的 List;如果不存在,则返回一个新的空 ArrayList。
List<String> list = map.getOrDefault(key, new ArrayList<String>());
// 8. 将当前原始字符串 str 添加到这个列表中。
// 例如,当 key 为 "aet" 时,将 "tea" 添加到包含 "eat" 的列表中。
list.add(str);
// 9. 将更新后的列表放回哈希表 map 中,与键 key 关联。
// 如果 key 已存在,此操作会更新其对应的值。
map.put(key, list);
}
// 10. 循环结束后,所有字母异位词都已分组。
// 返回一个新的 ArrayList,其内容为哈希表 map 中所有的值(即所有的字符串列表)。
// map.values() 返回一个包含所有 List<String> 的集合视图。
return new ArrayList<List<String>>(map.values());
}
}
核心思路与注意点
-
核心思路 :利用排序后的字符串作为唯一键。互为字母异位词的字符串,在排序后一定完全相同(例如 "eat" 和 "tea" 排序后都是 "aet")。通过这个唯一的键,可以轻松地将所有异位词分组到同一个列表中。
-
关键方法
getOrDefault:这是代码的精妙之处。它简化了逻辑:如果键存在,就获取已有的列表;如果不存在,就创建一个新列表。这避免了需要先判断map.containsKey(key)的繁琐写法。 -
时间与空间复杂度 :该方法的时间复杂度是 O(n * k log k) ,其中 n 是字符串数量,k 是最大字符串长度(主要来自排序操作)。空间复杂度是 O(n * k),用于存储哈希表和所有字符串。
-
适用场景 :此方法简洁易懂,适用于所有字符串(不仅限小写字母)。当字符串长度较短或数量适中时,性能表现良好。如果字符串很长,可以改用官方题解中的**"计数"方法**(使用长度为26的数组统计字母频率作为键),能将时间复杂度优化到 O(n * k)。
java
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
// 1. 创建一个 HashMap,用于存储分组结果。
// 键 (Key) 是字母频率的唯一标识(字符串),值 (Value) 是该组异位词的列表。
Map<String, List<String>> map = new HashMap<String, List<String>>();
// 2. 遍历输入的每个字符串
for (String str : strs) {
// 3. 创建一个长度为26的整型数组,用于统计每个字母的出现次数。
// 索引0对应'a',索引1对应'b',以此类推。
int[] counts = new int[26];
int length = str.length();
// 4. 遍历当前字符串的每个字符,并更新计数。
// str.charAt(i) - 'a' 计算出字符对应的索引(例如 'a'->0, 'e'->4)。
for (int i = 0; i < length; i++) {
counts[str.charAt(i) - 'a']++;
}
// 5. 使用 StringBuffer 高效地构建键(字符串)。
StringBuffer sb = new StringBuffer();
// 6. 遍历26个字母的计数数组
for (int i = 0; i < 26; i++) {
// 7. 如果某个字母的出现次数不为0,则将其拼接到键中。
// 拼接格式为:字母 + 出现次数。例如,出现1次'a'和1次'e'和1次't',键为"a1e1t1"。
// 这样确保了所有字母异位词(如"eat", "tea")生成相同的键。
if (counts[i] != 0) {
sb.append((char) ('a' + i)); // 将索引转换回字母
sb.append(counts[i]); // 追加出现次数
}
}
// 8. 将 StringBuffer 转换为字符串,作为哈希表的键。
String key = sb.toString();
// 9. 从 map 中获取键 key 对应的列表。
// 如果 key 不存在,则创建一个新的空 ArrayList。
List<String> list = map.getOrDefault(key, new ArrayList<String>());
// 10. 将当前原始字符串 str 添加到列表中。
list.add(str);
// 11. 将更新后的列表放回 map 中。
map.put(key, list);
}
// 12. 循环结束后,所有异位词已分组。
// 返回一个新的 ArrayList,包含 map 中所有的值(即所有分组列表)。
return new ArrayList<List<String>>(map.values());
}
}
核心思路与关键点
-
核心思路 :此方法利用字母出现频率作为唯一键 。因为互为字母异位词的字符串,其中每个字母出现的次数必然完全相同。所以,将每个字母及其出现次数拼接成字符串(如
"a1e1t1")作为键,就能准确地将所有异位词归入同一组。 -
与排序解法的对比 :排序解法的时间复杂度是 O(n * k log k) (主要来自排序),而计数解法是 O(n * k) ,性能更优 ,尤其适合长字符串。但计数解法假设字符串只包含小写字母(题目已说明
strs[i] 仅包含小写字母),适用场景更特定。 -
键的生成细节 :代码中拼接了字母和次数,例如
"a2b1"。这样可以避免歧义,并确保不同字母组合生成的键唯一。例如,"a2"和"a1b1"会被正确区分。 -
为什么用
StringBuffer:在循环中拼接字符串时,StringBuffer(或StringBuilder)比直接使用+操作符效率更高,因为后者会创建多个临时字符串对象。
java
class Solution {
public int longestConsecutive(int[] nums) {
// 1. 创建一个 HashSet,名为 num_set,用于存储数组中的所有数字。
// 使用 Set 的目的是为了去重,并提供 O(1) 时间复杂度的查找功能。
Set<Integer> num_set = new HashSet<Integer>();
// 2. 遍历数组 nums,将所有元素添加到 Set 中。
for (int num : nums) {
num_set.add(num);
}
// 3. 初始化变量 longestStreak,用于记录目前找到的最长连续序列的长度。
int longestStreak = 0;
// 4. 遍历 Set 中的每一个数字 num。
// 注意:这里遍历的是 Set,而不是原数组,可以自动去重,避免对重复元素进行不必要的处理。
for (int num : num_set) {
// 5. 关键判断:检查当前数字 num 是否是某个连续序列的"起点"。
// 如果 num - 1 存在于 Set 中,说明 num 不是序列的第一个元素,
// 那么以 num 为起点的序列长度一定不是最长的,因此跳过本次循环。
if (!num_set.contains(num - 1)) {
// 6. 当前数字是序列起点,初始化当前序列的数字 currentNum 和长度 currentStreak。
int currentNum = num;
int currentStreak = 1;
// 7. 使用 while 循环,持续检查 currentNum + 1 是否存在于 Set 中。
// 如果存在,则说明序列可以继续延伸,更新 currentNum 和 currentStreak。
while (num_set.contains(currentNum + 1)) {
currentNum += 1;
currentStreak += 1;
}
// 8. 当 while 循环结束,说明从起点开始的连续序列已经完整找到。
// 使用 Math.max 更新全局的最长序列长度 longestStreak。
longestStreak = Math.max(longestStreak, currentStreak);
}
}
// 9. 返回最终找到的最长连续序列的长度。
return longestStreak;
}
}
核心思路与关键点
-
核心思路 :利用 哈希集合(HashSet) 实现 O(1) 的查找,并通过只从序列起点开始计数 来避免重复计算,从而实现整体 O(n) 的时间复杂度。这是解决"最长连续序列"问题的经典且高效的方法。
-
为什么从起点开始? 这是整个算法的精髓。通过
if (!num_set.contains(num - 1))这个判断,我们保证了只有当num是一个连续序列的第一个元素时(即不存在前驱num-1),才进入内层循环 。这避免了例如[1,2,3,4]中,从2、3、4开始重复计数,使得每个元素在内层循环中最多被访问一次。 -
时间复杂度分析 :虽然代码中有
for循环嵌套while循环,但总体时间复杂度是 O(n)。因为外层循环遍历每个元素,内层循环总共只会遍历所有序列中的元素一次。 -
空间复杂度 :O(n),用于存储哈希集合。