常见算法题(二)

89 阅读3分钟

(1)找字符串最长回文子串

  • 指定一个字符串str,找到字符串中长度 >=2 的最长回文子串,不存在则返回null

举例

const str1 = 'abcdeed';
// 返回 'deed'
const str2 = 'abcbaefabc';
// 返回 'abcba'
const str3 = 'abcdefg';
// 返回 null

实现

/**
 * 函数描述: 判断字符串是否为回文串
 * @param {String} str 需要校验的字符串
 * @return {Boolean} 校验结果
 */
const testPDString = (str) => typeof str === 'string' ? !str.split('').some((ele, i) => ele !== str.at(-1 - i)) : alert('请入参字符串');

/**
 * 函数描述: 排列所有的字符串组合(不连续) 'abc', 其中 'ac'组合属于不连续的
 * @param {String || Array} str 字符串或由字符串组成的数组
 * @return {Array} 组合列表 
 * 举例1:输入 'abc' 返回 ['a', 'b', 'c', 'ab', 'ac', 'bc', 'abc']
 * 举例2: 输入 ['a', 'b', 'c'] 返回 ['a', 'b', 'c', 'ab', 'ac', 'bc', 'abc']
 */
const arrangeStrIntermittent = (str) => {
        const strComList = [];
        let strArr;
        if (typeof str === 'string') {
            strArr = str.split('');
        } else if (Array.isArray(str)) {
            strArr = [...str];
        } else {
            throw 'The arrangeStrIntermittent input parameter can only be a string or array';
        }
        for (let i = 0; i < strArr.length; i++) {
            strComList.push(strArr[i]); // 如果不需要字符本身,屏蔽这句代码
            loopStr(strArr, i, [strArr[i]]);
        }
        function loopStr(arr, i, prev) {
            i++;
            if (i < arr.length) {
                for (let j = i; j < arr.length; j++) {
                    const list = prev.concat(arr[j]);
                    strComList.push(list.join());
                    loopStr(arr, j, list)
                }
            }
        }
        return strComList;
};

/**
 * 函数描述: 排列所有的字符串组合(连续) 'abc', 其中 'ac'组合属于不连续的
 * @param {String || Array} str 字符串或由字符串组成的数组
 * @return {Array} 组合列表
 * 举例1:输入 'abc' 返回 ['a', 'b', 'c', 'ab', 'bc', 'abc']
 * 举例2: 输入 ['a', 'b', 'c'] 返回 ['a', 'b', 'c', 'ab', 'bc', 'abc']
 */
const arrangeStrContinuity = (str) => {
    const strComList = [];
    let strArr;
    if (typeof str === 'string') {
        strArr = str.split('');
    } else if (Array.isArray(str)) {
        strArr = [...str];
    } else {
        throw 'The arrangeStrContinuity input parameter can only be a string or array';
    }
    for (let i = 0; i < strArr.length; i++) {
        strComList.push(strArr[i]); // 如果不需要字符本身,屏蔽这句代码
        let prev = [strArr[i]];
        for (let j = i + 1; j < strArr.length; j++) {
            prev = prev.concat(strArr[j]);
            strComList.push(prev.join());
        }
    }
    return strComList;
};

/**
 * 函数描述: 找字符串中长度 >=2 的最长回文串
 * @param {String || Array} str 需要检索的字符串
 * @param {Boolean} strType 字符串组合方式
 * @return {String || Null} 最长的回文串 || null
 */
const findMaxLenStrPD = (str, strType = true) => {
    // 拿到字符串组合列表
    const list = strType ? arrangeStrContinuity(str) : arrangeStrIntermittent(str);
    // 排除非回文串与长度小于2的回文串
    const result = list.filter(ele => testPDString(ele) && ele.length > 1);
    // 存在回文串则返回长度最长的回文串,反之返回null
    return result.length === 0 ? null : result.reduce((ret, ele) => ret.length > ele.length ? ret : ele, '').replace(/,/g, '');
};

