1.两数相加(力扣2题)
1.1题目
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
1.2思路
- 先统计两个链表的长度
len1和len2,判断谁更长。 - 按长度分三种情况处理 :
- 长度相等:直接在
l1上做加法,每一位相加,有进位就直接给下一位val+1。 l1更长:前半部分和l2相加,剩余部分只处理l1的进位。l2更长:前半部分和l1相加,剩余部分只处理l2的进位。
- 长度相等:直接在
- 进位处理方式 :每一位相加 ≥10 时,当前位取
%10,下一位直接+1;如果是最后一位,就新建一个值为1的节点。
1.3完整代码
cpp
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//先遍历一遍判断个数
//从个数不同的地方开始相加
//从长的一个链表的个数不同位置开始替换数字
int len1=0,len2=0;
auto cur1=l1;
auto cur2=l2;
while(cur1){
len1++;
cur1=cur1->next;
}
while(cur2){
len2++;
cur2=cur2->next;
}
if(len1==len2){
cur1=l1;
cur2=l2;
while(cur1){
int num=cur1->val+cur2->val;
if(num>=10){
int newNum=num%10;
cur1->val=newNum;
if(cur1->next!=nullptr){
(cur1->next->val)++;
}
else if(cur1->next==nullptr){
auto newNode =new ListNode(1);
cur1->next=newNode;
return l1;
}
}
else{
cur1->val=num;
}
cur1=cur1->next;
cur2=cur2->next;
}
return l1;
}
else if(len1>len2){
// int dif=len1-len2;
cur1=l1;
cur2=l2;
// while(dif--){
// cur1=cur1->next;
// }
while(cur1&&cur2){
int num=cur1->val+cur2->val;
if(num>=10){
int newNum=num%10;
cur1->val=newNum;
if(cur1->next!=nullptr){
(cur1->next->val)++;
}
else if(cur1->next==nullptr){
auto newNode =new ListNode(1);
cur1->next=newNode;
return l1;
}
}
else{
cur1->val=num;
}
cur1=cur1->next;
cur2=cur2->next;
}
while(cur1){
if(cur1->val>=10){
int newNum=(cur1->val)%10;
cur1->val=newNum;
if(cur1->next!=nullptr){
(cur1->next->val)++;
}
else if(cur1->next==nullptr){
auto newNode =new ListNode(1);
cur1->next=newNode;
return l1;
}
}
cur1=cur1->next;
}
return l1;
}
else if(len2>len1){
// int dif=len2-len1;
cur1=l1;
cur2=l2;
while(cur2&&cur1){
int num=cur1->val+cur2->val;
if(num>=10){
int newNum=num%10;
cur2->val=newNum;
if(cur2->next!=nullptr){
(cur2->next->val)++;
}
else if(cur2->next==nullptr){
auto newNode =new ListNode(1);
cur2->next=newNode;
return l2;
}
}
else{
cur2->val=num;
}
cur1=cur1->next;
cur2=cur2->next;
}
while(cur2){
if(cur2->val>=10){
int newNum=(cur2->val)%10;
cur2->val=newNum;
if(cur2->next!=nullptr){
(cur2->next->val)++;
}
else if(cur2->next==nullptr){
auto newNode =new ListNode(1);
cur2->next=newNode;
return l2;
}
}
cur2=cur2->next;
}
return l2;
}
return NULL;
}
};
2. 买卖股票的最佳时机(121 题)
2.1 题目
给定一个数组 prices,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0。
示例:输入 [7,1,5,3,6,4],输出 5(第 2 天买入,第 5 天卖出,利润 6-1=5)。
2.2 思路
从后往前遍历数组,用 ** 大顶堆(优先队列)** 保存 "未来的卖出价格",动态计算当前天买入的最大利润:
- 从数组末尾向前遍历,遇到的价格都加入大顶堆,堆顶永远是 "未来能卖出的最高价"。
- 每次遍历到第
i天,用堆顶价格减去prices[i],得到 "第i天买入的最大利润",更新全局最大利润res。 - 遍历结束后返回
res(初始为 0,无正利润时自动返回 0)。
2.3 完整代码
cpp
class Solution {
public:
int maxProfit(vector<int>& prices) {
priority_queue<int> que;
int res=0;
vector<int> profits(prices.size(),0);
for(int i=prices.size()-1;i>=0;i--){
if(!que.empty()){
profits[i]=que.top()-prices[i];
res=max(profits[i],res);
}
que.push(prices[i]);
}
return res;
}
};
3. 删除链表的倒数第 N 个结点(19 题)
3.1 题目
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例:输入 head = [1,2,3,4,5], n = 2,输出 [1,2,3,5](删除倒数第 2 个节点4)。
3.2 思路
用虚拟头节点 + 快慢指针解决,避免处理 "删除头节点" 的特殊情况:
- 创建虚拟头节点
dummyHead指向原链表头,让fast和slow都指向dummyHead。 - 先让
fast先走n步,使fast和slow之间相差n个节点。 fast和slow同步向前走,直到fast走到链表末尾(fast->next == nullptr),此时slow->next就是要删除的倒数第n个节点。- 直接修改
slow->next = slow->next->next,跳过待删除节点,返回dummyHead->next。
3.3 完整代码
cpp
/**
* 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) {
auto dummyHead=new ListNode(0);
dummyHead->next=head;
auto fast=dummyHead;
auto slow=dummyHead;
while(n--){
fast=fast->next;
}
while(fast->next){
fast=fast->next;
slow=slow->next;
}
slow->next=slow->next->next;
return dummyHead->next;
}
};
4. 最长递增子序列(300 题)
4.1 题目
给你一个整数数组 nums,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。
示例:输入 nums = [10,9,2,5,3,7,101,18],输出 4(最长递增子序列为 [2,3,7,101])。
4.2 思路
动态规划解法,核心是定义 dp 数组记录状态:
- 定义
dp[i]:以nums[i]结尾的最长严格递增子序列的长度。 - 初始化
dp数组,每个元素设为1(每个元素本身就是长度为 1 的子序列)。 - 双重循环遍历:外层
i遍历每个元素,内层j遍历0~i-1的所有元素。 - 若
nums[j] < nums[i],说明nums[i]可以接在nums[j]后形成更长的子序列,更新dp[i] = max(dp[i], dp[j]+1)。 - 遍历结束后,
dp数组的最大值即为答案。
4.3 完整代码
cpp
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(),1);
for(int i=0;i<nums.size();i++){
for(int j=i;j>=0;j--){
if(nums[j]<nums[i]){
dp[i]=max(dp[i],dp[j]+1);
}
}
}
int res=0;
for(int i=0;i<dp.size();i++){
res=max(dp[i],res);
}
return res;
}
};
5. 最大子数组和(53 题)
5.1 题目
给你一个整数数组 nums,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:输入 nums = [-2,1,-3,4,-1,2,1,-5,4],输出 6(连续子数组 [4,-1,2,1] 的和最大)。
5.2 思路
贪心算法(Kadane 算法),用两个变量动态维护状态:
sum:记录当前连续子数组的和;res:记录全局最大子数组和(初始为INT_MIN,避免全负数数组出错)。- 遍历数组,每次将当前元素加到
sum中,更新res为sum和当前res的较大值。 - 若
sum < 0,说明当前子数组和为负,继续累加只会拉低后续和,因此重置sum = 0,重新开始计算新的子数组。 - 遍历结束后返回
res。
5.3 完整代码
cpp
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum=0;
int res=INT_MIN;
for(int i=0;i<nums.size();i++){
sum+=nums[i];
res=max(sum,res);
if(sum<0){
sum=0;
continue;
}
}
return res;
}
};
6. 搜索旋转排序数组(33 题)
6.1 题目
整数数组 nums 按升序排列,数组中的值互不相同。在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了向左旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]。
给你旋转后的数组 nums 和一个整数 target,如果 nums 中存在 target,则返回它的下标,否则返回 -1。题目要求设计时间复杂度为 O(log n) 的算法(注:你的代码为 O(n) 解法,以下按你的思路整理)。
示例:输入 nums = [4,5,6,7,0,1,2], target = 0,输出 4。
6.2 思路
先找到旋转点,再将数组还原为有序数组后遍历查找:
- 遍历数组找到旋转点
index:即第一个满足nums[i] < nums[i-1]的位置(无旋转时index=0)。 - 将数组拆分为两部分:前半部分
nums[0..index-1]和后半部分nums[index..end],再将后半部分拼接到前半部分后,还原为升序数组。 - 遍历还原后的数组,找到
target则返回下标,未找到则返回-1。
6.3 完整代码
cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
int index=0;
for(int i=1;i<nums.size();i++){
if(nums[i]<nums[i-1]){
index=i;
break;
}
}
vector<int> nums1(nums.begin(),nums.begin()+index);
vector<int> nums2(nums.begin()+index,nums.end());
for(int i=0;i<nums2.size();i++){
nums1.push_back(nums2[i]);
}
for(int i=0;i<nums1.size();i++){
if(target==nums1[i]){
return i;
}
}
return -1;
}
};
7. 环形链表 II(142 题)
7.1 题目
给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,则返回 null。不允许修改链表结构。
示例:输入 head = [3,2,0,-4], pos = 1(链表尾部连接到索引 1 的节点),输出索引为 1 的节点。
7.2 思路
Floyd 判圈算法(快慢指针法),分两步解决:
- 判断是否有环 :
fast指针每次走 2 步,slow指针每次走 1 步,若两者相遇则说明链表有环;若fast走到链表末尾则无环,返回null。 - 找到环的入口:相遇后,让其中一个指针回到链表头,两个指针每次同步走 1 步,再次相遇的节点即为环的入口节点。
7.3 完整代码
cpp
/**
* 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 NULL;
}
auto left=head;
auto right=head;
while(left&&right&&right->next){
right=right->next->next;
left=left->next;
if(right==left){
auto cur=head;
while(left!=cur){
left=left->next;
cur=cur->next;
}
return left;
}
}
return NULL;
}
};