算法题c++刷题记录之数据结构部分

数据结构

  • 链表
  • 栈和队列
  • 二叉树
  • 哈希表
  • 字符串
  • 数组与矩阵

1.两个栈实现一个队列
题目描述:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
分析:栈是先进后出,队列是先进先出
因此 push的话直接在stack1读入就好了
pop的话,需要从stack1压入stack2,再由stack2弹出
注意:若stack2是空的,需要从stack1向其压入数据
若stack2不空,则须stack2先弹出,空了之后,stack1压入
否则会影响元素弹出的位置

1# 运行时间:25ms
# 占用内存:5724k
# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stack1 = []
        self.stack2 = []
        
    def push(self, node):
        # write code here
        self.stack1.append(node)
        
    def pop(self):
        # return xx
        if len(self.stack1) == 0 and len(self.stack2) == 0:
            return 
        if len(self.stack2) == 0:
            while len(self.stack1) != 0:
                self.stack2.append(self.stack1.pop())
        return self.stack2.pop()``
 
  法2 
#运行时间:26ms 占用内存:5736k
class Solution:
    def __init__(self):
        self.stack1=[]
        self.stack2=[]
    def push(self, node):
        # write code here
        self.stack1.append(node)
    def pop(self):
        # return xx
        if self.stack2:
            return self.stack2.pop()
        else:
            while(self.stack1):
                self.stack2.append(self.stack1.pop())
            return self.stack2.pop()
    

2.旋转数组的最小值
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
借鉴大佬思路:二分法的变形,注意,若出现110111这样的,mid=high,只能high=high-1,挨个查找

#运行时间:665ms 占用内存:5852k
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        low,high=0,len(rotateArray)-1
        while low<high:
            mid=(low+high)//2
            if rotateArray[high]<rotateArray[mid]:
                low=mid+1
            elif rotateArray[high]>rotateArray[mid]:
                high=mid
            else:
                high=high-1
        return  rotateArray[low]

c++版本

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if (rotateArray.empty()) return 0;
        int left = 0, right = rotateArray.size() - 1;
        while (left < right) {
            //确认子数组是否是类似1,1,2,4,5,..,7的非递减数组
            if (rotateArray[left] < rotateArray[right]) return rotateArray[left];
             
            int mid = left + (right - left) / 2;
            //如果左半数组为有序数组
            if (rotateArray[left] < rotateArray[mid])
                left = mid + 1;
            //如果右半数组为有序数组
            else if (rotateArray[mid] < rotateArray[right])
                right = mid;
            //否则,rotateArray[left] == rotateArray[mid] == rotateArray[right]
            else {
                ++left;
            }
        }
        return rotateArray[left];
    }
};

3.二维数组中的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:矩阵有序,所以从左下角进行查找,若>目标值,向上一行,<目标值,向右一行,时间复杂度O(M+N)
注:python二维数组行列长度获取 输出行列array.shape
行:array.shape[0],列:array.shape[1]

class Solution:
    #array 二维列表
    def Find(self, target, array):
        row=len(array)
        col=len(array[0])
        i,j=row-1,0
        while i>=0 and j<col:
            if array[i][j]==target:
                return True
            elif array[i][j]>target:
                i=i-1
            else:
                j=j+1
        return False
        
    
        

4.数组中的重复数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
大佬的思路:将数组保存为哈希表,然后再遍历依次即可。哈希表的查找时间是O(1),但需要额外的存储空间。不知道为什么测试没有通过

class Solution:
    # 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
    # 函数返回True/False
    def duplicate(self, numbers, duplication):
        if numbers == None:
            return None
        count.dict={}
        for num im numbers:
            count_dict[num]=count_dict.get(num,0)+1
        for num in numbers:
            if count_dict[num]>1:
                duplication[0]=num
                return True
        return False

大佬做法2:直接利用sort()函数进行排序,不知道算不算作弊操作

class Solution:
    #这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
    # 函数返回True/False
    def duplicate(self, numbers, duplication):
        numbers.sort()  #sort对数组进行排序
        for i in range(len(numbers)-1):
            if numbers[i] == numbers[i+1]:
                duplication[0]=numbers[i]
                return True
        return False

5.数在排序数组中出现的速度
思路1:遍历

class Solution:
    def GetNumberOfK(self, data, k):
        count=0
        for num in data:
            if num==k:
                count +=1
        return count 

思路2:利用二分法,找到k的最左边和最右边对应的位置,相减
代码待补充

6.替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
大佬思路:先遍得到空格数count
再从后往前,遇到非空格往后挪count2个位置
遇到空格,该位置后移2
(count-1)个位置,其它依次

class Solution {
public:
    void replaceSpace(char *str,int length) {
        int count=0;
        for(int i=0;i<length;i++){
            if(str[i]==' ')
                count++;
        }
        for(int i=length-1;i>=0;i--){
            if(str[i]!=' '){
                str[i+2*count]=str[i];
            }
            else{
                count--;
                str[i+2*count]='%';
                str[i+2*count+1]='2';
                str[i+2*count+2]='0';
            }
        }
    }
};

7.二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
补码的表示方法是:正数的补码就是其本身,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

大佬思路:
一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。(负数用补码表示,所以是一样的道理)

class Solution {
public:
     int  NumberOf1(int n) {
         int count=0;
         if(n==0){return 0;}
         while(n!=0){
             count++;
             n=n & n-1;
                 
         }
          return count;   
         
     }
};

8.写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号
两个数异或:相当于每一位相加,而不考虑进位;
两个数相与,并左移一位:相当于求得进位;
将上述两步的结果相加

13+11 = ?;
13 的二进制 1 1 0 1 -----a 13
11 的二进制 1 0 1 1 -----b 11

(a&b) <<1 -> 1 0 0 1 0 -----d 18
a^b -> 0 1 1 0 -----e 6

(d&e) <<1 -> 0 0 1 0 0 ------f 4
d^e -> 1 0 1 0 0 -----g 20

(f&g) <<1 -> 0 1 0 0 0 ------h 8
f^g -> 1 0 0 0 0 ------i 16

(h&i) <<1 -> 0 0 0 0 0 ------h 0 ---- --------退出循环
h^i -> 1 1 0 0 0 ------i 24

#递归
#4ms
class Solution {
public:
    int Add(int num1, int num2)
    {
        if(num2==0)
            return num1;
        return Add(num1^num2, (num1&num2)<<1);
    }
};
2:迭代
//3ms
class Solution {
public:
    int Add(int num1, int num2)
    {
        while(num2!=0){
            int sum1=num1^num2;
            int sum2=(num1&num2)<<1;
            num1=sum1;
            num2=sum2;
                
        }
        return num1;
    }

9.设计一个函数, 可以将任意十进制的数, 转换成任意2到9的进制表示的形式
需要转换的数字x(0<=x<=1e18) 转换使用的进制k(2<=k<=9)

#include 
using namespace std;
 
int main(){
    long x;
    int k;
    cin>>x>>k;
    stack<int> s;
    if(x==0)
        cout<<0<<endl;
    else{
        while(x){
            s.push(x%k);
            x /= k;
        }
        while(!s.empty()){
            cout<<s.top();
            s.pop();
        }
        cout<<endl;
    }
    return 0;
}

10 跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
思路:第n阶可以从n-2阶跳,也可以用n-1阶跳。
所以:f(n)=f(n-1)+f(n-1) 斐波那契数列

#注意初始条件
#迭代
class Solution {
public:
    int jumpFloor(int number) {
        
        if(number==0){return 0;}
        if(number==1){return 1;}
        if(number==2){return 2;}
        int prenum=2;
        int preprenum=1;
        int result=0;
        for(int i=3;i<=number;i++){
            result=prenum+preprenum;
            preprenum=prenum;
            prenum=result;
        }
        return result;    
        
    }

10.变态版跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析:大佬的思路:
每个台阶可以看作一块木板,让青蛙跳上去,n个台阶就有n块木板,最后一块木板是青蛙到达的位子, 必须存在,其他 (n-1) 块木板可以任意选择是否存在,则每个木板有存在和不存在两种选择,(n-1) 块木板 就有 [2^(n-1)] 种跳法,可以直接得到结果。

class Solution {
public:
    int jumpFloorII(int number) {
        return 1<<(number-1);  //用位移运算做2^(n-1),速度更快

    }
};

因为n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
所以f(n)=f(n-1)+f(n-2)+…+f(1)
因为f(n-1)=f(n-2)+f(n-3)+…+f(1)
所以f(n)=2*f(n-1)
迭代

链表

11.寻找链表的倒数第k个节点
思路:定义两个指针,快的先跑k步然后快慢一起跑,快的到空的时候,慢指针就在倒数第k个节点
时间复杂度O(n),空间复杂度O(1)

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == nullptr || k < 1){
            return nullptr;
        }
        ListNode *pre = pListHead;
        ListNode *last = pListHead;
        while(k > 0){
            if(last == nullptr){
                return nullptr;
            }
            last = last->next;
            k--;
        }
        while(last != nullptr){
            last = last->next;
            pre = pre->next;
        }
        return pre;
    }
};

