LeetCode高频面试题:gh_mirrors/leet/leetcode项目重点题解

LeetCode高频面试题:gh_mirrors/leet/leetcode项目重点题解

【免费下载链接】leetcode LeetCode题解,151道题完整版。广告:推荐刷题网站 https://www.lintcode.com/?utm_source=soulmachine 【免费下载链接】leetcode 项目地址: https://gitcode.com/gh_mirrors/leet/leetcode

在技术面试中,算法题往往是考察候选人逻辑思维和编程能力的关键环节。本文将围绕gh_mirrors/leet/leetcode项目中的重点题解,从动态规划、广度优先搜索、深度优先搜索等多个维度,为你剖析高频面试题的解题思路与技巧,帮助你在面试中脱颖而出。

动态规划:从局部最优到全局最优

动态规划(Dynamic Programming, DP)是解决多阶段决策问题的有效方法,其核心思想是将复杂问题分解为重叠子问题,并通过存储子问题的解来避免重复计算。在LeetCode中,动态规划类题目出现频率极高,掌握这类题目的解题套路至关重要。

最大子数组和(Maximum Subarray)

问题描述:给定一个整数数组,找出其中和最大的连续子数组。

解题思路:设状态f[j]表示以S[j]结尾的最大连续子序列和。当遍历到数组中的某个元素时,它有两种选择:加入之前的子数组或自己另起一个子数组。若之前子数组的和大于0,则加入;否则,另起炉灶。状态转移方程为: f[j] = max(f[j-1] + S[j], S[j])

代码示例

// LeetCode, Maximum Subarray
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result = INT_MIN, f = 0;
        for (int i = 0; i < nums.size(); ++i) {
            f = max(f + nums[i], nums[i]);
            result = max(result, f);
        }
        return result;
    }
};

题目来源C++/chapDynamicProgramming.tex

最小路径和(Minimum Path Sum)

问题描述:给定一个包含非负整数的m x n网格,从左上角到右下角的路径中,找出路径上数字总和最小的路径。每次只能向下或向右移动一步。

解题思路:与Unique Paths类似,设状态f[i][j]表示从起点(0,0)到达(i,j)的最小路径和。状态转移方程为: f[i][j] = min(f[i-1][j], f[i][j-1]) + grid[i][j]

代码示例

// LeetCode, Minimum Path Sum
// 二维动规
class Solution {
public:
    int minPathSum(vector<vector<int> > &grid) {
        if (grid.empty()) return 0;
        const int m = grid.size();
        const int n = grid[0].size();

        int f[m][n];
        f[0][0] = grid[0][0];
        for (int i = 1; i < m; i++) {
            f[i][0] = f[i - 1][0] + grid[i][0];
        }
        for (int i = 1; i < n; i++) {
            f[0][i] = f[0][i - 1] + grid[0][i];
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                f[i][j] = min(f[i - 1][j], f[i][j - 1]) + grid[i][j];
            }
        }
        return f[m - 1][n - 1];
    }
};

题目来源:[C++/chapDynamicProgramming.tex#L745-L825]

广度优先搜索:逐层扩展的艺术

广度优先搜索(Breadth-First Search, BFS)是一种图形搜索算法,它从起始节点开始,逐层遍历其所有邻居节点,直到找到目标节点或遍历完所有节点。BFS通常用于解决最短路径、连通性等问题。

单词接龙(Word Ladder)

问题描述:给定两个单词(beginWord和endWord)和一个字典,找到从beginWord到endWord的最短转换序列的长度。转换需遵循每次只能改变一个字母,且转换过程中的中间单词必须在字典中。

解题思路:将每个单词视为图中的一个节点,若两个单词仅相差一个字母,则它们之间有一条边。问题转化为求从起始节点到目标节点的最短路径长度,适合用BFS求解。

代码示例(双队列优化)

// LeetCode, Word Ladder
// 双队列,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    int ladderLength(const string& start, const string &end,
            const unordered_set<string> &dict) {
        queue<string> current, next;    // 当前层,下一层
        unordered_set<string> visited;  // 判重

        int level = -1;  // 层次

        auto state_is_valid = & {
            return dict.find(s) != dict.end() || s == end;
        };
        auto state_is_target = & {return s == end;};
        auto state_extend = & {
            unordered_set<string> result;

            for (size_t i = 0; i < s.size(); ++i) {
                string new_word(s);
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == new_word[i]) continue;

                    swap(c, new_word[i]);

                    if (state_is_valid(new_word) &&
                        visited.find(new_word) == visited.end()) {
                        result.insert(new_word);
                    }
                    swap(c, new_word[i]); // 恢复该单词
                }
            }

            return result;
        };

        current.insert(start);
        visited.insert(start);
        while (!current.empty()) {
            ++ level;
            while (!current.empty()) {
                const auto state = current.front();
                current.pop();

                if (state_is_target(state)) {
                    return level + 1;
                }

                const auto new_states = state_extend(state);
                for (const auto& new_state : new_states) {
                    next.insert(new_state);
                    visited.insert(new_state);
                }
            }
            swap(next, current);
        }
        return 0;
    }
};

