深入解析:LeetCode经典算法题解详解

LeetCode经典算法题解详解

本文汇总了常见的LeetCode算法题目及其C++解法,涵盖字符串、数组、链表、动态规划等多个知识点,适合算法面试准备和日常练习。


目录

  1. 字符串处理
  2. 数组问题
  3. 链表操作
  4. 动态规划
  5. 树的遍历

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层序遍历

这些题目是面试高频考点,建议:

  1. 理解每种算法的核心思想
  2. 注意边界条件处理
  3. 分析时间和空间复杂度
  4. 多做类似题目加深理解

祝大家刷题顺利,offer多多!

posted on 2025-11-08 14:46  wgwyanfs  阅读(14)  评论(0)    收藏  举报

导航