LeetCode经典算法题解详解
LeetCode经典算法题解详解
本文汇总了常见的LeetCode算法题目及其C++解法,涵盖字符串、数组、链表、动态规划等多个知识点,适合算法面试准备和日常练习。
目录
- 字符串处理
- 数组问题
- 链表操作
- 动态规划
- 树的遍历
1. 字符串处理
1.1 计数二进制子串(Count Binary Substrings)
题目描述:给定一个字符串,统计连续的0和1数量相同的子串个数。
解题思路:
- 统计连续相同字符的分组长度
- 相邻两组可以形成的子串数量为两者的最小值
- 例如:"00111"分组为[2,3],可形成min(2,3)=2个子串
代码实现:
int countBinarySubstrings(string s) {vector<int> v;s += ' '; // 添加哨兵,方便处理最后一组int ssize = s.size();int num = 0;// 统计每组连续相同字符的数量for (int i = 0; i < ssize - 1; i++) {if (s[i] == s[i + 1]) {num++;} else {num++;v.push_back(num);num = 0;}}// 计算相邻两组能形成的子串数int res = 0;for (int i = 1; i < v.size(); i++) {res += min(v[i-1], v[i]);}return res;
}
时间复杂度:O(n)
空间复杂度:O(n)
1.2 无重复字符的最长子串(Longest Substring Without Repeating Characters)
题目描述:找出给定字符串中不含重复字符的最长子串长度。
解题思路:
- 使用滑动窗口+哈希集合
- 右指针不断扩展窗口,遇到重复字符时左指针收缩
- 维护最大窗口长度
代码实现:
#include <unordered_set>
#include <algorithm>
using namespace std;int lengthOfLongestSubstring(string s) {unordered_set<char> st;int n = s.size();int left = 0, right = 0;int maxLen = 0;while (right < n) {if (st.count(s[right]) == 0) {st.insert(s[right]);maxLen = max(maxLen, right - left + 1);right++;} else {st.erase(s[left]);left++;}}return maxLen;
}
时间复杂度:O(n)
空间复杂度:O(min(n, m)),m为字符集大小
1.3 最长回文子串(Longest Palindromic Substring)
题目描述:找出字符串中最长的回文子串。
解题思路:
- 中心扩展法:以每个字符为中心向两边扩展
- 需要考虑奇数长度和偶数长度两种情况
- 记录最长回文的起始位置和长度
代码实现:
string longestPalindrome(string s) {int start = 0, end = 0;int len = 0;int l = 0;for (int i = 0; i < s.size() - 1; i++) {// 奇数长度回文(中心为单个字符)start = i;end = i;while (start >= 0 && end < s.size() && s[start] == s[end]) {start--;end++;}if (end - start - 2 > len) {len = end - start - 1;l = start + 1;}// 偶数长度回文(中心为两个字符)start = i;end = i + 1;while (start >= 0 && end < s.size() && s[start] == s[end]) {start--;end++;}if (end - start - 2 > len) {len = end - start - 1;l = start + 1;}}return s.substr(l, len);
}
时间复杂度:O(n²)
空间复杂度:O(1)
2. 数组问题
2.1 两数之和(Two Sum)
题目描述:在数组中找到两个数,使其和等于目标值。
解题思路:
- 使用哈希表存储已遍历的数字及其索引
- 对于当前数字,查找target - nums[i]是否存在
- 一次遍历即可完成
代码实现:
vector<int> twoSum(vector<int>& nums, int target) {unordered_map<int, int> mp;for (int i = 0; i < nums.size(); i++) {if (mp.find(target - nums[i]) != mp.end()) {return {mp[target - nums[i]], i};} else {mp[nums[i]] = i;}}return {};
}
时间复杂度:O(n)
空间复杂度:O(n)
2.2 三数之和(Three Sum)
题目描述:找出数组中所有和为0的三元组。
解题思路:
- 先对数组排序
- 固定第一个数,用双指针找另外两个数
- 注意去重:跳过重复的元素
代码实现:
vector<vector<int>> threeSum(vector<int>& nums) {int n = nums.size();sort(nums.begin(), nums.end());vector<vector<int>> ans;for (int i = 0; i < n; i++) {// 跳过重复元素if (i > 0 && nums[i] == nums[i - 1]) continue;// 双指针,目标是找到 nums[l] + nums[r] = -nums[i]int l = i + 1, r = n - 1;int target = -nums[i];while (l < r) {int sum = nums[l] + nums[r];if (sum == target) {ans.push_back({nums[i], nums[l], nums[r]});l++;r--;// 跳过重复元素while (l < r && nums[l] == nums[l - 1]) l++;while (l < r && nums[r] == nums[r + 1]) r--;} else if (sum < target) {l++;} else {r--;}}}return ans;
}
时间复杂度:O(n²)
空间复杂度:O(1)(不计返回值)
2.3 最大子数组和(Maximum Subarray)
题目描述:找到数组中和最大的连续子数组。
方法一:动态规划(Kadane算法)
解题思路:
- dp[i]表示以nums[i]结尾的最大子数组和
- 状态转移:dp[i] = max(nums[i], dp[i-1] + nums[i])
- 可以优化为O(1)空间
代码实现:
int maxSubArray(vector<int>& nums) {int maxSum = nums[0];int pre = nums[0];for (int i = 1; i < nums.size(); i++) {pre = (pre + nums[i] < nums[i]) ? nums[i] : pre + nums[i];maxSum = (maxSum > pre) ? maxSum : pre;}return maxSum;
}
时间复杂度:O(n)
空间复杂度:O(1)
方法二:分治法
解题思路:
- 将数组分为左右两部分
- 最大子数组可能在:左半部分、右半部分、跨越中点
- 递归求解并合并结果
代码实现:
struct Status {int lSum; // 包含左边界的最大子数组和int rSum; // 包含右边界的最大子数组和int iSum; // 区间总和int mSum; // 区间最大子数组和
};Status get(vector<int>& a, int l, int r) {if (l == r) {return (Status){a[l], a[l], a[l], a[l]};}int m = (l + r) >> 1;Status lSub = get(a, l, m);Status rSub = get(a, m + 1, r);int iSum = lSub.iSum + rSub.iSum;int lSum = max(lSub.lSum, lSub.iSum + rSub.lSum);int rSum = max(rSub.rSum, rSub.iSum + lSub.rSum);int mSum = max(max(lSub.mSum, rSub.mSum), lSub.rSum + rSub.lSum);return (Status){lSum, rSum, mSum, iSum};
}int maxSubArray(vector<int>& nums) {return get(nums, 0, nums.size() - 1).mSum;
}
时间复杂度:O(n log n)
空间复杂度:O(log n)
2.4 第K大元素(Kth Largest Element)
题目描述:找出数组中第K大的元素。
解题思路:
- 使用快速选择算法(Quick Select)
- 第K大等价于第(n-k)小
- 快排的partition过程,不需要完全排序
代码实现:
int quicksort(vector<int>& nums, int l, int r) {int pivot = nums[r]; // 选最右边为基准int i = l; // 小于区的右边界for (int j = l; j < r; ++j) {if (nums[j] < pivot) {swap(nums[i++], nums[j]);}}swap(nums[i], nums[r]); // 把基准放到中间return i; // 返回基准位置
}int findKthLargest(vector<int>& nums, int k) {int l = 0, r = nums.size() - 1;int n = quicksort(nums, l, r);// 第k大 = 第(size-k)小while (n != nums.size() - k) {if (n < nums.size() - k) {l = n + 1;} else {r = n - 1;}n = quicksort(nums, l, r);}return nums[n];
}
时间复杂度:平均O(n),最坏O(n²)
空间复杂度:O(1)
2.5 合并区间(Merge Intervals)
题目描述:合并所有重叠的区间。
解题思路:
- 先按左端点排序
- 遍历区间,判断当前区间能否与上一个合并
- 能合并则更新右边界,否则加入结果并开启新段
代码实现:
vector<vector<int>> merge(vector<vector<int>>& intervals) {if (intervals.empty()) return {};// 按左端点升序排序sort(intervals.begin(), intervals.end(),[](const vector<int>& a, const vector<int>& b) {return a[0] < b[0];});vector<vector<int>> res;int l = intervals[0][0], r = intervals[0][1];for (size_t i = 1; i < intervals.size(); ++i) {// 能合并:当前区间左端点 <= 当前合并段的右端点if (intervals[i][0] <= r) {r = max(r, intervals[i][1]);} else {// 不能合并,把旧段加入结果res.push_back({l, r});l = intervals[i][0];r = intervals[i][1];}}res.push_back({l, r}); // 最后一段return res;
}
时间复杂度:O(n log n)
空间复杂度:O(log n)(排序栈空间)
2.6 买卖股票的最佳时机(Best Time to Buy and Sell Stock)
题目描述:只能买卖一次,求最大利润。
解题思路:
- 维护当前最低价格
- 遍历时计算当前价格卖出的利润
- 更新最大利润
代码实现:
int maxProfit(vector<int>& prices) {int cost = prices[0];int profit = 0;for (int i = 1; i < prices.size(); i++) {cost = min(cost, prices[i]);profit = max(profit, prices[i] - cost);}return profit;
}
时间复杂度:O(n)
空间复杂度:O(1)
2.7 合并两个有序数组(Merge Sorted Array)
题目描述:将nums2合并到nums1中(nums1有足够空间)。
解题思路:
- 从后往前填充,避免覆盖未处理元素
- 双指针分别指向两个数组的末尾
- 比较并将较大值放到nums1末尾
代码实现:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {int i1 = m - 1;int i2 = n - 1;for (int i = m + n - 1; i >= 0; i--) {if (i2 < 0 || (i1 >= 0 && nums1[i1] >= nums2[i2])) {nums1[i] = nums1[i1--];} else {nums1[i] = nums2[i2--];}}
}
时间复杂度:O(m + n)
空间复杂度:O(1)
3. 链表操作
3.1 反转链表(Reverse Linked List)
题目描述:反转一个单链表。
解题思路:
- 使用三个指针:prev、curr、next
- 迭代地修改每个节点的next指向
- 最后返回新的头节点
代码实现:
class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode* prev = nullptr;ListNode* curr = head;while (curr) {ListNode* next = curr->next;curr->next = prev;prev = curr;curr = next;}return prev;}
};
时间复杂度:O(n)
空间复杂度:O(1)
3.2 合并两个有序链表(Merge Two Sorted Lists)
题目描述:合并两个升序链表为一个新的升序链表。
解题思路:
- 使用递归方法
- 比较两个链表头节点,选择较小的
- 递归处理剩余节点
代码实现:
class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if (list1 == nullptr || list2 == nullptr) {return list1 == nullptr ? list2 : list1;}if (list1->val < list2->val) {list1->next = mergeTwoLists(list1->next, list2);return list1;} else {list2->next = mergeTwoLists(list1, list2->next);return list2;}}
};
时间复杂度:O(m + n)
空间复杂度:O(m + n)(递归栈)
4. 动态规划
4.1 硬币兑换(Coin Change)
题目描述:用最少的硬币数凑成目标金额。
方法一:完全背包DP
解题思路:
- dp[i]表示凑成金额i的最少硬币数
- 状态转移:dp[i] = min(dp[i], dp[i - coin] + 1)
- 初始化dp[0] = 0,其余为无穷大
代码实现:
class Solution {
public:int coinChange(vector<int>& coins, int amount) {int Max = amount + 1;vector<int> dp(amount + 1, Max);dp[0] = 0;for (int i = 1; i <= amount; ++i) {for (int j = 0; j < coins.size(); ++j) {if (coins[j] <= i) {dp[i] = min(dp[i], dp[i - coins[j]] + 1);}}}return dp[amount] > amount ? -1 : dp[amount];}
};
时间复杂度:O(amount × n)
空间复杂度:O(amount)
方法二:优化版本
代码实现:
int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount + 1, 100000);dp[0] = 0;// 预处理:单个硬币能直接凑成的金额for (int i = 0; i < coins.size() && coins[i] <= amount; i++) {dp[coins[i]] = 1;}for (int i = 1; i <= amount; i++) {for (int j = 0; j < coins.size(); j++) {if (i - coins[j] > 0 && dp[i] > dp[i - coins[j]] + 1) {dp[i] = dp[i - coins[j]] + 1;}}}return dp[amount] == 100000 ? -1 : dp[amount];
}
5. 树的遍历
5.1 二叉树的层序遍历(Binary Tree Level Order Traversal)
题目描述:按层输出二叉树的节点值。
解题思路:
- 使用队列进行BFS
- 记录每层节点数量,分层处理
- 将每层节点值存入结果数组
代码实现:
vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> res;queue<TreeNode*> q1;if (root != nullptr) {q1.push(root);}while (!q1.empty()) {int size = q1.size();vector<int> v;for (int i = 0; i < size; i++) {TreeNode* current = q1.front();q1.pop();if (current->left) {q1.push(current->left);}if (current->right) {q1.push(current->right);}v.push_back(current->val);}res.push_back(v);}return res;
}
时间复杂度:O(n)
空间复杂度:O(n)
总结
本文涵盖了常见的算法题型和解题技巧:
- 字符串:滑动窗口、中心扩展
- 数组:双指针、哈希表、快速选择
- 链表:双指针、递归
- 动态规划:状态定义与转移
- 树:BFS层序遍历
这些题目是面试高频考点,建议:
- 理解每种算法的核心思想
- 注意边界条件处理
- 分析时间和空间复杂度
- 多做类似题目加深理解
祝大家刷题顺利,offer多多!💪