「这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战」
题目
859. 亲密字符串
给你两个字符串 s 和 goal ,只要我们可以通过交换 s 中的两个字母得到与 goal 相等的结果,就返回 true ;否则返回 false 。
交换字母的定义是:取两个下标 i 和 j (下标从 0 开始)且满足 i != j ,接着交换 s[i] 和 s[j] 处的字符。
- 例如,在
"abcd"中交换下标0和下标2的元素可以生成"cbad"。
示例 1:
输入: s = "ab", goal = "ba"
输出: true
解释: 你可以交换 s[0] = 'a' 和 s[1] = 'b' 生成 "ba",此时 s 和 goal 相等。
示例 2:
输入: s = "ab", goal = "ab"
输出: false
解释: 你只能交换 s[0] = 'a' 和 s[1] = 'b' 生成 "ba",此时 s 和 goal 不相等。
示例 3:
输入: s = "aa", goal = "aa"
输出: true
解释: 你可以交换 s[0] = 'a' 和 s[1] = 'a' 生成 "aa",此时 s 和 goal 相等。
示例 4:
输入: s = "aaaaaaabc", goal = "aaaaaaacb"
输出: true
提示:
1 <= s.length, goal.length <= 2 * 104s和goal由小写英文字母组成
思路
-
一开始想着用暴力枚举的方式,实现这个功能;
-
从前往后,每次拿当前元素,和它后面的每一个元素进行交换,判断是否能找到符合条件的元素;
-
找到了直接跳出循环即可;
-
循环结束后,没找到就是不存在;
-
当前,循环之前我们可以先判断两个字符串长度是否相等,长度不一样再怎么变化都不相等;
-
交换字符串的位置可以改成直接拼接一个新的字符串: 字符串可以分成五部分。
比如: 我们交换
abcdefgh的c和g可以通过切割
c和g, 然后分成五部分ab、c、def、g、h;然后交换
c和g的位置,再拼接在一起,代码实现就是:s.slice(0, i) + s[j] + s.slice(i + 1, j) + s[i] + s.slice(j + 1)
实现:
/**
* @param {string} s
* @param {string} goal
* @return {boolean}
*/
var buddyStrings = function(s, goal) {
// 判断字符串长度是否相等
if (s.length !== goal.length) {
return false;
}
const n = s.length;
// 双重遍历,两两交换
for (let i = 0; i < n - 1; i++) {
for (let j = i + 1; j < n; j++) {
let cur = s.slice(0, i) + s[j] + s.slice(i + 1, j) + s[i] + s.slice(j + 1);
if (cur === goal) {
return true;
}
}
}
return false;
};
BOOM
事实证明,暴力解法不可取,时间超时。这时候我又想:有没有什么其他方法可以缩减时间复杂度的。O(n²)显然是行不通了。
优化
然后我想到这道题其实无非是想找两个不同位置的索引, 证明两个单词中其他位置都应该是一样的,只有两个位置是不一样的。那么问题就变得简单了。
- 用变量
diff1记录第一个不一样的元素的位置,遍历它们相同的长度n,找到不一样的位置索引并记录下来; - 然后再次遍历找到第二个位置后,就能通过我们上一轮的变换位置法,验证;
- 当前,可能我们找不到两个不同的元素,这时候我们需要判断,这两个单词是否完全相等,如果只有一个字符不相等,那怎么变化的没用;
- 判断完全等后,我们再判断是否有重复的元素, 比如
aba, 两个a一样可以自己换着玩; - 所以我们要是不想跑两次for循环,可以在第一轮遍历顺带记录有没有出现过重复的元素。
优化代码
/**
* @param {string} s
* @param {string} goal
* @return {boolean}
*/
var buddyStrings = function(s, goal) {
// 判断字符串长度是否相等
if (s.length !== goal.length) {
return false;
}
const n = s.length;
// 记录足迹,两个全等的字符串,要判断有没有相同的字符串可以交换完不影响结果
let set = new Set();
let hasRepeat = false;
// 第一个不一样的数字的索引
let diff1 = -1;
// 一次遍历
for (let i = 0; i < n; i++) {
if (s[i] !== goal[i]) {
// 判断是第一个不同的元素还是第二个
if (diff1 === -1) {
diff1 = i;
} else {
// 第二个的话直接判断结果即可
let cur = s.slice(0, diff1) + s[i] + s.slice(diff1 + 1, i) + s[diff1] + s.slice(i + 1);
return cur === goal;
}
} else {
// 记录是否有一样的字符串
if(!hasRepeat) {
if (set.has(s[i])) {
hasRepeat = true;
}
set.add(s[i]);
}
}
}
// 如果只有一个字符串不一样,那么无论如何变化也不相等
// 要么全等,全等的时候判断是否有重复元素可以换着玩
return diff1 === -1 && hasRepeat;
};
看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。