题目来源:[C++/chapBFS.tex#L10-L195]

被围绕的区域(Surrounded Regions)

问题描述:给定一个二维的矩阵,包含 'X' 和 'O'。将所有被 'X' 围绕的 'O' 转换为 'X'。被围绕的区域不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。

解题思路:从边界上的 'O' 开始进行BFS,将所有与边界相连的 'O' 标记为特殊符号(如 '+')。遍历结束后,矩阵中剩余的 'O' 就是被围绕的区域,将其转换为 'X',并将 '+' 恢复为 'O'。

代码示例

// LeetCode, Surrounded Regions
// BFS,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    void solve(vector<vector<char>> &board) {
        if (board.empty()) return;

        const int m = board.size();
        const int n = board[0].size();
        for (int i = 0; i < n; i++) {
            bfs(board, 0, i);
            bfs(board, m - 1, i);
        }
        for (int j = 1; j < m - 1; j++) {
            bfs(board, j, 0);
            bfs(board, j, n - 1);
        }
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if (board[i][j] == 'O')
                    board[i][j] = 'X';
                else if (board[i][j] == '+')
                    board[i][j] = 'O';
    }
private:
    void bfs(vector<vector<char>> &board, int i, int j) {
        typedef pair<int, int> state_t;
        queue<state_t> q;
        const int m = board.size();
        const int n = board[0].size();

        auto state_is_valid = & {
            const int x = s.first;
            const int y = s.second;
            if (x < 0 || x >= m || y < 0 || y >= n || board[x][y] != 'O')
                return false;
            return true;
        };

        auto state_extend = & {
            vector<state_t> result;
            const int x = s.first;
            const int y = s.second;
            // 上下左右
            const state_t new_states[4] = {{x-1,y}, {x+1,y},
                    {x,y-1}, {x,y+1}};
            for (int k = 0; k < 4;  ++k) {
                if (state_is_valid(new_states[k])) {
                    // 既有标记功能又有去重功能
                    board[new_states[k].first][new_states[k].second] = '+';
                    result.push_back(new_states[k]);
                }
            }

            return result;
        };

        state_t start = { i, j };
        if (state_is_valid(start)) {
            board[i][j] = '+';
            q.push(start);
        }
        while (!q.empty()) {
            auto cur = q.front();
            q.pop();
            auto new_states = state_extend(cur);
            for (auto s : new_states) q.push(s);
        }
    }
};

