算法奇想之最长公共前缀

386 阅读4分钟

起因

前几天接触到leetcode,开始写算法,在刷到最长公共前缀时,想到了一种奇怪的解法,所以记录下来。

题目描述

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""。

示例 1:

输入:strs = ["flower","flow","flight"] 输出:"fl"

示例 2:

输入:strs = ["dog","racecar","car"] 输出:""

解释:输入不存在公共前缀。

提示:

1 <= strs.length <= 200

0 <= strs[i].length <= 200

strs[i] 仅由小写英文字母组成

题解

var longestCommonPrefix = function(strs) {
    if (!strs.length) return ""; //如果数组为空,返回空字符串

    for (let i = 0; i < strs[0].length; i++) { //外层循环遍历第一个字符串中的每个字符
        const char = strs[0][i]; //获取第一个字符串的第i个字符

        for (let j = 1; j < strs.length; j++) { //内层循环遍历后续每个字符串
            //如果已经超过了某个字符串的长度,
            //或者第j个字符串的第i个字符与第一个字符串第i个字符不同
            if (i == strs[j].length || strs[j][i] !== char) {
                return strs[0].substring(0, i); //返回第一个字符串的0到i-1的子串
            }
        }
    }

    return strs[0]; //如果所有字符串都是第一个字符串的前缀,返回第一个字符串
};

这是一个比较普遍的解法,以第一个字符串(假设为A)为基准,将A的每一个位置的字符与后续每一个字符串的对应位置进行比较:

以 ["flower","flow","flight"]为例,先取出第一项的第一个字符f,然后比较后面每一项的第一个字符当比较到第三项时,发现第三个字符串flight的第三项是i,与第一项flowero不一致,表示第一个字符串的前两项是公共子串

华点

我突然想到一个方向,众所周知,在js中,对象的key只能是字符串或者symbolsymbol在此不是重点)是唯一的,那么我可以在上述题解的基础上修改一下,将每个字符串的每一项都注入对象,成为对象的key(属性值任意),然后比较对象自有属性的数量是否为1,如果大于1,则表示当前位置不是公共子串。 代码如下:

var longestCommonPrefix = function(arr) {
   //如果输入的数组为空,或者数组的第一个字符串为空字符串,直接返回空字符串。
   if (arr == null || arr[0].length == 0) return "";
   
   //声明一个对象,用于保存前缀
   let obj = {};

   //外层循环,遍历第一个字符串的每一个字符
   for (let i = 0; i < arr[0].length; i++) {
        //以当前前缀作为键,将其保存到对象中,值为true
        //这里利用了Object的键唯一性,如果后续的字符串有与之相同的前缀,
        //由于键冲突,不会在对象中增加新的元素,
        //可以取前i个,也可以一个一个的取,那么后续内层循环里面就相应取值就行
        obj[arr[0].slice(0, i + 1)] = true;
        //obj[arr[0][i+1]] = true

       //内层循环,遍历剩余的字符串
       for (let j = 1; j < arr.length; j++) {

            //如果当前索引已经超过了某个字符串的长度,说明该字符串到头了,
            //返回当前的最大公共前缀
            if (arr[j].length === i) {

                return arr[0].slice(0, i);
            }
            //同样的操作,用每个字符串的前缀作为键,存入对象中
            obj[arr[j].slice(0, i + 1)] = true;
             //obj[arr[j][i+1]] = true
            
        }
       
        //每轮内层循环结束后,检查对象中的键的个数
        //如果键的个数大于1,说明有字符串的当前前缀与第一个字符串的当前前缀不同,
        //返回最大公共前缀
        if (Object.keys(obj).length > 1) {
            return arr[0].slice(0, i);
        }

        //如果所有字符串的当前前缀都相同,则清空当前对象,进入下一轮前缀比对
        obj = {}
    }
    //如果所有字符串都被比对完,所有字符串的前缀都相同,
    //则返回第一个字符串,这就是最大公共前缀
    return arr[0];
};

我想顺着这个思路,大家应该很快就能想到Set,没错,Set类型的数据的每一项也是唯一的,完全可以取代obj对象:

 longestCommonPrefix(arr) {
    if (arr == null || arr[0].length == 0) return "";
    let obj = {}
    let mySet = new Set();
    for (let i = 0; i < arr[0].length; i++) {
        // obj[arr[0].slice(0, i + 1)] = true;
        mySet.add(arr[0].slice(0, i + 1));
        for (let j = 1; j < arr.length; j++) {

            if (arr[j].length === i) {

                return arr[0].slice(0, i);
            }
            // obj[arr[j].slice(0, i + 1)] = true;
            mySet.add(arr[j].slice(0, i + 1));
        }

        // if (Object.keys(obj).length > 1) {
        //     return arr[0].slice(0, i);
        // }
       
        if (mySet.size > 1) {
            return arr[0].slice(0, i);
        }

        // obj = {}
        mySet.clear();
    }
    return arr[0];
}

结语

上述内容是本人的突发奇想,不符合传统算法思想,利用js特性来实现功能,单纯就是提供一种实现方法,仅供娱乐。


这是本人的第一篇文章,非常粗糙,如果有朋友看到,还请见谅!