LeetCode hot 100(C++版本)
哈希
第一题:两数之和
题干 :给定一个整数数组 nums和一个整数目标值 target,请你在该数组中找出和为目标值 target的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。你可以按任意顺序返回答案。
示例:
- 示例 1:输入:
nums = [2,7,11,15],target = 9;输出:[0,1](解释:nums[0] + nums[1] == 9)。 - 示例 2:输入:
nums = [3,2,4],target = 6;输出:[1,2]。 - 示例 3:输入:
nums = [3,3],target = 6;输出:[0,1]。
C++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> numMap;//存储{数组:下标}
for(int i=0;i<nums.size();i++)
{
int complement=target-nums[i];
if(numMap.find(complement)!=numMap.end())
{
return {numMap[complement],i};
}
numMap[nums[i]]=i;//插入/更新操作,将数值 nums[i]作为键,下标 i作为值,存入哈希表
}
return {};//为了保证有返回值,不会执行
}
};
第二题:最长连续序列
题干 :给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为 O(n)的算法解决此问题。
示例:
- 示例 1:输入:
nums = [100,4,200,1,3,2];输出:4(解释:最长连续序列是[1,2,3,4],长度为 4)。 - 示例 2:输入:
nums = [0,3,7,2,5,8,4,6,0,1];输出:9。 - 示例 3:输入:
nums = [1,0,1,2];输出:3。
C++
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> numSet(nums.begin(),nums.end());
int maxLength=0;
for(int num:numSet)
{
// 只有当前数字是序列起点时才处理
if(numSet.find(num-1)==numSet.end())
{
int currentNum=num;
int currentLength=1;
// 向后延伸连续序列
while(numSet.find(currentNum+1)!=numSet.end())
{
currentNum++;
currentLength++;
}
maxLength=max(maxLength,currentLength);
}
}
return maxLength;
}
};
双指针
第三题: 移动零
题干 :给定数组 nums,将所有 0移到数组末尾,保持非零元素相对顺序,原地操作(不复制数组)。
示例:
-
输入:
nums = [0,1,0,3,12];输出:[1,3,12,0,0]。 -
输入:
nums = [0];输出:[0]。C++class Solution { public: void moveZeroes(vector<int>& nums) { for(int cur=0,dest=-1;cur<nums.size();cur++) if(nums[cur]) swap(nums[++dest],nums[cur]); } };
第四题:盛最多水的容器
题干 :给定长度为 n的整数数组 height(表示 n条垂线的高度),选两条线与 x轴构成容器,求最大容水量(不能倾斜)。
示例:
- 输入:
height = [1,8,6,2,5,4,8,3,7];输出:49(两线高度8和7,宽度7,面积7×7=49)。 - 输入:
height = [1,1];输出:1(两线高度1,宽度1,面积1×1=1)。
C++
class Solution {
public:
int maxArea(vector<int>& height) {
int left=0,right=height.size()-1,ret=0;
while(left<right)
{
int v=min(height[left],height[right])*(right-left);
ret=max(ret,v);
if(height[left]<height[right])left++;
else right--;
}
return ret;
}
};
第五题: 三数之和
题干 :给定整数数组 nums,找出所有和为 0且不重复的三元组 [nums[i], nums[j], nums[k]](满足 i≠j≠k),返回所有符合条件的三元组。
示例:
- 输入:
nums = [-1,0,1,2,-1,-4];输出:[[-1,-1,2], [-1,0,1]]。 - 输入:
nums = [0,1,1];输出:[](无和为0的三元组)。 - 输入:
nums = [0,0,0];输出:[[0,0,0]]。
C++
class Solution
{
public:
vector<vector<int>> threeSum(vector<int>& nums)
{
vector<vector<int>> ret;
// 1. 排序
sort(nums.begin(), nums.end());
// 2. 利⽤双指针解决问题
int n = nums.size();
for(int i = 0; i < n; ) // 固定数 a
{
if(nums[i] > 0) break; // ⼩优化
int left = i + 1, right = n - 1, target = -nums[i];
while(left < right)
{
int sum = nums[left] + nums[right];
if(sum > target) right--;
else if(sum < target) left++;
else
{
ret.push_back({nums[i], nums[left], nums[right]});
left++, right--;
// 去重操作 left 和 right
while(left < right && nums[left] == nums[left - 1]) left++;
while(left < right && nums[right] == nums[right + 1])
right--;
}
}
// 去重 i
i++;
while(i < n && nums[i] == nums[i - 1]) i++;
}
return ret;
}
};
滑动窗口
第六题:无重复字符的最长子串
题干 :给定字符串 s,找出其中不含有重复字符的最长子串的长度。
示例:
- 输入:
s = "abcabcbb";输出:3(最长子串如"abc"、"bca"、"cab")。 - 输入:
s = "bbbbb";输出:1(最长子串为"b")。 - 输入:
s = "pwwkew";输出:3(最长子串为"wke")。
C++
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int ret=0;
int n=s.size();
for(int i=0;i<n;i++)
{
int hash[128]={0};
for(int j=i;j<n;j++)
{
hash[s[j]]++;
if(hash[s[j]]>1)
{
break;
}
ret=max(ret,j-i+1);
}
}
return ret;
}
};
第七题: 找到字符串中所有字母异位词
题干 :给定字符串 s和 p,找到 s中所有 p的字母异位词的子串,返回这些子串的起始索引(不考虑输出顺序)。
示例:
- 输入:
s = "cbaebabacd", p = "abc";输出:[0,6](子串"cba"、"bac"是"abc"的异位词)。 - 输入:
s = "abab", p = "ab";输出:[0,1,2](子串"ab"、"ba"、"ab"是"ab"的异位词)。
C++
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> res;
int n=s.size(),m=p.size();
if(n<m) return res;
vector<int> pCount(26,0),winCount(26,0);
// 统计 p 的字符频次
for(char c:p)
{
pCount[c-'a']++;
}
// 初始化第一个窗口
for(int i=0;i<m;i++)
{
winCount[s[i]-'a']++;
}
// 比较第一个窗口
if(pCount==winCount)
{
res.push_back(0);
}
// 滑动窗口
for(int i=m;i<n;i++)
{
// 加入新字符
winCount[s[i]-'a']++;
// 移除旧字符
winCount[s[i-m]-'a']--;
// 检查是否匹配
if(pCount==winCount)
{
res.push_back(i-m+1);
}
}
return res;
}
};
子串
第八题:和为 K 的子数组
题干 :给你一个整数数组 nums和一个整数 k,统计并返回该数组中和为 k的子数组的个数(子数组是数组中元素的连续非空序列)。
示例:
- 示例 1:输入:
nums = [1,1,1], k = 2;输出:2(子数组[1,1](前两个元素)、[1,1](后两个元素)的和为 2)。 - 示例 2:输入:
nums = [1,2,3], k = 3;输出:2(子数组[1,2]、[3]的和为 3)。
C++
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
unordered_map<int,int> prefixCount;
prefixCount[0] =1;// 初始状态:前缀和为0出现1次
int sum=0,count=0;
for(int num:nums)
{
sum+=num;//当前前缀和
// 如果之前出现过 prefixSum = sum - k,说明存在子数组和为k
if (prefixCount.find(sum - k) != prefixCount.end()) {
count += prefixCount[sum - k];
}
// 更新当前前缀和的出现次数
prefixCount[sum]++;
}
return count;
}
};
普通数组
第九题:最大子数组和
题干 :给定整数数组 nums,找出一个连续子数组(至少包含一个元素),使其和最大,返回这个最大和。
示例:
- 输入:
nums = [-2,1,-3,4,-1,2,1,-5,4];输出:6(最大和子数组为[4,-1,2,1])。 - 输入:
nums = [1];输出:1(仅一个元素,和为自身)。 - 输入:
nums = [5,4,-1,7,8];输出:23(最大和子数组为[5,4,-1,7,8])。
C++
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int current_sum = nums[0];
int max_sum = nums[0];
for (int i = 1; i < nums.size(); i++) {
// 如果 current_sum 大于 0,保留并累加;否则抛弃,从当前元素重新开始
current_sum = max(nums[i], current_sum + nums[i]);
// 更新全局最大值
max_sum = max(max_sum, current_sum);
}
return max_sum;
}
};
第十题: 合并区间
题干 :给定若干区间的集合 intervals(每个区间为 [start_i, end_i]),合并所有重叠的区间,返回一个不重叠且恰好覆盖所有输入区间的新区间数组。
示例:
- 输入:
intervals = [[1,3],[2,6],[8,10],[15,18]];输出:[[1,6],[8,10],[15,18]](区间[1,3]与[2,6]重叠,合并为[1,6])。 - 输入:
intervals = [[1,4],[4,5]];输出:[[1,5]](区间[1,4]与[4,5]可视为重叠,合并为[1,5])。 - 输入:
intervals = [[4,7],[1,4]];输出:[[1,7]](区间[4,7]与[1,4]可视为重叠,合并为[1,7])。
C++
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.empty()) return {};
// 1. 按区间起点排序
sort(intervals.begin(), intervals.end());
vector<vector<int>> res;
res.push_back(intervals[0]); // 第一个区间直接加入
for (int i = 1; i < intervals.size(); i++) {
// 当前区间的起点 <= 上一个区间的终点 → 有重叠
if (intervals[i][0] <= res.back()[1]) {
// 合并:更新上一个区间的终点为较大值
res.back()[1] = max(res.back()[1], intervals[i][1]);
} else {
// 无重叠,直接加入
res.push_back(intervals[i]);
}
}
return res;
}
};
第十一题: 轮转数组
题干 :给定整数数组 nums,将数组中的元素向右轮转 k个位置 (k为非负数),需原地修改数组(或满足空间复杂度要求)。
示例:
- 输入:
nums = [1,2,3,4,5,6,7], k = 3;输出:[5,6,7,1,2,3,4](向右轮转 3 步后的结果)。 - 输入:
nums = [-1,-100,3,99], k = 2;输出:[3,99,-1,-100](向右轮转 2 步后的结果)。
C++
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
if (n == 0) return;
k = k % n; // 防止 k 大于 n
if (k == 0) return;
// 1. 整体反转
reverse(nums.begin(), nums.end());
// 2. 反转前 k 个
reverse(nums.begin(), nums.begin() + k);
// 3. 反转剩余 n-k 个
reverse(nums.begin() + k, nums.end());
}
};
第十二题: 除了自身以外数组的乘积
题干 :给定整数数组 nums,返回数组 answer,其中 answer[i]等于 nums中除 nums[i]之外其余所有元素的乘积。要求:不使用除法,时间复杂度 O(n),且任意元素的前缀/后缀乘积在 32 位整数范围内。
示例:
- 输入:
nums = [1,2,3,4];输出:[24,12,8,6](answer[0]=2×3×4=24,answer[1]=1×3×4=12,依此类推)。 - 输入:
nums = [-1,1,0,-3,3];输出:[0,0,9,0,0](answer[0]=1×0×(-3)×3=0,answer[2]=(-1)×1×(-3)×3=9,依此类推)。
C++
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> answer(n);
// 1. 计算前缀积:answer[i] = nums[0] * nums[1] * ... * nums[i-1]
answer[0] = 1; // 第一个元素左边没有数,乘积为1
for (int i = 1; i < n; i++) {
answer[i] = answer[i - 1] * nums[i - 1];
}
// 2. 计算后缀积并乘入 answer:从右往左,用变量 rightProduct 记录右边乘积
int rightProduct = 1;
for (int i = n - 1; i >= 0; i--) {
answer[i] *= rightProduct;
rightProduct *= nums[i];
}
return answer;
}
};
矩阵
第十三题:矩阵置零
题干 :给定一个 m x n的矩阵,若某个元素为 0,则将其所在的行和列的所有元素 都设为 0。要求使用原地算法(直接修改输入的矩阵,不使用额外矩阵)。
示例:
- 输入:
matrix = [[1,1,1],[1,0,1],[1,1,1]];输出:[[1,0,1],[0,0,0],[1,0,1]](中间元素为0,其所在行、列均置0)。 - 输入:
matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]];输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]](第一列和第四列的元素因含0被置0,第一行和第四行也因含0被置0)。
C++
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m=matrix.size();
int n=matrix[0].size();
vector<int> row(m),col(n);
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(!matrix[i][j])
{
row[i]=col[j]=true;
}
}
}
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
if(row[i]||col[j])
{
matrix[i][j]=0;
}
}
}
}
};
第十四题:螺旋矩阵
题干 :给定一个 m行 n列的矩阵 matrix,按照顺时针螺旋顺序返回矩阵中的所有元素。
示例:
- 输入:
matrix = [[1,2,3],[4,5,6],[7,8,9]];输出:[1,2,3,6,9,8,7,4,5](按顺时针螺旋遍历:右→下→左→上→右...)。 - 输入:
matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]];输出:[1,2,3,4,8,12,11,10,9,5,6,7](螺旋遍历所有元素)。
c++
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.empty() || matrix[0].empty()) return {};
int m = matrix.size(), n = matrix[0].size();
int top = 0, bottom = m - 1, left = 0, right = n - 1;
vector<int> result;
while (top <= bottom && left <= right) {
// 1. 左 → 右
for (int j = left; j <= right; j++) {
result.push_back(matrix[top][j]);
}
top++;
// 2. 上 → 下
for (int i = top; i <= bottom; i++) {
result.push_back(matrix[i][right]);
}
right--;
// 3. 右 → 左(需判断是否还有行)
if (top <= bottom) {
for (int j = right; j >= left; j--) {
result.push_back(matrix[bottom][j]);
}
bottom--;
}
// 4. 下 → 上(需判断是否还有列)
if (left <= right) {
for (int i = bottom; i >= top; i--) {
result.push_back(matrix[i][left]);
}
left++;
}
}
return result;
}
};
第十五题: 旋转图像
题干 :给定一个 n x n的二维矩阵(表示图像),将图像顺时针旋转 90 度 。要求原地旋转(直接修改输入的矩阵,不使用额外矩阵)。
示例:
- 输入:
matrix = [[1,2,3],[4,5,6],[7,8,9]];输出:[[7,4,1],[8,5,2],[9,6,3]](顺时针旋转 90 度后的结果)。 - 输入:
matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]];输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]](顺时针旋转 90 度后的结果)。
C++
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n=matrix.size();
// Step 1: 转置矩阵(沿主对角线)
for(int i=0;i<n;i++)
{
for(int j=i+1;j<n;j++)
{
swap(matrix[i][j],matrix[j][i]);
}
}
// Step 2: 反转每一行
for(int i=0;i<n;i++)
{
reverse(matrix[i].begin(),matrix[i].end());
}
}
};
第十六题: 搜索二维矩阵 II
题干 :编写一个高效的算法,搜索 m x n矩阵 matrix中的一个目标值 target。矩阵特性:
-
每行的元素从左到右升序排列;
-
每列的元素从上到下升序排列。
示例:
-
输入:
matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5;输出:true(目标值5存在于矩阵中)。 -
输入:
matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20;输出:false(目标值20不存在于矩阵中)。
C++
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.empty()||matrix[0].empty())
{
return false;
}
int m=matrix.size(),n=matrix[0].size();
int row=0,col=n-1;//从右上角开始
while(row<m&&col>=0)
{
if(matrix[row][col]==target)
{
return true;
}
else if(matrix[row][col]>target)
{
col--;//向左
}
else
{
row++;//向下移动
}
}
return false;
}
};
链表
第十七题: 相交链表
题干 :给定两个单链表的头节点 headA、headB,找到并返回它们的相交起始节点 (若无相交返回 null)。保证链表无环。
核心 :双指针法(A走完走 B,B走完走 A,相遇时即为相交点,或都走到 null)。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (!headA || !headB) return nullptr;
ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA ? pA->next : headB;
pB = pB ? pB->next : headA;
}
return pA;
}
};
第十八题:反转链表
题干 :给定单链表头节点 head,反转链表并返回新头节点。
核心 :迭代法(三指针 prev, curr, next)或递归法(从后往前反转)。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr; // 指向前一个节点
ListNode* curr = head; // 当前节点
while (curr != nullptr) {
ListNode* nextTemp = curr->next; // 临时保存下一个节点
curr->next = prev; // 反转指针
prev = curr; // 移动 prev
curr = nextTemp; // 移动 curr
}
return prev; // prev 现在是新链表的头
}
};
第十九题:回文链表
题干 :判断单链表是否为回文链表(正读和反读相同)。
核心:快慢指针找中点,反转后半部分,比较前半和后半是否一致。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (!head || !head->next) return true;
// 1. 快慢指针找中点
ListNode *slow = head, *fast = head;
while (fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next;
}
// 2. 反转后半部分(从 slow->next 开始)
ListNode *secondHalf = reverseList(slow->next);
slow->next = nullptr; // 断开前半部分和后半部分(可选,便于比较)
// 3. 比较前后两部分
ListNode *p1 = head, *p2 = secondHalf;
bool result = true;
while (p1 && p2) {
if (p1->val != p2->val) {
result = false;
break;
}
p1 = p1->next;
p2 = p2->next;
}
// 4. (可选)恢复链表结构
slow->next = reverseList(secondHalf);
return result;
}
private:
ListNode* reverseList(ListNode* head) {
ListNode *prev = nullptr, *curr = head, *nextTemp = nullptr;
while (curr) {
nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
};
第二十题: 环形链表
题干 :判断单链表是否有环(节点可通过 next再次到达)。
核心:快慢指针(快指针走两步,慢指针走一步,相遇则有环)。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head || !head->next) return false;
ListNode *slow = head;
ListNode *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
return true;
}
}
return false;
}
};
第二十一题: 环形链表 II
题干 :找到环形链表中环的入口节点 (若无环返回 null)。
核心:快慢指针相遇后,一个指针回到头,两指针同速前进,相遇点即为环入口。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if (!head || !head->next) return nullptr;
ListNode *slow = head, *fast = head;
// 第一步:找相遇点,判断是否有环
do {
if (!fast || !fast->next) return nullptr; // 无环
slow = slow->next;
fast = fast->next->next;
} while (slow != fast);
// 第二步:找环入口
slow = head; // slow 回到起点
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow; // 返回环入口节点
}
};
第二十二题:合并两个有序链表
题干 :将两个升序链表合并为一个新的升序链表,返回新链表头节点。
核心:迭代法(新建虚拟头节点,依次拼接较小的节点)或递归法(合并剩余部分)。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
// 创建伪头节点
ListNode dummy(0);
ListNode* tail = &dummy;
// 双指针遍历两个链表
while (list1 != nullptr && list2 != nullptr) {
if (list1->val <= list2->val) {
tail->next = list1;
list1 = list1->next;
} else {
tail->next = list2;
list2 = list2->next;
}
tail = tail->next;
}
// 拼接剩余部分
if (list1 != nullptr) {
tail->next = list1;
} else {
tail->next = list2;
}
return dummy.next;
}
};
第二十三题:两数相加
题干 :两个非负整数的链表(逆序存储每位数字),将它们相加,返回和的链表(同样逆序)。
核心:逐位相加,处理进位,注意链表长度不同时的补零。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* cur1 = l1, *cur2 = l2;
ListNode* newhead = new ListNode(0); // 创建虚拟头结点
ListNode* prev = newhead; // 尾指针
int t = 0; // 记录进位
while (cur1 || cur2 || t) {
// 加上第一个链表
if (cur1) {
t += cur1->val;
cur1 = cur1->next;
}
// 加上第二个链表
if (cur2) {
t += cur2->val;
cur2 = cur2->next;
}
// 创建新节点,存储当前位结果
prev->next = new ListNode(t % 10);
prev = prev->next;
t /= 10; // 计算进位
}
prev = newhead->next;
delete newhead; // 释放虚拟头结点
return prev;
}
};
第二十四题: 删除链表的倒数第 N 个结点
题干 :删除单链表的倒数第 n个节点,返回头节点。
核心 :双指针法(快指针先走 n步,然后快慢同走,快到末尾时慢指针指向待删节点的前一个)。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0); // 创建哑节点
dummy->next = head;
ListNode* fast = dummy;
ListNode* slow = dummy;
// 快指针先走 n 步
for (int i = 0; i < n; i++) {
fast = fast->next;
}
// 快慢指针同时走,直到快指针到末尾
while (fast->next) {
fast = fast->next;
slow = slow->next;
}
// 删除倒数第 n 个节点
ListNode* toDelete = slow->next;
slow->next = slow->next->next;
delete toDelete; // 可选:释放内存(C++)
ListNode* result = dummy->next;
delete dummy; // 释放哑节点
return result;
}
};
第二十五题:两两交换链表中的节点
题干 :两两交换链表中的相邻节点,返回新头节点。要求只交换节点(不修改节点值)。
核心:迭代法(虚拟头节点 + 三指针)或递归法(交换前两个节点,递归处理剩余)。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// 边界情况
if (!head || !head->next) return head;
// 创建虚拟头节点
ListNode* dummy = new ListNode(0);
dummy->next = head;
ListNode* prev = dummy;
while (head && head->next) {
ListNode* first = head;
ListNode* second = head->next;
// 交换节点指针
prev->next = second;
first->next = second->next;
second->next = first;
// 移动指针准备下一轮
prev = first;
head = first->next;
}
return dummy->next;
}
};
第二十六题:复制带随机指针的链表
题干 :复制一个含 random指针的链表(每个节点有 val、next、random),要求深拷贝,且新链表的 random指针与原链表对应节点一致。
核心:哈希表存储原节点与新节点的映射,或原地插入复制节点后再拆分。
C++
// Definition for a Node.
// class Node {
// public:
// int val;
// Node* next;
// Node* random;
// Node(int _val) {
// val = _val;
// next = nullptr;
// random = nullptr;
// }
// };
class Solution {
public:
Node* copyRandomList(Node* head) {
if (head == nullptr) {
return nullptr;
}
// 第一步:在每个原节点后面插入一个拷贝节点
Node* pcur = head;
while (pcur != nullptr) {
Node* copyNode = new Node(pcur->val);
copyNode->next = pcur->next;
pcur->next = copyNode;
pcur = copyNode->next;
}
// 第二步:设置拷贝节点的random指针
pcur = head;
while (pcur != nullptr) {
Node* copyNode = pcur->next;
if (pcur->random != nullptr) {
copyNode->random = pcur->random->next;
}
pcur = copyNode->next;
}
// 第三步:拆分原链表和拷贝链表
pcur = head;
Node* newHead = head->next;
Node* newTail = newHead;
while (pcur != nullptr) {
// 恢复原链表
pcur->next = newTail->next;
pcur = pcur->next;
// 构建新链表
if (pcur != nullptr) {
newTail->next = pcur->next;
newTail = newTail->next;
}
}
return newHead;
}
};
第二十七题: 排序链表
题干 :给定单链表头节点 head,将链表升序排列后返回。
C++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
if(!head||!head->next) return head;
ListNode *slow=head;
ListNode *fast=head->next;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
ListNode* rightHead=slow->next;
slow->next = nullptr; // 断开
ListNode* left = sortList(head);
ListNode* right = sortList(rightHead);
// 3. 合并两个有序链表
return merge(left, right);
}
private:
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode dummy(0);
ListNode* tail = &dummy;
while (l1 && l2) {
if (l1->val <= l2->val) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
tail->next = l1 ? l1 : l2;
return dummy.next;
}
};
第二十八题: LRU 缓存
题干 :设计实现 LRUCache数据结构,满足以下要求:
-
LRUCache(int capacity):以正整数capacity为容量初始化缓存。 -
int get(int key):若key存在则返回对应value,否则返回-1。 -
void put(int key, int value):若key存在则更新value;否则插入键值对。若插入后缓存大小超过capacity,则逐出最近最少使用的键值对。 -
get和put操作的平均时间复杂度需为O(1)。C++class LRUCache { public: LRUCache(int capacity) : _capacity(capacity) {} int get(int key) { auto it = _hashmap.find(key); if (it == _hashmap.end()) return -1; // 移动到头部 auto listit = it->second; pair<int, int> kv = *listit; _list.erase(listit); _list.push_front(kv); _hashmap[key] = _list.begin(); return kv.second; } void put(int key, int value) { auto it = _hashmap.find(key); if (it == _hashmap.end()) { // 新增 if (_list.size() >= _capacity) { _hashmap.erase(_list.back().first); // 删除哈希记录 _list.pop_back(); // 删除链表尾 } _list.push_front({key, value}); _hashmap[key] = _list.begin(); } else { // 更新 auto listit = it->second; pair<int, int> kv = *listit; kv.second = value; _list.erase(listit); _list.push_front(kv); _hashmap[key] = _list.begin(); } } private: list<pair<int, int>> _list; size_t _capacity; unordered_map<int, list<pair<int, int>>::iterator> _hashmap; };
二叉树
第二十九题:二叉树的中序遍历
题干 :给定二叉树的根节点 root,返回它的中序遍历结果(左子树→根节点→右子树)。
示例:
- 输入:
root = [1,null,2,3];输出:[1,3,2](中序遍历:1→3→2)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
inorder(root, result);
return result;
}
private:
void inorder(TreeNode* node, vector<int>& result) {
if (node == nullptr) return;
inorder(node->left, result); // 1. 遍历左子树
result.push_back(node->val); // 2. 访问根节点
inorder(node->right, result); // 3. 遍历右子树
}
};
第三十题:二叉树的最大深度
题干 :给定二叉树 root,求其最大深度(从根节点到最远叶子节点的最长路径上的节点数)。
示例:
- 输入:
root = [3,9,20,null,null,15,7];输出:3(路径:3→20→15 或 3→20→7,长度3)。
C++
/*
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) {
return 0;
}
int leftDepth = maxDepth(root->left);
int rightDepth = maxDepth(root->right);
return max(leftDepth, rightDepth) + 1;
}
};
第三十一题:对称二叉树
题干 :给定二叉树 root,判断它是否为对称二叉树(沿根节点的垂直中线折叠后,左右子树完全重合)。
示例:
-
输入:
root = [1,2,2,3,4,4,3];输出:true(左右子树对称)。C++/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: bool isSymmetric(TreeNode* root) { if (!root) return true; return isMirror(root->left, root->right); } private: bool isMirror(TreeNode* left, TreeNode* right) { if (!left && !right) return true; if (!left || !right) return false; if (left->val != right->val) return false; return isMirror(left->left, right->right) && isMirror(left->right, right->left); } };
第三十二题:二叉树的层序遍历
题干 :给定二叉树 root,按层序遍历(从顶部到底部,每层从左到右)返回节点值的列表。
示例:
- 输入:
root = [3,9,20,null,null,15,7];输出:[[3],[9,20],[15,7]](逐层输出)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
if(!root) return result;
queue<TreeNode*> q;
q.push(root);
while(!q.empty())
{
int levelSize=q.size();
vector<int> currentLevel;
for(int i=0;i<levelSize;i++)
{
TreeNode* node=q.front();
q.pop();
currentLevel.push_back(node->val);
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
result.push_back(currentLevel);
}
return result;
}
};
第三十三题:将有序数组转换为二叉搜索树
题干 :给定升序数组 nums,将其转换为一棵高度平衡的二叉搜索树(左右子树高度差≤1,且左子树所有节点<根<右子树所有节点)。
示例:
- 输入:
nums = [-10,-3,0,5,9];输出:[0,-3,9,-10,null,5](平衡BST)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
// 入口函数:调用递归辅助函数,初始范围是整个数组
return buildBST(nums, 0, nums.size() - 1);
}
private:
// 递归辅助函数:在区间 [left, right] 上构建 BST
TreeNode* buildBST(vector<int>& nums, int left, int right) {
// 基础情况:如果左边界超过右边界,说明没有元素可构建,返回空
if (left > right) {
return nullptr;
}
// 取中间位置(防止整数溢出,推荐使用 left + (right - left) / 2)
int mid = left + (right - left) / 2;
// 创建根节点
TreeNode* root = new TreeNode(nums[mid]);
// 递归构建左子树(左半部分)
root->left = buildBST(nums, left, mid - 1);
// 递归构建右子树(右半部分)
root->right = buildBST(nums, mid + 1, right);
// 返回当前构建好的子树的根节点
return root;
}
};
第三十四题: 二叉树的右视图
题干 :给定二叉树 root,返回从右侧观察能看到的节点值(即每层最右边的节点)。
示例:
- 输入:
root = [1,2,3,null,5,null,4];输出:[1,3,4](每层最右节点:1→3→4)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> result;
if (!root) return result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size(); // 当前层的节点数
for (int i = 0; i < levelSize; ++i) {
TreeNode* curr = q.front();
q.pop();
// 如果是当前层的最后一个节点,加入结果
if (i == levelSize - 1) {
result.push_back(curr->val);
}
// 先左后右入队(保证下一层从左到右处理)
if (curr->left) q.push(curr->left);
if (curr->right) q.push(curr->right);
}
}
return result;
}
};
第三十五题: 翻转二叉树
题干 :给定二叉树 root,翻转该树(每个节点的左右子树交换位置),返回翻转后的根节点。
示例:
- 输入:
root = [4,2,7,1,3,6,9];输出:[4,7,2,9,6,3,1](左右子树交换)。
C++
**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
// 基础情况:空节点直接返回
if (!root) return nullptr;
// 递归翻转左右子树
TreeNode* left = invertTree(root->left);
TreeNode* right = invertTree(root->right);
// 交换左右子树
root->left = right;
root->right = left;
return root;
}
};
第三十六题: 二叉树的直径
题干 :给定二叉树 root,求其直径(任意两节点之间最长路径的边数,路径可能不经过根节点)。
示例:
- 输入:
root = [1,2,3,4,5];输出:3(路径:4→2→5 或 5→2→1→3,边数3)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int diameterOfBinaryTree(TreeNode* root) {
int m=0;
dfs(root,m);
return m;
}
private:
int dfs(TreeNode* node,int &m)
{
if(!node) return 0;
int dleft=dfs(node->left,m);
int dright=dfs(node->right,m);
m=max(m,dleft+dright);
return max(dleft,dright)+1;
}
};
第三十七题:二叉搜索树中第 K 小的元素
题干 :给定二叉搜索树 root和整数 k,返回树中第 k 小的元素(按升序排列的第 k 个节点值)。
示例:
- 输入:
root = [3,1,4,null,2], k=1;输出:1(升序:1→2→3→4,第1小是1)。
C++
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
int result = 0;
inorder(root, k, result);
return result;
}
private:
void inorder(TreeNode* node, int& k, int& result) {
if (!node) return;
inorder(node->left, k, result); // 左
k--; // 访问当前节点,计数减一
if (k == 0) { // 找到第k个
result = node->val;
return;
}
inorder(node->right, k, result); // 右
}
};
第三十八题:验证二叉搜索树
题干 :给定二叉树 root,判断它是否为有效二叉搜索树(左子树所有节点<根,右子树所有节点>根,且子树自身也满足BST)。
示例:
- 输入:
root = [2,1,3];输出:true(1<2<3,子树合法)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isValidBST(TreeNode* root) {
long long prev=LLONG_MIN;
return inorder(root,prev);
}
private:
bool inorder(TreeNode *node,long long&prev)
{
if (!node) return true;
// 遍历左子树
if (!inorder(node->left, prev)) return false;
// 检查当前节点是否大于前一个节点
if (node->val <= prev) return false;
prev = node->val; // 更新前一个值
// 遍历右子树
return inorder(node->right, prev);
}
};
第三十九题:路径总和 III
题干 :给定二叉树的根节点 root和整数 targetSum,求该二叉树里节点值之和等于 targetSum的路径数目。路径不需要从根节点开始,也不需要在叶子节点结束,但路径方向必须是向下的(只能从父节点到子节点)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int pathSum(TreeNode* root, int targetSum) {
if(!root) return 0;
//从当前节点开始
int count=dfs(root,targetSum);
count+=pathSum(root->left,targetSum);
count+=pathSum(root->right,targetSum);
return count;
}
private:
int dfs(TreeNode* node,long long target)
{
if(!node) return 0;
int count=0;
if(node->val==target) count++;
// 继续向下找,目标和减去当前节点值
count += dfs(node->left, target - node->val);
count += dfs(node->right, target - node->val);
return count;
}
};
第四十题:二叉树的最近公共祖先
题干 :给定二叉树的两个节点 p、q,找到它们的最近公共祖先 (LCA)。最近公共祖先定义为:一个节点 x,满足 x是 p和 q的祖先,且 x的深度尽可能大(节点本身也可以是自己的祖先)。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root) return nullptr;
if (p == root || q == root) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (right && left) return root;
if (left) return left;
if (right) return right;
return nullptr;
}
};
第四十一题:从前序与中序遍历序列构造二叉树
题干 :给定两个整数数组 preorder(先序遍历)和 inorder(中序遍历),构造该二叉树并返回其根节点。
C++
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
// 建立值到索引的映射,加速查找
unordered_map<int, int> indexMap;
for (int i = 0; i < inorder.size(); ++i) {
indexMap[inorder[i]] = i;
}
// 递归函数:传入前序和中序的左右边界
function<TreeNode*(int, int, int, int)> dfs = [&](int preLeft, int preRight, int inLeft, int inRight) -> TreeNode* {
// 递归终止条件
if (preLeft > preRight || inLeft > inRight) return nullptr;
// 前序第一个元素是当前根节点
int rootVal = preorder[preLeft];
TreeNode* root = new TreeNode(rootVal);
// 在中序中找到根节点位置
int rootIdx = indexMap[rootVal];
// 计算左子树节点数
int leftSize = rootIdx - inLeft;
// 递归构建左子树:前序[preLeft+1, preLeft+leftSize],中序[inLeft, rootIdx-1]
root->left = dfs(preLeft + 1, preLeft + leftSize, inLeft, rootIdx - 1);
// 递归构建右子树:前序[preLeft+leftSize+1, preRight],中序[rootIdx+1, inRight]
root->right = dfs(preLeft + leftSize + 1, preRight, rootIdx + 1, inRight);
return root;
};
return dfs(0, preorder.size() - 1, 0, inorder.size() - 1);
}
};
第四十二题:二叉树展开为链表
题干 :给定二叉树的根节点 root,将其展开为一个单链表。展开后的链表应使用 TreeNode,其中 right指针指向链表的下一个节点,left指针始终为 null。链表应与二叉树的先序遍历顺序相同。
C++
class Solution {
public:
void flatten(TreeNode* root) {
if (!root) return;
// 递归展开左右子树
flatten(root->left);
flatten(root->right);
// 暂存右子树
TreeNode* right = root->right;
// 将左子树移到右子树位置
root->right = root->left;
root->left = nullptr;
// 找到当前右链表的末尾
TreeNode* p = root;
while (p->right) {
p = p->right;
}
// 接上原右子树
p->right = right;
}
};
图论
第四十三题:岛屿数量
给定由 '1'(陆地)和 '0'(水)组成的二维网格,计算岛屿数量。岛屿定义为水平/竖直方向相邻的陆地,且网格四边均被水包围。
C++
class Solution {
int m, n;
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
public:
int numIslands(vector<vector<char>>& grid) {
if(grid.empty() || grid[0].empty()) return 0;
m = grid.size(), n = grid[0].size();
int ret = 0;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++) {
if(grid[i][j] == '1') {
ret++;
dfs(grid, i, j);
}
}
return ret;
}
private:
void dfs(vector<vector<char>>& grid, int i, int j) {
if(i < 0 || i >= m || j < 0 || j >= n || grid[i][j] != '1')
return;
grid[i][j] = '0'; // 标记为已访问
for(int k = 0; k < 4; k++) {
int x = i + dx[k], y = j + dy[k];
dfs(grid, x, y);
}
}
};
第四十四题: 腐烂的橘子
在 m×n网格中,单元格值 0(空)、1(新鲜橘子)、2(腐烂橘子)。每分钟,腐烂橘子会使其上下左右相邻 的新鲜橘子腐烂。求所有橘子腐烂所需的最小分钟数 ;若存在新鲜橘子永远无法腐烂(与腐烂橘子不连通),返回 -1。
C++
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
queue<pair<int, int>> q;
int fresh = 0; // 新鲜橘子计数
int time = 0; // 腐烂所需时间
// 初始化:找到所有腐烂橘子入队,统计新鲜橘子
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 2) {
q.push({i, j});
} else if (grid[i][j] == 1) {
fresh++;
}
}
}
// 特判:如果没有新鲜橘子,直接返回0
if (fresh == 0) return 0;
// 四个方向:上、下、左、右
vector<pair<int, int>> dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
// BFS 多源扩散
while (!q.empty()) {
int size = q.size();
bool rottenThisRound = false;
for (int k = 0; k < size; k++) {
auto [x, y] = q.front(); q.pop();
for (auto& dir : dirs) {
int nx = x + dir.first, ny = y + dir.second;
if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
grid[nx][ny] = 2; // 标记为腐烂
q.push({nx, ny});
fresh--; // 新鲜橘子减少
rottenThisRound = true;
}
}
}
// 如果本轮有感染发生,时间+1
if (rottenThisRound) time++;
}
// 如果还有新鲜橘子没被感染,说明无法全部腐烂
return fresh == 0 ? time : -1;
}
};
第四十五题: 课程表
给定 numCourses门课程(编号 0~numCourses-1)和先修关系数组 prerequisites(prerequisites[i] = [a_i, b_i]表示学 a_i前需先学 b_i)。判断是否能完成所有课程(即先修关系无循环依赖)。
C++
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
//构建邻接表和入度数组
vector<vector<int>> graph(numCourses);
vector<int> indegree(numCourses,0);
for(auto& pre:prerequisites)
{
int course=pre[0];
int prereq=pre[1];
graph[prereq].push_back(course);
indegree[course]++;
}
queue<int> q;
for(int i=0;i<numCourses;i++)
{
if(indegree[i]==0)
{
q.push(i);
}
}
int visited=0;
while(!q.empty())
{
int cur=q.front();
q.pop();
visited++;
for(int next:graph[cur])
{
indegree[next]--;
if(indegree[next]==0)
{
q.push(next);
}
}
}
return visited==numCourses;
}
};
第四十六题:实现 Trie(前缀树)
实现前缀树(Trie)数据结构,支持以下操作:
insert(word):插入字符串word;search(word):判断word是否已插入(精确匹配);startsWith(prefix):判断是否存在已插入的字符串以prefix为前缀。
C++
class Trie {
private:
struct TrieNode {
bool isEnd;
TrieNode* children[26];
TrieNode() {
isEnd = false;
for (int i = 0; i < 26; i++) {
children[i] = nullptr;
}
}
};
TrieNode* root;
public:
Trie() {
root = new TrieNode();
}
void insert(string word) {
TrieNode* node = root;
for (char ch : word) {
int idx = ch - 'a';
if (node->children[idx] == nullptr) {
node->children[idx] = new TrieNode();
}
node = node->children[idx];
}
node->isEnd = true;
}
bool search(string word) {
TrieNode* node = root;
for (char ch : word) {
int idx = ch - 'a';
if (node->children[idx] == nullptr) {
return false;
}
node = node->children[idx];
}
return node->isEnd;
}
bool startsWith(string prefix) {
TrieNode* node = root;
for (char ch : prefix) {
int idx = ch - 'a';
if (node->children[idx] == nullptr) {
return false;
}
node = node->children[idx];
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
回溯
第四十七题:全排列
给定不含重复数字 的数组 nums,返回其所有可能的全排列(顺序任意)。
C++
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> result;
backtrack(nums,0,result);
return result;
}
private:
void backtrack(vector<int>&nums,int start,vector<vector<int>>&result)
{
if (start == nums.size()) {
result.push_back(nums);
return;
}
for (int i = start; i < nums.size(); ++i) {
swap(nums[start], nums[i]); // 交换,固定当前位置
backtrack(nums, start + 1, result); // 递归处理剩余部分
swap(nums[start], nums[i]); // 撤销交换,回溯
}
}
};
第四十八题:子集
给定元素互不相同 的整数数组 nums,返回其所有可能的子集(幂集,解不能含重复子集,顺序任意)。
C++
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
vector<int> path;
backtrack(nums, 0, path, result);
return result;
}
private:
void backtrack(const vector<int>& nums, int start, vector<int>& path, vector<vector<int>>& result) {
// 每进入一次递归,就记录当前路径(即一个子集)
result.push_back(path);
// 从 start 开始,避免重复选择前面的元素(保证子集无序且不重复)
for (int i = start; i < nums.size(); ++i) {
path.push_back(nums[i]); // 选择当前元素
backtrack(nums, i + 1, path, result); // 递归处理下一个元素
path.pop_back(); // 回溯,撤销选择
}
}
};
第四十九题:电话号码的字母组合
给定仅包含数字 2-9的字符串 digits,返回其能表示的所有字母组合(数字到字母映射同电话按键,顺序任意)。
C++
class Solution {
public:
vector<string> letterCombinations(string digits) {
// 边界情况
if (digits.empty()) return {};
// 建立数字到字母的映射
vector<string> mapping = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz" // 9
};
vector<string> result;
string path;
// 回溯函数(lambda 表达式或可另写为成员函数)
function<void(int)> backtrack = [&](int index) {
// 终止条件:已处理完所有数字
if (index == digits.size()) {
result.push_back(path);
return;
}
// 获取当前数字对应的字母串
int digit = digits[index] - '0'; // 转为整数
string letters = mapping[digit];
// 遍历当前数字的所有可选字母
for (char c : letters) {
path.push_back(c); // 做选择
backtrack(index + 1); // 递归下一层
path.pop_back(); // 撤销选择(回溯)
}
};
backtrack(0);
return result;
}
};
第五十题: 组合总和
给定无重复元素 的整数数组 candidates和目标整数 target,找出 candidates中数字和为 target的所有不同组合 (candidates中同一数字可无限重复选取,顺序任意)。
C++
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> path;
sort(candidates.begin(), candidates.end()); // 排序便于剪枝
backtrack(candidates, target, 0, path, res);
return res;
}
private:
void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& res) {
if (target == 0) {
res.push_back(path);
return;
}
for (int i = start; i < candidates.size(); i++) {
if (candidates[i] > target) break; // 剪枝:当前数已大于剩余目标
path.push_back(candidates[i]);
backtrack(candidates, target - candidates[i], i, path, res); // i 不加1,允许重复选
path.pop_back(); // 回溯
}
}
};
第五十一题: 括号生成
数字 n代表生成括号的对数,设计函数生成所有可能的有效括号组合。
C++
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
string current;
backtrack(result,current,0,0,n);
return result;
}
private:
void backtrack(vector<string> &result,string ¤t,int open,int close,int max)
{
if(current.size()==2*max)
{
result.push_back(current);
return;
}
if(open<max)
{
current.push_back('(');
backtrack(result,current,open+1,close,max);
current.pop_back();
}
if(close<open)
{
current.push_back(')');
backtrack(result,current,open,close+1,max);
current.pop_back();
}
}
};
第五十二题:单词搜索
给定 m×n二维字符网格 board和字符串 word,若 word可通过水平/垂直相邻 (不可重复使用单元格)的字母按顺序构成,返回 true;否则返回 false。
C++
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
int m = board.size();
int n = board[0].size();
// 从每个格子作为起点尝试
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (dfs(board, word, i, j, 0)) {
return true;
}
}
}
return false;
}
private:
bool dfs(vector<vector<char>>& board, string& word, int i, int j, int k) {
// 匹配完成
if (k == word.size()) return true;
// 越界、字符不匹配、已访问
if (i < 0 || i >= board.size() || j < 0 || j >= board[0].size() || board[i][j] != word[k]) {
return false;
}
// 标记当前格子为已访问
char temp = board[i][j];
board[i][j] = '#';
// 四个方向搜索
bool found = dfs(board, word, i + 1, j, k + 1) ||
dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) ||
dfs(board, word, i, j - 1, k + 1);
// 回溯:恢复原字符
board[i][j] = temp;
return found;
}
};
第五十三题:分割回文串
给定字符串 s,将其分割成若干子串,使每个子串都是回文串 ,返回 s所有可能的分割方案。
C++
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> result;
vector<string> path;
backtrack(s, 0, path, result);
return result;
}
private:
void backtrack(const string& s,int start,vector<string>& path,vector<vector<string>>& result)
{
// 终止条件:已处理完所有字符
if(start==s.size())
{
result.push_back(path);
return;
}
// 尝试从 start 开始,切割到每一个位置 i
for(int i=start;i<s.size();i++)
{
if (isPalindrome(s, start, i)) {
path.push_back(s.substr(start, i - start + 1));
backtrack(s, i + 1, path, result);
path.pop_back(); // 回溯
}
}
}
bool isPalindrome(const string& s, int start, int end) {
while (start < end) {
if (s[start] != s[end]) return false;
start++;
end--;
}
return true;
}
};
二分查找
第五十四题: 搜索旋转排序数组
升序数组(元素互不相同)经未知下标左旋转后,给定旋转数组和目标值,用O(log n)时间找目标下标(不存在则-1)。
C++
class Solution {
public:
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;
}
if(nums[left]<=nums[mid])
{
if(target>=nums[left]&&target<=nums[mid])
{
right=mid-1;
}
else
{
left=mid+1;
}
}
else{
if(target>=nums[mid]&&target<=nums[right])
{
left=mid+1;
}
else
{
right=mid-1;
}
}
}
return -1;
}
};
第五十五题:在排序数组中查找元素的第一个和最后一个位置
非递减数组(含重复元素)中,用O(log n)时间找目标值的起始和结束位置(不存在则返回[-1,-1])。
C++
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
int start=-1,end=-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(nums[mid]==target)
{
start=mid;
right=mid-1;
}
else if(nums[mid]<target)
{
left=mid+1;
}
else{
right=mid-1;
}
}
if(start==-1)
{
return {-1,-1};
}
left=0;
right=nums.size()-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(nums[mid]==target)
{
end=mid;
left=mid+1;
}
else if(nums[mid]<target)
{
left=mid+1;
}
else
{
right=mid-1;
}
}
return {start,end};
}
};
第五十六题: 搜索插入位置
升序无重复数组,找目标值索引(不存在则返回插入位置),要求O(log n)时间。
C++
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int left=0;// 左指针,初始指向数组起始位置
int right=nums.size()-1;// 右指针,初始指向数组末尾位置
while(left<=right)// 二分查找的循环条件:左指针不超过右指针
{
int mid=left+(right-left)/2;// 计算中间位置,避免(left+right)溢出
if(nums[mid]==target)
{
return mid;// 找到目标值,返回其索引
}
else if(nums[mid]<target)
{
left=mid+1;// 目标值在右半部分,调整左指针
}
else if(nums[mid]>target)
{
right=mid-1;// 目标值在左半部分,调整右指针
}
}
// 循环结束未找到,left即为插入位置(此时left > right,left是第一个大于target的位置)
return left;
}
};
第五十七题: 搜索二维矩阵
m×n矩阵满足"每行非严格递增、每行首元素>前一行尾元素",判断目标值是否存在。
C++
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.size()==0||matrix[0].size()==0)
{
return false;
}
int m=matrix.size();
int n=matrix[0].size();
int left=0,right=m*n-1;
while(left<=right)
{
int mid=left+(right-left)/2;
int row=mid/n;
int col=mid%n;
int val=matrix[row][col];
if(val==target)
{
return true;
}
else if(val<target)
{
left=mid+1;
}
else
{
right=mid-1;
}
}
return false;
}
};
第五十八题:寻找旋转排序数组中的最小值
升序数组(元素互不相同)经多次旋转后,找其中的最小元素,要求O(log n)时间。
C++
class Solution {
public:
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])
{
right = mid; // 最小值在左半部分(含mid)
}
else
{
left=mid+1;// 最小值在右半部分(不含mid)
}
}
return nums[left];// 循环结束时 left == right,即为最小值下标
}
};
栈
第五十九题: 有效的括号
给定一个只含 (), [], {}的字符串,判断其是否有效。
有效条件:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有对应的左括号。
C++
class Solution {
public:
bool isValid(string s) {
// 优化:奇数长度必然无效
if (s.length() % 2 != 0) return false;
stack<char> stk;
unordered_map<char, char> pairs = {
{')', '('},
{'}', '{'},
{']', '['}
};
for (char c : s) {
// 如果是左括号,入栈
if (c == '(' || c == '{' || c == '[') {
stk.push(c);
}
// 如果是右括号
else {
// 栈空 或 栈顶不匹配 → 无效
if (stk.empty() || stk.top() != pairs[c]) {
return false;
}
stk.pop(); // 匹配成功,弹出
}
}
// 最终栈为空才有效
return stk.empty();
}
};
第六十题: 最小栈
设计一个支持 push、pop、top操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack类,包含初始化、压栈、弹栈、取栈顶、取最小值等方法。
C++
class MinStack {
private:
stack<int> st;// 主栈
stack<int> Minst;// 辅助栈,存每个位置对应的历史最小值
public:
MinStack() {
}
void push(int val) {
st.push(val);
if(Minst.empty()||val<=Minst.top()) // 注意:<= 保证重复最小值也能正确记录
{
Minst.push(val);
}
else
{
Minst.push(Minst.top());//否则延续之前的
}
}
void pop() {
st.pop();
Minst.pop();
}
int top() {
return st.top();
}
int getMin() {
return Minst.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(val);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
第六十一题: 字符串解码
给定编码字符串 s,格式为 k[encoded_string],表示方括号内的字符串 encoded_string要重复 k次(k为正整数)。返回解码后的字符串。
假设输入总是有效,数字仅表示重复次数,原始数据不含数字,且方括号格式正确。
C++
class Solution {
public:
string decodeString(string s) {
stack<pair<string, int>> st;
string res = "";
int num = 0;
for (char c : s) {
if (isdigit(c)) {
num = num * 10 + (c - '0');
} else if (c == '[') {
st.push({res, num});
res = "";
num = 0;
} else if (c == ']') {
auto [prev_str, count] = st.top(); st.pop();
string repeated = "";
for (int i = 0; i < count; i++) {
repeated += res;
}
res = prev_str + repeated;
} else {
res += c;
}
}
return res;
}
};
第六十二题:每日温度
给定整数数组 temperatures,表示每天的温度,返回一个数组 answer,其中 answer[i]表示第 i天之后下一个更高温度 出现在几天后;若之后不会升高,则为 0。
C++
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n=temperatures.size();
vector<int> answer(n,0);//初始化全为0
stack<int> st;//存储索引,维持单调递减
for(int i=0;i<n;i++)
{
// 当前温度比栈顶温度高,则栈顶找到了"下一个更高温度"
while(!st.empty()&&temperatures[st.top()]<temperatures[i])
{
int prevIndex=st.top();
st.pop();
answer[prevIndex]=i-prevIndex;
}
st.push(i);
}
return answer;
}
};
堆
第六十三题:215. 数组中的第K个最大元素
给定整数数组 nums和整数 k,返回数组排序后第 k个最大的元素(非去重后的第 k个不同元素)。要求设计算法时间复杂度为 O(n)。
C++
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int,vector<int>,greater<int>> minHeap;
for(int num:nums)
{
minHeap.push(num);
if(minHeap.size()>k)
{
minHeap.pop();
}
}
return minHeap.top();
}
};
第六十四题:347. 前K个高频元素
给定整数数组 nums和整数 k,返回数组中出现频率前 k高的元素(顺序任意)。
C++
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> freqMap;
for(int num:nums)
{
freqMap[num]++;
}
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
for (auto& entry : freqMap) {
int num = entry.first;
int freq = entry.second;
minHeap.push({freq, num});
if (minHeap.size() > k) {
minHeap.pop(); // 弹出频率最小的,保持堆大小为 k
}
}
// 3. 提取堆中的元素(频率前 k 高)
vector<int> result;
while (!minHeap.empty()) {
result.push_back(minHeap.top().second);
minHeap.pop();
}
// 注意:题目允许任意顺序,所以不需要排序
return result;
}
};
贪心算法
第六十五题:买卖股票的最佳时机
题干 :给定数组 prices(prices[i]为第 i天股票价格),只能选一天买入、未来某天卖出,求最大利润(无利润返回0)。
C++
class Solution {
public:
int maxProfit(vector<int>& prices) {
int min_price=INT_MAX;
int max_profit=0;
for(int price:prices)
{
if(price<min_price)
{
min_price=price;
}
else
{
max_profit=max(max_profit,price-min_price);
}
}
return max_profit;
}
};
第六十六题:跳跃游戏
题干 :非负整数数组 nums(每个元素表示当前位置最大跳跃长度),初始在第一个下标,判断能否到达最后一个下标(能则返回 true,否则 false)。
C++
class Solution {
public:
bool canJump(vector<int>& nums) {
int max_reach = 0;
int n = nums.size();
for (int i = 0; i < n; ++i) {
// 如果当前位置已不可达,返回false
if (i > max_reach) return false;
// 更新最远可达位置
max_reach = max(max_reach, i + nums[i]);
// 如果已能到达或超过最后一个位置,提前返回true
if (max_reach >= n - 1) return true;
}
// 理论上不会执行到这里,但语法安全
return true;
}
};
第六十七题:跳跃游戏 II
题干 :0索引整数数组 nums(每个元素 nums[i]表示从 i向后最大跳跃长度),初始在 0下标,返回到达最后一个下标(n-1)的最小跳跃次数(保证可到达)。
C++
class Solution {
public:
int jump(vector<int>& nums) {
int n = nums.size();
int jumps = 0;
int current_end = 0;
int farthest = 0;
for (int i = 0; i < n - 1; ++i) { // 注意:不需要遍历最后一个元素
farthest = max(farthest, i + nums[i]);
if (i == current_end) {
jumps++;
current_end = farthest;
if (current_end >= n - 1) {
break; // 已经能到达终点,提前结束
}
}
}
return jumps;
}
};
第六十八题:划分字母区间
题干 :字符串 s(仅小写字母),划分为尽可能多的片段,同一字母最多出现在一个片段中。返回各片段的长度列表(拼接后与原字符串一致)。
C++
class Solution {
public:
vector<int> partitionLabels(string s) {
// 1. 记录每个字符最后出现的位置
unordered_map<char, int> lastPos;
for (int i = 0; i < s.size(); i++) {
lastPos[s[i]] = i;
}
vector<int> result;
int start = 0; // 当前片段的起始位置
int end = 0; // 当前片段的结束位置(动态扩展)
// 2. 遍历字符串,贪心划分
for (int i = 0; i < s.size(); i++) {
end = max(end, lastPos[s[i]]); // 扩展当前片段的右边界
if (i == end) { // 当前位置是片段终点
result.push_back(end - start + 1); // 记录片段长度
start = end + 1; // 更新下一个片段的起始位置
}
}
return result;
}
};
动态规划
第六十九题:爬楼梯
给定整数 n(需到达的楼顶台阶数,每次可爬1或2阶),求爬到楼顶的不同方法数。
C++
class Solution {
public:
int climbStairs(int n) {
if(n<=2)
return n;
int a=1,b=2,c;
for(int i=3;i<=n;i++)
{
c=a+b;
a=b;
b=c;
}
return b;
}
};
第七十题: 杨辉三角
给定非负整数 numRows,生成杨辉三角的前 numRows行(每个数等于其左上方和右上方数的和)。
C++
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> result;
for(int i=0;i<numRows;i++)
{
vector<int> row(i + 1, 1); // 初始化全为1,长度为 i+1
for (int j = 1; j < i; j++) { // 从第二个元素到倒数第二个元素
row[j] = result[i-1][j-1] + result[i-1][j];
}
result.push_back(row);
}
return result;
}
};
第七十一题: 打家劫舍
给定非负整数数组 nums(每间房屋的金额),相邻房屋不能同时偷窃,求不触发警报的最大偷窃金额。
C++
class Solution {
public:
int rob(vector<int>& nums) {
int n=nums.size();
if(n==1) return nums[0];
vector<int> f(n,0);
vector<int> g(n,0);
f[0]=nums[0];
g[0]=0;
for(int i=1;i<n;i++)
{
f[i]=g[i-1]+nums[i];
g[i]=max(f[i-1],g[i-1]);
}
return max(f[n-1],g[n-1]);
}
};
第七十二题: 完全平方数
给定整数 n,求和为 n的完全平方数的最少数量(完全平方数:如1、4、9、16等,即整数的平方)。
C++
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+1,INT_MAX);
dp[0]=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j*j<=i;j++)
{
dp[i]=min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
};
第七十三题:零钱兑换
给定硬币数组 coins(不同面额,数量无限)和总金额 amount,求凑出总金额的最少硬币个数(无法凑出则返回-1)。
C++
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
// dp[i] 表示凑出金额 i 所需的最少硬币数
vector<int> dp(amount + 1, amount + 1); // 初始化为一个不可能达到的值
dp[0] = 0; // 凑出0元需要0个硬币
// 遍历每个金额
for (int i = 1; i <= amount; ++i) {
// 尝试每一种硬币
for (int coin : coins) {
if (i >= coin) {
dp[i] = min(dp[i], dp[i - coin] + 1);
}
}
}
// 返回结果,若仍为初始值说明无法凑出
return dp[amount] == amount + 1 ? -1 : dp[amount];
}
};
第七十四题: 单词拆分
给定字符串 s和字符串列表 wordDict(字典,单词可重复使用),判断 s是否能被字典中一个或多个单词拼接而成。
C++
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
int n = s.size();
vector<bool> dp(n + 1, false);
dp[0] = true; // 空字符串可拼接
for (int i = 1; i <= n; i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordSet.find(s.substr(j, i - j)) != wordSet.end()) {
dp[i] = true;
break; // 找到一种拼法即可
}
}
}
return dp[n];
}
};
第七十五题:最长递增子序列
给定整数数组 nums,找到其中最长严格递增子序列的长度(子序列不改变元素相对顺序,可删除元素)。
C++
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
vector<int> dp(n,1);
int ret=1;
for(int i=1;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(nums[j]<nums[i])
dp[i]=max(dp[i],dp[j]+1);
ret=max(ret,dp[i]);
}
}
return ret;
}
};
第七十六题: 乘积最大子数组
给定整数数组 nums,找出乘积最大的非空连续子数组,并返回其乘积(子数组至少含一个数字)。
C++
class Solution {
public:
int maxProduct(vector<int>& nums) {
if (nums.empty()) return 0;
int maxProd = nums[0]; // 全局最大乘积
int currMax = nums[0]; // 以当前元素结尾的最大乘积
int currMin = nums[0]; // 以当前元素结尾的最小乘积
for (int i = 1; i < nums.size(); ++i) {
// 保存上一轮的最大/最小值,用于当前计算
int tempMax = currMax;
int tempMin = currMin;
// 当前位置的最大乘积 = max(当前数, 当前数×之前最大, 当前数×之前最小)
currMax = max({nums[i], nums[i] * tempMax, nums[i] * tempMin});
// 当前位置的最小乘积 = min(当前数, 当前数×之前最大, 当前数×之前最小)
currMin = min({nums[i], nums[i] * tempMax, nums[i] * tempMin});
// 更新全局最大值
maxProd = max(maxProd, currMax);
}
return maxProd;
}
};
第七十七题:分割等和子集
给定只包含正整数的非空数组 nums,判断是否可将其分割成两个元素和相等的子集。
C++
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int num : nums) sum += num;
// 总和为奇数,无法平分
if (sum % 2 != 0) return false;
int target = sum / 2;
vector<bool> dp(target + 1, false);
dp[0] = true; // 和为0总是可以达成(空集)
for (int num : nums) {
// 从后往前遍历,避免重复使用当前数字
for (int j = target; j >= num; j--) {
dp[j] = dp[j] || dp[j - num];
}
}
return dp[target];
}
};
多维动态规划
第七十八题:不同路径
题干:机器人从 m×n网格左上角出发,每次仅能向下或向右移动一步,求到达右下角的不同路径总数。
C++
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n, 1));
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
第七十九题: 最小路径和
题干 :给定含非负整数的 m×n网格 grid,机器人每次只能向下或向右移动一步,求从左上角到右下角的路径中,数字总和最小的路径的和。
C++
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
vector<int> dp(n, 0);
// 初始化第一行
dp[0] = grid[0][0];
for (int j = 1; j < n; j++) {
dp[j] = dp[j-1] + grid[0][j];
}
// 逐行更新
for (int i = 1; i < m; i++) {
dp[0] += grid[i][0]; // 第一列只能从上往下
for (int j = 1; j < n; j++) {
dp[j] = min(dp[j], dp[j-1]) + grid[i][j];
}
}
return dp[n-1];
}
};
第八十题:最长回文子串
题干:给定字符串 s,找到其中最长的回文子串(回文:正读和反读一致的字符串)。
C++
class Solution {
public:
string longestPalindrome(string s) {
int begin=0,len=0,n=s.size();
for(int i=0;i<n;i++)
{
int left=i,right=i;
while(left>=0&&right<n&&s[left]==s[right])
{
left--;
right++;
}
if(right-left-1>len)
{
begin=left+1;
len=right-left-1;
}
left=i,right=i+1;
while(left>=0&&right<n&&s[left]==s[right])
{
left--;
right++;
}
if(right-left-1>len)
{
begin=left+1;
len=right-left-1;
}
}
return s.substr(begin,len);
}
};
第八十一题:最长公共子序列
题干 :给定两个字符串 text1和 text2,返回它们的最长公共子序列的长度。子序列定义为原字符串不改变字符相对顺序、删除部分字符(或不删)得到的新字符串;公共子序列是两个字符串共有的子序列。
C++
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m=text1.size();
int n=text2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
if (text1[i - 1] == text2[j - 1]) {
// 当前字符相等,LCS 长度加 1
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// 当前字符不相等,取左边或上边的最大值
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[m][n];
}
};
第八十二题:编辑距离
题干 :给定两个单词 word1和 word2,求将 word1转换成 word2所需的最少操作数。允许的操作:插入一个字符、删除一个字符、替换一个字符。
C++
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.length(), n = word2.length();
vector<int> dp(n + 1, 0);
// 初始化第一行
for (int j = 0; j <= n; j++) dp[j] = j;
for (int i = 1; i <= m; i++) {
int prev = dp[0]; // 保存左上角的值
dp[0] = i; // 当前行的第一列
for (int j = 1; j <= n; j++) {
int temp = dp[j]; // 保存当前值,作为下一次的prev
if (word1[i-1] == word2[j-1]) {
dp[j] = prev;
} else {
dp[j] = min({dp[j], // 删除(即上一行的值)
dp[j-1], // 插入(即当前行前一列的值)
prev // 替换(即左上角的值)
}) + 1;
}
prev = temp;
}
}
return dp[n];
}
};
一些技巧题
第八十三题: 只出现一次的数字
题干 :给定非空整数数组 nums(除一个元素仅出现一次外,其余元素均出现两次),找出该只出现一次的元素。要求线性时间复杂度、常量额外空间。
C++
class Solution {
public:
int singleNumber(vector<int>& nums) {
int result=0;
for(int num:nums)
{
result^=num;
}
return result;
}
};
第八十四题:颜色分类
题干 :给定含 n个元素的数组 nums(元素为 0、1、2,分别表示红、白、蓝),原地 排序使相同颜色相邻且按红(0)、白(1)、蓝(2)顺序排列。不允许使用库内置 sort函数,进阶要求常数空间、一趟扫描。
C++
class Solution {
public:
void sortColors(vector<int>& nums) {
int left = 0; // 指向下一个 0 的位置
int right = nums.size() - 1; // 指向下一个 2 的位置
int i = 0; // 当前处理位置
while (i <= right) {
if (nums[i] == 0) {
swap(nums[i], nums[left]);
left++;
i++; // 交换后 i 位置一定是 1 或刚放来的 0,安全前进
} else if (nums[i] == 1) {
i++; // 中间区域,无需交换
} else { // nums[i] == 2
swap(nums[i], nums[right]);
right--; // 交换后 i 位置的值未检查,不能 i++
}
}
}
};