206. 反转链表(见LeetCode Hot100 链表、JavaScript 算法基础第二天)

var reverseList = function(head) {
    let prev = null
    let curr = head
    while(curr) {
        let temp = curr.next
        curr.next = prev
        prev = curr
        curr = temp
    }
    return prev
};

复杂度分析

时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。

空间复杂度:O(1),只使用了常数额外空间。

207. 课程表

var canFinish = function(numCourses, prerequisites) {
    const len = numCourses
 
    const grap = Array.from({length: len}, () => [])
    const deep = Array.from({length: len}, () => 0)
 
    for(let [course, prev] of prerequisites) {
        grap[prev].push(course)
        deep[course]++
    }
 
    let finish = []
    for(let i = 0; i< len; i++){
        if(deep[i] === 0) {
            finish.push(i)
        }
    }
    let finished = 0
    while(finish.length){
        let curr = finish.pop()
        finished++
        for(let item of grap[curr]) {
            deep[item]--
            if(deep[item] === 0) {
                finish.push(item)
            }
        }
    }
    return finished === len
};

复杂度分析

时间复杂度:O(V+E),其中 V 是课程数(numCourses),E 是先修课程数(prerequisites 的长度)。构建邻接表需要 O(E),拓扑排序遍历每个节点和边各一次。

空间复杂度:O(V+E),邻接表存储了 E 条边,入度数组使用了 O(V) 空间。

215. 数组中的第K个最大元素

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题(放弃小顶堆,采用快速选择 QuickSelect 原地划分)。

/**
 * 快速选择 QuickSelect(复用 912 题 Hoare 双指针模板 + 随机化基准)
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    const target = nums.length - k; // 升序排列后的目标下标
 
    const qs = (l, r) => {
        if (l >= r) return nums[target];
 
        // 1. 基准随机偏移 l
        const randomIndex = Math.floor(Math.random() * (r - l + 1)) + l;
        const pivot = nums[randomIndex];
 
        let i = l, j = r;
 
        // 2. 双指针对撞严格比较
        while (i <= j) {
            while (nums[i] < pivot) i++;
            while (nums[j] > pivot) j--;
            if (i <= j) {
                [nums[i], nums[j]] = [nums[j], nums[i]];
                i++;
                j--;
            }
        }
 
        // 3. 门神法则:入左院、进右院、夹中间直取果
        if (target <= j) return qs(l, j); // target <= j 入左院 [l, j]
        if (target >= i) return qs(i, r); // target >= i 进右院 [i, r]
        return nums[target];              // 夹在中间过道,直接命中
    };
 
    return qs(0, nums.length - 1);
};

复杂度分析:

  • 时间复杂度:平均 O(n),最坏通过随机基准避免退化;快速选择每次剪枝抛弃一半数据,总处理量为等比级数 。
  • 空间复杂度:O(log n),系统递归调用栈消耗(纯原地交换,0 额外数组开销)。

关键套路与记忆点:

  • 与 912 快排同源:底层同样是单次对撞双指针分区,快排“全都要”双边递归 ,快选“只要一个”单边递归剪枝 。
  • 定位门神诀:错开后 。target <= j 入左院,target >= i 进右院,夹中间直取果。

详细思路与记忆法见 215 数组中的第K个最大元素

208. 实现 Trie (前缀树)

 
var Trie = function() {
    this.ch = {}
    this.isEnd = false
};
 
/**
 * @param {string} word
 * @return {void}
 */
Trie.prototype.insert = function(word) {
    let node = this
    for(let c of word) {
        if(!node.ch[c]) {
            node.ch[c] = new Trie()
        }
        node = node.ch[c]
    }
    node.isEnd = true
};
 
Trie.prototype.seachPrefix = function(word) {
    let node = this
 
    for(let c of word) {
        if(!node.ch[c]) {
            return null
        }
        node = node.ch[c]
    }
 
    return node
};
 
 
/**
 * @param {string} word
 * @return {boolean}
 */
Trie.prototype.search = function(word) {
    const res = this.seachPrefix(word)
    return !!res?.isEnd
};
 
/**
 * @param {string} prefix
 * @return {boolean}
 */
Trie.prototype.startsWith = function(prefix) {
    const res = this.seachPrefix(prefix)
    return res !== null
};
 
/**
 * Your Trie object will be instantiated and called as such:
 * var obj = new Trie()
 * obj.insert(word)
 * var param_2 = obj.search(word)
 * var param_3 = obj.startsWith(prefix)
 */

复杂度分析

  • insert:时间复杂度 O(L),空间复杂度 O(L),其中 L 为单词长度,每次插入最多创建 L 个新节点
  • search:时间复杂度 O(L),空间复杂度 O(1)
  • startsWith:时间复杂度 O(L),空间复杂度 O(1)

200. 岛屿数量

/**
 * @param {character[][]} grid
 * @return {number}
 */
const dfs =(grid, i, j, x, y) => {
    if(i<0 || j<0 || i>=y || j>=x || grid[i][j] === '0') {
        return
    }
    grid[i][j] = '0'
    dfs(grid, i+1, j, x, y)
    dfs(grid, i-1, j, x, y)
    dfs(grid, i, j+1, x, y)
    dfs(grid, i, j-1, x, y)
}
var numIslands = function(grid) {
    const y = grid.length
    const x = grid[0].length
    let res = 0
 
    for(let i = 0; i < y; i++) {
        for(let j = 0; j < x; j++) {
            if(grid[i][j] === '1') {
                res++
                dfs(grid, i, j, x, y)
            }
        }
    }
    return res
};
 
 

复杂度分析

时间复杂度:O(m×n),其中 m 和 n 分别是网格的行数和列数。每个单元格最多被访问一次。

空间复杂度:O(m×n),最坏情况下(整个网格都是陆地)递归深度为 m×n。

二刷 2026-10-16:递归深度精确等于 n²(全 1 的 n×n 网格,实测 n=5→25、n=50→2500、n=80→6400),本地 Node 在 100×100 全陆即 RangeError: Maximum call stack size exceeded;本题约束 m, n ≤ 300,外推约 90000 层,存在爆栈风险。改法:① 显式栈 / 队列(一份骨架,pop() 是 DFS、queue[head++] 是 BFS);② 四周套一圈 '0' 哨兵边框(内层判断 5 个条件 → 1 个,且不改入参)。打卡见 每日题单 2026-10-16。

198. 打家劫舍(见JavaScript 算法基础第九天)

/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function(nums) {
    const dp = []
    dp[0] = nums[0]
    dp[1] = Math.max(dp[0], nums[1])
    for(let i =2; i < nums.length; i++){
        dp[i] = Math.max(dp[i-2]+ nums[i], dp[i-1])
    }
    return dp[nums.length-1]
};

复杂度分析

时间复杂度:O(n),其中 n 是房屋数量。只需遍历数组一次。

空间复杂度:O(n),使用了大小为 n 的 DP 数组。可优化为 O(1) 使用滚动变量。

var rob = function(nums) {
    if(nums.length ===1) return nums[0]
    let last = nums[0]
    let curr = Math.max(last, nums[1])
    for(let i =2; i < nums.length; i++){
        let temp = curr
        curr = Math.max(last+ nums[i], curr)
        last = temp
    }
    return curr
};

相关笔记