题目目录
- 简单
- No.1(#1) Two Sum
- No.2(#20)Valid Parentheses
- No.3(#21)Merge Two Sorted Lists
- DP No.4(#53)Maximum Subarray
- DP No.5(#70)Climbing Stairs
- No.6.0(#94)Binary Tree Inorder Traversal
- No.6.1(#101)Symmetric Tree
- No.7(#104)Maximum Depth of Binary Tree
- DP No.8(#121)Best Time to Buy and Sell Stock
- No.9(#136)Single Number
- No.10(#141) Linked List Cycle
- SKIP No.11(#155)Min Stack
- No.12(#160)Intersection of Two Linked Lists
- No.13(#169)Majority Element
- No.14(#206)Reverse Linked List
- No.15(#226)Invert Binary Tree
- No.16(#234)Palindrome Linked List
- No.17(#283)Move Zeroes
- No.18(#448)Find All Numbers Disappeared in an Arrary
- No.19(#461) Hamming Distance
- No.20(#543)Diameter of Binary Tree
- No.21(#617)Merge Two Binary Tree
一、简单
No.1(#1) Two Sum
No.2(#20)Valid Parentheses
No.3(#21)Merge Two Sorted Lists
No.4(#53)Maximum Subarray
No.5(#70)Climbing Stairs
No.6(#101)Symmetric Tree
No.7(#104)Maximum Depth of Binary Tree
No.8(#121)Best Time to Buy and Sell Stock
No.9(#136)Single Number
No.10(#141) Linked List Cycle
No.11(#155)Min Stack
No.12(#160)Intersection of Two Linked Lists
No.13(#169)Majority Element
No.14(#206)Reverse Linked List
No.15(#226)Invert Binary Tree
No.16(#234)Palindrome Linked List
No.17(#283)Move Zeroes
No.18(#448)Find All Numbers Disappeared in an Arrary
No.19(#461) Hamming Distance
No.20(#543)Diameter of Binary Tree
No.21(#617)Merge Two Binary Tree
简单
No.1(#1) Two Sum
思路一(双指针):先将数组排序至递增,利用双指针思想:和大于target就左移right指针一位,和小于target就右移左指针一位,最终找到和等于target的值,记录至left_value和right_value中,再去原数组中找到他们的位置。
时间复杂度为O(n),空间复杂度为O(n)。
// C++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int>ans,temp_nums=nums;
sort(nums.begin(),nums.end());
int left=0,right=nums.size()-1,left_value,right_value;
while(left<right){//类似于快排的双指针思想
if(nums[left]+nums[right]==target){
left_value=nums[left];
right_value=nums[right];
break;
}
if(nums[left]+nums[right]>target){
right--;
}
if(nums[left]+nums[right]<target){
left++;
}
}
for(int i=0;i<temp_nums.size();i++){
int flag=0;//当flag==2时,说明left和right的位置都找到了
if(temp_nums[i]==left_value){
ans.push_back(i);
flag++;
}
else if(temp_nums[i]==right_value){
ans.push_back(i);
flag++;
}
if(flag==2) break;
}
return ans;
}
};
思路一优化(双指针):在这基础上进行优化,可以用一个数组保存索引,对原数组排序时,同时对索引数组排序,这样对于排完序的两个数组,值和索引是对应的,就不需要再重新遍历一遍原数组去找位置了。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
idx_list = list(range(len(nums)))
for i in range(len(nums)):
flag = True # 冒泡排序的标记
for j in range(len(nums)-1-i): # 排序原数组和索引数组
if nums[j] > nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
idx_list[j], idx_list[j+1] = idx_list[j+1], idx_list[j]
flag = False
if flag:
break
left = 0
right = len(nums)-1
while left < right:
if nums[left] + nums[right] < target:
left += 1
elif nums[left] + nums[right] > target:
right -= 1
else:
return [idx_list[left], idx_list[right]]
return []
思路二(Hash):考虑数组里包含重复数字,会不会发生hash表值被覆盖的问题呢?可能有以下三种情况:1. 两个数字的和等于target。检索到第二个数字时,可以直接返回答案;2. 两个数字都不是答案,hash表值被覆盖无影响; 3. 答案只包含其中一个数字。题目已经指出这种情况不存在,因此只考虑前两种情况。
# python3
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
mp = {}
for i,num in enumerate(nums):
if target-num in mp:
return [i,mp[target-num]]
mp[nums[i]] = i
return []
No.2(#20)Valid Parentheses
思路一:系统给的测例是忽略(、[、{的先后次序的,即({})也正确,因此我们只要遵循左括号入栈,右括号与栈顶匹配则栈顶出栈,不匹配则返回错误的原则遍历字符串就行。若匹配,最后的栈一定是空的;若不匹配,会在前面的错误检测中直接返回错误。
我们还可以对括号赋值,如’(’=1,’)’=-1,若两者相加为零则匹配。并且可以利用这个特性,先计算字符串总体的值,若为零且括号个数为偶数,才有可能匹配,但也可能不匹配,如:"())("。这样先验算一遍就可以省去不必要的步骤。
class Solution {
map<char,int> mp={
{'(',1},{'[',2},{'{',3},{')',-1},{']',-2},{'}',-3},{'#',0}
};//和为0则括号匹配
stack<char> st;
public:
bool isValid(string s) {
int sum=0;
for(int i=0;i<s.size();i++){
sum+=mp[s[i]];
}
if(sum!=0||s.size()%2!=0) return false;//若括号个数为单或者总和不为零则说明括号一定不匹配
st.push('#');//栈顶为#时表示栈空
for(int i=0;i<s.size();i++){
if(mp[s[i]]>0){//左括号直接入栈
st.push(s[i]);
}
else{
if(mp[s[i]]+mp[st.top()]==0){//右括号且与栈顶匹配则出栈
st.pop();
}
else return false;//右括号且与栈顶不匹配则出错
}
}
return true;
}
};
思路二:按照思路一,如果没有完全考虑到会发生错误的可能性,那么错误的括号匹配也会返回true。我们还可以利用正确匹配的括号序列最后栈一定为空这一特性来找思路。对于左括号无脑入栈;对于右括号,若匹配则出栈,若不匹配则将右括号入栈。若括号序列正确,最后栈为空,反之非空。
// C++
class Solution {
stack<char> st;
public:
bool isValid(string s) {
for(int i=0;i<s.size();i++){
if(st.empty()){
st.push(s[i]);
}
else{
if(s[i]=='('||s[i]=='['||s[i]=='{'){
st.push(s[i]);
}
else{
if(s[i]-1==st.top()||s[i]-2==st.top()){//左右括号的ASCII码相差1或2
st.pop();
}
else{
st.push(s[i]);
}
}
}
}
return st.empty();
}
};
# python
class Solution:
def isValid(self, s: str) -> bool:
if len(s)%2: return False # 单数序列比不可能为有效序列
st = []
mp = {
'(' : 1,
'[' : 2,
'{' : 3,
')' : -1,
']' : -2,
'}' : -3
}
for ch in s:
if ch == '}' or ch == ']' or ch == ')':
if not st: return False # 首字符为右括号,直接退出
if(mp[ch] + mp[st[len(st)-1]] == 0):# 若匹配,将栈顶弹出
st.pop()
continue
else: return False# 若不匹配,直接退出
st.append(ch) # 将左括号入栈
return not st
No.3(#21)Merge Two Sorted Lists
思路一:将两个链表合并后再用头插法排序。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==NULL) return l2;
else if(l2==NULL) return l1;
if(l1->val<l2->val) {//把首元素较小的链表作为新链表的表头
return list_merge(l1,l2);
}
else {
return list_merge(l2,l1);
}
}
ListNode* list_merge(ListNode* head1,ListNode* head2){
ListNode*p=head1,*pre,*q;
while(p->next!=NULL){
p=p->next;
}
p->next=head2;
p=head1->next;
head1->next=NULL;
while(p!=NULL){
pre=head1;
while(pre->next!=NULL&&p->val>pre->next->val){
pre=pre->next;
}//第二层的while两个条件的顺序不能倒,否则会发生空指针错误
q=p->next;
p->next=pre->next;
pre->next=p;
p=q;
}
return head1;
}
};
思路二(递归):用递归做题最重要的一点是千万不要用脑子去模拟他的递归过程(如果模型简单的话可以尝试模拟),不然可能自己把自己递归进棺材里然后出都出不来。
按照我的理解,用递归做题可以先写出一个大概的模板,本题伪代码如下:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==NULL) 边界条件;
if(l2==NULL) 边界条件;
if(l1->val<=l2->val){
mergeTwoLists();
}
else{
mergeTwoLists();
}
}
先跳过边界条件,讨论递归操作。我们可以把一次递归的结果封装起来,如下图所示:
满足条件l1->val<=l2->val,我们就可以由l1指向l1->next与l2合并后的链表得到最终答案,那么我们就将问题转换成了合并l1->next与l2这两个链表,这就是第一次递归操作。
同样的,第二次递归如下图所示:
我们将l1->next,l2分别作为新的链头传入下一次递归,第二次递归完成的任务也是将较小链头指向合并后的链表。
由上述思路我们可以写出递归的语句如下:
if(l1->val<=l2->val){
l1->next=mergeTwoLists(l1->next,l2);
return l1;
}
else{
l2->next=mergeTwoLists(l1,l2->next);
return l2;
}
根据第一次递归的图示,我们可以很清晰的发现,由黄色l1和黄色圈里的结点组成的新链表就是我们所要的答案,虽然圈里的结点并不是一条真正意义上的的链表,但我们可以利用封装的思想把他简单化,因此我们只需要找到新链头l1的下一结点(下一次递归的任务),然后return l1就可以了。
重复上述步骤,直至l1,l2其中一者到达NULL。
此时,本次递归的红色l2先指向NULL,说明第二条链已经到尾巴了,而第一条链还有结点,因此返回给绿色l2(上一次递归的链头)的就应该是指向非空的指针,即红色l1。至此,从最深层的递归逐层返回。递归的边界条件如下:
if(l1==NULL) return l2;
if(l2==NULL) return l1;
完整代码如下:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1==NULL) return l2;
if(l2==NULL) return l1;
if(l1->val<=l2->val){
l1->next=mergeTwoLists(l1->next,l2);
return l1;
}
else{
l2->next=mergeTwoLists(l1,l2->next);
return l2;
}
}
};
DP No.4(#53)Maximum Subarray
DP跳过
DP No.5(#70)Climbing Stairs
DP跳过
No.6.0(#94)Binary Tree Inorder Traversal
思路一(递归):略
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root is None:
return []
ans = []
ans.extend(self.inorderTraversal(root.left))
ans.append(root.val)
ans.extend(self.inorderTraversal(root.right))
return ans
思路二(迭代):利用栈来模拟人工读取中序遍历的顺序。针对根节点,我们做如下操作:根节点入栈,重复直至没有左节点,然后可以将此时的根节点读入列表,再去访问根节点的右子树。该子树又可以当作新的根节点来重复上述操作。
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
def add_all_left(node):
while node:
st.append(node)
node = node.left
st = []
ans = []
add_all_left(root)
while st:
cur = st.pop()
ans.append(cur)
add_all_left(cur.right)
思路三(Morris):与迭代思路不同的是,迭代需要利用栈来模拟中序遍历的顺序,而Morris遍历直接找到当前结点的前驱(该前驱没有右孩子),并将前驱的右孩子指向当前结点,以此保存结构的连续性,而不需要使用栈。因此,遍历的思路主要分为两步:第一步是找前驱,第二步是找序列。
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
ans = []
while root:
if root.left: # 继续遍历左子树
pred = root.left # 先找到前驱
while pred.right and pred.right != root:
# 第二条件不加会造成死循环
pred = pred.right
if pred.right: # 第二次遍历到前驱时
# 说明该节点的左子树已经全部遍历完毕,继续遍历右子树
ans.append(root.val)
pred.right = None # 利用完前驱后记得切断链接
root = root.right
else: # 第一次遍历到前驱时,需要修改前驱的右孩子指向当前结点
pred.right = root
root = root.left # 修改完前驱右孩子后,继续往左遍历
else: # 当前结点无左子树,可以直接将结点纳入序列
ans.append(root.val)
root = root.right
return ans
No.6.1(#101)Symmetric Tree
思路一:采用两次层次遍历,其中左子树和右子树的入队时机相反,所得到的两序列相同则是镜像二叉树,不同则不是镜像二叉树。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
vector<int> v1,v2;
v1=layerorder(root);
v2=layerorder_m(root);
if(v1==v2) return true;
else return false;
}
vector<int> layerorder(TreeNode* root){//层次遍历
vector<int>v;
queue<TreeNode*> q;
q.push(root);
TreeNode* top;
while(!q.empty()){
top=q.front();
q.pop();
if(top==NULL) {
v.push_back(101);//val的范围是-100~100,因此用101代替null
continue;//没有continue的话,下面的top会发生nullptr错误
}
else v.push_back(top->val);
if(top->left!=NULL) q.push(top->left);
else q.push(NULL);//若结点为空,入队NULL,对应序列中的null
if(top->right!=NULL) q.push(top->right);
else q.push(NULL);
}
return v;
}
vector<int> layerorder_m(TreeNode* root){//镜像层次遍历
vector<int>v;
queue<TreeNode*> q;
q.push(root);
TreeNode* top;
while(!q.empty()){
top=q.front();
q.pop();
if(top==NULL) {
v.push_back(101);
continue;
}
else v.push_back(top->val);
if(top->right!=NULL) q.push(top->right);
else q.push(NULL);
if(top->left!=NULL) q.push(top->left);
else q.push(NULL);
}
return v;
}
};
思路二(思路一升级版):在思路一的基础上,不必再找到两个序列后作比较,可以边出队边比较。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
queue<TreeNode*> q1,q2;
q1.push(root);
q2.push(root);
while(!q1.empty()&&!q2.empty()){
TreeNode* top1=q1.front();
TreeNode* top2=q2.front();
q1.pop();
q2.pop();
if(top1==NULL&&top1==NULL){
//两者都为空正确
continue;
}
else if(top1==NULL||top2==NULL) return false;
//有且仅有一者为空,错误
else if(top1->val==top2->val){
//值相等,正确,并将其孩子入队
//不管是否为空都要入队,省去判断条件
q1.push(top1->left);
q1.push(top1->right);
q2.push(top2->right);
q2.push(top2->left);
}
else return false;//仅有上述两种情况是正确的
}
return true;//能通过while即为对称二叉树
}
};
思路三(递归):若该树是对称二叉树,则必须满足以下两个条件:①结点p相同;②结点p的左孩子的左、右孩子分别与结点p的右孩子的右、左孩子相同。我们可以令指针p、q从根结点开始,分别向左右孩子遍历来判断。写成代码为:
if(p->val==q->val&&check(p->left,q->right)&&
check(p->right,q->left))
return true;
按照这个判断条件,可以写出如下递归模板:
bool check(TreeNode* p,TreeNode* q){
if(边界条件) 返回;
if(满足对称条件){
return true;
}
return false;
}
显然,当指针指向空结点时,无法再往下遍历,所以边界条件为指针指向空,分为两种情况:①两者均为空,满足对称要求,返回true;②两者有且仅有一者为空,不满足对称要求,返回false。写成代码为:
if(p== NULL&&q==NULL) return true;
if(p== NULL||q==NULL) return false;
将代码进一步完整:
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return check(root,root);
}
bool check(TreeNode* p,TreeNode* q){
if(p==NULL&&q==NULL) return true;
if(p==NULL||q==NULL) return false;
return (p->val==q->val)&&check(p->left,q->right)&&check(p->right,q->left);
//同时满足三个条件则返回true
}
};
No.7(#104)Maximum Depth of Binary Tree
思路一(递归):树的最大深度为根结点的左子树和右子树的最大深度+1,调用maxDepth即可求左子树和右子树的最大深度。
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL) return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
思路二:根据广度遍历,每遍历完一层的结点,深度加一。我们可以对原本的广度遍历稍加修改,原先的BFS出队就立即访问该结点,现在为了确保同一层结点都访问过后才增加深度,我们可以将本次出队的元素先存放在一个数组里,然后再统一对数组里的元素进行访问。
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL) return 0;
queue<TreeNode*>q;
int count=0;
q.push(root);
while(!q.empty()){
vector<TreeNode*> temp;
while(!q.empty()){//将本层元素都存入temp中
temp.push_back(q.front());
q.pop();
}
count++;//计算深度
for(int i=0;i<temp.size();i++){//依次访问同一层的元素
if(temp[i]->left!=NULL) q.push(temp[i]->left);
if(temp[i]->right!=NULL) q.push(temp[i]->right);
}
}
return count;
}
};
DP No.8(#121)Best Time to Buy and Sell Stock
DP跳过
No.9(#136)Single Number
思路一:先将数组排序,若有两个相同数字则二者连续,即nums[i]==nums[i+1]。因为只有一个单数,所以数组长为奇数,即最后一个数字,不能按照nums[i]==nums[i+1]判断,需单独判断。
class Solution {
public:
int singleNumber(vector<int>& nums) {
if(nums.size()==1) return nums[0];//单元素则直接返回
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size()-1;i++){
if(nums[i]==nums[i+1]){
i++;//若有双元素,则i+2
}
else return nums[i];
}
return nums[nums.size()-1];//如果前n-1个元素都不能找到目标元素,则其一定是末尾
}
};
思路二:利用哈希表记录元素是否重复,如果第一次访问该元素,则哈希表记录为false,如果第二次访问该元素,则哈希表记录改为true。再次扫描数组,返回记录为false的元素即可。
class Solution {
public:
int singleNumber(vector<int>& nums) {
map<int,bool> mp={};
for(int i=0;i<nums.size();i++){
auto it=mp.find(nums[i]);
if(it!=mp.end()){
mp[nums[i]]=true;
}
else{
mp[nums[i]]=false;
}
}
for(int i=0;i<nums.size();i++){
if(!mp[nums[i]]) return nums[i];
}
return 0;
}
};
思路三:我们观察到数组元素的特点:有若干对重复元素和一个不重复元素,可以联想到异或运算同零异一的特性。利用异或运算同零异一的规则:①a异或0=a(若a=0,结果为0;若a≠0,则a的每一位二进制位都与0异或,根据同零异一规则,结果为a);②a异或a=0;③异或满足交换律和结合律。由此可知,任意两个相同元素异或后为零,而零与任意元素异或为元素本身。若把数组元素逐个异或,最后得到的结果就是不重复的元素。
class Solution {
public:
int singleNumber(vector<int>& nums) {
for(int i=1;i<nums.size();i++){
nums[0]^=nums[i];
//对所有元素执行异或运算,结果保存在nums[0]中
}
return nums[0];
}
};
No.10(#141) Linked List Cycle
思路一(快慢指针):利用快慢指针的原理,slow指针每次后移一个结点,fast指针每次后移两个结点。若链表无环,则fast先到达末尾,则退出循环返回false;若链表有环,则两指针早晚都会进入环内,并在某一时刻两指针相遇。
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL) return false;
ListNode*slow=head,*fast=head->next;
while(fast!=NULL&&fast->next!=NULL){//先判断fast!=NULL可以保证fast->next不发生空指针错误
if(slow==fast) return true;
slow=slow->next;
fast=fast->next->next;//fast->next已经由循环条件保证不为空指针
}
return false;
}
};
思路二(哈希表):可以把节点元素被访问次数记录在哈希表中,可以用map、vector、set来记录,因为记录的节点的地址,所以元素值相同的话也不会错误计数。
class Solution {//map
public:
bool hasCycle(ListNode *head) {
if(head==NULL) return false;//空链
map<ListNode*,int>mp={};
while(head!=NULL){
if(mp.find(head)==mp.end()){
mp[head]=1;
}
else{
mp[head]++;
}
if(mp[head]>1) return true;
head=head->next;
}
return false;
}
};
class Solution {//set和vector
public:
bool hasCycle(ListNode *head) {
if(head==NULL) return false;//空链
set<ListNode*> s;
while(head!=NULL){
//若用vector,if条件则为if(count(s.begin(),s.end(),head))
if(s.count(head)) return true;
//插入前先查看集合里是否已有该元素,若无则将其加入,若有则存在环
s.insert(head);
head=head->next;
}
return false;
}
};
SKIP No.11(#155)Min Stack
没看懂
No.12(#160)Intersection of Two Linked Lists
思路一(双指针):题意为找到两链表的第一个公共结点,并返回指针。如果两链表有公共结点且两链表长度不相等,那么在公共结点之前,两链表的结点数量一定不相等。因此我们先把较长链表的多余部分先遍历掉(因为这些结点一定不是公共结点),此后两指针就可以同步往后遍历,直至找到第一个公共结点(返回指针)或未找到公共结点(返回null)。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int length_a,length_b;
length_a=find_length(headA);
length_b=find_length(headB);
ListNode* p1,*p2;
if(length_a>length_b){//令p1始终指向较长的链表
p1=headA;
p2=headB;
}
else{
p1=headB;
p2=headA;
}
for(int i=abs(length_a-length_b);i>0;i--){//遍历长链表多余的结点
p1=p1->next;
}
while(p1!=NULL){//找到公共结点
if(p1==p2) return p1;
else{
p1=p1->next;
p2=p2->next;
}
}
return NULL;
}
int find_length(ListNode* head){//计算表长
int count=0;
while(head!=NULL){
count++;
head=head->next;
}
return count;
}
};
思路二(哈希表):可以先扫描一遍链A,用哈希表保存节点是否被访问过的记录,再扫描一遍链B,查看链上的节点是否已被访问过,访问过的节点就是两条链的公共结点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
map<ListNode*,int> mp={};
while(headA!=NULL){
mp[headA]=1;
headA=headA->next;
}
while(headB!=NULL){
if(mp[headB]==1) return headB;
headB=headB->next;
}
return NULL;
}
};
思路三:在思路二上稍加改进,能否在依次扫描中,分别移动指针就能达到目的呢?假设两条链不等长且存在公共结点,那么两个指针同步从链头往后移动,那么就一定有一个指针会先扫描到公共结点,那么第二个指针就会在稍后再次扫描到这个节点。我们可以利用这个特点,结合哈希表,保存并查找节点的访问情况。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode*> s;
while(headA!=NULL||headB!=NULL){
if(s.count(headA)&&headA!=NULL) return headA;
//若节点已记录在集合内,且不是空节点的话,返回指针
s.insert(headA);//将未记录的节点放进集合里
if(s.count(headB)&&headB!=NULL) return headB;
//若不判断head是否为空,那么一个空结点就会被存入集合,
//下次扫描集合时,就会返回一个指向空的指针
s.insert(headB);
if(headA!=NULL) headA=headA->next;
//指针走到末尾后不再移动
if(headB!=NULL) headB=headB->next;
}
return NULL;
}
};
No.13(#169)Majority Element
思路一:先将数组排序,若为多数元素,则第i个元素和第i+n/2个元素一定相等,且满足此条件的元素有且仅有一个,并能在前n/2个元素中找到。
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(),nums.end());
for(int i=0;i<=nums.size()/2;i++){
if(nums[i]==nums[i+nums.size()/2]) return nums[i];
}
return 0;
}
};
思路二:在有序数组里,若一个元素存在n/2个重复元素,则第n/2个元素一定是该元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(),nums.end());
return nums[nums.size()/2];
}
};
思路三(哈希表):先扫描一遍数组记录每个元素之前的次数,在扫描一遍哈希表,选出多数元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
map<int,int>mp={};
for(int i=0;i<nums.size();i++){
if(mp.find(nums[i])!=mp.end()){
mp[nums[i]]++;
}
else mp[nums[i]]=1;
}
int max_count=mp[nums[0]],max_val=nums[0];
for(auto it1=mp.begin();it1!=mp.end();it1++){
if(max_count<it1->second){
max_count=it1->second;
max_val=it1->first;
}
}
return max_val;
}
};
思路四(分治):已知元素数量大于等于n/2的数称为众数,从数组分成两个部分,那么众数一定在左边或者右边。我们可以先找到左边的众数,记为left_maj,找到右边的众数,记为right_maj,那么这个数组的众数不是left_maj就是right_maj,之后我们只要比较这两者谁是整个数组的众数即可。
我们可以先写出如下雏形:
int get_majority( ){
if(边界条件) 返回;
//显然,只有一个元素时,函数无法继续递归,
//因此递归的边界条件为left==right
int left_maj=get_majority( );
//get_majority需要传递数组和数组的边界
int right_maj=get_majority( );
if(left_maj在左部分和右部分的集合中占一半多){
//我们可以通过调用记数的函数来判断该元素是否为众数
return left_maj;
}
if(rigth_maj在左部分和右部分的集合中占一半多){
return right_maj;
}
return 0;
}
根据解析进一步完善代码后:
class Solution {
public:
int majorityElement(vector<int>& nums) {
return get_majority(nums,0,nums.size()-1);
}
int get_majority(vector<int>& nums,int left,int right){
if(right==left) return nums[left];
int mid=(left+right)/2;
int left_maj=get_majority(nums,left,mid);
int right_maj=get_majority(nums,mid+1,right);
if(count_maj(nums,left_maj,left,right)>(right-left)/2){
return left_maj;
}
if(count_maj(nums,right_maj,left,right)>(right-left)/2){
return right_maj;
}
return 0;
}
int count_maj(vector<int>& nums,int maj,int left,int right){
int count=0;
for(int i=left;i<=right;i++){
//传递的边界为闭区间
if(nums[i]==maj) count++;
}
return count;
}
};
No.14(#206)Reverse Linked List
思路一:看代码注释部分。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==NULL) return head;
ListNode*pre=head,*p=head->next;
pre->next=NULL;//把链表分成两个部分,再逐个结点添加
while(p!=NULL){
head=p->next;//head指向链2的第一个结点
p->next=pre;//指向前一个结点
pre=p;
p=head;
}
return pre;
}
};
思路二(递归):我们可以把链表看成两个部分,一个部分是未反转的节点,一部分是已经反转完成的节点,假设程序已经递归返回到第一层,即只有头结点未反转,其余节点均已反转,如图①所示:
那么接下来我们要完成的任务有两个:把节点2下一节点指向节点1,并把节点1的下一节点指向空,这样就完成了链表的反转。在这一层的递归中,指向节点1的指针是head,指向节点2的指针是head->next,所以要完成的操作有:①head->next->next=head;②head->next=NULL(区分先后)。为了验证这组操作是否正确,再看一下递归返回到第二层的情况,如图②所示:
在这层递归中,链表节点有2、3、4,其中节点2未反转,3、4节点都已反转完成,头结点为节点2,即指针head指向head。执行上述两步操作后,节点2、3、4均反转完成,即本次递归的链表已反转。我们可以返现,每次递归需要反转的只有头结点和头结点的下一结点这两个节点的方向。
解决了递归里的操作问题,接下来就要解决返回值的问题。
假设到了递归的最后第一层,如图④所示,可见链表只有一个节点或者为空链时,不需要反转,因此直接返回该结点即可。来到递归最后第二层,如图③所示,我们反转了节点3和节点4的方向后就完成了整个链的反转(在本层递归中)。我们可以用一个指针p指向反转链的链头,并在执行完反转操作后返回它。
由此,可以写出代码:
ListNode* reverseList(ListNode* head) {
if(head->next==NULL||head==NULL) return head;
//若只有一个节点,无需反转,直接返回即为链头
ListNode* p=reverseList(head->next);
//p用来记录反转后的链头,并且p一直指向反转后的链头不移动
head->next->next=head;
head->next=NULL;
return p;
//返回的是指针P,记录反转后的链头
}
No.15(#226)Invert Binary Tree
思路一(递归):假设在当前递归层中,root指向根结点,用invertTree(root->left)和invertTree(root->right)分别表示已经反转好的左子树和右子树,那么root的左子树就应该为invertTree(root->right),右子树就应该为invertTree(root->left),写成代码形式就是:
root->left=invertTree(root->right);
root->right=invertTree(root->left);
但如果直接赋值的话,root的left和right指针可能会被下一层递归里的指针覆盖,从而发生use after free错误。因此应该写成如下形式:
TreeNode* left=invertTree(root->right);
TreeNode* right=invertTree(root->left);
root->left=right;
root->right=left;
代码如下:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==NULL) return root;
TreeNode* left=invertTree(root->left);
TreeNode* right=invertTree(root->right);
//left和right指针用以暂存反转树的根结点
root->left=right;
root->right=left;
return root;
}
};
思路二(BFS):对于图中的树,如果我们把树的每层元素按序写出来,有如下序列:
反转前:4/27/1369;反转后:4/72/9631,若把每层的节点序列倒序排列,即可得到反转后的序列。因此我们可以在层次遍历一棵二叉树时,用数组记录下每层序列,得到一个反转序列之后,用这个序列建一棵树。
思路三(递归):思路一的递归是从下往上返回已反转的子树,那么思路三就是从上往下不断交换左右子树的递归。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==NULL) return NULL;
//完成该节点的左右子树交换
TreeNode* temp=root->left;
root->left=root->right;
root->right=temp;
//完成本节点的交换后,继续分别向左右子树继续遍历
invertTree(root->left);
invertTree(root->right);
return root;
}
};
No.16(#234)Palindrome Linked List
思路一:将前n/2个元素记录到数组中,再继续遍历链表,并倒序遍历数组,比较两个值是否相同,都相同则为回文串。
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head->next==NULL) return true;
vector<int> v;
int length=fun(head);
for(int i=0;i<length/2;i++) {
v.push_back(head->val);
head=head->next;
}
if(length%2!=0) head=head->next;//如果链长为单数,则中位数需跳过
int j=v.size()-1;
while(head!=NULL){
if(head->val!=v[j--]) return false;
head=head->next;
}
return true;
}
int fun(ListNode* head){//得到链表长度
int count=0;
while(head!=NULL){
head=head->next;
count++;
}
return count;
}
};
思路二:将链表后半部分倒序,并逐个与前半部分比较
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head->next==NULL) return true;
ListNode* slow=head,*fast=head;
while(fast!=NULL&&fast->next!=NULL){
slow=slow->next;
fast=fast->next->next;
}
ListNode* head2=invertList(slow);
while(head!=NULL&&head2!=NULL){
if(head->val!=head2->val) return false;
head=head->next;
head2=head2->next;
}
return true;
}
ListNode* invertList(ListNode* head){
if(head==NULL||head->next==NULL) return head;
ListNode* p=invertList(head->next);
head->next->next=head;
head->next=NULL;
return p;
}
};`在这里插入代码片`
思路三(快慢指针):边移动指针边反转前半部分。
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head->next==NULL) return true;
ListNode*slow=head,*fast=head;
ListNode*p=head,*pre=NULL;
while(fast!=NULL&&fast->next!=NULL){
//边移动指针边反转前半段链
p=slow;
slow=slow->next;
fast=fast->next->next;
p->next=pre;
pre=p;
}
if(fast!=NULL) slow=slow->next;
//若链长为单数,则fast->next==null,则slow需后移一位才与p对称;
//若链长为偶,则fast==null;
ListNode* headL=p,*headR=slow;
bool flag=true;
//为了复原链表,用flag记录是否为回文链
while(headL!=NULL&&headR!=NULL){
if(headL->val!=headR->val){
flag=false;
break;
}
headL=headL->next;
headR=headR->next;
}
while(p!=NULL){//复原前半段链
ListNode* q=p->next;
p->next=slow;
slow=p;
p=q;
}
return flag;
}
};
No.17(#283)Move Zeroes
思路一:遍历数组,遇到第一个非零的元素,将其覆盖到数组第一个位置,遇到第二个非零的元素,将其覆盖到数组第二个位置,重复上述操作直至数组遍历完毕。假设覆盖了k次,那么数组k~n-1的元素均置零。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int k=0;
for(int i=0;i<nums.size();i++){
if(nums[i]!=0){
nums[k++]=nums[i];
}
}
for(int i=k;i<nums.size();i++){
nums[i]=0;
}
return;
}
};
思路二(双指针):设置两个指针,指针i始终指向零元素,并指向已排好序的序列末尾后一位,初始位置为0,另一个指针j从位置1开始扫描到数组末尾,将非零的元素与前边i指向的元素交换。nums[i]和nums[j]的值一共有四种情况:
①nums[i]非零无需改变位置,nums[j]为零无需提前,因此i,j均后移一位;
②nums[i]为零,位置正确无需后移,nums[j]非零,位置正确,此时应交换i,j位置的元素;
③nums[i],nums[j]均为零,i位置正确,j不正确,因此j后移;
④nums[i],nums[j]均非零,虽然j已经指向非零元素,但该元素是正确排序的,所以i和j仍然要后移。初始态时,i和j是相邻的,若nums[i]非零而nums[j]为零,i和j会同时后移,因此不会发生交换的元素导致乱序的情况发生。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
if(nums.size()==1) return;
for(int i=0,j=1;j<nums.size();){
if(nums[j]!=0&&nums[i]==0){
swap(nums[i],nums[j]);
i++;
j++;
}
else if(nums[i]==0&&nums[j]==0){
j++;
}
else{
i++;
j++;
}
}
return;
}
};
思路三(双指针):指针i指向数组的第一个零元素,其左边为已排好序的元素;指针j指向非零元素,其右边为未排序的元素,i和j中间均为0元素。
class Solution {
public:
void moveZeroes(vector<int>& nums) {
if(nums.size()==1) return;
if(nums.size()==2){
//不懂为啥下面的while循环判断两个元素的数组会报错
if(nums[0]==0) swap(nums[0],nums[1]);
return;
}
int i=0,j=1;
while(i<nums.size()&&j<nums.size()){
while(nums[i]!=0&&i<nums.size()) i++;
//令i始终指向第一个零元素
while(nums[j]==0&&j<nums.size()) j++;
//令j始终指向非零元素
if(nums[i]==0&&nums[j]!=0&&i<nums.size()&&j<nums.size()){
swap(nums[i],nums[j]);
}
}
return;
}
};
No.18(#448)Find All Numbers Disappeared in an Arrary
思路一(空间O(n)):用长度为n的bool数组记录数字是否已出现,bool数组下标代表nums中元素。
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
bool flag[100000]={false};
vector<int> ans;
int max=nums[0];
for(int i=0;i<nums.size();i++){
flag[nums[i]]=true;
if(max<nums[i]) max=nums[i];
}
for(int i=1;i<=max||i<=nums.size();i++){
if(flag[i]==false)
ans.push_back(i);
}
return ans;
}
};
思路二:用数组v保存1~n的元素值,再把nums中出现的元素从v中删去,最终v剩下的元素就是未出现的元素
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
if(nums.size()==1) return nums;
vector<int> v={0},ans;
for(int i=1;i<=nums.size();i++) v.push_back(i);
for(int i=0;i<nums.size();i++){
v[nums[i]]=0;
}
for(int i=0;i<v.size();i++){
if(v[i]!=0) ans.push_back(v[i]);
}
return ans;
}
};
思路三:先将数组排序,两个元素之间的差值若大于1,则缺失的元素一定在这两元素之间。
class Solution {//该算法时空复杂度均不满足
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
if(nums.size()==1) return nums;
sort(nums.begin(),nums.end());
vector<int> ans;
for(int i=0,j=1;j<nums.size();i++,j++){
if(nums[j]-nums[i]>1){
int t=nums[j]-1;
while(t>nums[i]){
ans.push_back(t);
t++;
}
}
}
if(nums[nums.size()-1]<nums.size()){
int t=nums[nums.size()-1]+1;
while(t<=nums.size()){
ans.push_back(t);
t++;
}
}
return ans;
}
};
思路四:将原数组作为哈希表,对于元素xi,可以将其对应位置xi-1(数组下标从0开始)的元素置反(-nums[xi-1]),以此来表示该元素存在,最后扫描一遍数组,若元素大于0,则其下标对应元素不存在。
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> ans;
for(int i=0;i<nums.size();i++){
int pos=abs(nums[i])-1;//数组下标从0开始
nums[pos]=-abs(nums[pos]);//将对应元素置反
}
for(int i=0;i<nums.size();i++){
if(nums[i]>0){
ans.push_back(i+1);//元素和对应下标差1
}
}
return ans;
}
};
No.19(#461) Hamming Distance
思路一:先将x与y转化为二进制,再将位数对齐,最后逐位比较。
时间复杂度为O(n),空间复杂度为O(n)。
class Solution {
public:
int hammingDistance(int x, int y) {
vector<int> x_num,y_num;
while(x!=0){//转为二进制
x_num.push_back(x%2);
x/=2;
}
while(y!=0){
y_num.push_back(y%2);
y/=2;
}
//int x_size=x_num.size(),y_size=y_num.size();
if(x_num.size()>y_num.size()){//补零
for(int i=0;i<x_num.size()-y_num.size();i++){
//此处以及下面的for条件出错,x_num.size()和y_num.size()均是
//动态变化的,应提前记录值
//for(int i=0;i<x_size-y_size;i++)
y_num.push_back(0);
}
}
else{
for(int i=0;i<y_num.size()-x_num.size();i++){//出错
//for(int i=0;i<y_size-x_size;i++)
x_num.push_back(0);
}
}
int count=0;//计算不同位的个数
for(int i=0;i<x_num.size();i++){
if(x_num[i]!=y_num[i]){
count++;
}
}
return count;
}
};
//无需补零对齐,多余的位为一必不同
int hammingDistance(int x, int y) {
vector<int> n1=get_binary_list(x);
vector<int> n2=get_binary_list(y);
int count=0,i,j;
for(i=0,j=0;i<n1.size()&&j<n2.size();i++,j++){
if(n1[i]!=n2[j]) count++;
}
if(n1.size()>=n2.size()) fun(i,count,n1);
else fun(j,count,n2);
return count;
}
vector<int> get_binary_list(int n){
vector<int> v;
while(n){
v.push_back(n%2);
n/=2;
}
return v;
}
void fun(int index,int &count,vector<int> n){
for(;index<n.size();index++){
if(n[index]==1) count++;
}
}
思路二:对x和y异或运算,得到数z,z中1的位数即x与y不同的位数
class Solution {
public:
int hammingDistance(int x, int y) {
int temp=x^y,dis=0;
while(temp){
dis+=(temp%2);
temp/=2;
}
return dis;
}
};
No.20(#543)Diameter of Binary Tree
思路一(DFS):一个树的最大直径等于节点Ni的左子树最大层数与右子树最大层数之和,也等于最大直径序列的节点数-1。那么按照整个思路,只要我们能求出当前节点cur_node左子树的最大层数cur_left和右子树最大层数cur_right,当前最大直径cur_len=cur_left+cur_right,并且与全局变量max_len比较并记录最大值。
class Solution {
int max_len=0;//max_len作为全局变量
public:
int diameterOfBinaryTree(TreeNode* root) {
DFS(root);
return max_len;
}
int DFS(TreeNode* root){
if(root==NULL) return 0;
int cur_left=DFS(root->left)+1,cur_right=DFS(root->right)+1;
//DFS记录的是当前root的子树的最大层数
//DFS+1则表示包括当前节点的最大层数
int cur_len=cur_left+cur_right-2;
//cur_len表示当前子树的最大半径
if(cur_len>max_len) max_len=cur_len;
return max(cur_left,cur_right);
//返回的是当前root的最大子树层数(不包括root本身)
}
};
No.21(#617)Merge Two Binary Tree
思路一(递归):新建一棵树root,root的左子树为已合并的树mergeTrees(root1->left,root2->left),root的右子树为已合并的树mergeTrees(root1->right,root2->right),节点的值有四种情况:
①仅root1为空,可以新建节点,其值为root2->val也可直接用root2代替;
②仅root2为空,节点用root1代替;
③均不为空,新建节点值为root1->val+root2->val;
④均为空,无节点生成。
/**
* 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* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1==NULL) return root2;//case 1 and 4
if(root2==NULL) return root1;//case 2 and 4
TreeNode* root=new TreeNode(root1->val+root2->val);// case 3
root->left=mergeTrees(root1->left,root2->left);
root->right=mergeTrees(root1->right,root2->right);
return root;
}
};
思路二(广度):我们可以设定三个队列q,q1,q2分别对应三棵树root,root1,root2。遍历root1和root2时,有三种情况:
① root1和root2的左子树均不为空,此时应新建一个节点作为root的左子树。而且root1和root2可能还有其他子树,因此root1->left,root2->right,root->right都应该入队;
② root1->left和root2->left均为空,此时root->left也为空;
③ root1和root2的左子树只有一者为空。设root1->left为空,root2->left不为空,此时root->left=root2->left,可以直接将整个子树移植过来,不需要入队操作。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1==NULL) return root2;
if(root2==NULL) return root1;
TreeNode* root=new TreeNode(root1->val+root2->val);
queue<TreeNode*> q,q1,q2;
q.push(root);
q1.push(root1);
q2.push(root2);
while(!q1.empty()||!q2.empty()){
auto node=q.front(),node1=q1.front(),node2=q2.front();
q.pop();
q1.pop();
q2.pop();
auto left1=node1->left,left2=node2->left,
right1=node1->right,right2=node2->right;
if(left1!=NULL||left2!=NULL){
if(left1!=NULL&&left2!=NULL){
auto left=new TreeNode(left1->val+left2->val);
node->left=left;
q.push(left);
q1.push(left1);
q2.push(left2);
}
else if(left1!=NULL){
node->left=left1;
}
//若仅有一者为空,则可以把整棵子树移过去
else if(left2!=NULL){
node->left=left2;
}
}
if(right1!=NULL||right2!=NULL){
if(right1!=NULL&&right2!=NULL){
auto right=new TreeNode(right1->val+right2->val);
node->right=right;
q.push(right);
q1.push(right1);
q2.push(right2);
}
else if(right1!=NULL){
node->right=right1;
}
else if(right2!=NULL){
node->right=right2;
}
}
}
return root;
}
};
``