指南

算法思想

这篇文章用简单易懂的语言,结合前端常见场景,讲解递归、分治、贪心、动态规划和双指针滑动窗口算法思想。

🎯 学会这篇文章,我能做什么事情?实际上有什么帮助?用一段话来描述(100字左右)

掌握这篇文章中的算法思想,前端小白可以更好地理解和解决复杂问题,如页面数据处理、动画状态管理和性能优化。文章用简单示例和口语化讲解,帮助你轻松入门递归、分治、贪心、动态规划和双指针技巧,让你在写代码时更加自信和高效。


🌀 递归与迭代

递归和迭代是两种解决问题的基本思路。递归就是函数自己调用自己,像穿越迷宫一样,一步步往里走;迭代则是通过循环,一次次重复做相同的事情。

比如,计算 1 到 n 的和。

递归思路是拆成 n + (1 到 n-1 的和),代码如下:

function sumRecursive(n) {
    if (n === 1) return 1;
    return n + sumRecursive(n - 1);
}
console.log(sumRecursive(5)); // 15

迭代的做法是用循环累加:

function sumIterative(n) {
    let total = 0;
    for (let i = 1; i <= n; i++) {
        total += i;
    }
    return total;
}
console.log(sumIterative(5)); // 15
递归更适合问题可以分成相同的子问题,代码简洁易懂。迭代效率高,避免调用栈溢出。

🪓 分治思想

分治是一种“拆分问题,分别解决,再合并结果”的思想。适合处理复杂问题,比如排序。

举个例子,快速排序(Quick Sort)就是分治的典型应用。

原理:

  • 选一个基准值(pivot)
  • 把数组分成比基准值小和大的两部分
  • 递归对这两部分分别排序
  • 合并结果

示例代码:

function quickSort(arr) {
    if (arr.length <= 1) return arr;

    const pivot = arr[0];
    const left = [];
    const right = [];

    for (let i = 1; i < arr.length; i++) {
        if (arr[i] < pivot) left.push(arr[i]);
        else right.push(arr[i]);
    }

    return [...quickSort(left), pivot, ...quickSort(right)];
}

console.log(quickSort([3, 6, 2, 8, 4])); // [2,3,4,6,8]
分治可以把大问题拆成小块,代码更清晰,适合解决排序、归并、树结构遍历等。

💰 贪心算法

贪心算法的核心就是:每一步都选择局部最优解,希望最终得到全局最优。

比如,最简单的找零钱问题,假设零钱有 10 元、5 元和 1 元,要找 18 元零钱。

贪心算法会先拿最大面额:

  • 拿一张 10 元,剩 8 元
  • 拿一张 5 元,剩 3 元
  • 拿三张 1 元,剩 0 元

代码示例:

function getChange(amount) {
    const coins = [10, 5, 1];
    const result = [];

    for (let coin of coins) {
        while (amount >= coin) {
            amount -= coin;
            result.push(coin);
        }
    }

    return result;
}

console.log(getChange(18)); // [10, 5, 1, 1, 1]
贪心算法不一定总是最优,但在很多实际问题中表现不错。

🧩 动态规划入门

动态规划(Dynamic Programming,简称 DP)是优化递归的一种方法,适合解决有重叠子问题和最优子结构的问题。

举个例子,经典的斐波那契数列:

function fib(n) {
    const dp = [0, 1];
    for (let i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

console.log(fib(10)); // 55

动态规划把之前算过的结果保存起来,避免重复计算。

DP 适合解决路径规划、最短距离、背包问题等场景,前端中可以用它来优化状态管理和缓存计算。

🧮 双指针滑动窗口

双指针是一种用两个索引(指针)在数组或字符串上移动来解决问题的技巧。

滑动窗口是双指针的变种,常用来处理子串或子数组问题。

举个例子,找字符串中最长的不含重复字符的子串长度。

思路是用两个指针维护一个窗口,不断移动右指针,同时调整左指针保证窗口内字符无重复。

示例代码:

function lengthOfLongestSubstring(s) {
    let left = 0;
    let maxLen = 0;
    const map = new Map();

    for (let right = 0; right < s.length; right++) {
        const char = s[right];
        if (map.has(char) && map.get(char) >= left) {
            left = map.get(char) + 1;
        }
        map.set(char, right);
        maxLen = Math.max(maxLen, right - left + 1);
    }

    return maxLen;
}

console.log(lengthOfLongestSubstring('abcabcbb')); // 3
双指针和滑动窗口适合查找满足条件的子串、子数组,常见于字符串处理、数组遍历等。

🧾 小节总结

  • 递归与迭代是解决问题的基础,两者可根据需求选择
  • 分治把复杂问题拆解成小问题,便于解决排序等任务
  • 贪心算法每步选最优,简单快速但不总是最优解
  • 动态规划通过保存中间结果避免重复计算,适合复杂状态计算
  • 双指针滑动窗口技巧高效查找字符串和数组的子区间

❓ 知识问答(Q&A)

Q:递归和迭代有什么优缺点?
A:递归代码简洁,适合表达树形结构或分治;但可能导致栈溢出。迭代更节省内存,效率高,但代码有时较复杂。

Q:分治和动态规划有什么区别?
A:分治把问题拆成互相独立的小问题,动态规划适合有重叠子问题且要重复利用子结果的问题。


🎉 恭喜你已经掌握 递归与迭代、分治思想、贪心算法、动态规划入门、双指针滑动窗口 技能啦!继续练习,你会成为前端算法高手!