删除倒数第k个节点

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        if(head==nullptr ||  n<1){
            return nullptr;
        }
        ListNode* pre=head;
        ListNode* last=head;
        while(n>0){
            if(last==nullptr){
                return nullptr;
            }
            last=last->next;
            n--;
        }
        //当n=链表的长度时,直接删除头节点,返回head->next
        if(!last){
            return head -> next;    
        }
        while(last->next!=nullptr){ //这样找出来的pre为第k+1个节点
            last=last->next;
            pre=pre->next;
        }
        pre->next=pre->next->next;
        return head;


    }
};

12,合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

//递归法
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode* node=NULL;
        if(pHead1==NULL){return node=pHead2;}
        if(pHead2==NULL){return node=pHead1;}
        if(pHead1->val<pHead2->val){
            node=pHead1;
            node->next=Merge(pHead1->next,pHead2);
        }else
            {
            node=pHead2;
            node->next=Merge(pHead1,pHead2->next);
        }
        return node;
         
    }
     
};
//非递归
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
       
        ListNode *p = new ListNode(0);
        ListNode *head = p;
        while (pHead1 && pHead2)
        {
            if (pHead1->val < pHead2->val)
            {
                head->next = pHead1; 
                pHead1 = pHead1->next;
            }
            else
            {
                head->next= pHead2;
                pHead2 = pHead2->next;
            }
            head = head->next;
        }
        if (pHead1)
            head->next = pHead1;
        if (pHead2)
            head->next = pHead2;
        return p->next; 
    }
};

13.链表反转

//头插法
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode* root=new ListNode(0);
        ListNode* next=NULL;
        while(pHead!=NULL){
            next=pHead->next;
            pHead->next=root->next;  //将根节点的后继变成pHead的后继
            root->next=pHead;//将pHead的值给根结点的后继
            pHead=next;
                
        }
        return root->next;

    }
//依次反转法(三个指针)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre=NULL;
        ListNode* curr=head;
        while(curr!=NULL){
            ListNode* next=curr->next;//保存后节点的值
            curr->next=pre;
            pre=curr; //前指针后移一步
            curr=next;//当前指针后移一步
        }
        return pre;


    }
};
//递归的方法其实是非常巧的,它利用递归走到链表的末端,
//然后再更新每一个node的next 值 ,实现链表的反转。
//而newhead 的值没有发生改变,为该链表的最后一个结点
//所以,反转后,我们可以得到新链表的head。

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        //如果链表为空或者链表中只有一个元素
        if(pHead==NULL||pHead->next==NULL) return pHead;
         
        //先反转后面的链表,走到链表的末端结点
        ListNode* pReverseNode=ReverseList(pHead->next);
         
        //再将当前节点设置为后面节点的后续节点
        pHead->next->next=pHead;
        pHead->next=NULL;
         
        return pReverseNode;
         
    }
};

14.从尾到头打印链表

//利用栈先进后出的特性
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> value;
        stack<int> stk;
        ListNode* p=NULL;
        p=head;
        while(p!=NULL){
            stk.push(p->val);
            p=p->next;
        }
        while(!stk.empty()){
            value.push_back(stk.top());
            stk.pop();
        }
        return value;
        
            
        
    }
};
//递归
class Solution {
public:
    vector<int> value;
    vector<int> printListFromTailToHead(ListNode* head) {
        
        if(head!=NULL){
            if(head->next!=NULL){
                value=printListFromTailToHead(head->next);
            }
            value.push_back(head->val);
        }
        return value;
    }
};

15.找两个链表的第一个公共节点
思路:若有公共节点,公共节点长度为c
则:A=a+c;B=b+c;
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点
A: a1 → a2

c1 → c2 → c3

B: b1 → b2 → b3

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode* l1=pHead1;
        ListNode* l2=pHead2;
        while(l1!=l2){  //当l1和l2相等时,证明到了第一个公共节点,退出循环,返回当前的l1或l2
            l1=(l1==NULL)? pHead2:l1->next;
            l2=(l2==NULL)? pHead1:l2->next;
        }
        return l1;
        

16 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次
思路1:递归套路解决链表问题:
找终止条件:当head指向链表只剩一个元素的时候,自然是不可能重复的,因此return
想想应该返回什么值:应该返回的自然是已经去重的链表的头节点
每一步要做什么:宏观上考虑,此时head.next已经指向一个去重的链表了,而根据第二步,我应该返回一个去重的链表的头节点。因此这一步应该做的是判断当前的head和head.next是否相等,如果相等则说明重了,返回head.next,否则返回head

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head==NULL || head->next==NULL){
            return head;
        }
        head->next= deleteDuplicates(head->next);
        if(head->val==head->next->val){
            head=head->next;
        }
        return head;
//常规方法
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* current=head;
        while (current != NULL && current->next != NULL) {
        if (current->next->val == current->val) {
            current->next = current->next->next;
        } else {  //注意else不可省,不然容易跳过连续重复的
            current = current->next;
        }
    }
    return head;
    }
};

17给定一个链表。两两交换相邻的节点,并返回链表

//三指针法
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* node = new ListNode(-1);
        node->next=head;
        ListNode* pre=node;
        while(pre->next!=NULL&&pre->next->next!=NULL){
            ListNode* l1=pre->next;
            ListNode* l2=pre->next->next;
            ListNode* next=l2->next;
            l1->next=next;
            l2->next=l1;
            pre->next=l2;
            pre=l1;
     
        }
        return node->next;

    }
};

大佬的递归法
使用递归来解决该题,主要就是递归的三部曲:
1.找终止条件:本题终止条件很明显,当递归到链表为空或者链表只剩一个元素的时候,没得交换了,自然就终止了。
2.找返回值:返回给上一层递归的值应该是已经交换完成后的子链表。
3.单次的过程:因为递归是重复做一样的事情,所以从宏观上考虑,只用考虑某一步是怎么完成的。我们假设待交换的俩节点分别为head和next,next的应该接受上一级返回的子链表(参考第2步)。就相当于是一个含三个节点的链表交换前两个节点,就很简单了,想不明白的画画图就ok
上一个大佬分析递归的链接学习递归

class Solution {
    public ListNode* swapPairs(ListNode* head) {
        if(head ==NULL || head.next == NULL){
            return head;
        }
        ListNode* next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    
}

18两数之和
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
思路:用两个栈存储两个链表,栈顶就是链表尾部,相加,进位存储下来。利用头插法将新链表翻转过来。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        stack<int> s1;
        stack<int> s2;
        while(l1){
            s1.push(l1->val);
            l1=l1->next;
        }
        while(l2){
            s2.push(l2->val);
            l2=l2->next;
        }
        int count=0;
        int n1=0,n2=0;
        ListNode *root=new ListNode(-1);
        while(!s1.empty()|| !s2.empty() || count!=0){
            if(!s1.empty()){n1=s1.top();s1.pop();}
            else{n1=0;}
            if(!s2.empty()){n2=s2.top();s2.pop();}
            else{n2=0;}
            int sum=n1+n2+count;
            ListNode *node=new ListNode(sum%10);
            node->next=root->next;
            root->next=node;
            count=sum/10;

        }
        return root->next;
    
    }
};

