深入解析: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多多!
浙公网安备 33010602011771号