返回

刷题不只有算法题,还有经典题型「前端刷题」系列之三:串联所有单词的子串

前端

在前端开发中,算法题目不仅是考察开发者逻辑思维的重要方式之一,也是锻炼编程技巧的有效手段。其中,串联所有单词的子串是一个经常出现在各类技术面试和在线评测平台上的经典问题。本文将详细讲解如何利用动态规划和滑动窗口这两种方法来解决这个问题,并通过代码示例帮助理解。

动态规划法

解题思路

动态规划适用于具有最优子结构的问题,其中当前状态的解决方案依赖于之前的决策结果。在串联所有单词的子串问题中,我们可以通过构建一个二维数组来保存到目前为止找到的所有有效子串信息,并在此基础上不断更新状态以求得最终解。

代码示例

function findSubstring(str, words) {
    if (!str || !words.length) return [];
    const len = str.length;
    const wordLen = words[0].length;
    const totalWordsLength = wordLen * words.length;
    
    let dp = new Array(len).fill(false);
    for (let i = 0; i <= len - totalWordsLength; i++) {
        if (checkSubstring(str, i, words)) {
            dp[i] = true;
        }
    }

    function checkSubstring(s, start, wordBank) {
        let tempBank = [...wordBank];
        for (let j = start; j < start + totalWordsLength; j += wordLen) {
            const word = s.substr(j, wordLen);
            if (!tempBank.includes(word)) return false;
            tempBank.splice(tempBank.indexOf(word), 1);
        }
        return true;
    }

    let resultIndices = [];
    for (let i in dp) {
        if (dp[i]) resultIndices.push(parseInt(i));
    }
    
    return resultIndices;
}

滑动窗口法

解题思路

滑动窗口方法是一种常用的数据结构,可以有效减少搜索时间。通过维护一个固定大小的窗口来检查当前子串是否满足条件,这种方法在处理大量数据时显得尤为高效。

代码示例

function findSubstringWithSlidingWindow(str, words) {
    if (!str || !words.length) return [];
    const len = str.length;
    const wordLen = words[0].length;
    const totalWordsLength = wordLen * words.length;

    let resultIndices = [];

    for (let i = 0; i <= len - totalWordsLength; i++) {
        if (isWindowValid(str, i, words)) {
            resultIndices.push(i);
        }
    }

    function isWindowValid(s, start, wordBank) {
        const map = new Map();
        for (const word of wordBank) {
            map.set(word, (map.get(word) || 0) + 1);
        }

        let tempMap = new Map(map);

        for (let j = start; j < start + totalWordsLength; j += wordLen) {
            const word = s.substr(j, wordLen);
            if (!tempMap.has(word)) return false;
            tempMap.set(word, tempMap.get(word) - 1);
            if (tempMap.get(word) === 0) tempMap.delete(word);
        }

        return !tempMap.size;
    }
    
    return resultIndices;
}

安全建议

在使用动态规划和滑动窗口方法解决串联所有单词的子串问题时,务必注意边界条件的处理。例如,在初始化数组或构建窗口时,确保不会超出字符串的有效范围。同时,考虑到效率和内存消耗,选择合适的方法并优化代码结构是必要的。

以上介绍了如何利用动态规划和滑动窗口两种方法来解决前端刷题中的经典题目——串联所有单词的子串问题,并提供了详细的代码示例和解释。通过这样的练习可以加深对算法的理解,提升解决问题的能力。