console.log(findMaxLenStrPD('abcdeed')); // deed
console.log(findMaxLenStrPD('abcbaefabc')); // abcba
console.log(findMaxLenStrPD('abcdefg')); // null
console.log(findMaxLenStrPD('abcbaefghgfe')); // efghgfe

(2)找字符串数组中的最长公共前缀

  • 如果不存在公共前缀返回 ""

举例

const list1 = ['apple', 'app', 'angel'];
// 返回 'a'
const list2 = ['abcd', 'efg', 'hijk'];
// 返回 ''

实现

/**
 * 函数描述: 找字符串数组中的最长公共前缀
 * @param {Array} arr 需要检索的字符串数组
 * @return {String} 最长公共前缀 || ''
 */
const findLongStrPrefix = (arr) => {
    if (!Array.isArray(arr)) {
        throw 'The findLongStrPrefix input parameter must be an array';
    }
    // 将arr第一个元素分割成一个数组
    const strArr = arr[0].split('');
    // 定义公共前缀默认值
    let str = '';
    // 外循环:根据arr第一个元素的长度遍历
    for (let i = 0; i < strArr.length; i++) {
        // 从arr第二个元素开始遍历
        for (let j = 1; j < arr.length; j++) {
            // 比较arr第一个元素第i个字符是否全等于arr其它元素的第i个字符
            if (arr[j][i] !== strArr[i]) {
                return str;
            }
        }
        // 内循环没有被中断,证明arr所有元素的第i个字符均一致,保存该字符。
        str += strArr[i];
    }
    return str;
}
console.log(findLongStrPrefix(['apple', 'app', 'angel'])); // a
console.log(findLongStrPrefix(['abcd', 'efg', 'hijk'])); // ''

(3)找字符串中无重复字符的最长子串

  • 给定一个字符串 str ,找出其中不含有重复字符的【最长子串】的长度。

举例

const str1 = 'abcdefghaccba';
// 返回 8  满足条件的子串为'abcdefgh'
const str2 = 'appopqrstaubvcwxyzppa';
// 返回 15   满足条件的子串为 'opqrstaubvcwxyz'
const str3 = 'eeqwrdf';
// 返回 6  满足条件的子串为 'eqwrdf'

实现

/**
 * 函数描述: 找字符串不含有重复字符的最长子串的长度。
 * @param {String} str 需要检索的字符串数组
 * @return {Number} subString.length
 */
const findLongSubStringLen = (str) => {
    if (typeof str !== 'string') {
        throw 'The findLongSubStringLen input parameter must be a string';
    }
    // 方法1: 
    const newArr = str.split(''), strList = [], len = newArr.length;
    for (let i = 0; i < len; i++) {
        let prev = [newArr[i]];
        strList.push(prev);
        for (let j = i + 1; j < len; j++) {
            if (strList[i].includes(newArr[j])) {
                break;
            } else {
                strList[i].push(newArr[j]);
            }
        }
    }
    return strList.reduce((res, ele) => res.length > ele.length ? res : ele, []).length;

    // 方法2: (比方法1 效率高)
    let endIndex = 0, startIndex = 0, step = 0; maxStr = '', maxLen = 0;
    const subStrDict = {};
    while (endIndex < str.length) {
        const endStr = str[endIndex];
        endIndex++;
        if (subStrDict[endStr]) {
            step++;
            subStrDict[endStr]++;
        } else {
            subStrDict[endStr] = 1;
        }
        
        while (step || endIndex === str.length) {
            // console.log(startIndex, endIndex);
            if (maxLen < endIndex - startIndex) {
                maxLen = endIndex - startIndex;
                maxStr = str.slice(startIndex, step ? endIndex - 1 : endIndex);
            }
            const startStr = str[startIndex];
            startIndex++;
            subStrDict[startStr]--;
            if (startStr === endStr) step--;
            if (endIndex === str.length) endIndex++;
        }
    }
    return maxStr.length;
};
console.log(findLongSubStringLen('abcdefghaccba')); // 8
console.log(findLongSubStringLen('appopqrstaubvcwxyzppa')); // 15
console.log(findLongSubStringLen('eeqwrdf')); // 6

