作为一名长期奋战在算法刷题一线的开发者,我深知掌握经典算法题目的重要性。今天我将分享LeetCode第56到100题中的精选题目解析,涵盖动态规划、二分查找、双指针、栈等高频考点。这些题目不仅面试中经常出现,更是提升编程思维能力的绝佳材料。
问题描述:给定两个整数数组nums1和nums2,返回两个数组中公共的、长度最长的子数组的长度。
这道题是典型的动态规划问题。我们先来看一个具体例子:
python复制输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是[3,2,1]
DP五部曲分析:
优化技巧:
cpp复制class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size()+1, vector<int>(nums2.size()+1, 0));
int res = 0;
for(int i=1; i<=nums1.size(); i++){
for(int j=1; j<=nums2.size(); j++){
if(nums1[i-1] == nums2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
res = max(res, dp[i][j]);
}
}
}
return res;
}
};
二分查找是算法中的基础但极其重要的技术,下面我们看几个典型变种。
最基础的二分查找,适合作为模板记忆:
cpp复制int search(vector<int>& nums, int target) {
int left = 0, right = nums.size()-1;
while(left <= right){
int mid = left + (right-left)/2;
if(nums[mid] == target) return mid;
else if(nums[mid] < target) left = mid+1;
else right = mid-1;
}
return -1;
}
在排序数组中查找元素的第一个和最后一个位置,需要两次二分查找:
cpp复制vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1,-1};
// 找左边界
int l = 0, r = nums.size()-1;
while(l < r){
int mid = l + (r-l)/2;
if(nums[mid] >= target) r = mid;
else l = mid+1;
}
if(nums[r] != target) return {-1,-1};
// 找右边界
int L = r;
l = 0, r = nums.size()-1;
while(l < r){
int mid = l + (r-l+1)/2;
if(nums[mid] <= target) l = mid;
else r = mid-1;
}
return {L, r};
}
旋转排序数组的二分查找需要特别注意边界条件:
cpp复制int findMin(vector<int>& nums) {
int left = 0, right = nums.size()-1;
while(left < right){
int mid = left + (right-left)/2;
if(nums[mid] > nums[right]) left = mid+1;
else right = mid;
}
return nums[right];
}
二分查找的常见陷阱:
left <= right还是left < right?(left+right)/2可能导致整数溢出mid还是mid±1?双指针是解决数组/字符串问题的利器,下面看几个典型应用。
在有序数组中找出两个数使它们的和等于目标值:
cpp复制vector<int> twoSum(vector<int>& numbers, int target) {
int left = 0, right = numbers.size()-1;
while(left < right){
int sum = numbers[left] + numbers[right];
if(sum == target) return {left+1, right+1};
else if(sum < target) left++;
else right--;
}
return {};
}
判断字符串s是否为t的子序列:
cpp复制bool isSubsequence(string s, string t) {
int i = 0, j = 0;
while(i < s.size() && j < t.size()){
if(s[i] == t[j]) i++;
j++;
}
return i == s.size();
}
双指针的三种常见模式:
栈和队列虽然是基础数据结构,但能解决许多看似复杂的问题。
设计一个支持push、pop、top操作,并能常数时间检索最小元素的栈:
cpp复制class MinStack {
stack<int> data;
stack<int> min_stack;
public:
MinStack() { min_stack.push(INT_MAX); }
void push(int val) {
data.push(val);
min_stack.push(min(min_stack.top(), val));
}
void pop() {
data.pop();
min_stack.pop();
}
int top() { return data.top(); }
int getMin() { return min_stack.top(); }
};
根据特殊规则计算棒球比赛得分:
cpp复制int calPoints(vector<string>& ops) {
vector<int> scores;
for(auto& op : ops){
if(op == "C") scores.pop_back();
else if(op == "D") scores.push_back(scores.back()*2);
else if(op == "+") scores.push_back(scores.back() + scores[scores.size()-2]);
else scores.push_back(stoi(op));
}
return accumulate(scores.begin(), scores.end(), 0);
}
栈的典型应用场景:
字符串处理是算法面试中的常客,下面看几个典型问题。
简化Unix风格的绝对路径:
cpp复制string simplifyPath(string path) {
vector<string> dirs;
stringstream ss(path);
string dir;
while(getline(ss, dir, '/')){
if(dir.empty() || dir == ".") continue;
if(dir == ".." && !dirs.empty()) dirs.pop_back();
else if(dir != "..") dirs.push_back(dir);
}
string res;
for(auto& d : dirs) res += "/" + d;
return res.empty() ? "/" : res;
}
解码形如k[encoded_string]的字符串:
cpp复制string decodeString(string s) {
stack<string> chars;
stack<int> nums;
string res;
int num = 0;
for(char c : s){
if(isdigit(c)) num = num*10 + (c-'0');
else if(isalpha(c)) res += c;
else if(c == '['){
chars.push(res);
nums.push(num);
res = "";
num = 0;
}
else if(c == ']'){
string tmp = res;
res = chars.top();
chars.pop();
int repeat = nums.top();
nums.pop();
while(repeat--) res += tmp;
}
}
return res;
}
字符串处理的注意事项:
在实际刷题过程中,我总结了一些实用的优化和调试技巧:
测试用例设计:
调试方法:
性能优化:
代码风格:
以二分查找为例,分享一个调试模板:
cpp复制int binarySearch(vector<int>& nums, int target) {
int left = 0, right = nums.size()-1;
while(left <= right){
int mid = left + (right-left)/2;
cout << "left=" << left << ", right=" << right
<< ", mid=" << mid << ", nums[mid]=" << nums[mid] << endl;
if(nums[mid] == target) return mid;
else if(nums[mid] < target) left = mid+1;
else right = mid-1;
}
return -1;
}
根据我的面试经验,以下题型出现频率极高:
数组/字符串处理:
链表操作:
树结构:
动态规划:
图算法:
建议按照专题进行系统性的学习和练习,每个专题掌握3-5道典型题目及其变种。
根据我的经验,推荐以下刷题路线:
基础阶段(2-4周):
提高阶段(4-8周):
冲刺阶段(2-4周):
面试前(1-2周):
记住,刷题质量比数量更重要。每道题目都应该做到:
在算法学习和面试过程中,我踩过不少坑,总结出以下常见错误:
边界条件处理不当:
复杂度分析错误:
代码实现问题:
面试表现问题:
建议在平时练习中养成良好习惯:
最后分享一些我认为优质的学习资源:
书籍推荐:
在线资源:
学习建议:
算法能力的提升需要时间和坚持,希望这些经验分享能帮助你在刷题路上少走弯路。记住,每解决一个问题都是对自己能力的一次提升,保持耐心和热情,你一定能看到进步!