[前端]_一起刷leetcode 859. 亲密字符串

183 阅读4分钟

「这是我参与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 * 104
  • s 和 goal 由小写英文字母组成

思路

  1. 一开始想着用暴力枚举的方式,实现这个功能;

  2. 从前往后,每次拿当前元素,和它后面的每一个元素进行交换,判断是否能找到符合条件的元素;

  3. 找到了直接跳出循环即可;

  4. 循环结束后,没找到就是不存在;

  5. 当前,循环之前我们可以先判断两个字符串长度是否相等,长度不一样再怎么变化都不相等;

  6. 交换字符串的位置可以改成直接拼接一个新的字符串: 字符串可以分成五部分。

    比如: 我们交换 abcdefghcg

    可以通过切割cg, 然后分成五部分abcdefgh;

    然后交换cg的位置,再拼接在一起,代码实现就是: 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

image.png

事实证明,暴力解法不可取,时间超时。这时候我又想:有没有什么其他方法可以缩减时间复杂度的。O(n²)显然是行不通了。

优化

然后我想到这道题其实无非是想找两个不同位置的索引, 证明两个单词中其他位置都应该是一样的,只有两个位置是不一样的。那么问题就变得简单了。

  1. 用变量diff1记录第一个不一样的元素的位置,遍历它们相同的长度n,找到不一样的位置索引并记录下来;
  2. 然后再次遍历找到第二个位置后,就能通过我们上一轮的变换位置法,验证;
  3. 当前,可能我们找不到两个不同的元素,这时候我们需要判断,这两个单词是否完全相等,如果只有一个字符不相等,那怎么变化的没用;
  4. 判断完全等后,我们再判断是否有重复的元素, 比如aba, 两个a一样可以自己换着玩;
  5. 所以我们要是不想跑两次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;
};

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。