19 回文链表(要求时间O(n),空间o(1))
思路:找链表中点(快慢指针),后半段翻转,与前半段逐一比较

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head==NULL|| head->next==NULL){
            return true;
        }
        ListNode* slow=head;
        ListNode* fast=head;
       while (fast && fast->next){
            slow = slow->next;  
            fast = fast->next->next;
        }  
        ListNode* pre=NULL;
        while(slow!=NULL){
            ListNode* next = slow->next;
            slow->next = pre;
            pre = slow;
            slow = next;
        }
        while(pre){
            if(pre->val!=head->val){
                return false;
            }
            pre=pre->next;
            head=head->next;

        }
        return true;



    }
};

栈和队列

20.有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:``
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
思路:一旦遇到左括号,就压入栈,遇到右括号,就看与栈顶元素是否匹配,若匹配,移除栈顶,否则返回false.最后判断栈是否为空
注意:空栈无法调用top(),所以加了一个判断。

class Solution {
public:
    bool isValid(string s) {
        stack<char> stack;
        int l=s.length();
        if(l%2==1){return false;}
        for(int i=0;i<l;i++){
            if(s[i]=='(' ||s[i]=='['||s[i]=='{'){
                stack.push(s[i]);
            }
            else{
                 if(stack.empty()){return false;} //判断
                 else if(s[i]==')'&&stack.top()=='('){stack.pop();}
                 else if(s[i]==']'&&stack.top()=='['){stack.pop();}
                 else if(s[i]=='}'&&stack.top()=='{'){stack.pop();}
                 else{ return false; }
            }
        }
        return stack.empty();

    }
};

21.栈实现队列 leetcode232
使用栈实现队列的下列操作:

push(x) – 将一个元素放入队列的尾部。
pop() – 从队列首部移除元素。
peek() – 返回队列首部的元素。
empty() – 返回队列是否为空。
示例:

MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false

class MyQueue {
public:
    /** Initialize your data structure here. */
    stack<int> s1;
    stack<int> s2;
    MyQueue() {
       

    }
    
