给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例 1:
输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:
输入: s = “rat”, t = “car”
输出: false
说明:
你可以假设字符串只包含小写字母。
进阶:
如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
第一步读懂题:
先读懂题目弄清我们的目标,要解决的到底是什么问题。如果题目都没读懂一起都是空谈,不管你接下来的算法有多么精妙,最后做出来也是错的。
刚开始在读题的时候并不清楚什么是字母异位词,不知道大家是一开始读就明白了,还是我太菜了😂。后来问了一下度娘才知道,原来异位词指的是:字母异位词指字母相同,但排列不同的字符串。在弄清楚了字母异位词后,现在我们再来分析一下这道题,其实就是比较s和t这两个字符串包含的字符是否相同,不能多也不能少,只是字符的排列顺序不同。
第二步分析解题思路:
先用最容易想到的思路来求解,往往就是刚读到题目时第一个想到的求解思路。
首先根据字母异位词的定义我们知道s和t的长度必须相同,所以如果s.length() != t.length()之间返回false。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
...
}
}
接下来最容易想到的就是把s在的每一个字符拿出来到t中查找看是否存在,如果s中的每一个字符在t中都存在我们就返回true?这里之所以打了一个问好就是想要大家思考一下这样真的对吗?
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
for (int i = 0; i < s.length(); i++) {
//如果t中的字符有一个在s中找不到就返回false
if (t.indexOf(s.charAt(i)) < 0) {
return false;
}
}
return true;
}
}
如果s=“abc”,t="cba"这样的不重复字符串还行,但要是像这样有重复的字符s=“abc”,t=“cbaa”,字符串s中的字符a在字符串t中也可以找到,很明显它们并不是字母异位词。所以我们要先解决重复字符的问题,如果我们把重复的字符合并呢?像下面这样:
s = “anagram”
a | n | g | r | m |
---|---|---|---|---|
3 | 1 | 1 | 1 | 1 |
t = “nagaram”
n | a | g | r | m |
---|---|---|---|---|
1 | 3 | 1 | 1 | 1 |
为了解决重复字符的问题,最容易想到的是用Map来存储,然后我们再来比较s和t,现在要同时比较字符和字符出现的次数。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
Map<Character, Integer> sMap = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
sMap.put(s.charAt(i), sMap.getOrDefault(s.charAt(i), 0) + 1);
}
Map<Character, Integer> tMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
tMap.put(t.charAt(i), tMap.getOrDefault(t.charAt(i), 0) + 1);
}
for (Map.Entry<Character, Integer> entry : sMap.entrySet()) {
if (!entry.getValue().equals(tMap.get(entry.getKey()))) {
return false;
}
}
return true;
}
}
提交倒是通过了但是这效率也太惨不忍睹了😂
第三步优化题解:
提高代码的可读性使其简洁明了,优化算法时间复杂度和空间复杂度。
在优化前我们先分析一下当前代码有哪些明显的优化空间,为了比较s和t中重复的字符我们分别对s和t整理了一遍然后再比较它们的字符和字符出现的次数,能不能在一次遍历中就搞定这些呢?
我们还是用一个Map来存储字符和字符出现的次数,但不同的是如果字符在s中出现我们就把计数加1,如果在t中出现我们就把计数减1,只有当次数为零时表示字符在s和t中出现了相同的次数。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1);
map.put(t.charAt(i), map.getOrDefault(t.charAt(i), 0) - 1);
}
for (Integer value : map.values()) {
if (value != 0) {
return false;
}
}
return true;
}
}
代码是简洁了一些,然而效率并没有提升😭
我们继续优化,我们都知道英文字母一共就26个a-z,所以不论字符串有多长都在这26个字母内只是出现的次数不同而已。对应字母a-z的ASCII码是97-122,我们只需要把所有的字母映射到一个长度为26的一维数组上即可。即每一个字母减最小编码,如a的编码是97-97=0映射到下标为0的数组上,数字的值还是保存字母出现的次数。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] counter = new int[26];
for (int i = 0; i < s.length(); i++) {
counter[s.charAt(i) - 'a']++;
counter[t.charAt(i) - 'a']--;
}
for (int i = 0; i < counter.length; i++) {
if (counter[i] != 0) {
return false;
}
}
return true;
}
}
效率还是提高了不少的
第四步一题多解:
思考一下问题还有没有其他的解法。
字母异位词的定义是字符相同顺序不同,如果我们把两个字符串中的字符按相同的方式排序一下是不是就能得到一样的字符串。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] sArr = s.toCharArray();
Arrays.sort(sArr);
char[] tArr = t.toCharArray();
Arrays.sort(tArr);
return Arrays.equals(sArr, tArr);
}
}
既然我们是刷题还是少用api自己写排序,前面用长度为26的一维数组解法是不是很像计数排序,而且计数排序很适合这种有固定范围重复度到的数据排序,我们试试用计数排序的方式来求解。
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] sArr = s.toCharArray();
countSort(sArr);
char[] tArr = t.toCharArray();
countSort(tArr);
return Arrays.equals(sArr,tArr);
}
private void countSort(char[] a){
if(a.length<2){
return;
}
char[] counter=new char['z'-'a'];
for(int i=0; i<a.length; i++){
counter[a[i]-'a']++;
}
int index=0;
for(int i=0; i<counter.length; i++){
while(counter[i]>0){
a[index++]=(char)(i+'a');
counter[i]--;
}
}
}
}
到这来题就解完了,进阶的问题大家可以思考一下,看看上面的解题思路是否还适用,还有没有其他的解法?如果有其他更好的解法欢迎评论交流。