(4)找A、B字符串中A对B的最小覆盖子串

  • 有A、B两个字符串,从A字符串中寻找涵盖B字符串全部字符的【最小子串】;如果不存在该子串,返回""
  • 对于【B】中重复字符,我们在【A】寻找的子字符串中该字符数量不得少于【B】中该字符数量。

举例

const strA = 'appopqrsctaubvctwxyzppa', strB = 'atv';
// 返回 'taubv'
const strA = 'bcbc', strB = 'bb';
// 返回 'bcb'
const strA = 'bc', strB = 'cc';
// 返回 ''

实现(利用滑动窗口实现)

/**
 * 函数描述:找A、B字符串中A对B的最小覆盖子串
 * @param {String} parent === A
 * @param {String} sub === B
 * @return {String} 最小覆盖子串 || ''
 */
const findMinSubString = (parent, sub) => {
    // 方式1: 
    const subDict = {}, parentDict = {};
    for (const ele of sub) subDict[ele] ? subDict[ele]++ : subDict[ele] = 1;
    const subDictLen = Object.keys(subDict).length;
    // LIndex: 滑动窗口的收缩指针  RIndex: 滑动窗口的扩张指针  minStrLen: 扩张收缩指针的历史最小间距
    // step: 记录找到的目标字符  minSubStr: 保存当前【最小覆盖子串】
    let LIndex = RIndex = step = 0, minStrLen = parent.length + 1, minSubStr = '';
    while (RIndex < parent.length) {
        const p = parent[RIndex];
        RIndex++;
        parentDict[p] ? parentDict[p]++ : parentDict[p] = 1
        
        if (subDict[p] && subDict[p] === parentDict[p]) step++;
        while (step === subDictLen) {
            if (minStrLen > RIndex - LIndex) {
                minStrLen = RIndex - LIndex;
                minSubStr = parent.slice(LIndex, RIndex);
            }
            const s = parent[LIndex];
            parentDict[s]--;
            LIndex++;
            if (subDict[s] && subDict[s] > parentDict[s]) step--;
        }
    }

    // // 方式2:
    // const subDict = {};
    // for (const ele of sub) subDict[ele] ? subDict[ele]++ : subDict[ele] = 1;
    // // LIndex: 滑动窗口的收缩指针  RIndex: 滑动窗口的扩张指针  minStrLen: 扩张收缩指针的历史最小间距
    // // step: 记录找到的目标字符  minSubStr: 保存当前【最小覆盖子串】
    // let LIndex = RIndex = 0, step = Object.keys(subDict).length, minStrLen = parent.length + 1, minSubStr = '';
    // while (RIndex < parent.length) {
    //     const p = parent[RIndex];
    //     RIndex++;
    //     if (p in subDict) {
    //         subDict[p]--;
    //         if (subDict[p] === 0) step--;
    //     }
    //     while (step === 0) {
    //         if (minStrLen > RIndex - LIndex) {
    //             minStrLen = RIndex - LIndex;
    //             minSubStr = parent.slice(LIndex, RIndex);
    //         }
    //         const s = parent[LIndex];
    //         LIndex++;
    //         if (s in subDict) {
    //             subDict[s]++
    //             if (subDict[s] === 1) step++
    //         };
    //     }
    // }

    return minSubStr;
};
console.log(findMinSubString('appopqrsctaubvctwxyzppa', 'atv')); // 'taubv'
console.log(findMinSubString('bcbc', 'bb')); // 'bcb'
console.log(findMinSubString('bc', 'cc')); // ''