题目来源:[C++/chapBFS.tex#L639-L741]

深度优先搜索:探索所有可能的路径

深度优先搜索(Depth-First Search, DFS)是另一种重要的图遍历算法。它沿着树的深度优先遍历树的节点,尽可能深地搜索树的分支。当节点v的所有边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。DFS常用于解决排列组合、子集生成等问题。

N皇后问题(N-Queens)

问题描述:在一个N×N的棋盘上放置N个皇后,使得它们不能互相攻击(即任意两个皇后不能处于同一行、同一列或同一斜线上)。找出所有可能的放置方案。

解题思路:使用回溯法(DFS的一种)。用一个数组C记录每一行皇后所在的列编号,C[i]表示第i行皇后在第C[i]列。在放置第row行皇后时,尝试每一列col,检查是否与之前放置的皇后冲突,若不冲突则继续递归放置下一行。

代码示例

// LeetCode, N-Queens
// 深搜+剪枝,时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
    vector<vector<string> > solveNQueens(int n) {
        vector<vector<string> > result;
        vector<int> C(n, -1);  // C[i]表示第i行皇后所在的列编号
        dfs(C, result, 0);
        return result;
    }
private:
    void dfs(vector<int> &C, vector<vector<string> > &result, int row) {
        const int N = C.size();
        if (row == N) { // 终止条件,也是收敛条件,意味着找到了一个可行解
            vector<string> solution;
            for (int i = 0; i < N; ++i) {
                string s(N, '.');
                for (int j = 0; j < N; ++j) {
                    if (j == C[i]) s[j] = 'Q';
                }
                solution.push_back(s);
            }
            result.push_back(solution);
            return;
        }

        for (int j = 0; j < N; ++j) {  // 扩展状态,一列一列的试
            const bool ok = isValid(C, row, j);
            if (!ok) continue;  // 剪枝,如果非法,继续尝试下一列
            // 执行扩展动作
            C[row] = j;
            dfs(C, result, row + 1);
            // 撤销动作
            // C[row] = -1;
        }
    }
    
    /**
     * 能否在 (row, col) 位置放一个皇后.
     *
     * @param C 棋局
     * @param row 当前正在处理的行,前面的行都已经放了皇后了
     * @param col 当前列
     * @return 能否放一个皇后
     */
    bool isValid(const vector<int> &C, int row, int col) {
        for (int i = 0; i < row; ++i) {
            // 在同一列
            if (C[i] == col) return false;
            // 在同一对角线上
            if (abs(i - row) == abs(C[i] - col)) return false;
        }
        return true;
    }
};