    /** Push element x to the back of queue. */
    void push(int x) {
        s1.push(x);

    }
    
    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        if(s2.empty()){
            while(!s1.empty()){
                s2.push(s1.top());
                s1.pop();
        }
        }
        int r=s2.top();
        s2.pop();
        return r;

    }
    
    /** Get the front element. */
    int peek() {
        if(s2.empty()){
             while(!s1.empty()){
                  s2.push(s1.top());
                  s1.pop();
        }
        }
             return s2.top();
    }
    
    /** Returns whether the queue is empty. */
    bool empty() {
        if(s1.empty()&&s2.empty()){
            return true;
        }
        return false;


    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */

22.用队列实现栈
使用队列实现栈的下列操作:

push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
知识点:queue.pop() 移除队头;front()获取队头元素;back()获取队尾元素
empty(),length()获取长度
队列遍历是从队头遍历队尾

class MyStack {
public:
    /** Initialize your data structure here. */
    queue<int> q;
    int i;
    int r;
    MyStack() {

    }
    
    /** Push element x onto stack. */
    void push(int x) {
        q.push(x);

    }
    
    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        for(i=0;i<q.size()-1;i++){
            q.push(q.front());
            q.pop();
        }
        r=q.front();
        q.pop();
        return r;

    }
    
    /** Get the top element. */
    int top() {
       return q.back();
    }
    
    /** Returns whether the stack is empty. */
    bool empty() {
        return q.empty();

    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */

23.最小值栈
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。

push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素。
示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.

思路:辅助栈用来装最小元素,次小元素。。。。。。

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> dataS;
    stack<int> minS;
    MinStack() {

    }
    
    void push(int x) {
        dataS.push(x);
        if(minS.empty()||x<=minS.top()){
            minS.push(x);
        }



    }
    
    void pop() {
        if(dataS.top()==minS.top()){
            minS.pop();
        }
        dataS.pop();

    }
    
    int top() {
        return dataS.top();

    }
    
    int getMin() {
        return minS.top();

    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

```cpp
//高票链表解法
class MinStack {
    private Node head;
    
    public void push(int x) {
        if(head == null) 
            head = new Node(x, x);
        else 
            head = new Node(x, Math.min(x, head.min), head);
    }

    public void pop() {
        head = head.next;
    }

    public int top() {
        return head.val;
    }

    public int getMin() {
        return head.min;
    }
    
    private class Node {
        int val;
        int min;
        Node next;
        
        private Node(int val, int min) {
            this(val, min, null);
        }
        
        private Node(int val, int min, Node next) {
            this.val = val;
            this.min = min;
            this.next = next;
        }
    }
}

用一个int 辅助元素的解法

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {
    }
    
    void push(int x)
        if(x <= minn) {  //当前元素x小于等于最小值minn时,要先把minn进栈,再更新,即minn=x
            s.push(minn);
            minn = x;
        }
        s.push(x);
    }
    
    void pop() {
        if(!s.empty()) {
            if(s.top() == minn)  {  //最小元素minn要更新成次栈顶元素
                s.pop();
                minn = s.top();
            }
        }
         s.pop();//把多余栈顶元素出栈,也是就当前的minn
    }
    
    int top() {
        if(!s.empty()) return s.top();
        return NULL;
    }
    
    int getMin() {
        return minn;
    }
    private:
        stack<int>s;
        int minn = INT_MAX; //初始化成int类型的最大值
};

24.每日温度(数组中元素与下一个比它大的元素之间的距离)
根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

思路:在遍历数组时用栈把数组中的数存起来,如果当前遍历的数比栈顶元素来的大,说明栈顶元素的下一个比它大的数就是当前元素。此时可把栈顶元素移除,接着比较,直到移除了所有比当前元素小的元素,然后将当前元素入栈。

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        stack<int> index;
        int n=T.size();
        vector<int> dist(n,0);
        for(int i=0;i<n;i++){
            while(!index.empty()&&T[i]>T[index.top()]){
                int j=index.top();
                index.pop();
                dist[j]=i-j;

            }
            index.push(i);
        }
        return dist;
        

    }
};

25.循环数组中比当前元素大的下一个元素
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

示例 1:

输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

思路与上题一样,不过要x循环数组,所以遍历两次,并且使用j=i%n来保证下标循环遍历

class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        stack<int> index;
        int n=nums.size();
        vector<int> dist(n,-1);
        for(int i=0;i<2*n;i++){
            int j=i%n;
            while(!index.empty()&&nums[index.top()]<nums[j]){
                dist[index.top()]=nums[j];
                index.pop();
                
            }
            if(i<n){  #注意判断是否越界
            index.push(j);
            }
        }
        return dist;
    }
};

二叉树

26.二叉树的最大深度
给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树 [3,9,20,null,null,15,7],

3

/
9 20
/
15 7
返回它的最大深度 3 。

//递归
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root==NULL){
            return 0;
        }
        int leftDepth=maxDepth(root->left);
        int rightDepth=maxDepth(root->right);
        return max(leftDepth,rightDepth)+1;

    }
};

27.平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

示例 1:

给定二叉树 [3,9,20,null,null,15,7]

3

/
9 20
/
15 7
返回 true 。

//自下向上递归,在每一次回溯时都进行一次比较。时间复杂度为O(n)
//注意bool型的返回值,应定义一个bool型变量,令变量为false或true,然后返回变量
class Solution {
    private:bool result=true;
    public:
     bool isBalanced(TreeNode* root) {
         maxDepth(root);
         return result;


    }
public: 
    int maxDepth(TreeNode* root) {
        if(root==NULL){
            return 0;
        }
        int leftdepth=maxDepth(root->left);
        int rightdepth=maxDepth(root->right);
        if(abs(leftdepth-rightdepth)>1){
            result=false;
        }
        return max(leftdepth,rightdepth)+1;
    }
};

28.二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。

示例 :
给定二叉树

      1
     / \
    2   3
   / \     
  4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

注意:两结点之间的路径长度是以它们之间边的数目表示。

class Solution {
private:int result=0;
public:
    int diameterOfBinaryTree(TreeNode* root) {
        maxDepth(root);
        return result;

    }
public:
    int maxDepth(TreeNode* root) {
        if(root==NULL){
            return 0;
        }
        int leftdepth=maxDepth(root->left);
        int rightdepth=maxDepth(root->right);
        result=max(result,leftdepth+rightdepth);
        return max(leftdepth,rightdepth)+1;
    }
};

29.二叉树的翻转

//层次遍历 递归
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if(root==NULL){return NULL;}
        TreeNode* left=root->left;
        root->left=invertTree(root->right);
        root->right=invertTree(left);
        return root;

    }
};
//void型版本
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==NULL){return;}
        TreeNode* left=pRoot->left;
        pRoot->left=pRoot->right;
        pRoot->right=left;
        if(pRoot->left!=NULL){
            Mirror(pRoot->left);
        }
        if(pRoot->right!=NULL){
            Mirror(pRoot->right);
}
    }
};

30.合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/
4 5
/ \ \
5 4 7
注意: 合并必须从两个树的根节点开始。

//递归
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if(t1==NULL){return t2;}
        if(t2==NULL){return t1;}
        t1->val+=t2->val;
        t1->left=mergeTrees(t1->left,t2->left);
        t1->right=mergeTrees(t1->right,t2->right);
        return t1;

    }
};

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

示例:
给定如下二叉树,以及目标和 sum = 22,

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \      \
    7    2      1

返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

//递归 
class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) {
        if(root==NULL){return false;}
        if(root->left==NULL && root->right==NULL&& root->val==sum){
            return true;
        }
        return  hasPathSum(root->left,sum-root->val)||hasPathSum(root->right,sum-root->val);

    }
};
//输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前) 需要输出所有符合的路径

class Solution {
public:
    vector<vector<int>> FindPath(TreeNode* root,int expectNumber) {
        hasPathSum(root,expectNumber);
        return result;
 
    }
    vector<vector<int>> result;//最终输出的二维数组
    vector<int> temp;//一次路径的一维数组
    void hasPathSum(TreeNode* root, int sum) {
        if(root==NULL){return;}
        temp.push_back(root->val);
        if(root->left==NULL && root->right==NULL&& root->val==sum){
            result.push_back(temp);
        }
         
        hasPathSum(root->left,sum-root->val);
        hasPathSum(root->right,sum-root->val);
        temp.pop_back();//注意遍历一次后,清空暂存的一维数组
    }
};

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2){
        if(pRoot1==NULL || pRoot2==NULL){
            return false;
        }
       // if(judge(pRoot1,pRoot2)){
        //       return true;
        //    }  
        return judge(pRoot1,pRoot2)||HasSubtree(pRoot1->left,pRoot2)||HasSubtree(pRoot1->right,pRoot2);
    }
public:
    bool judge(TreeNode* root, TreeNode* subtree){
        if(subtree==NULL){return true;}
        if(root==NULL){return false;}
        if(root->val==subtree->val){
            return judge(root->left,subtree->left)&&judge(root->right,subtree->right);
        }
        return false;
    }
};

33.判断是否为对称树
判断左边的左子树是否等于右边的右子树
左边的右子树是否等于右边的左子树

class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
      if(pRoot==NULL){return true;}
       return  isSymmetrical(pRoot->left,pRoot->right);
    
    }
    bool isSymmetrical(TreeNode* root1,TreeNode* root2){
        if(root1==NULL&&root2==NULL){return true;}
        if(root1==NULL||root2==NULL){return false;}
        if(root1->val!=root2->val){return false;};
        return isSymmetrical(root1->left,root2->right) && isSymmetrical(root1->right,root2->left);
    }
};

34.二叉树的层平均值
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组. 637

示例 1:

输入:
3
/
9 20
/
15 7
输出: [3, 14.5, 11]

BFS(广度优先搜索)
层次遍历,利用队列
队列保存每一个层的节点

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        vector<double> result;
        queue<TreeNode*> q;
        q.push(root); //根节点
        while(!q.empty()){
            double sum=0;
            int n=q.size();
            for(int i=0;i<n;i++){
                TreeNode* temp=q.front();
                q.pop(); //循环一个节点需要清理一个节点然后存放该节点的左右子节点
                sum+=temp->val;
                if(temp->left!=NULL){q.push(temp->left);}
                if(temp->right!=NULL){q.push(temp->right);}
            }
            result.push_back(sum/n);
        }
        return result;


    }
};

35.给定一个二叉树,在树的最后一行找到最左边的值。513

示例 1:

输入:

2

/
1 3

输出:
1

//bfs 层次遍历 先右后左
class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> q;
        q.push(root);
        TreeNode* temp;
        while(!q.empty()){
            temp=q.front();
            q.pop();
            if(temp->right!=NULL){q.push(temp->right);} 
            if(temp->left!=NULL){q.push(temp->left);}
    
        }
        return temp->val;


    }
};

知识点:二叉树的前中后序遍历**
1
/
2 3
/ \
4 5 6
层次遍历顺序:[1 2 3 4 5 6]
前序遍历顺序:[1 2 4 5 3 6]
中序遍历顺序:[4 2 5 1 3 6]
后序遍历顺序:[4 5 2 6 3 1]
层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。

前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。利用栈

① 前序 先根再左再右

void dfs(TreeNode root) {
visit(root);
dfs(root.left);
dfs(root.right);
}
② 中序 先左再根再右

void dfs(TreeNode root) {
dfs(root.left);
visit(root);
dfs(root.right);
}
③ 后序 先左再右再根

void dfs(TreeNode root) {
dfs(root.left);
dfs(root.right);
visit(root);
}

递归写法

//三种遍历的递归写法
class Solution {
public:vector<int> res;
public:
    vector<int> Traversal(TreeNode* root) {
         if(root==NULL){return res;}
         //前序遍历
         res.push_back(root->val);
         Traversal(root->left);
         Traversal(root->right);
         //中序遍历
         Traversal(root->left);
         res.push_back(root->val);
         Traversal(root->right);
         //后序遍历
         Traversal(root->left);
         Traversal(root->right);
         res.push_back(root->val);
         //输出结果
         return res;
       }
    };
         
              

36.给定一个二叉树,返回它的 前序 遍历。

示例:

输入: [1,null,2,3]
1

2
/
3

输出: [1,2,3]

dfs(深度优先搜索),利用栈

//非递归版本
//dfs(深度优先搜索),利用栈
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> res;
        s.push(root);
        while(!s.empty()){
            TreeNode* temp=s.top();
            s.pop();
            if(temp==NULL){continue;} 
            s.push(temp->right); //右先入栈保证左先被遍历
            s.push(temp->left);
            res.push_back(temp->val);

        }
        return res;

    }
};

37.二叉树的中序遍历

//先出栈的都是最左的左孩子,然后是根,然后判断右孩子还有没有左孩子
//curr节点既是左孩子也是根
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> res;
        TreeNode* curr=root;

        while(curr!=NULL || !s.empty()){
            while(curr!=NULL){
                s.push(curr);
                curr=curr->left;
            }
            TreeNode* temp=s.top();
            s.pop();
            res.push_back(temp->val);
            curr=temp->right;  #判断出栈节点的右孩子是否存在,若存在,则继续遍历其右孩子的左孩子,压入栈中,直到为空

        }
        return res;
    }
};

38.后续遍历
法一:

//迭代写法,利用pre记录上一个访问过的结点,与当前结点比较,如果是当前结点的子节点,说明其左右结点均已访问,将当前结点出栈,更新pre记录的对象。
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> res;
        if(root == NULL){return res;}
        TreeNode* pre = NULL;
        s.push(root);
        while(!s.empty()){
            TreeNode* curr = s.top();  
             //如果当前结点左右子节点为空或上一个访问的结点为当前结点的子节点时,当前结点出栈          
            if((curr->left == NULL && curr->right == NULL) ||
           (pre != NULL && (pre == curr->left || pre == curr->right))){
                res.push_back(curr->val);
                pre = curr;
                s.pop(); 
           } 
            else{
            if(curr->right != NULL){ s.push(curr->right);} //先将右结点压栈
            if(curr->left != NULL) {s.push(curr->left);}   //再将左结点入栈
        }            
    }
        return res;
    } 
};

法二:前序遍历为 root -> left -> right,后序遍历为 left -> right -> root。可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反。

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> res;
        s.push(root);
        while(!s.empty()){
            TreeNode* temp=s.top();
            s.pop();
            if(temp==NULL){continue;}
            s.push(temp->left);
            s.push(temp->right); 
            res.push_back(temp->val);

        }
        return reverse(res);

二叉查找树(BST)
根节点大于等于左子树所有节点,小于等于右子树所有节点。
二叉查找树中序遍历有序。
(1)二叉搜索树中第K小的元素(230 medium)
给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。
说明:
你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

//中序遍历有序 返回第k个元素 迭代版中序遍历
//时间复杂度:O(H+k),其中 HH 指的是树的高度,由于我们开始遍历之前,要先向下达到叶,当树是一个平衡树时:复杂度为O(logN+k)。当树是一个不平衡树时:复杂度为 O(N+k),此时所有的节点都在左子树。
//空间复杂度O(H+k)。当树是一个平衡树时:\O(logN+k)。当树是一个非平衡树时:O(N+k)。
class Solution {
public:
    int kthSmallest(TreeNode* root, int k) {
        stack<TreeNode*> s;
        int res;
        int num=0;
        TreeNode* curr=root; 
        while(curr!=NULL || !s.empty()){
            while(curr!=NULL){
                s.push(curr);
                curr=curr->left;
            }
            TreeNode* temp=s.top();
            s.pop();
            num++;
            if(num==k){
                res=temp->val;
            }
            curr=temp->right; 
        }
        return res;

    }
};
//一个JAVA的递归解法
public int kthSmallest(TreeNode root, int k) {
    int leftCnt = count(root.left);
    if (leftCnt == k - 1) return root.val;
    if (leftCnt > k - 1) return kthSmallest(root.left, k);
    return kthSmallest(root.right, k - leftCnt - 1);
}
private int count(TreeNode node) {
    if (node == null) return 0;
    return 1 + count(node.left) + count(node.right);
}

(2)修剪二叉搜索树(669 easy)
给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。
示例:
输入:
3
/
0 4

2
/
1

L = 1
R = 3

输出:
3
/
2
/
1

分析:
如果根结点太小,根结点的左子树的所有结点只会更小,说明根结点及其左子树都应该剪掉,因此直接返回右子树的修剪结果。
如果根结点太大,根结点的右子树的所有结点只会更大,说明根结点及其右子树都应该剪掉,因此直接返回左子树的修剪结果。
如果根结点没问题,则递归地修剪左子结点和右子结点。
如果结点为空,说明无需修剪,直接返回空即可。
左右子结点都修剪完后,返回自身。

//时间复杂度:O(N),其中 N是给定的树的全部节点。我们最多访问每个节点一次。
//空间复杂度:O(N)
class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int L, int R) {
        if(root==NULL){return root;}
        if(root->val>R){return trimBST(root->left,L,R);}
        if(root->val<L){return trimBST(root->right,L,R);}
        root->left=trimBST(root->left,L,R);
        root->right=trimBST(root->right,L,R);
        return root;
    }
};

(3)二叉搜索树转换为累加树(538 easy)
给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。
例如:

输入: 原始二叉搜索树:
5
/
2 13

输出: 转换为累加树:
18
/
20 13

思路:相等于从大到小做一个累加,因此先遍历右子树,将根节点值累加,再遍历左子树(中序遍历反过来,因此也可以有迭代版本

//O(n) O(n)
class Solution {
private: int sum=0;
public:
    TreeNode* convertBST(TreeNode* root) {
        if(root==NULL){
            return root;
        }
        convertBST(root->right);
        root->val=root->val+sum;
        sum=root->val;
        convertBST(root->left);
        return root;
    }
};

(3)二叉搜索树的最近公共祖先(235 easy)
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
算法题c++刷题记录之数据结构部分_第1张图片
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

//根据BST的特性,若p,q都大于root,在右子树,反之在左子树,一大一小(包括等于),root就是祖先
//O(n) O(n)
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==NULL){return root;}
        if(p->val>root->val && q->val>root->val){
            return lowestCommonAncestor(root->right,p,q);
        }
        if(p->val<root->val && q->val<root->val){
            return lowestCommonAncestor(root->left,p,q);
        }
        return root;
        
    }
};

(4)二叉树的最近公共祖先(236 medium)
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
算法题c++刷题记录之数据结构部分_第2张图片
示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

具体思路:
(1) 如果当前结点 root等于NULL,则直接返回NULL
(2) 如果 root 等于 p或者 q ,那这棵树一定返回 p 或者 q
(3) 然后递归左右子树,因为是递归,使用函数后可认为左右子树已经算出结果,用 left 和 right 表示
(4) 此时若left为空,那最终结果只要看right;若 right为空,那最终结果只要看 left
(5) 如果 left 和 right 都非空,因为只给了 p 和 q 两个结点,都非空,说明一边一个,因此 root是他们的最近公共祖先
(6) 如果 left 和 right 都为空,则返回空(其实已经包含在前面的情况中了)

//O(n) O(n)
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL)
            return NULL;
        if(root == p || root == q) 
            return root;
            
        TreeNode* left =  lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
       
        if(left == NULL)
            return right;
        if(right == NULL)
            return left;      
        if(left && right) // p和q在两侧
            return root;
        
        return NULL; // 必须有返回值
    }
};


哈希表

使用 O(N) 空间复杂度存储数据,并且以 O(1) 时间复杂度求解问题。
HashSet 用于存储一个集合,可以查找元素是否在集合中。如果元素有穷,并且范围不大,那么可以用一个布尔数组来存储一个元素是否存在。
Java 中的 HashMap 主要用于映射关系,从而把两个元素联系起来。HashMap 也可以用来对元素进行计数统计,此时键为元素,值为计数。和 HashSet 类似,如果元素有穷并且范围不大,可以用整型数组来进行统计。

39.两数之和
map的基本操作函数:

C++ maps是一种关联式容器,包含“关键字/值”对
begin() 返回指向map头部的迭代器
clear() 删除所有元素
count() 判断元素是否出现,出现返回1,否则返回0
empty() 如果map为空则返回true
end() 返回指向map末尾的迭代器
equal_range() 返回特殊条目的迭代器对
erase() 删除一个元素
find() 查找一个元素
get_allocator() 返回map的配置器
insert() 插入元素
key_comp() 返回比较元素key的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向map尾部的逆向迭代器
rend() 返回一个指向map头部的逆向迭代器
size() 返回map中元素的个数
swap() 交换两个map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素value的函数

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> res;
        map<int, int> tmpmap;
        for(int i=0;i<nums.size();i++){
            tmpmap[nums[i]]=i;
        }
        for(int i=0;i<nums.size();i++){
            if(tmpmap.count(target-nums[i]) && tmpmap[target-nums[i]]>i){
                    res.push_back(i);
                    res.push_back(tmpmap[target-nums[i]]);
               
        }
        }
        return res;    
    }
};

40.判断数组中是否存在重复的元素
set是STL中一种标准关联容器。它底层使用平衡的搜索树——红黑树实现,插入删除操作时仅仅需要指针操作节点即可完成,不涉及到内存移动和拷贝,所以效率比较高set,顾名思义是“集合”的意思,**在set中元素都是唯一的,而且默认情况下会对元素自动进行升序排列,**支持集合的交(set_intersection),差(set_difference) 并(set_union),对称差(set_symmetric_difference) 等一些集合上的操作,如果需要集合中的元素允许重复那么可以使用multiset。使用时注意包含头文件

s.begin()  返回set容器的第一个元素
s.count()
s.end()      返回set容器的最后一个元素
s.clear() 删除set容器中的所有的元素
s.empty()     判断set容器是否为空
s.insert() 插入一个元素 O(1)
s.erase() 删除一个元素
s.size()     返回当前set容器中的元素个数

//O(n),O(n)
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        set<int> s;
        for(auto i : nums){
            s.insert(i);
        }
        return s.size()!=nums.size();
    }
};

41.最长和谐子序列
和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。

现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度。

示例 1:

输入: [1,3,2,2,5,2,3,7]
输出: 5
原因: 最长的和谐数组是:[3,2,2,2,3].
可以用一个哈希映射(HashMap)来存储每个数出现的次数

class Solution {
public:
    int findLHS(vector<int>& nums) {
        map<int,int> nummap;
        int longest=0;
        for(auto i : nums){
            nummap[i]++;
        }
        for(auto it:nummap){
            if(nummap.count(it.first+1)){
                longest=max(longest,nummap[it.first]+nummap[it.first+1]);
                }
         }
    return longest;
    }
};

42.最长连续子序列
给定一个未排序的整数数组,找出最长连续序列的长度。

要求算法的时间复杂度为 O(n)。

示例:

输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。

思路:hashset保存数组,找num-1不存在的数最为公共序列的第一个num,然后找该数的连续+1是否一直存在,记录长度。遍历set,直到找到最长的一个序列。

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        set<int> s;
        int maxlength=0;
        for(int num:nums){
            s.insert(num);
        }
        for(int num:s){
            if(!s.count(num-1)){
                int currnum=num;
                int currlength=1;
                while(s.count(currnum+1)){
                    currnum+=1;
                    currlength+=1;
                }
                maxlength=max(maxlength,currlength);
            }        
            }
        return maxlength;
    }
};

字符串

字符循环移动
将字符串向右(左)循环移动 k 位。
例:abc123循环左(右)移动k位
将 abcd123 中的 abcd 和 123 单独翻转,得到 dcba321,然后对整个字符串进行翻转,得到 123abcd。

class Solution {
public:
    string LeftRotateString(string str, int n) {
        string res1,res2;
        if(n==0 || n>str.size()){
            return str;
        }
        for(int i=0;i<str.size();i++){
            if(i<n){
                res1.push_back(str[i]);
            }
            else{
                res2.push_back(str[i]);
            }
            
        }
        reverse(res1.begin(),res1.end());
        reverse(res2.begin(),res2.end());
        res1+=res2;
        reverse(res1.begin(),res1.end());
        return res1;          
    }
};

43.翻转字符串中的单词
给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例 1:

输入: “Let’s take LeetCode contest”
输出: “s’teL ekat edoCteeL tsetnoc”

//1.利用栈 O(n) O(n)
class Solution {
public:
    string reverseWords(string s) {
        string res;
        stack<char> stack;
        for(int i=0;i<s.size();i++){
            if(s[i]==' '){
                while(!stack.empty()){
                    res +=stack.top();
                    stack.pop();
                }
                res+=s[i];

            }
            else{
                stack.push(s[i]);
            }
        }
        while(!stack.empty()){
            res +=stack.top();
            stack.pop();

        }
        return res;

    }
};
//使用reverse
class Solution {
public:
    string reverseWords(string s) {
        string res;
        string temp;
        for(int i=0;i<s.size();i++){
            if(s[i]==' '){
                if(temp!=""){
                    reverse(temp.begin(),temp.end());
                    res +=temp;
                    temp="";
                }
                res+=s[i];

            }
            else{
                temp+=s[i];
            }
        }
        if(temp!=""){
            reverse(temp.begin(),temp.end());
            res +=temp;
       
        }
        return res;

    }
};

44.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。(除了字母顺序不同其它都相同的词

示例 1:

输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:

输入: s = “rat”, t = “car”
输出: false
说明:
你可以假设字符串只包含小写字母

//利用一个整型数组,用来统计每个字母出现的次数
class Solution {
public:
    bool isAnagram(string s, string t) {
        int num[26]={0};
        if(s.size()!=t.size()){
            return false;
        }
        for(int i=0;i<s.size();i++){
            num[s[i]-'a']++;   //前者统计正数
            num[t[i]-'a']--;   //后者统计负数
        }
        for(int i=0;i<26;i++){
            if(num[i]!=0){
                return false;
            }
        }
    return true;
    }
 };

45 最长回文串
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。

在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。

注意:
假设字符串的长度不会超过 1010。

示例 1:

输入:
“abccccdd”

输出:
7

解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。

思路:数组记录字母出现的次数,每个字符的偶数个可以用来构成回文串,记录个数,最后判断又没有奇数存在,若有,个数+1

lass Solution {
public:
    int longestPalindrome(string s) {
        int nums[256]={0};
        for(int i=0;i<s.size();i++){
            nums[s[i]]++;
        }
        int count=0;
        for(int num:nums){
            count +=(num/2)*2;
            }
        if(count<s.size()){
            count +=1;
        }
        return count;
    }
};

46 回文字符串
给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。

示例 1:

输入: “abc”
输出: 3
解释: 三个回文子串: “a”, “b”, “c”.
示例 2:

输入: “aaa”
输出: 6
说明: 6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”.

思路:把字符串每一位和两个字符的中间位都当做一个回文字符串的中心,像 两边扩展,看是否满足两边对称。

class Solution {
private:int count=0;
public:
    int countSubstrings(string s) {
        for(int i=0;i<s.size();i++){
            expendstring(s,i,i);  //回文字符串长度为奇数
            expendstring(s,i,i+1);//回文字符串长度为偶数
        }
        return count;


    }
private:
    void expendstring(string s,int start,int end){
        while(start>=0 && end<s.size() &&s[start]==s[end]){
            start--;
            end++;
            count++;

        }
    } 
};

动态规划法:待补充

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1:

输入: 121
输出: true
示例 2:

输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:

输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。

将整数分成左右两部分,右边那部分需要转置,然后判断这两部分是否相等。

//O(n):时间复杂度:O(\log_{10}(n))O(log 10(n)),对于每次迭代,我们会将输入除以10,因此时间复杂度为 O(\log_{10}(n))O(log 10(n))。 空间0(1)


class Solution {
public:
    bool isPalindrome(int x) {
        if(x==0){return true;}
        if(x<0 ||x%10==0){return false;}
        int right_rev=0;
        while(x>right_rev){
            right_rev=right_rev*10+x%10; //从右到左慢慢转置,知道中间
            x=x/10;
        }
        return x==right_rev ||x==right_rev/10; //偶数 || 奇数
        
    }
};

48
给定一个字符串 s,计算具有相同数量0和1的非空(连续)子字符串的数量,并且这些子字符串中的所有0和所有1都是组合在一起的。

重复出现的子串要计算它们出现的次数。

示例 1 :

输入: “00110011”
输出: 6
解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。

请注意,一些重复出现的子串要计算它们出现的次数。

另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起

思路:计算每一个连续0或连续1小模块的长度,当前模块不断与上个模块比较

O(n) O(1class Solution {
public:
    int countBinarySubstrings(string s) {
        int preLen = 0,curLen = 1,count = 0;
        for (int i = 1; i < s.size(); i++) {
            if (s[i] == s[i-1]) {
                curLen++;
            }   
            else {
                preLen = curLen; //当前模块遍历完毕,更新上个模块长度,
                curLen = 1;//准备进行下一个模块的比较
            }

//当前模块长度小于前一个模块长度时,子串数量加1,例如0001111,遍历在1111时,需要不断给子串数量+1,知道遍历到第四个1,不再满足子串要求。             
            if(prelen>=curlen){
                count++;
            }
         }
         return count;

    }
    };

49 字符串的同构
给定两个字符串 s 和 t,判断它们是否是同构的。

如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。

示例 1:

输入: s = “egg”, t = “add”
输出: true
示例 2:

输入: s = “foo”, t = “bar”
输出: false
示例 3:

输入: s = “paper”, t = “title”
输出: true
说明:
你可以假设 s 和 t 具有相同的长度。
思路一:映射的数字一样则是同构
例1:
s = papper
s–>121134
t = tittle
t–>121134

class Solution {
public:
    bool isIsomorphic(string s, string t) {
        
    // 字符是第几个出现的不同的字符
        int ss[128] = {0};
        int st[128] = {0};
        int cs = 1, ct = 1;
        for (int i = 0; s[i] != '\0'; i ++){
        // ss[s[i]] == 0说明该字符没出现过
            if (ss[s[i]] == 0) ss[s[i]] = cs++;
            if (st[t[i]] == 0) st[t[i]] = ct++;
            if (ss[s[i]] != st[t[i]]) return false;
        }
        return true;

    }
};

50.扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

//先排序,然后判断王(0)的个数,然后判断不连续的数的总间隔,若总间隔数==王的数 或 总间隔数为0,即可组成顺子。
class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.size()<5){return false;}
        int count1=0,count2=0;
        sort(numbers.begin(),numbers.end());
        for(int i=0;i<numbers.size()-1;i++){
            if(numbers[i]==0){
                count1+=1;
            }
            else{ 
                int gap=numbers[i+1]-numbers[i]-1;
                if(gap<0){return false;}
                else{count2 +=gap;}
                
            }
        }
        if((count1!=count2)&&(count2!=0)){
            return false;
        }
        return true;      
    }
};

数组与矩阵

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int index=0;
        for(int num:nums){
            if(num!=0){
                nums[index]=num;
                index++;
            }
            
        }
        while(index<nums.size()){
            nums[index++]=0;
        }

    }
};

52.重朔矩阵
在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据。
给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表示想要的重构的矩阵的行数和列数。
重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。
如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。

示例 1:

输入:
nums =
[[1,2],
[3,4]]
r = 1, c = 4
输出:
[[1,2,3,4]]
解释:
行遍历nums的结果是 [1,2,3,4]。新的矩阵是 1 * 4 矩阵, 用之前的元素值一行一行填充新矩阵。
思路:定义count 变量,该变量对于遍历的每个元素都会递增,就像我们将元素放在一维中一样获取新列数,其中 count / c 是行号count \%c $是列数字。

//O(m*n) O(m*n)
class Solution {
public:
    vector<vector<int>> matrixReshape(vector<vector<int>>& nums, int r, int c) {
        int m=nums.size(),n=nums[0].size();
       
        if(m*n!=r*c){return nums;}; 
       vector<vector<int>> reshapeNums(r,vector<int>(c));
        int count=0;
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                reshapeNums[count/c][count%c]=nums[i][j];
                count++;    
            }
        }
        return reshapeNums;

    }
};

53.最大连续1的个数
给定一个二进制数组, 计算其中最大连续1的个数。

示例 1:
输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.

class Solution {
public:
    int findMaxConsecutiveOnes(vector<int>& nums) {
        int maxlength=0,curr=0;
        for(int i=0;i<nums.size();i++){
           curr=nums[i]==0?0:curr+1;
           maxlength=max(maxlength,curr);
        }
        return maxlength;

    }
};

54.搜索二维矩阵
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

//从左下角或右上角开始搜索
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int row=matrix.size()-1,col=0;
        while(row>=0 &&col<matrix[0].size()){
            if(target>matrix[row][col]){
                col++;
            }
            else if(target<matrix[row][col]){
                row--;
                }
            else{
                return true;
        }
        }
        return false;
        
    }
};

55.有序矩阵中第K小的元素(leecode 378)
给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。
请注意,它是排序后的第k小元素,而不是第k个元素。

示例:

matrix = [
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
],
k = 8,

返回 13。

//二分查找法
//不能确定“mid是第几小的数”,只能说mid是第cnt或更大的数,然后用二分法找第一个“第cnt或更大的数”
//时间?,其中X为最大值和最小值的差值
class Solution {
public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        int m = matrix.size(), n = matrix[0].size();
        int lo = matrix[0][0], hi = matrix[m - 1][n - 1];
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2; //mid为矩阵值,而不是索引。
            int cnt = 0;
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n && matrix[i][j] <= mid; j++) { //寻找前半部分第cnt小的数
                    cnt++;
            }
        }
            if (cnt < k) {lo = mid + 1;} //证明第K小的在右半部分
            else{hi = mid-1 ;} //证明第k小的在左半部分
    }
    return lo;
}
};

堆排序法(待补充)

56 找到重复和缺失的数
集合 S 包含从1到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。
给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。
示例 1:

输入: nums = [1,2,2,4]
输出: [2,3]

//先排序
//时间复杂度:O(nlog n) 排序需要O(nlogn) 的时间
//空间复杂度:O(log n),排序需要 O(logn) 的空间。
class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        vector<int> res;
        int repeat,missing;
        sort(nums.begin(),nums.end());
        
        for(int i=0;i<nums.size()-1;i++){
            if(nums[i]==nums[i+1]){
            repeat=nums[i];
            }
            else if(nums[i+1]-nums[i]>1){
                missing=nums[i]+1;
            }

        }
        if(nums[0]!=1){ //注意[2,2]这种情况
            missing=1;
        }
        if(nums[nums.size()-1]!=nums.size()){ //注意[1 2 3 3]这种情况
            missing=nums.size();
        }
        
        res.push_back(repeat);
        res.push_back(missing);
        return res;
    }
};
//O(n) O(n)
//利用整型数组计每一个数字出现的次数 空间从map的2n减少到n ,整型是索引代表数字
class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        int counts[nums.size()+1]={0};
        for(auto num:nums){
            counts[num]++;

        }
        int repeat,missing;
        vector<int> res;
        for(int i=1;i<=nums.size();i++){
            if(counts[i]==2){
                repeat=i;
            }
            else if(counts[i]==0){
                missing=i;
            }
        }
        res.push_back(repeat);
        res.push_back(missing);
        return res;

    }
};

记录一种O(n) O(1)的方法思想
已知 numsnums 中所有数字都是正数,且处于 1 到 n之间。遍历 nums中的所有数字,根据数字 i 找到 nums[i],如果是第一次访问 nums[i],将它反转为负数。如果是第二次访问,则会发现它已经是负数。因此,可以根据访问一个数字时它是否为负数找出重复数字。
完成上述操作后,所有出现过的数字对应索引处的数字都是负数,只有缺失数字 j对应的索引处仍然是正数。

//代码如下
class Solution {
public:
    vector<int> findErrorNums(vector<int>& nums) {
        int dup = -1, missing = 1;
        vector<int> res;
        for (int n: nums) {
            if (nums[abs(n) - 1] < 0)
                dup =abs(n);
            else
                nums[abs(n) - 1] *= -1;
        }
        for (int i = 1; i < nums.size(); i++) {
            if (nums[i] > 0)
                missing = i + 1;
        }
        res.push_back(repeat);
        res.push_back(missing);
        return res;
    }
}

57.寻找重复数(Medium 287)
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例 1:
输入: [1,3,4,2,2]
输出: 2
注意
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

//时间复杂度
//二分查找法,共有n个数,找中间数,及小于中间数的个数,或大于,证明有重复
//arr = [1,3,4,2,2] 此时数字在 1 — 4 之间
//mid = (1 + 4) / 2 = 2 arr小于等于的2有3个(1,2,2),h=mid=2,1到2中肯定有重复的值
//mid = (1 + 2) / 2 = 1 arr小于等于的1有1个,2-2中有重复的数字,l=mid+1=2,此时l=h,退出循环,2是重复的值
class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int l=1,h=nums.size()-1;
        while(l<h){
           int mid=l+(h-l)/2;
           int count=0;
           for(int i=0;i<nums.size();i++){
               if(nums[i]<=mid){
                   count++;
               }
           }
            if(count<=mid){
                l=mid+1;
                }
            else{
                h=mid;
            }    
        }
        return l;
    }
};

方法二:双指针法,类似于寻找有环链表的入口 (待补充)

58.分隔数组(easy 769)
数组arr是[0, 1, …, arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。
我们最多能将数组分成多少块?

示例 1:
输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。
示例 2:
输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。

//O(n) 0(1)
//首先找到从左块开始最小块的大小。如果前 k 个元素为 [0, 1, ..., k-1],可以直接把他们分为一个块。当我们需要检查 [0, 1, ..., n-1] 中前 k+1 个元素是不是 [0, 1, ..., k] 的时候,只需要检查其中最大的数是不是 k 就可以了。
class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        if(arr.size()==0){
            return 0;
        }
        int res=0;
        int right=arr[0];
        for(int i=0;i<arr.size();i++){
            right=max(right,arr[i]);
            if(right==i){
                res++;
            }
        }
        return res;
    }
};

59.数组的度(easy 697)
给定一个非空且只包含非负数的整数数组 nums, 数组的度的定义是指数组里任一元素出现频数的最大值。
你的任务是找到与 nums 拥有相同大小的度的最短连续子数组,返回其长度。
示例 1:
输入: [1, 2, 2, 3, 1]
输出: 2
解释:
输入数组的度是2,因为元素1和2的出现频数最大,均为2.
连续子数组里面拥有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短连续子数组[2, 2]的长度为2,所以返回2.

思路:
具有度数 d 的数组必须有一些元素 x 出现 d 次。如果某些子数组具有相同的度数,那么某些元素 x (出现 d 次)。最短的子数组是将从 x 的第一次出现到最后一次出现的数组。
对于给定数组中的每个元素,让我们知道 left 是它第一次出现的索引; right 是它最后一次出现的索引。例如,当 nums = [1,2,3,2,5] 时,left[2] = 1 和 right[2] = 3。
然后,对于出现次数最多的每个元素 x,right[x] - left[x] + 1 将是我们的候选答案,我们将取这些候选的最小值。

//时间复杂度:O(N)。其中 N 是 nums 的长度。每个循环需要 O(N)的时间。
//空间复杂度:O(N),left,right,count 使用的空间。
class Solution {
public:
    int findShortestSubArray(vector<int>& nums) {
        map<int, int> left,right,count;
        for (int i = 0; i < nums.size(); i++) {
            int x = nums[i];
            if (!left.count(x)){left[x]=i;}
            right[x]=i;
            count[x]++;
        }
        int degree=0;
        for(auto it:count){
            degree=max(degree,it.second);
        }
        int ans = nums.size();
        for (auto x: count) {
            if (x.second== degree) {
                ans = min(ans, right[x.first] - left[x.first]+1);
            }
        }
        return ans;

    }
};

60 嵌套数组(medium 565)
索引从0开始长度为N的数组A,包含0到N - 1的所有整数。找到并返回最大的集合S,S[i] = {A[i], A[A[i]], A[A[A[i]]], … }且遵守以下的规则。
假设选择索引为i的元素A[i]为S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]… 以此类推,不断添加直到S出现重复的元素。
示例 1:
输入: A = [5,4,0,3,1,6,2]
输出: 4
解释:
A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.

其中一种最长的 S[K]:
S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}
注意:
N是[1, 20,000]之间的整数。
A中不含有重复的元素。
A中的元素大小在[0, N-1]之间。

//Java代码
//在最后一种方法中,visitedvisited 数组仅用于跟踪已经访问过的数组元素。我们可以在原始数组 numsnums 本身中标记访问过的元素,而不是使用单独的数组来跟踪它们。因为元素的范围只能在 1 到 20,000 之间,所以我们可以在访问过的位置放置一个非常大的整数值 
//时间复杂度:O(n)。nums数组的每个元素最多只考虑一次。
//空间复杂度:O(1)。使用了常数级的额外空间public int arrayNesting(int[] nums) {
    int max = 0;
    for (int i = 0; i < nums.length; i++) {
        int cnt = 0;
        for (int j = i; nums[j] != -1; ) {
            cnt++;
            int t = nums[j];
            nums[j] = -1; // 标记该位置已经被访问
            j = t;

        }
        max = Math.max(max, cnt);
    }
    return max;

1.判断是否为二分图
给定一个无向图graph,当这个图为二分图时返回true。
如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。

graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。

示例 1:
输入: [[1,3], [0,2], [1,3], [0,2]]
输出: true
解释:
无向图如下:
0----1
| |
| |
3----2
我们可以将节点分成两组: {0, 2} 和 {1, 3}。

思路: 实际上就是一个上色问题, 依次判断每个节点(防止非连通图中有未访问的节点), 如果未被上色则选择一种颜色对其上色,并dfs的对其邻接点上相反的颜色, 如果邻接点已有相同的颜色说明上色失败返回false, 否则说明没有矛盾,最终返回true
0: 未上色, -1: 红色, 1: 黑色

//BFS染色
class Solution {
public:
    bool isBipartite(vector<vector<int>>& graph) {
        int n=graph.size();
        vector<int> m(n,0);//用1,-1两种值表示两个集合,0表示未赋值
        unordered_set<int> mi;//储存未被访问的节点
        for(int i=1;i<n;i++) mi.insert(i);
        m[0]=-1;//节点0标记为-1
        queue<int> s;
        s.push(0);
        while(!s.empty()){
            int l=s.size();
            for(int i=0;i<l;i++){
                int curr=s.front();
                mi.erase(curr);
                for(auto x:graph[curr]){
                    if(m[x]==m[curr]) return 0;  //相同意味着一条线的两端节点颜色相同,直接返回错误
                    else if(m[x]==0){
                        m[x]=-m[curr];
                        s.push(x);
                    }
                }
                s.pop();
            }
            //对于下一个一个单独的点及联通区域
            if(s.empty()&&!mi.empty()){
                s.push(*mi.begin());
                m[*mi.begin()]=-1;
                mi.erase(mi.begin());
            }
        }
        return 1;
    }
};

2,并查集(DSU)
一个简单有趣的并查集详解
并查集可以动态地连通两个点,并且可以非常快速地判断两个点是否连通。
合并操作,查找操作

并查集的路径压缩
并查集里的find函数里可以进行路径压缩,是为了更快速的查找一个点的祖先节点,有的题不路径压缩就会超时,所以路径压缩还是很重要的。总的来说就是把一个数传进这个函数,先找到这个数的祖先节点,然后把这个数到这个数的祖先节点中的所有点都直接连向a的祖先节点

int unionsearch(int root) //查找根结点
{
	int son, tmp;
	son = root;
	while(root != pre[root]) //我的上级不是掌门,就while到找到掌门为止
		root = pre[root];
	while(son != root) //son以上的每一级的上级都直接变成掌门
	{
		tmp = pre[son];
		pre[son] = root;直接让son节点的上级变成掌门
		son = tmp;
	}
	return root; //掌门驾到~~



**leecode684.冗余连接 (medium)**
 在本问题中, 树指的是一个连通且无环的无向图。
输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。
返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。
示例 1:

输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
  1
 / \
2 - 3
示例 2:

输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解释: 给定的无向图为:
5 - 1 - 2
    |   |
    4 - 3
注意:

输入的二维数组大小在 31000。
二维数组中的整数在1到N之间,其中N是输入数组的大小。
```cpp
class Solution {
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        int N=edges.size();
        for(int i=0;i<=N;i++){  //先将0-N(树中过的所有元素)存入数组,此时每个元素都是自成一派,互不连通,每个元素都是自己的掌门,即p[x]=x
            p.push_back(i);
        }
        for(auto num:edges){
            int u=num[0],v=num[1];
            if(find(u)!=find(v)){
                p[find(v)]=find(u);  //对于不在一个集合里(不连通的)的合并集合
            }
            else{ //两个点同在一个集合,而且在一条边的两端
                return {u,v};
            }
        }
        return {};

    }
    vector<int> p; //存储每个元素的上级
    //找到每一个点的“掌门”
    int find(int x){
        if(p[x]!=x){
            p[x]=find(p[x]);
        }
        return p[x];
    }
};

你可能感兴趣的:(算法入门)