(C++)
标题目录
21. 合并两个有序链表
链接:21. 合并两个有序链表
迭代难点:内存申请的时机,next的指向
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1==nullptr) return list2;
if(list2==nullptr) return list1;
ListNode* newlist=new ListNode();
ListNode* newHead=newlist;
// newlist.val=(list1.val<list2.val)?list1.val:list2.val;
while(list1!=nullptr && list2!=nullptr){
// newlist=new ListNode();//在循环内申请空间对空链表友好
if(list1->val<=list2->val){
newlist->val=list1->val;
list1=list1->next;
}else{
newlist->val=list2->val;
list2=list2->next;
}
// cout<<newlist->val<<" "<<list1->val<<" "<<list2->val<<endl;
newlist->next=new ListNode();
newlist=newlist->next;
}
// cout<<newlist->val<<" "<<list2->val<<endl;
if(list1!=nullptr) {
newlist->val=list1->val;
newlist->next=list1->next;
}
if(list2!=nullptr) {
newlist->val=list2->val;
newlist->next=list2->next;
}
return newHead;
}
};
101. 对称二叉树
101. 对称二叉树
进阶:你可以运用递归和迭代两种方法解决这个问题吗?
迭代:
/**
* 迭代的方法
*/
class Solution {
public:
bool isSymmetric(TreeNode* root) {
queue<TreeNode*> ql, qr;
ql.push(root->left);
qr.push(root->right);
while (!ql.empty() && !qr.empty()) {
TreeNode* l = ql.front(); ql.pop();
TreeNode* r = qr.front(); qr.pop();
if (l == nullptr && r == nullptr) {
continue; // 左右子树都为空,继续检查下一层
}
if (l == nullptr || r == nullptr || l->val != r->val) {
return false; // 左右节点有一个为空或者值不相等,则不是轴对称
}
ql.push(l->left);
qr.push(r->right);
ql.push(l->right);
qr.push(r->left);
}
// 队列长度必须同时为空,否则不是对称的
return ql.empty() && qr.empty();
}
};
递归:
/**
* 递归的方法
*/
class Solution {
public:
bool check(TreeNode* l,TreeNode* r){
//如果两个节点都为空,这两个节点对称
if(l==nullptr && r==nullptr) return true;
//其中 一方空一方不空,不对称;值不同不对称
if(l==nullptr || r==nullptr || l->val != r->val) return false;
//最终的结果为左子树和右子树都对称
return check(l->left,r->right) && check(l->right,r->left);
}
bool isSymmetric(TreeNode* root) {
TreeNode* l=root->left;
TreeNode* r=root->right;
return check(l,r);
}
};
104. 二叉树的最大深度
链接:104. 二叉树的最大深度
方法:递归
/**
* 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;
if(root->left==nullptr && root->right==nullptr) return 1;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
160. 相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
题目数据保证整个链式结构中不存在环。
注意,函数返回结果后,链表必须保持其原始结构。
进阶:你能否设计一个时间复杂度 O(m + n) 、仅用 O(1) 内存的解决方案?
思路1(似乎不对):快慢指针
AB链表都分为长度为偶数、奇数两种情况:
- 如果AB长度都为偶数:偶数一次走两步 则只会出现在
- 如果AB长度一奇一偶:奇数一次走两步、偶数一次走一步,一定会遇到
- 如果AB长度都为奇数:一步和两步都能遍历链表中的所有元素,
思路2(通过):将地址放进set中。分别遍历两个链表,首次出现一样地址的就是答案。
时间O(m+n)、空间O(m+n)。
/**
* 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) {
set<ListNode*> s;//指针类型的set 学到了
ListNode *p=headA;
while(p!=NULL){
s.insert(p);
p=p->next;
}
p=headB;
while(p!=NULL){
if(s.find(p)==s.end())
s.insert(p);
else
return p;
p=p->next;
}
return p;
}
};
思路3(通过):双指针。如果AB相交,假设从相交的地方划分开,公共段称为l,AB分别可以分为la+l和lb+l。要想判断出相交 就要创造出两个指针同时指着同一个地方的情景。即使公式两边相等:la+l+()=lb+l+()。往两边添加不同的项la+l+(lb)=lb+l+(la),公式成立。
对应两个指针分别从headA、headB出发,到达链表末尾后交换,指针相等的地方恰好是相交处。
时间O(m+n),空间O(1)。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA==NULL || headB==NULL) return NULL;
ListNode *pa=headA;
ListNode *pb=headB;
while(pa!=NULL && pb!=NULL){//由于循环开始时没有判断null的情况 所以需要在前面判断
pa=pa->next;
pb=pb->next;
}//在某一个链表遍历完成后会退出循环
if(pa==NULL) pa=headB;
if(pb==NULL) pb=headA;
while(pa!=NULL && pb!=NULL){
if(pa==pb) return pa;
pa=pa->next;
pb=pb->next;
}
if(pa==NULL) pa=headB;
if(pb==NULL) pb=headA;
while(pa!=NULL && pb!=NULL){
if(pa==pb) return pa;
pa=pa->next;
pb=pb->next;
}
return NULL;
}
};
- 代码优化后:先统计长度,再对齐指针
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr) return nullptr;
ListNode *longer = headA, *shorter = headB;
int lenA = 0, lenB = 0;
// 计算两个链表的长度
while (longer) {
++lenA;
longer = longer->next;
}
longer = headA; // 重置longer为headA,因为在计算长度时已遍历至nullptr
while (shorter) {
++lenB;
shorter = shorter->next;
}
shorter = headB; // 重置shorter为headB
// 调整指针,使较长链表的指针先走差值步
if (lenA > lenB) {
for (int i = 0; i < lenA - lenB; ++i) longer = longer->next;
} else {
for (int i = 0; i < lenB - lenA; ++i) shorter = shorter->next;
}
// 同时遍历两个链表直到找到交点或都为空
while (longer != shorter) {
longer = longer->next;
shorter = shorter->next;
}
return longer; // 或shorter,因为此时两者相等
}
};
136. 只出现一次的数字
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
思路:要么1次要么2次 这个次数很特殊
题解:异或运算
class Solution {
public:
int singleNumber(vector<int>& nums) {
//要么1次要么2次 这个次数很特殊啊
//线性时间复杂度 O(n)
//如果用桶的话 空间复杂度可能会超 桶大小=6e4
//set/map
//排序时间复杂度O(nlogn) 不需要额外空间
//ok final 异或运算
int result=0;
for(int i=0;i<nums.size();i++){
result=result^nums[i];
}
return result;
}
};
141. 环形链表
链接:141. 环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
进阶:你能用 O(1)(即,常量)内存解决此问题吗?
思路1:哈希 时间O(n)空间O(n)
思路2:快慢指针 时间O(n)空间O(1)
/**
* 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==nullptr) return false;
ListNode* pre=head->next;
ListNode* last=head;
while(pre != last){
//前面的指针走到末尾
if(pre==nullptr || pre->next==nullptr) return false;
//前面的可走则后面一定可走
pre=pre->next->next;
last=last->next;
}
//一定有(pre==last)
return true;
}
};
169. 多数元素
进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
方法1:hashmap时间复杂度为 O(n)、空间复杂度为 O(n)
class Solution {
public:
int majorityElement(vector<int>& nums) {
//多数元素最多只存在1个
//hashMap
int n=nums.size()/2;
map<int,int> m;
for(auto& val:nums){
if(m.find(val)==m.end()){
m[val]=1;
}else{
m[val]=m[val]+1;
}
if(m[val]>n){
return val;
}
}
return -1;
}
};
方法2:摩尔投票 时间复杂度为 O(n)、空间复杂度为 O(1)
题解链接:https://leetcode.cn/problems/majority-element/solutions/2362000/169-duo-shu-yuan-su-mo-er-tou-piao-qing-ledrh/
当n1 != x时,抵消的所有数字中 n1占一半 非n1占一半,那么x有多少个?最少0个最多也就是一半。
所以前面的部分中x占比小于等于一半 则在后面的部分 x占比一定大于一半,仍为后面的众数,也即“缩小剩余数组区间”。
摩尔投票流程:
假设首位为众数x,票数总计votes=0.
遍历中投票数=0 ——》当前数字设为x,并一直按照投票规则投票;
返回最终的x。
摩尔投票的正确性: 一开始很怀疑摩尔投票的正确性,因为总觉得有特殊反例的存在。
但官方题解指出——投票数votes一定非负,因为每当开始新的一轮计数第一个数的存在使得votes>=1,而当votes=0时刷新x,开始新一轮的投票于是votes又是从1开始。
以及后面一段结束时count==value的证明也很精彩。
class Solution {
public:
int majorityElement(vector<int>& nums) {
//摩尔投票
int n=nums.size()/2;
int votes=0;
int x=-1;
for(auto& val:nums){
if(votes==0){
x=val;
votes=1;
}else{
if(x==val) votes++;
else votes--;
}
}
return x;
}
};
206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
**进阶:**链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
思路:遍历的同时头插法创立链表
递归:
/**
* 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* headInsert(ListNode* head,int num){
// ListNode* newhead=new ListNode(num,head);
// return newhead;
// }
ListNode* reverseList(ListNode* head) {
ListNode* p=head;
ListNode* newhead=nullptr;
while(p!=nullptr){
newhead=new ListNode(p->val,newhead);
p=p->next;
}
return newhead;
}
};
迭代:
/**
* 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* headInsert(ListNode* head,int num){
// ListNode* newhead=new ListNode(num,head);
// return newhead;
// }
ListNode* reverseList(ListNode* head) {
ListNode* p=head;
ListNode* newhead=nullptr;
while(p!=nullptr){
newhead=new ListNode(p->val,newhead);
p=p->next;
}
return newhead;
}
};
226. 翻转二叉树
链接:226. 翻转二叉树
方法:递归
思路:将大问题拆解成小问题。要做的事情对于每个节点也就是将左右指针的指向交换。
/**
* 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==nullptr) return root;
TreeNode* tmp=root->left;
root->left=invertTree(root->right);
root->right=invertTree(tmp);
return root;
}
};
234. 回文链表
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
**进阶:**你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
思路1:放进数组中判断回文。O(n) 时间复杂度、O(n) 空间复杂度。
思路2:要求空间O(1)的话,找出前一半与后一半的对应关系,多段遍历时间可能O(n^2)。
题解链接🔗:官方题解
题解思路1:空间O(1)+对称,使用函数递归栈。
题解思路2:改变链表结构然后再还原回去。
class Solution {
private:
ListNode* reverseList(ListNode* head,ListNode* tail){
if(head==tail) {
head->next=nullptr;
return head;
}
ListNode* idx=head->next;
ListNode* pre=head;
ListNode* nxt;
while(idx!=nullptr && pre!=tail){
nxt=idx->next;
idx->next=pre;
pre=idx;
idx=nxt;
}
head->next=nullptr;
return tail;
}
public:
bool isPalindrome(ListNode* head) {
if(head==nullptr||head->next==nullptr) return true;
//反转前一半的链表然后比较前后
ListNode* tail=head;
int len=1;
while(tail->next!=nullptr){
tail=tail->next;
len++;
}
ListNode* half=head;
for(int i=1;i<len/2;i++){
half=half->next;
}
ListNode* rear;
if(len%2==0)
rear=half->next;
else
rear=half->next->next;
reverseList(head,half);
ListNode* pre=half;
// while(pre!=nullptr){
// cout<<pre->val<<" ";
// pre=pre->next;
// }
// cout<<endl;
// while(rear!=nullptr){
// cout<<rear->val<<" ";
// rear=rear->next;
// }
while(pre!=nullptr && rear!=nullptr){
// cout<<pre->val<<" "<<rear->val<<endl;
if(pre->val!=rear->val){
reverseList(head,half);
half->next=rear;
return false;
}
pre=pre->next;
rear=rear->next;
}
return true;
}
};
543. 二叉树的直径
二叉树的直径是指树中任意两个节点之间最长路径的长度。这条路径可能经过也可能不经过根节点root。
思路:两个根节点之间一定有唯一一个最大公共祖先(不一定是root)。
/**
* 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 depth(TreeNode* root){
if(root==nullptr) return 0;
// if(root->left==nullptr&&root->right==nullptr) return 1;
// if(root->left==nullptr) return
return max(depth(root->left),depth(root->right))+1;
}
int diameterOfBinaryTree(TreeNode* root) {
if(root==nullptr) return 0;
//左空 右空
if(root->left==nullptr&&root->right==nullptr) return 0;
//左空 右不空
if(root->left==nullptr) return max(diameterOfBinaryTree(root->right),depth(root->right));
//左不空 右空
if(root->right==nullptr) return max(diameterOfBinaryTree(root->left),depth(root->left));
//左有 右有
return max(max(diameterOfBinaryTree(root->left),diameterOfBinaryTree(root->right)) ,depth(root->left)+depth(root->right));
}
};
其他思路: 关于“任意两点间的距离”,
如果路径经过node,则maxlen=maxlen(经过左)+maxlen(经过右)
如果不经过node,则maxlen=maxlen(经过左)和maxlen(经过右)中更大的那个。
方法:DFS
617. 合并二叉树
链接:617. 合并二叉树
/**
* 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* mergeNodes(TreeNode* node1, TreeNode* node2){
if(node1==nullptr && node2==nullptr) return nullptr;
if(node1==nullptr) return node2;
if(node2==nullptr) return node1;
//递归的部分卡壳了 主要在考虑是否需要申请新空间
//以下代码对node1进行补充
node1->val +=node2->val;
node1->left=mergeNodes(node1->left,node2->left);
node1->right=mergeNodes(node1->right,node2->right);
return node1;
// else return new pair<TreeNode*,TreeNode*>(node1,node2);
}
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
return mergeNodes(root1,root2);
}
};