题目来源:[C++/chapDFS.tex#L452-L508] 8皇后问题示意图

组合总和(Combination Sum)

问题描述:给定一个无重复元素的数组candidates和一个目标数target,找出candidates中所有可以使数字和为target的组合。candidates中的数字可以无限制重复使用。

解题思路:排序数组后进行DFS。对于每个元素,我们可以选择将其加入当前组合或不加入。如果加入后组合的和仍小于等于target,则继续递归;否则,剪枝回溯。

代码示例

// LeetCode, Combination Sum
// 时间复杂度O(n!),空间复杂度O(n)
class Solution {
public:
    vector<vector<int> > combinationSum(vector<int> &nums, int target) {
        sort(nums.begin(), nums.end());
        vector<vector<int> > result; // 最终结果
        vector<int> path; // 中间结果
        dfs(nums, path, result, target, 0);
        return result;
    }

private:
    void dfs(vector<int>& nums, vector<int>& path, vector<vector<int> > &result,
            int gap, int start) {
        if (gap == 0) {  // 找到一个合法解
            result.push_back(path);
            return;
        }
        for (size_t i = start; i < nums.size(); i++) { // 扩展状态
            if (gap < nums[i]) return; // 剪枝
            path.push_back(nums[i]); // 执行扩展动作
            dfs(nums, path, result, gap - nums[i], i);
            path.pop_back();  // 撤销动作
        }
    }
};

题目来源:[C++/chapDFS.tex#L773-L830]

贪心算法:每一步都是最优选择

贪心算法(Greedy Algorithm)是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。贪心算法在很多优化问题中能得到最优解或近似最优解。

跳跃游戏(Jump Game)

问题描述:给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。

解题思路:贪心法。维护一个变量reach表示当前能够到达的最远位置。遍历数组,对于每个位置i,如果i <= reach,则更新reachmax(reach, i + nums[i])。如果reach >= 数组最后一个位置的索引,则返回true;否则,返回false。

代码示例

// LeetCode, Jump Game
// 思路1,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
    bool canJump(const vector<int>& nums) {
        int reach = 1; // 最右能跳到哪里
        for (int i = 0; i < reach && reach < nums.size(); ++i)
            reach = max(reach,  i + 1 + nums[i]);
        return reach >= nums.size();
    }
};

题目来源:[C++/chapGreedy.tex#L4-L94]

买卖股票的最佳时机(Best Time to Buy and Sell Stock)

问题描述:给定一个数组,它的第i个元素是一支给定股票第i天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

解题思路:贪心法。遍历数组,记录当前遇到的最低价格cur_min。对于每一天的价格,计算当天卖出能获得的利润(prices[i] - cur_min),并更新最大利润。

代码示例

// LeetCode, Best Time to Buy and Sell Stock
// 时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
    int maxProfit(vector<int> &prices) {
        if (prices.size() < 2) return 0;
        int profit = 0;  // 差价,也就是利润
        int cur_min = prices[0]; // 当前最小

        for (int i = 1; i < prices.size(); i++) {
            profit = max(profit, prices[i] - cur_min);
            cur_min = min(cur_min, prices[i]);
        }
        return profit;
    }
};

题目来源:[C++/chapGreedy.tex#L179-L212]

树的遍历:掌握二叉树的操作

树是面试中另一个高频考点,尤其是二叉树。掌握二叉树的各种遍历方式(前序、中序、后序、层次遍历)及其应用,对于解决树相关问题至关重要。

二叉树的层序遍历(Binary Tree Level Order Traversal)

问题描述:给你一个二叉树,请你返回其按层序遍历得到的节点值。(即逐层地,从左到右访问所有节点)。

解题思路:使用队列进行BFS。首先将根节点入队,然后每次从队列中取出一个节点,访问其值,并将其左右孩子入队(如果存在)。为了区分不同的层,可以使用两个队列或者一个队列加一个标记变量。

代码示例(迭代版)

// LeetCode, Binary Tree Level Order Traversal
// 迭代版,时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
    vector<vector<int> > levelOrder(TreeNode *root) {
        vector<vector<int> > result;
        queue<TreeNode*> current, next;
        
        if(root == nullptr) {
            return result;
        } else {
            current.push(root);
        }

        while (!current.empty()) {
            vector<int> level; // elments in one level
            while (!current.empty()) {
                TreeNode* node = current.front();
                current.pop();
                level.push_back(node->val);
                if (node->left != nullptr) next.push(node->left);
                if (node->right != nullptr) next.push(node->right);
            }
            result.push_back(level);
            swap(next, current);
        }
        return result;
    }
};

题目来源:[C++/chapTree.tex#L370-L457] 机器人迷宫示意图

验证二叉搜索树(Validate Binary Search Tree)

问题描述:给定一个二叉树,判断其是否是一个有效的二叉搜索树(BST)。假设一个BST具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是BST。

解题思路:利用BST的中序遍历特性——中序遍历结果是一个严格递增的序列。可以在中序遍历过程中,记录前一个节点的值,若当前节点值小于等于前一个节点值,则不是BST。

代码示例(中序遍历)

// 基于中序遍历的方法
class Solution {
public:
    bool isValidBST(TreeNode* root) {
        vector<int> vals;
        inorder(root, vals);
        for (int i = 1; i < vals.size(); ++i) {
            if (vals[i] <= vals[i-1]) return false;
        }
        return true;
    }
private:
    void inorder(TreeNode* root, vector<int>& vals) {
        if (!root) return;
        inorder(root->left, vals);
        vals.push_back(root->val);
        inorder(root->right, vals);
    }
};

题目来源:[C++/chapTree.tex] (类似问题可参考树的遍历与验证相关章节)

总结与展望

本文通过对gh_mirrors/leet/leetcode项目中动态规划、BFS、DFS、贪心算法及树的遍历等重点题解的剖析,展示了各类算法在解决实际问题时的应用。掌握这些高频面试题不仅能帮助你应对面试,更能提升你的算法思维和编程能力。

建议你在学习过程中,不仅要记住解题代码,更要理解其背后的算法思想和解题套路。通过大量练习,将这些知识内化为自己的能力,以便在面对新问题时能够灵活运用。

项目的完整题解可参考:C++/leetcode-cpp.pdf。祝你在算法学习的道路上不断进步,面试顺利!

【免费下载链接】leetcode LeetCode题解,151道题完整版。广告:推荐刷题网站 https://www.lintcode.com/?utm_source=soulmachine 【免费下载链接】leetcode 项目地址: https://gitcode.com/gh_mirrors/leet/leetcode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值