242. 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
示例 1: 输入: s = “anagram”, t = “nagaram” 输出: true
示例 2: 输入: s = “rat”, t = “car” 输出: false
方法一:排序
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1);
Arrays.sort(str2);
return Arrays.equals(str1, str2);
}
}
这段代码定义了一个名为 Solution
的类,其中包含一个名为 isAnagram
的公共方法。这个方法用于判断两个字符串 s
和 t
是否为彼此的字母异位词(anagram)。字母异位词指的是组成两个单词的字符种类和每种字符的数量完全相同,但字符的顺序可以不同,例如 “listen” 和 “silent”。
方法的逻辑如下:
-
长度检查:首先,检查两个字符串的长度。如果长度不相等,它们不可能是异位词,因此直接返回
false
。 -
转换为字符数组:然后,将字符串
s
和t
分别转换为字符数组str1
和str2
。这是为了能够对字符进行排序和比较。 -
排序:使用
Arrays.sort()
方法对两个字符数组进行排序。排序后,如果两个字符串是异位词,它们排序后的数组应该是完全相同的。 -
比较并返回结果:最后,使用
Arrays.equals()
方法比较排序后的两个字符数组是否相等。如果相等,说明s
和t
是异位词,返回true
;否则,返回false
。
这种方法简单直观,但注意它在最坏情况下的时间复杂度为 O(n log n),其中 n 是字符串的长度,因为排序操作是最耗时的部分。如果面试官期望更高效的解法,可以考虑使用字符计数的方法,即将每个字符出现的次数进行统计然后比较,这样可以在 O(n) 时间内完成。
方法二:哈希表
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] table = new int[26];
for (int i = 0; i < s.length(); i++) {
table[s.charAt(i) - 'a']++;
}
for (int i = 0; i < t.length(); i++) {
table[t.charAt(i) - 'a']--;
if (table[t.charAt(i) - 'a'] < 0) {
return false;
}
}
return true;
}
}
这段代码定义了一个名为 Solution
的类,其中包含一个公共方法 isAnagram
,用于判断两个字符串 s
和 t
是否为字母异位词(anagrams)。字母异位词是指两个字符串中的字符种类和每种字符的数量完全相同,但顺序可以不同,例如 “listen” 和 “silent”。
方法的逻辑如下:
-
长度检查:首先,比较两个字符串的长度。如果长度不相等,它们不可能是异位词,直接返回
false
。 -
字符计数表:定义一个大小为26的小写英文字母表
table
,索引是从 ‘a’ 开始的,因此 ‘a’ 对应索引0,‘b’ 对应索引1,依此类推直到 ‘z’ 对应索引25。这个表用于统计每个字符出现的次数。 -
统计s中的字符:遍历字符串
s
,将每个字符出现的次数在table
中相应位置累加。这里利用了字符'a'
的ASCII值做偏移位,减去'a'
的ASCII值,得到的差即为字符在表中的索引。 -
校验t中的字符:遍历字符串
t
,减少table
中对应字符的计数。如果在减少后某字符的计数变为负数,说明t
中该字符的出现次数超过了s
中的次数,直接返回false
。 -
返回结果:如果遍历完
t
后没有返回false
,说明两字符串是异位词,返回true
。
这种方法的时间复杂度为 O(n),其中 n 是字符串的长度,因为它只需要遍历每个字符串一次,相比之前的排序解法更高效。空间复杂度为 O(1),因为固定大小的计数表不依赖于输入字符串的长度。
349. 两个数组的交集
给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
方法:两个集合
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<Integer>();
Set<Integer> set2 = new HashSet<Integer>();
for (int num : nums1) {
set1.add(num);
}
for (int num : nums2) {
set2.add(num);
}
return getIntersection(set1, set2);
}
public int[] getIntersection(Set<Integer> set1, Set<Integer> set2) {
if (set1.size() > set2.size()) {
return getIntersection(set2, set1);
}
Set<Integer> intersectionSet = new HashSet<Integer>();
for (int num : set1) {
if (set2.contains(num)) {
intersectionSet.add(num);
}
}
int[] intersection = new int[intersectionSet.size()];
int index = 0;
for (int num : intersectionSet) {
intersection[index++] = num;
}
return intersection;
}
}
这段代码定义了一个名为 Solution
的类,其中包含两个方法,用来找出两个整数数组(nums1
和 nums2
)的交集,并以数组的形式返回结果。交集元素不重复,且结果数组中的元素按元素的升序排列(尽管代码逻辑中并未直接排序,但因HashSet的特性及遍历顺序,结果会自然按升序输出)。
方法分析:
1. intersection
方法
- 输入:两个整数数组
nums1
和nums2
。 - 输出:它们的交集数组。
- 逻辑:
- 分别将两个数组中的元素添加到两个哈希集合(
HashSet
)中,哈希集合自动去重。 - 调用
getIntersection
方法,传入两个集合,以获取交集。
- 分别将两个数组中的元素添加到两个哈希集合(
2. getIntersection
方法
- 输入:两个哈希集合
set1
和set2
,代表两个数组去重后的元素集合。 - 输出:两个集合的交集,以数组形式返回。
- 逻辑:
- 优化:如果
set1
的大小大于set2
,交换集合进行处理,以减少遍历次数,优化性能。
- 优化:如果
- 初始化一个新的哈希集合
intersectionSet
用于存放交集。 - 遍历较小集合 (
set1
),检查每个元素是否在另一个集合 (set2
) 中存在,存在则添加到intersectionSet
。 - 将
intersectionSet
转换为数组:初始化一个大小等于交集大小的数组intersection
,遍历intersectionSet
,将元素填充到数组中。 - 返回交集数组。
总结
这段代码实现了一个高效寻找两个整数数组交集的方法,利用哈希集合自动去重和查找存在的特性,简化了查找交集的过程。通过先转换为集合,再求交集,最后转换回数组的形式,实现了既去重又找交集的目标,且代码逻辑清晰易懂。
202. 快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例:
输入:19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
根据我们的探索,我们猜测会有以下三种可能。
最终会得到 111。
最终会进入循环。
值会越来越大,最后接近无穷大。
方法:用哈希集合检测循环
class Solution {
private int getNext(int n) {
int totalSum = 0;
while (n > 0) {
int d = n % 10;
n = n / 10;
totalSum += d * d;
}
return totalSum;
}
public boolean isHappy(int n) {
Set<Integer> seen = new HashSet<>();
while (n != 1 && !seen.contains(n)) {
seen.add(n);
n = getNext(n);
}
return n == 1;
}
}
这段代码定义了一个名为Solution
的类,该类包含了两个方法:getNext
和isHappy
。这段代码是用来实现一个经典的编程问题——快乐数(Happy Number)判断。快乐数是指,将这个数的各个位数的平方和相加,然后再将这个和的各位数平方和相加,如此反复进行下去,最终会得到1,则称这个数为快乐数;若无限循环不为1,则不是快乐数。
getNext
方法
- 功能:计算一个整数的各位数字的平方和。
- 参数:
n
—— 输入的整数。 - 返回值:计算得到的各位数字平方和。
- 逻辑:
- 初始化
totalSum
为0,用于累加每位数字的平方和。 - 当
n > 0
时,循环执行以下操作:- 取
n
的个位数d
(n % 10
)。 - 将
n
除以10并整除,去掉个位数(n / 10
)。 - 将
d
的平方累加到totalSum
中。
- 取
- 初始化
- 返回累加后的
totalSum
。
isHappy
方法
- 功能:判断一个整数是否为快乐数。
- 参数:
n
—— 需要判断的整数。 - 返回值:
true
如果n
是快乐数,否则false
。 - 逻辑:
- 初始化一个哈希集合
seen
来存储已经计算过的数,避免无限循环。 - 当
n
不等于1且seen
中没有见过n
时,执行循环:- 将
n
添加到seen
中,标记已访问。 - 更新
n
为n
的下一个数字的平方和(调用getNext(n)
)。
- 将
- 循环结束时,如果
n
为1,说明是快乐数,返回true
;否则,不是快乐数,返回false
。
- 初始化一个哈希集合
整体来看,这段代码实现了判断一个整数是否为快乐数的逻辑,利用了循环和哈希集合来避免无限循环,以及计算每一位数字的平方和的逻辑来逐步检验是否能收敛到1。
1. 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
方法一:暴力枚举
class Solution {
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[0];
}
}
这段代码定义了一个名为 Solution
的类,其中包含一个方法 twoSum
。这个方法用于解决经典的 “两数之和” 问题:给定一个整数数组 nums
和一个目标值 target
,找出数组中和为目标值的那两个数的索引,并以数组形式返回这两个索引。注意,答案中的索引是无序的,且保证一定存在唯一解。
代码逻辑分析:
-
初始化:首先获取数组的长度
n
。 -
双重循环:
- 外层循环(由变量
i
控制)遍历数组中的每个元素。 - 内层循环(由变量
j
控制)从i+1
开始遍历到数组末尾,这样保证了不会重复计算且避免自我匹配(比如(i, i)
的情况)。 - 条件判断:在内层循环中,检查当前的
nums[i]
和nums[j]
之和是否等于目标值target
。- 如果相等,立即返回一个新数组,包含两个索引
{i, j}
。
- 如果相等,立即返回一个新数组,包含两个索引
- 外层循环(由变量
-
特殊情况处理:如果遍历完所有可能的组合都没有找到满足条件的索引对,理论上根据题目描述应该不存在这种情况(因为保证有唯一解),但为了代码完整性,这里返回一个空数组
{}
。
注意事项:
- 代码中存在一个小错误,外层循环的条件应当是
i < n
而非i++
。修正后的循环应为for (int i = 0; i < n; ++i)
改为for (int i = 0; i < n; i++)
。这是书写习惯问题,不影响逻辑理解,但可能导致语法错误。 - 代码逻辑本身可以优化,例如使用哈希表来降低时间复杂度至O(n),但基于原始问题要求,此解答是直接的暴力解法实现。
方法二:哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
}
这段代码定义了一个名为 Solution
的类,其中包含一个方法 twoSum
,用于解决「两数之和」问题。给定一个整数数组 nums
和一个目标值 target
,找到 nums
中的两个数,使得它们的和等于 target
,返回这两个数的数组下标。
代码逻辑分析:
-
初始化哈希表(HashMap):创建一个哈希表
hashtable
来存储数组中的值及其对应的下标,以供快速查找。 -
遍历数组:遍历数组
nums
。- 对于每个元素
nums[i]
,计算target - nums[i]
,查看该差值是否在hashtable
中存在。 - 若存在,说明找到了和为目标值的一对数,直接返回这对数在原数组中的下标
{hashtable.get(target - nums[i]), i}
。 - 若不存在,将当前元素及其下标
nums[i], i
存入hashtable
,以便后续查找。
- 对于每个元素
-
特殊情况处理:如果遍历完整个数组都没找到符合条件的数对(理论上题目保证有解,此处逻辑更多是出于代码完整性考虑),返回一个空数组
{}
。
代码优化点:
- 相较于暴力解法(两重循环),该方法通过使用哈希表将时间复杂度优化到了 O(n),空间换时间的经典范例。
- 注意,代码中存在一处小错误,外层循环的条件应为
i < nums.length
而非i++
,正确写法应为for (int i = 0; i < nums.length; ++i)
,这是书写疏忽导致的笔误,需修正以避免语法错误。
整体而言,此段代码实现了一个高效且常见的「两数之和」问题解决方案,利用哈希表降低了搜索配对的成本,是面试和实际应用中的常见解法。