剑指offer总结

复习笔记 专栏收录该内容
14 篇文章 0 订阅

文章目录

重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

在二叉树的前序遍历和中序遍历中确定根节点的值,分别找到前序遍历和中序遍历左右子树对应的子序列,再以同样的方法,构建左右子树,用递归方法完成。

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if(pre.empty()||vin.empty())
            return NULL;
        //前序遍历的第一个数值就是根节点的值
        TreeNode* root=new TreeNode(pre[0]);
        //在中序遍历序列中找到根节点的值
        int root_index,i;
        for(int i=0;i<vin.size();i++)
        {
            if(vin[i]==pre[0])
            {
                root_index=i;
                break;
            }
        }
        
        vector<int> pre_left,pre_right,vin_left,vin_right;
        for(i=0;i<root_index;i++)
        {
            pre_left.push_back(pre[i+1]);
            vin_left.push_back(vin[i]);
        }
        for(i=root_index+1;i<vin.size();i++)
        {
            pre_right.push_back(pre[i]);
            vin_right.push_back(vin[i]);
        }
        
       /* 递归调用,层层调用,直到调用到叶子节点,返回NULL*/
        //构建左子树
        root->left=reConstructBinaryTree(pre_left,vin_left);
        //构建右子树
        root->right=reConstructBinaryTree(pre_right,vin_right);
        
        return root;

    }
};

二叉树的下一个节点

给定一棵二叉树的其中一个节点,请找出中序遍历序列的下一个节点。
树中的结点不仅包含左右子节点的指针,同时包含指向父结点的指针。
思路:

  • 如果一个节点有右子树,那么它的下一个节点就是它的右子树的最左子节点
  • 没有右子树,分两种情况

1.它是它父节点的左子节点,那么它的下一个节点就是它的父节点
2.它是它父节点的右子节点,可以沿着指向父节点的指针一直向上遍历,直到找到一个是它的父节点的左子节点的节点,如果这个节点存在,那么这个节点的父节点就是要找的下一个节点

 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode *father;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL), father(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* inorderSuccessor(TreeNode* p) {
        if(p==NULL)
            return NULL;
            
        TreeNode* p_next=NULL;
        
        if(p->right)
        {
            TreeNode* p_right=p->right;
            while(p_right->left)
            {
               p_right=p_right->left;
            }
            
            p_next=p_right;
        }
        
        else if(p->father)
        {
            TreeNode* p_current=p;
            TreeNode* p_father=p->father;
            while(p_father && p_current==p_father->right)
            {
                p_current=p_father;
                p_father=p_father->father;
            }
            
            p_next=p_father;
        }
        
        return p_next;
    }
};

两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

思路:

  • 首先把元素插入stack1;
  • 当stack2为空时,把stack1的元素逐个弹出并压入stack2
  • 当stack2步为空时,在stack2的栈顶元素最先进入队列的元素,可以弹出
class Solution
{
public:
/*
s.empty();         //如果栈为空则返回true, 否则返回false;
s.size();          //返回栈中元素的个数
s.top();           //返回栈顶元素, 但不删除该元素
s.pop();           //弹出栈顶元素, 但不返回其值
s.push();          //将元素压入栈顶
*/
    void push(int node) {
        stack1.push(node);
        
    }

    int pop() {
        if(stack2.size()==0)
        {
            while(stack1.size()>0)
            {
                int data=stack1.top();
                stack1.pop();
                stack2.push(data);
            }
        }

        int head=stack2.top();
        stack2.pop();
        return head;
        
    }

private:
    stack<int> stack1;
    stack<int> stack2;
};

旋转数字的最小整数

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

  • 二分查找:

    • 用两个指针,分别指向数组的第一个和最后一个元素
    • 接着找到中间元素,如果中间元素位于前面的递增子数组(中间元素大于或等于第一个元指针指向元素)把第一个指针指向中间元素
    • 如果中间元素位于后面的递增子数组(中间元素大于或等于第一个元指针指向元素),把第二个指针指向中间元素
    • 最终,两指针会指向相邻的元素,第二个指针刚好指向是最小元素

二分查找:

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size()<=0)
            return 0;
        int low=0;
        int high=rotateArray.size()-1;
        int mid = low;//防止数组的前面0个元素搬到最后面
        
        while(rotateArray[low]>=rotateArray[high])
        {
            if(high-low==1)
            {
                mid=high;
                break;
            }
            
            mid=(low+high)/2;
            //特殊情况
            if(rotateArray[low]==rotateArray[high] && rotateArray[low]==rotateArray[mid])
            {
                int result=rotateArray[low];
                for(int i=low;i<=high;i++)
                {
                    if(rotateArray[i]<result)
                        result=rotateArray[i];
                }
                return result;
            }
            if(rotateArray[mid]>=rotateArray[low])
                low=mid;
            else if(rotateArray[mid]<=rotateArray[high])
                high=mid;
        }
        
        return rotateArray[mid];
    }

};

二进制中1的个数

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

二进制的5种位运算

与(&)

或(|)

异或(^)

左移(<<) :左移n位时,做左边的n位被丢弃,最右边不是那个补上n个0

右移(>>) :右移n位时,最右边的n位被丢弃,左边分两种情况

1.如果数字为正,右移最左边补n个0

2.如果数字为负,右移最左边补n个1

  1. 先判断整数二进制最右边是不是1,接着把输入的整数右移一位,再判断是不是1,直到这个整数变为0为止

  2. 怎么判断一个整数为1?

  1. 把整数与1做与运算,但当输入是负数时,如果一直右移运算,最终的数字会变成oxFFFFFFFF陷入死循环

  2. 为避免死循环,首先把n与1做与运算,判断n的最低位是不是1,接着把1左移一位得到2,再和n做与运算,就能判断n的次地位是不是1,反复左移……

  3. 把一个整数减去1,再和原整数做与运算,会把整个整数最右边的1变为0

class Solution {
public:
     int  NumberOf1(int n) {
         int result=0;
         while(n)
         {
             result++;//一个非0数字至少有一个1
             n=(n-1)&n;
         }
         return result;
     }
};

数值的整数次方

class Solution {
public:
    double myPow(double x, int n) {
        if(abs(x-0.0)< 1e-8 && n<0)
            return 0.0;
        
        long absX;
        if(n<0)
            absX=-(long)n;//防止越界-2^31~2^31-1
        else
            absX=n;
        
        double res=PowerWithUnsignedExponent(x,absX);
        if(n<0)
            res=1.0/res;
        
        return res;
    }

    double PowerWithUnsignedExponent(double x,unsigned int n)
    {
         if(n==0)
             return 1;
         if(n==1)
             return x;
         
         //用右移运算符代替除以2
         double res=PowerWithUnsignedExponent(x,n>>1);
         res*=res;
         //求余运算符(%)判断一个数是奇数还是偶数
         if(n & 0x1==1)
             res*=x;
         
         return res;
     }


};

打印从1到最大的n位数

输入数字n,按顺序打印出从1最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。

  1. 用一个循环从1开始逐个打印,但当n很大时,有可能用整型(int)或长整型(long)都会溢出。

  2. 字符串模拟运算的解法,用字符串或者数组表达大数

    把字符串中的每一个数字都初始化为’0’
    每一次为字符串表示的数字加1,再打印出来

void Print1ToMaxOfNDigits_2(int n)
{
    if (n <= 0)
        return;

    char* number = new char[n + 1];
    number[n] = '\0';

    for (int i = 0; i < 10; ++i)
    {
        number[0] = i + '0';
        Print1ToMaxOfNDigitsRecursively(number, n, 0);
    }

    delete[] number;
}

void Print1ToMaxOfNDigitsRecursively(char* number, int length, int index)
{
    if (index == length - 1)
    {
        PrintNumber(number);
        return;
    }

    for (int i = 0; i < 10; ++i)
    {
        number[index + 1] = i + '0';
        Print1ToMaxOfNDigitsRecursively(number, length, index + 1);
    }
}

void PrintNumber(char* number)
{
    bool isBeginning0 = true;
    int nLength = strlen(number);

    for (int i = 0; i < nLength; ++i)
    {
        if (isBeginning0 && number[i] != '0')
            isBeginning0 = false;

        if (!isBeginning0)
        {
            printf("%c", number[i]);
        }
    }

    printf("\t");
}

删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

**示例 1:**

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

```c++
#include <iostream>
#include<vector>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};


class Solution {
public:

    ListNode* deleteNode(ListNode* head, int toDeleteVal) {
        if(head == nullptr)
            return nullptr;
        ListNode* node = head;
        while(node != nullptr)
        {
            if(node->val == toDeleteVal)
            {
                node->val=node->next->val;
                node->next=node->next->next;
            }
            else
            {
                node=node->next;
            }
        }

        return head;
    }
};

int main() {
    ListNode* phead=new ListNode(-1);
    int n,val,toDeleteVal;
    cin>>n>>toDeleteVal;
    ListNode* node=phead;
    for(int i=0;i<n;i++)
    {
        cin>>val;
        node->next=new ListNode(val);
        node=node->next;
    }

    Solution s;
    ListNode* res=s.deleteNode(phead->next,toDeleteVal);
    while(res)
    {
        cout<<res->val<<" ";
        res=res->next;
    }
    return 0;
}
```

删除链表中重复的结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode *res=new ListNode(-1);
        res->next=head;
        ListNode *cur= res;

        ListNode *left;
        ListNode *right;
        while(cur->next)
        {
            left=cur->next;
            right=left;
            while(right->next && left->val==right->next->val)
            {
                right=right->next;
            }
            if(left==right)//没有移动right,后一个元素和left不相等
            {
                cur=cur->next;
            }
            else
            {
                cur->next=right->next;
            }

        }

        return res->next;
        
    }
};

pre、cur、nex 分别代表的是前中后三个指针

我们在考虑的情况中,如果头节点开始就重复,我们就处理很起来多了一种情况就需要额外处理,所以我们添加一个头节点,变成带头节点,保证了头节点开始不会重复

#include <iostream>
#include<vector>
using namespace std;


struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};

/*三指针,处理好头结点情况*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if(pHead==NULL || pHead->next==NULL)
            return pHead;
        //新建一个头节点,防止第一个节点被删除
        ListNode* newHead=new ListNode(-1);
        newHead->next=pHead;

        //建立索引指针
        ListNode* cur=pHead;      // 当前节点
        ListNode* pre=newHead;  // 当前节点的前序节点
        ListNode* nex=cur->next;    // 当前节点的后序节点

        while(cur!=NULL && nex!=NULL)
            //while(cur!=NULL && cur->next!=NULL)
        {
            if(cur->val==nex->val)//如果当前节点的值和下一个节点的值相等
            {
                // 循环查找,找到与当前节点不同的节点
                while(nex!=NULL && nex->val==cur->val)
                {
                    //ListNode* temp=nex;
                    nex=nex->next;
                    // 删除内存中的重复节点
                    //delete temp;
                    //temp = nullptr;
                }

                pre->next=nex;
                cur=nex;
            }
            else//如果当前节点和下一个节点值不等,则向后移动一位
            {
                pre=cur;
                cur=cur->next;
            }
            //nex=cur->next;
            nex=nex->next;
        }
        return newHead->next;
    }
};

int main() {
    ListNode* head=new ListNode(-1);
    ListNode* node=head;
    int n,val;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>val;
        node->next=new ListNode(val);
        node=node->next;
    }

    Solution s;
    //ListNode* res=head->next;
    ListNode* res=s.deleteDuplication(head->next);
    while (res)
    {
        cout<<res->val<<" ";
        res=res->next;
    }

    return 0;
}

给定一排序链表,删除节点中部分重复的节点,重复节点保留

#include <iostream>
#include<vector>
using namespace std;

/* 给定一排序链表,删除节点中部分重复的节点*/
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};

class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if(pHead==NULL && pHead->next)
            return pHead;
        ListNode* node=pHead;
        while(node && node->next)
        {
            if(node->val==node->next->val)
            {
                ListNode* temp=node->next;
                node->next=temp->next;
                node=node->next;
            }
            else
            {
                node=node->next;
            }
        }

        return pHead;
    }
};

int main() {
    ListNode* head=new ListNode(0);
    ListNode* node=head;
    int n,val;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>val;
        node->next=new ListNode(val);
        node=node->next;
    }

    Solution s;
    //ListNode* res=head->next;
    ListNode* res=s.deleteDuplication(head->next);
    while (res)
    {
        cout<<res->val<<" ";
        res=res->next;
    }

    return 0;
}

剑指 Offer 19. 正则表达式匹配

难度困难73收藏分享切换为英文关注反馈

请实现一个函数用来匹配包含'. ''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但与"aa.a""ab*a"均不匹配。

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:

输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。

示例 5:

输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母以及字符 .*,无连续的 '*'

注意:本题与主站 10 题相同:https://leetcode-cn.com/problems/regular-expression-matching/

class Solution {
public:
    bool isMatch(string s, string p) {
        if(p.empty()) return s.empty();
        if(p[1] == '*'){
            return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || p[0] == '.')) && isMatch(s.substr(1), p);
        }
        else{
            return !s.empty() && (s[0] == p[0] || p[0] == '.') && (isMatch(s.substr(1), p.substr(1)));
        }
    }
};

剑指 Offer 20. 表示数值的字符串

难度中等33收藏分享切换为英文关注反馈

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、“±5”、"-1E-16"及"12e+5.4"都不是。

注意:本题与主站 65 题相同:https://leetcode-cn.com/problems/valid-number/

class Solution {
public:
    bool isNumber(string s) {
        //A[.[B]][e|EC]//.B[e|EC]
        //A带符号的整数;B无符号整数,e后面必有一个(有符号)整数,小数点前后必有数字,空格只能出现在字符串首尾;
        int flag=0;//用来标记是否检测到数字
        //空,直接返回
        if(s=="") return false;
        //检测字符串之前是否有空格
        while(s[0]==' ') s=s.substr(1);
        //遇到正负号,向后移 
        if(s[0]=='+'||s[0]=='-') s=s.substr(1);
        //检测小数点前是否有数字,有的话后移,并标记
        while(((s[0]-'0')>=0) && ((s[0]-'0')<=9)){
            s=s.substr(1);flag=1; 
        } 
        //如果后面是.,那就向后移,有.就要判断后面有没有数字
        if(s[0]=='.'){
            s=s.substr(1);
            while(((s[0]-'0')>=0) && ((s[0]-'0')<=9)){
                s=s.substr(1);flag=1; 
            }
        }
        //判断前半部分有没有数字
        if(flag==0) 
            return false;
        flag=0;
        //接下来判断是有e|E;与上面是串联关系,所以用if不用else
        //如果存在e|E,那就一定要判断,它后面是否跟了数字
        if(s[0]=='e'||s[0]=='E'){
            s=s.substr(1);
            //判断有没有正负号
            if(s[0]=='+'||s[0]=='-') s=s.substr(1);           
            //判断整数
            while(((s[0]-'0')>=0) && ((s[0]-'0')<=9)){
                s=s.substr(1);flag=1; 
            }
            //如果有e没有数字,那就出错了
            if(flag==0) return false;     
        }
        //判断空格结尾
        while(s[0]==' ') s=s.substr(1);
        //如果结束了那就是true,如果还有其他字母,那就false
        if(s=="") return true;
        return false;
    }
};

调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分

思路:

  • 最简单从头遍历,每碰到一个偶数,拿出这个数字,把位于这个偶数后面的所有数字往前移一位,再把这个数字放在数组末尾,时间复杂度 O ( n 2 ) O(n^2) O(n2)

  • 需要两个指针分别指向数组的头和尾,依次比较。

    1. 如果头指针指向的数组位置为奇数,那么就判断尾指针指向的数组位置的奇偶性。如果是奇数,则头指针后移一个位置,如果是偶数,则尾指针前移一个位置。
    2. 如果头指针指向的数组位置为偶数,那么就判断尾指针指向的数组位置的奇偶性。如果是奇数,则交换头尾指针指向的数组元素,如果是偶数,则尾指针前移一个位置。

vector表示

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        int i=0;
        int j=array.size()-1;
        if(j==0)
            return ;
         
        while(i<j)
        {
            while(array[i]&0x1)
                i++;
            while((array[j]&0x1)==0)
                j--;
            if(i<j)
            {
                int temp=array[i];
                array[i]=array[j];
                array[j]=temp;
            }
        }
    }
};

扩展

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)

法1:

//运行时间:3ms,占用内存:492k

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> vec1,vec2 ;
        int j=0,k=0;
        for(int i=0;i<array.size();i++)
        {
            if(array[i]& 0x1)
            {
                vec1.push_back(array[i]);
                j++;
            }
                
            else
            {
                vec2.push_back(array[i]);
                k++;
            }
        }
        
        array.clear();
        for(int i=0;i<j;i++)
            array.push_back(vec1[i]);
        for(int i=0;i<k;i++)
            array.push_back(vec2[i]);
        }
    }        

法2:

//运行时间:3ms,占用内存:484k


class Solution {
public:
    void reOrderArray(vector<int> &array) {
        //记录第一个为奇数的位置
        int j=0;
        for(int i=0;i<array.size();i++)
        {
            if(array[i]&0x1)//找到第一个奇数
            {
                int temp=array[i];//记录第一个奇数
                int k=i;
                for(;k>j;k--)//将奇数之前的所有元素往后移一个位置
                {
                    array[k]=array[k-1];
                }
                array[j]=temp;
                j++;
            }
        }
    }

链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。

思路:

  1. 先统计链表节点的个数n,再从头遍历到第n-k+1个节点
  2. 为了实现只遍历一次,可以定义两个指针,第一个指针从链表的头指针开始遍历向前走k-1步,第二个指针保持不动,从第k步开始,第二个指针也开始从链表的头指针开始遍历

思维全面,注意代码的鲁棒性 。

#include <iostream>
using namespace std;
struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead==NULL || k==0)
            return NULL;
        ListNode *first=pListHead;
        ListNode *second=pListHead;
        while(--k )
        {
            if(first->next)
                first=first->next;
            else //防止k大于链表个数
                return NULL;
        }

        while(first->next )
        {
            first=first->next;
            second=second->next;
        }

        return second;

    }
};
int main() {
    ListNode* head=new ListNode(-1);
    ListNode* node=head;
    int val,n,k;
    cin>>n>>k;
    while(n--)
    {
        cin>>val;
        node->next=new ListNode(val);
        node=node->next;
    }

    Solution s;
    ListNode* res=s.FindKthToTail(head->next,k);
    cout<<res->val<<endl;
}
/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead==NULL || k==0)
            return NULL;
        ListNode *first=new ListNode(-1);
        first->next=pListHead;
        ListNode *second=pListHead;
        while(k--)
        {
            if(first->next)
                first=first->next;
            else //防止k大于链表个数
                return NULL;
        }
        
        while(first->next )
        {
            first=first->next;
            second=second->next;
        }
        
        return second;
    
    }
};

链表中环的入口结点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

数学推到下关系,比较简单:

L 1 L_1 L1= L 2 L_2 L2(2-k)

L 1 L_1 L1为从节点头部到相遇节点的经过节点数

L 2 L_2 L2为环节点的节点数

顾k=1

L 1 L_1 L1= L 2 L_2 L2

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead==NULL || pHead->next==NULL || pHead->next->next==NULL)
            return NULL;
        ListNode *first=pHead->next;
        ListNode *second=pHead->next->next;
        /*快指针比慢指针多走环的一圈*/
        while(first!=second)
        {
            first=first->next;
            second=second->next->next;
        }
        
        
        first=pHead;
        while(first!=second)
        {
            first=first->next;
            second=second->next;
        }
        
        return first;
    }
};

树的子结构

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

注意检测空指针

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if(A==NULL || B==NULL)
            return false;
        return dfs(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }

    bool dfs(TreeNode * A,TreeNode *B)
    {
        if(B==NULL)
            return true;
        if(A==NULL)
            return false;
        if(A->val!=B->val)
            return false;
        else
        {
            return dfs(A->left,B->left) && dfs(A->right,B->right);
        }
    }
};

二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

输入描述:

二叉树的镜像定义:源二叉树 
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9 11
    	镜像二叉树
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7  5

先序遍历这棵树的每个结点,如果遍历到的结点有子结点,则交换它的两个子结点。当交换完所有非叶子结点的左右子结点之后,就得到了树的镜像。

递归:

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if(pRoot==NULL)
            return;
        if(pRoot->left==NULL && pRoot->right==NULL)
            return;
        
        TreeNode *temp=pRoot->left;
        pRoot->left=pRoot->right;
        pRoot->right=temp;
        
        if(pRoot->left)
            Mirror(pRoot->left);
        if(pRoot->right)
            Mirror(pRoot->right);

    }
};

循环:

利用栈的“后进先出”特性

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        
	if (pRoot == NULL)
	{
		return;
	}
 
	stack<TreeNode*> stackTreeNode;
	stackTreeNode.push(pRoot);
 
	while (stackTreeNode.size() > 0)
	{
		TreeNode *parent = stackTreeNode.top();
		stackTreeNode.pop();
 
		TreeNode *Temp = parent->left;
		parent->left= parent->right;
		parent->right = Temp;
 
		if (parent->left)
		{
			stackTreeNode.push(parent->left);
		}
 
		if (parent->right)
		{
			stackTreeNode.push(parent->right);
		}
    } 

对称的二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。如果一颗二叉树和它的镜像一样,那么他是对称的。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        return isSymmetrical(pRoot,pRoot);
    }
    
    bool isSymmetrical(TreeNode* pRoot1,TreeNode* pRoot2)
    {
        if(pRoot1==NULL && pRoot2==NULL)
            return true;
        if(pRoot1==NULL || pRoot2==NULL)
            return false;
        if(pRoot1->val!=pRoot2->val)
            return false;
        
        return isSymmetrical(pRoot1->left,pRoot2->right) && isSymmetrical(pRoot1->right,pRoot2->left);
    }

};

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

定义2个变量保存行数和列数,定义4个变量保存边界值,然后使用4个循环就可以了

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> ret;
        ret.clear();
         
        if(!matrix.empty())
        {
            int row = matrix.size();
            int col = matrix[0].size();
             
            int top = 0;
            int bottom = row - 1;
            int left = 0;
            int right = col - 1;
 
            while((top <= bottom) && (left <= right))
            {
                for(int i = left; i <= right; i++)
                {
                    ret.push_back(matrix[top][i]);
                }
                for(int i = top + 1; i <= bottom; i++)
                {
                    ret.push_back(matrix[i][right]);
                }
                for(int i = right - 1 ; i >= left && top < bottom ; i--)    //已经打印过了的不用再打印
                {
                    ret.push_back(matrix[bottom][i]);
                }
                for(int i = bottom - 1; i > top && left < right; i--)    //已经打印过了的不用再打印
                {
                    ret.push_back(matrix[i][left]);
                }
                top++;
                right--;
                bottom--;
                left++;
            }
        }
        return ret;
    }
};

包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

构造一个数据栈和辅助栈,把每次的最小元素压入辅助栈

class MinStack {
private:
    stack<int>st1;
    stack<int>st2;
public:

    /** initialize your data structure here. */
    MinStack() {
        
    }
    
    void push(int x) {
        st1.push(x);
        if(st2.empty() || x<st2.top())
            st2.push(x);
        else
            st2.push(st2.top());
    }
    
    void pop() {
        st1.pop();
        st2.pop();
        
    }
    
    int top() {
        return st1.top();//只能为st1   
    }
    
    int getMin() {
        return st2.top();//只a是获取最小值靠st2
    }
};

/**
 * 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();
 */

栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

建立一个辅助栈,把输入的第一个序列中的数字依次压入辅助栈,并按照第二个序列的顺序依次从该栈中弹出元素

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        stack<int>stackV;
        int j=0;
        for(int i=0;i<pushV.size();i++)
        {
            stackV.push(pushV[i]);//将入栈序元素入栈
            while(!stackV.empty() && stackV.top()==popV[j])//栈顶元素等于出栈序,则出栈
            {
                stackV.pop();
                j++;//出栈序下标往后移动一下
            }
        }
        return stackV.empty();

    }
};

从上到下打印二叉树

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

借助queue的层次遍历

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> p;
        if(!root)
            return p;

        queue<TreeNode*> q;
        q.push(root);

        while(q.size())
        {
            TreeNode* pNode=q.front();//返回队首元素的值,但不删除该元素
            q.pop();//删除队列首元素但不返回其值
            p.push_back(pNode->val);
            if(pNode->left)
                q.push(pNode->left);
            if(pNode->right)
                q.push(pNode->right);
        }
        return p;

    }
};

从上到下打印二叉树 II

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

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

    3  
   / \
  9  20
    /  \
   15   7

返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]

借助queue的层次遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>>res;
        if(root==NULL)return res;
        queue<TreeNode*>q;
        q.push(root);
        while(!q.empty()){
            vector<int>r;
            int l=q.size();
            for(int i=0;i<l;i++){ //当前层节点都在队列中,依次取出
                TreeNode* t=q.front();
                r.push_back(t->val);
                q.pop();
                if(t->left)q.push(t->left);
                if(t->right)q.push(t->right);
            }
            res.push_back(r);
        }
        return res;
    }
};

剑指 Offer 32 - III. 从上到下打印二叉树 III

难度中等33收藏分享切换为英文关注反馈

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

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

    3
   / \
  9  20
    /  \
   15   7

返回其层次遍历结果:

[
  [3],
  [20,9],
  [15,7]
]

提示:

  1. 节点总数 <= 1000
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (root==NULL)
            return res;
        bool flag = true; //从左向右打印为true,从右向左打印为false
        deque<TreeNode*> q;
        q.push_back(root);
        while (!q.empty())
        {
            int n = q.size();
            vector<int> out;
            TreeNode* node;
            while (n>0)
            {
                if (flag) // 前取后放:从左向右打印,所以从前边取,后边放入
                {
                    node = q.front();
                    q.pop_front();
                    if (node->left)
                        q.push_back(node->left);  // 下一层顺序存放至尾
                    if (node->right)
                        q.push_back(node->right);
                }
                else  //后取前放: 从右向左,从后边取,前边放入
                {
                    node = q.back();
                    q.pop_back();
                    if (node->right)
                        q.push_front(node->right);  // 下一层逆序存放至首
                    if (node->left)
                        q.push_front(node->left);
                }
                out.push_back(node->val);
                n--;
            }
            flag = !flag;
            res.push_back(out);
        }
        return res;
    }
};

二叉树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

  • 定义:二叉排序树 ( Binary Sort Tree),又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树。.
  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值
  • 若它的右子树不空 ,则右子树上所有结点的值均大于宫的根结点的值
  • 它的左、右子树也分别为二叉排序树。
class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size()==0)
            return false;
        
        return _VerifySquenceOfBST(sequence,0,sequence.size()-1);
    }
    
    bool _VerifySquenceOfBST(vector<int> s,int start,int end)
    {
        if(start>=end)
            return true;
        
        int root=s[end];
        int i=start;
        
        while(s[i]<root)
            i++;
        
        int j=i;
        while(j<end)
        {
            if(s[j]<root)
                return false;
            j++;
        }
        
        return _VerifySquenceOfBST(s,start,i-1) && _VerifySquenceOfBST(s,i,end-1);
        
        
    }
    
};

二叉树中和为某一路径

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

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    vector<vector<int> > p;
    vector<int> q;
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root==NULL)
            return p;
        
        q.push_back(root->val);
        //如果是叶结点,并且路径上结点的和等于输入的值
        if(root->left==NULL && root->right==NULL && root->val==expectNumber)
            p.push_back(q);
        //如果不是叶结点,则遍历它的子结点
        if(root->val<expectNumber && root->left!=NULL)
            FindPath(root->left,expectNumber-root->val);
        
        if(root->val<expectNumber && root->right!=NULL )
            FindPath(root->right,expectNumber-root->val);
        //在路径上删除当前结点
        q.pop_back();
        
        return p;

    }
};

复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

法1

​ 首先遍历一遍原链表,创建新链表(赋值label和next),用map关联对应结点;再遍历一遍,更新新链表的random指针。(注意map中应有NULL ----> NULL的映射)

法2:

分为三步走:

  • 给原来的链表的每个结点复制一个新节点并且插入到对应的后面

img

  • 把复制的结点的random指针指向被复制结点的random指针的下一个结点

    img

  • 拆分成两个链表,奇数位置为原链表,偶数位置为复制链表,注意复制链表的最后一个结点的next指针不能跟原链表指向同一个空结点None,next指针要重新赋值None(判定程序会认定你没有完成复制)

img

/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead==NULL)
            return NULL;
        //1.给原来的链表的每个结点复制一个新节点并且插入到对应的后面
        RandomListNode* old=pHead;
        while(old)
        {
            RandomListNode *temp=new RandomListNode(old->label);
            temp->next=old->next;
            old->next=temp;
            old=old->next->next;//由于已经插入了新节点,因此遍历下一个老节点得走2个next
        }
        
        //2.把复制的结点的random指针指向被复制结点的random指针的下一个结点
        old=pHead;
        while(old)
        {
            RandomListNode * temp=old->next;//新节点紧跟在与其对应的老节点后面
            if(old->random)
            {
                temp->random=old->random->next;
            }
            old=old->next->next;
        }
        
        //3.拆分新/老链表
        old=pHead;
 /*       RandomListNode *newHead=old->next;//新链表的头是老链表头的下一个
        while(old)
        {
            RandomListNode *temp=old->next;//先保存老节点的后一节点,即新节点
            old->next=temp->next;//恢复老节点的next指向
            if(old->next)//老节点的下一指向
            {
                temp->next=old->next->next;//恢复新节点的next指向
                //temp->next=temp->next->next;
            }
            
            old=old->next;//由于oldnode的next域上面已经恢复了,因此下一个老节点就是oldnode->next
        }
        */
        RandomListNode* copy=pHead->next;
        RandomListNode* newHead=pHead->next;
        while(copy->next!=NULL){
            old->next=copy->next;
            copy->next=old->next->next;
            old=old->next;
            copy=copy->next;
        }
        old->next=NULL;
        return newHead;
    }
};
RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead==nullptr)
            return nullptr;
        
        //第一步:复制链表结点
        RandomListNode* oldnode=pHead;
        while(oldnode)
        {
            RandomListNode* newnode = new RandomListNode(oldnode->label);//用老节点的值申请一个新节点
            newnode->next=oldnode->next;//将新节点的next链上老节点的next
            oldnode->next=newnode;//将新节点插在老节点后面
            oldnode=oldnode->next->next;//由于已经插入了新节点,因此遍历下一个老节点得走2个nexto
            //oldnode=newnode->next;
            
        }
        
        //第二步:置信结点的random指针
        oldnode=pHead;//oldnode指向链表的开始
        while(oldnode)
        {
            RandomListNode* newnode=oldnode->next;//由于上面的复制,新节点紧跟在与其对应的老节点后面
            if(oldnode->random!=nullptr)
            {
                //oldnode->random是老节点的random域,再指向next就是该老节点对应的新节点应该指向的对应的random域
                newnode->random=oldnode->random->next;
            }
            oldnode=oldnode->next->next;//只走老节点(为其对应的新节点链上对应的randoum域)
        }
        
        //拆分新/老链表
        oldnode=pHead;//让oldnode指向链表开始
        RandomListNode* newHead=oldnode->next;//新链表的头是老链表头的下一个(之前复制了的)
        
        while(oldnode)
        {
            RandomListNode* newnode=oldnode->next;//保证newnode一直紧跟oldnode后面(老、新节点相对位置)
            oldnode->next=newnode->next;//恢复老节点的next指向
            
            if(oldnode->next!=nullptr)
            {
                newnode->next=oldnode->next->next;//恢复新节点的next指向
            }
            oldnode=oldnode->next;//由于oldnode的next域上面已经恢复了,因此下一个老节点就是oldnode->next
        }
      return newHead;
    }

剑指 Offer 36. 二叉搜索树与双向链表

难度中等79收藏分享切换为英文关注反馈

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。

为了让您更好地理解问题,以下面的二叉搜索树为例:

img

我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。

下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。

img

特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。

中序遍历,vector

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    vector<Node *>arr;
public:
    Node* treeToDoublyList(Node* root) {
        if(root==NULL)
            return NULL;
        help(root);
        int i;
        for(i=0;i<arr.size()-1;i++)
        {
            arr[i]->right=arr[i+1];
            arr[i+1]->left=arr[i]; 
        } 
        arr[i]->right=arr[0];
        arr[0]->left=arr[i];

        return arr[0]; 
    }
    
    void help(Node *root)
    {
        if(root==NULL)
            return;
        help(root->left);
        arr.push_back(root);
        help(root->right);
    }
};

中序遍历二分搜索树 双指针 pre cur

中序遍历,当前遍历节点为cur

pre 初始化为 nullptr

当pre为nullptr 时 表示cur->val 为最小即为head头结点 只需要令head = cur,再将pre指向cur
当pre不为空时 需要将cur->left = pre, pre->right = cur , 再将pre指向cur

最终pre指向二分搜索树中的最大值 即为tail尾结点(pre一直指向cur,cur在中序遍历二分搜索树,最终pre记录最大值节点,即尾结点)

最后再将head->left指向尾结点pre
pre->right指向头结点head

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
private:
	Node* head;
	Node* pre;
	//cur中序遍历
	void inorder(Node* cur) {
		if (cur == nullptr)
            return;
		inorder(cur->left);
		//转换
		//当pre不为空时 需要将cur->left = pre, pre->right = cur , 再将pre指向cur
		if (pre != nullptr) 
        {
			pre->right = cur;
			cur->left = pre;
            pre = cur;
		}
		else 
        {
		    //当pre为nullptr 时 表示cur->val 为最小即为head头结点 只需要令head = cur,再将pre指向cur
			head = cur;
            pre = cur;
		}
        
		inorder(cur->right);
	}
public:
	Node* treeToDoublyList(Node* root) {
		if (root == nullptr)return root;
		inorder(root);

		//处理头尾
		head->left = pre;
		pre->right = head;
		return head;
	}
};

剑指 Offer 37. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

示例:

你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

递归

前序遍历

class Codec {
public:

    //1、前序遍历递归 序列化
    string serialize(TreeNode* root) {

        if (root == nullptr)
        {
            return "#,";
        }

        string s;
        s += to_string(root->val) + ",";
        s += serialize(root->left);
        s += serialize(root->right);

        return s;
    }

    //反序列化
    TreeNode* deserialize(string data) {

        vector<string> v = split(data, ","); //分割字符串

        int index = 0;
        return preorder_build(v, index);
    }
    
    //前序遍历递归 反序列化
    TreeNode* preorder_build(const vector<string>& v, int& index)
    {
        const string& s = v.at(index); //index不会越界  
        ++index;

        if (s == "#")
        {
            return nullptr;
        }

        TreeNode* node = new TreeNode(stoi(s));
        node->left = preorder_build(v, index);
        node->right = preorder_build(v, index);

        return node;
    }
    //辅助函数
    vector<string> split(string& s, const char* delim)
    {
        vector<string> v;
        // char *strtok(char *str, const char *delim) 分解字符串 str 为一组字符串,delim 为分隔符
        char* str = strtok((char*)s.c_str(), delim);
        while (str)
        {
            v.push_back(string(str));
            //NULL的作用只是为了使得每次调用时,都不是从s的头开始,而是从上次调用时查找所停止的位置开始
            str = strtok(NULL, delim);
        }
        return v;
    }
};

迭代

前序遍历

    //2、前序遍历迭代
    //出栈时拼串
    string serialize(TreeNode* root) {

        if (root == nullptr)
        {
            return "#,";
        }

        stack<TreeNode*> s;
        s.push(root);

        string res;

        while (!s.empty())
        {
            TreeNode* node = s.top();
            s.pop();

            if (node)
            {
                res += to_string(node->val) + ",";

                //入栈顺序:先右后左 注意:空节点也入栈
                s.push(node->right);
                s.push(node->left);
            }
            else //空节点
            {
                res += "#,";
            }
        }

        return res;
    }

    // 入栈时拼串
    string serialize(TreeNode* root) {

        if (root == nullptr)
        {
            return "#,";
        }

        stack<TreeNode*> s;
        TreeNode* cur = root;

        string res;

        while (cur || !s.empty())
        {
            if (cur)
            {
                s.push(cur);

                res += to_string(cur->val) + ",";

                cur = cur->left;
            }
            else //空节点
            {
                res += "#,";

                cur = s.top();
                s.pop();

                cur = cur->right;
            }
        }

        res += "#,"; //最后一个节点已出栈,其右为空 需要补一个

        return res;
    }


    //反序列化
    //入栈时处理
    TreeNode* deserialize(string data) {

        vector<string> v = split(data, ","); //分割字符串

        int index = 0;
        TreeNode* root = generate_node(v.at(index++));
        TreeNode* cur = root;

        stack<TreeNode*> s;

        while (cur || !s.empty())
        {
            if (cur)
            {
                s.push(cur);

                cur->left = generate_node(v.at(index++));
                cur = cur->left;
            }
            else
            {
                cur = s.top();
                s.pop();

                cur->right = generate_node(v.at(index++));
                cur = cur->right;
            }
        }

        return root;
    }

    //辅助函数
    vector<string> split(string& s, const char* delim)
    {
        vector<string> v;
        // char *strtok(char *str, const char *delim) 分解字符串 str 为一组字符串,delim 为分隔符
        char* str = strtok((char*)s.c_str(), delim);
        while (str)
        {
            v.push_back(string(str));
            //NULL的作用只是为了使得每次调用时,都不是从s的头开始,而是从上次调用时查找所停止的位置开始
            str = strtok(NULL, delim);
        }
        return v;
    }

层序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root==NULL)
            return "null,";
        string res;
        queue<TreeNode*>qu;
        qu.push(root);
        res = to_string(root->val) + ",";
        while(!qu.empty())
        {
            int len=qu.size();
            auto node=qu.front();
            qu.pop();
            
            if(node->left)
            {
                res+=to_string(node->left->val)+ ",";
                qu.push(node->left);
            }  
            else
                res+=("null,");

            if(node->right)
            {
                res+=to_string(node->right->val)+ ",";
                qu.push(node->right);
            }     
            else
                res+=("null,");     
        }

        return res;
        
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        vector<string>v = split(data, ',');
        queue<TreeNode*>qu;
        int i=0;
        TreeNode *root=generate_node(v[i++]);
        if(root)
            qu.push(root);
        while(!qu.empty())
        {
            TreeNode *node=qu.front();
            qu.pop();
            //左子节点
            node->left = generate_node(v[i++]);
            if(node->left)
                qu.push(node->left);

            //右子节点
            node->right=generate_node(v[i++]);
            if(node->right)
                qu.push(node->right);     
        }

        return root;
    
    }

    vector<string>split(string &str, char c)
    {
        vector<string>v;
		for (int left = -1, right = 0; right < str.length(); right++)
		{
			if (str[right] == c)
			{
                //++left指向子串开始的位置,right - left是长度
				v.push_back(str.substr(++left, right - left));
				left = right;//left下一位才是子串开始位置
			}
		}
		return v;
    }

    TreeNode* generate_node(const string& s)
    {
        if (s == "null")
        {
            return nullptr;
        }

        return new TreeNode(stoi(s));
    }
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));

字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

1、求得所有可能出现在第一个位置上的字符,将第一个位子上的字符与后面的交换

2、固定第一个字符,求后面字符的排列,就涉及递归的问题了

class Solution {
public:
    vector<string> Permutation(string str) {
        vector<string> result;
        int len=str.length();

        if(!len)
            return result;
        Permutations(result,str,0,len);
        return result;
    }
    
        void Permutations(vector<string> &result,string str,int index,int len)
        {
            if(index==len)
            {
                result.push_back(str);
                return;
            }
            for(int i=index;i<len;i++)
            {
                if(i!=index && str[i]==str[index])
                    continue;
                swap(str[i],str[index]);
                Permutations(result,str,index+1,len);
            }

        }
};

数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

  • 法1

时间复杂度: O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty())
            return 0;
        int length=numbers.size();
        for(int i=1;i<length;i++)
        {
            for(int j=length-2;j>=i;j--)
            {
                if(numbers[j]>numbers[j+1])
                {
                    int temp=numbers[j+1];
                    numbers[j+1]=numbers[j];
                    numbers[j]=temp;
                }
            }
        }
        int times=0;
        for(int i=0;i<length;i++)
        {
            if(numbers[i]==numbers[length/2])
                times++;
        }
        if(times*2>length)
            return  numbers[length/2];
        else
            return 0;
            

    
    }
};
  • 法2

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

  • 法3

时间复杂度: O ( n ) O(n) O(n)

遍历数组时保存连个值,一个是数组中的数字,一个是出现的次数,遍历到一个数字时,如果该数字和之前保存的数字相同,则次数加一,如果该数字和之前保存的数字不同,则次数减一,如果次数为零,遍历下一个数组元素,并把次数设为一。

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty())
            return 0 ;
        int length=numbers.size();
        
        int result=numbers[0];
        int times=1;
        for(int i=1;i<length;i++)
        {
            if(times==0)//该数字已经被自减到0 肯定不是所找的数,换下一个数组元素
            {
                result=numbers[i];
                times=1;
            }
            else if(numbers[i]==result)
                times++;
            else
                times--;
        }
        
        // verify whether the input is valid
        times=0;
        for(int j=0;j<length;j++)
        {
            if(numbers[j]==result)
                times++;
        }
        if(times*2>length)
            return result;
        else
            return 0;

最小的K个数

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

快排

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        int length=input.size();
        vector<int> result;
         
        if(input.empty()|| k>input.size())
            return result;
         
        quickSort(input,0,length-1);
 
        for(int i=0;i<k;i++)
            result.push_back(input[i]);
         
        return result;
         
    }
     
    int Partition(vector<int> &v,int low ,int high)
    {
        int pivot;
        pivot=v[low];//用子表的第一个记录作枢轴记录
        while(low<high)
       {
            while(low<high && v[high]>=pivot)
                high--;
            swap(v[low],v[high]);//high指示的值小于pivot,交换
            while(low<high && v[low]<=pivot)
                low++;
            swap(v[low],v[high]);//low指示的值大于pivot,交换
       }

       return low;
    }

    void quickSort(vector<int> &v,int low ,int high)
    {
        int i;
        if(low<high)
        {
            i=Partition(v,low,high);
            quickSort(v,low,i-1);
            quickSort(v,i+1,high);
        }

    }
};

优先队列

时间复杂度:O(nlog⁡k)

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        priority_queue<int,vector<int>,greater<int>> MinStack;
        vector<int> vec;

        for(auto c :arr)
            MinStack.push(c);

        for(auto i = 0; i< k;i++)
        {
            vec.push_back(MinStack.top());
            MinStack.pop();
        }

        return vec;
    }
};

剑指 Offer 41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。

示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制:

最多会对 addNum、findMedia进行 50000 次调用。
注意:本题与主站 295 题相同:https://leetcode-cn.com/problems/find-median-from-data-stream/

二分查找插入

class MedianFinder {
    vector<int>data;
public:
    /** initialize your data structure here. */
    MedianFinder() {

    }
    
    void addNum(int num)
    {
        if (data.empty())
            data.push_back(num);
        else
            data.insert(lower_bound(data.begin(), data.end(), num), num);     // binary search and insertion combined
    }
    double findMedian() {
        if(data.size() & 0x1)
            return data[data.size()/2];
        else
            return (data[data.size()/2] + data[data.size()/2-1])/2.0;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7QmpzImN-1598374982551)(/home/xiaohu/.config/Typora/typora-user-images/image-20200727214854261.png)]

优先队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EEc0Ak9i-1598374982554)(/home/xiaohu/.config/Typora/typora-user-images/image-20200727212358545.png)]

建立两个堆,最大堆、最小堆

addNum:

  • 奇数个数,下一个num压入最大堆
  • 偶数个数,下一个num压入最小堆
// 优先队列实现(注意“优先队列”用法类似stack,和普通队列不一样)
class MedianFinder {
    // 注意这里less默认最大堆,而greater是最小堆。
    //大顶堆保持较小元素
    priority_queue<int, vector<int>, less<int> > mMax;     // 大根堆
    //小顶堆保持较大元素
    priority_queue<int, vector<int>, greater<int> > mMin;  // 小根堆
public:
    /** initialize your data structure here. */
    MedianFinder() {

    }

    void addNum(int num)
    {
        int len = mMax.size() + mMin.size();
        if(len & 0x1){
            if(!mMin.empty() && num > mMin.top())
            {
                int temp = mMin.top();
                mMin.pop();
                mMin.push(num);
                num=temp;
            }
            mMax.push(num);//奇数个数,下一个num压入最大堆

        }
        else//偶数个数
        {
            if(!mMax.empty() && num < mMax.top())
            {
                int temp = mMax.top();
                mMax.pop();
                mMax.push(num);
                num=temp;
            }
            mMin.push(num);//偶数个数,下一个num压人最小堆
        }
    }

    double findMedian()
    {
        int len = mMax.size() + mMin.size();
        if(len & 0x1)
            return mMin.top();
        else
            return (mMax.top() + mMin.top()) / 2.0;
    }

};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

295. 数据流的中位数

class MedianFinder {
    priority_queue<int,vector<int>,less<int>>maxSt;//大根堆
    priority_queue<int,vector<int>,greater<int>>minSt;//小根堆
public:
    /** initialize your data structure here. */
    MedianFinder() {

    }
    
    void addNum(int num) {
        int len=minSt.size()+maxSt.size();
        if(len & 0x1)//奇数个数,下一个压如最大堆
        {
            if(!minSt.empty() && num > minSt.top())
            {
                int temp=minSt.top();
                minSt.pop();
                minSt.push(num);
                num=temp;
            }
            maxSt.push(num);
        }
        else    //偶数个数,下一个压如最小堆
        {
            if(!maxSt.empty() && num < maxSt.top())
            {
                int temp=maxSt.top();
                maxSt.pop();
                maxSt.push(num);
                num=temp;
            }
            minSt.push(num);
        }

    }
    
    double findMedian() {
        int len=minSt.size()+maxSt.size();
        if(len & 0x1)
            return minSt.top();
        else
            return (minSt.top() + maxSt.top())/2.0;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

动态规划

  • 1:令状态 dp[i] 表示以 A[i] 作为末尾的连续序列的最大和(这里是说 A[i] 必须作为连续序列的末尾)。

  • 2:做如下考虑:因为 dp[i] 要求是必须以 A[i] 结尾的连续序列,那么只有两种情况:

    这个最大和的连续序列只有一个元素,即以 A[i] 开始,以 A[i] 结尾,最大和就是 A[i] 本身。

    这个最大和的连续序列有多个元素,即从前面某处 A[p] 开始 (p<i),一直到 A[i] 结尾,最大和是 dp[i-1]+A[i]。
    得到状态转移方程

d p [ i ] = m a x ( A [ i ] , d p [ i − 1 ] + A [ i ] ) dp[i] = max(A[i], dp[i-1]+A[i]) dp[i]=max(A[i],dp[i1]+A[i])

时间复杂度: O ( n ) O(n) O(n)

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        vector<int> dp;
        int maxSum;
        dp.push_back(array[0]);
        
        for(int i=1;i<array.size();i++)
        {
            if((dp[i-1]+array[i])>array[i])
                dp.push_back(dp[i-1]+array[i]);
            else
                dp.push_back(array[i]);
        }
        
        maxSum=dp[0];
        for(int i=1;i<array.size();i++)
        {
            if(dp[i]>maxSum)
                maxSum=dp[i];
        }
        
        return maxSum;

    }
};

1~n整数中1出现的次数

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

暴力

时间复杂度: O ( n l o g n ) O(nlog n) O(nlogn)

class Solution {
public:
    int countDigitOne(int n) {
        long res=0;
        for(int i=1;i<=n;i++)
        {
            int j=i;
            while(j)
            {
                if(j%10==1)
                    res++;
                j=j/10;
            }
        }

        return res;
    }
};

超出时间限制!!!

数学规律

image.png

class Solution {
public:
    int countDigitOne(int n) {
      if(!n) return 0;
      vector<int> nums;
      int res=0;
      //将数的低位到高位依次存入数组中
      while(n) nums.push_back(n%10) , n=n/10;
      for(int i=nums.size()-1;i>=0;i--){
        auto left=0,right=0,t=1;
        for(int j=nums.size()-1;j>i;j--) 
            left=left*10+nums[j];
        for(int j=i-1;j>=0;j--)  
            right=right*10+nums[j],t=t*10;
        res+=left*t;
        if(nums[i]>1) 
            res+=t;
        if(nums[i]==1) 
            res+=right+1;
      }
      return res;
    }
};

剑指 Offer 44. 数字序列中某一位的数字

400. 第N个数字

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

请写一个函数,求任意第n位对应的数字。

示例 1:

输入:n = 3
输出:3

示例 2:

输入:n = 11
输出:0

限制:

0 <= n < 2^31
注意:本题与主站 400 题相同:https://leetcode-cn.com/problems/nth-digit/

参考:https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/mian-shi-ti-44-shu-zi-xu-lie-zhong-mou-yi-wei-de-6/

Picture1.png

1.确定所求数位的所在数字的位数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4YzuJFG-1598374982561)(/home/xiaohu/.config/Typora/typora-user-images/image-20200725095149577.png)]

2. 确定所求数位所在的数字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TNzJp3xt-1598374982564)(/home/xiaohu/.config/Typora/typora-user-images/image-20200725095136666.png)]

3. 确定所求数位在 numnum 的哪一数位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGmmfPRo-1598374982566)(/home/xiaohu/.config/Typora/typora-user-images/image-20200725095228414.png)]

class Solution {
public:
    int findNthDigit(int n) {
        long digit=1,start=1;
        long long count=9;
        while(n>count)
        {
            n-=count;
            digit++;
            start*=10;
            count=9*start*digit;
        }
        
        int num=start+(n-1)/digit;
        string s=to_string(num);
        int res=s[(n-1)%digit]-'0';
        return res;

    }
};

把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

把数字转换成字符串

sort()函数

sort(begin, end, cmp)
  • 参数
    第一个是要排序的数组的起始地址。
    第二个是结束的地址(最后一位要排序的地址)
    第三个参数是排序的方法,可以是从大到小也可是从小到大,还可以不写第三个参数,此时默认的排序方法是从小到大排序。
class Solution {
public:
    static bool cmp(int a,int b)
    {
        string A=to_string(a)+to_string(b);
        string B=to_string(b)+to_string(a);
        return A<B;
    }

    string minNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end(),cmp);
        string res;
        for(auto m:nums)
        {
            res+=to_string(m);
        }

        return res;
    }
};

剑指 Offer 46. 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"

提示:

0 <= num < 231

class Solution {
public:
    int translateNum(int num) {
        //unordered_map<int,string>v;
        // for(int i=0;i<26;i++)
        //     v[i]='a'+i;
        string s=to_string(num);
        int len=s.size();
        vector<int>dp(len+1);
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=len;i++)
        {
            //int temp=10*(s[i-2]-'0')+(s[i-1]-'0');//切记指引i不要写错
            int temp=atoi(s.substr(i-2,2).c_str());
            if(temp<26 && temp>=10)
                dp[i]=dp[i-1]+dp[i-2];
            else 
                dp[i]=dp[i-1];
        }

        return dp[len];

    }
};

剑指 Offer 47. 礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12

解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

提示:

  • 0 < grid.length <= 200
  • 0 < grid[0].length <= 200

动态规划

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        if(grid.empty() || grid[0].empty())
            return 0;
        int row=grid.size();
        int col=grid[0].size();
        for(int i=1;i<row;i++)
        {
            grid[i][0]+=grid[i-1][0];
        }
        for(int i=1;i<col;i++)
        {
            grid[0][i]+=grid[0][i-1];
        }
        for(int i=1;i<row;i++)
        {
            for(int j=1;j<col;j++)
            {
                grid[i][j]+=max(grid[i-1][j],grid[i][j-1]);
            }
        }

        return grid[row-1][col-1];


    }
};

3.无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

思路:dp[i]表示以i为下标的字符结尾的最长不重复子串的长度,分两种情况处理

  1. 如果str[i]在前面没出现,则dp[i] = dp[i-1] + 1
  2. 如果str[i]在前面出现,设在前面出现的位置为j,计算距离d = i - j
    • 如果d > dp[i-1],则dp[i] = dp[i-1] + 1;//重复字符在以s[i-1]结尾的不重复序列前
    • 如果d < dp[i-1],则dp[i] = d;//重复字符在以s[i-1]结尾的不重复序列后

动态规划+hash表

思路和 方法1 一样,动态规划。同时利用结构unordered_map作为哈希表,统计字符目前为止最后一次出现的位置(假设递推公式计算到dp[i],那么哈希表统计的是s[i]0~i-1最后出现的位置)。这样在递推计算dp[i]的时候,避免了在 0~i-1 查找s[i]的过程,空间换时间,降低了时间复杂度。

  • 时间复杂度O(n)
  • 空间复杂度O(n)
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.empty())
            return 0;
        int len=s.size();
        vector<int>dp(len,1);
        unordered_map<char,int>mp;
        mp[s[0]]=0;
        int res=1;
        for(int i=1;i<len;i++)
        {
            if(mp.count(s[i]) == 0) 
                dp[i] = dp[i-1] +1;
            else
            {
                int d = i - mp[s[i]];
                if(d > dp[i-1]) //重复字符在以s[i-1]结尾的不重复序列前
                    dp[i] = dp[i-1] + 1;
                else //重复字符在以s[i-1]结尾的不重复序列后
                    dp[i] = d;
            }
            mp[s[i]] = i;
            res=max(res,dp[i]);
        }

        return res;

    }
};

压缩空间

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size()==0)
            return 0;
        unordered_map<char,int>v;
        int res=1,temp=1;
        v[s[0]]=0;
        for(int i=1;i<s.size();i++)
        {
            if(v.find(s[i])==v.end())
            //if(v.count(s[i]) == 0) 
                temp++;
            else
            {
                int d=i-v[s[i]];
                if(d>temp)//重复字符在以s[i-1]结尾的不重复序列前
                    temp++;
                else//重复字符在以s[i-1]结尾的不重复序列后
                    temp=d;
            }
            v[s[i]]=i;
            res=max(res,temp);
        }
        return res;
    }
};

丑数

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

暴力

class Solution {
public:
    bool isUglyNumber(int n)
    {
        while(n>1)
        {
            if(n%2!=0 && n%3!=0 && n%5!=0)
                break;
            if(n%2==0)
                n/=2;
            if(n%3==0)
                n/=3;
            if(n%5==0)
                n/=5;
        }
        if(n==1)
            return true;
        else
            return false;
    }
    int nthUglyNumber(int n) {
        int i=1,count=0;
        for(;count<n;i++)
        {
            if(isUglyNumber(i))
                count++;
        }

        return i-1;
    }
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQXJewIp-1598374982568)(/home/xiaohu/.config/Typora/typora-user-images/image-20200725134622034.png)]

动态规划

创建数组保存已经找到的丑数,用空间换时间,不需要在非丑数的整数上计算

丑数 *2,3,5还是丑数

class Solution {
public:
    int nthUglyNumber(int n) {
        if(n==0)
            return 0;
        vector<int>dp(n);
        dp[0]=1;
        int number_2=0,number_3=0,number_5=0;
        for(int i=1;i<n;i++)//i从1开始
        {
            dp[i]=min(min(dp[number_2]*2,dp[number_3]*3),dp[number_5]*5);
            //去除重复元素 要进行多次比较
            if(dp[i]==dp[number_2]*2)
                number_2++;
            if(dp[i]==dp[number_3]*3)
                number_3++;
            if(dp[i]==dp[number_5]*5)
                number_5++;   
        }

        return dp[n-1];
    }
};

第一次只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写)

暴力

class Solution {
public:
    char firstUniqChar(string s) {
        if(s.empty())
            return ' ';
        unordered_map<char,int>m;
        for(int i=0;i<s.size();i++)
        {
            if(count(s.begin(),s.end(),s[i])==1)
                return s[i];
        }
        return ' ';

    }
};

哈希:两次遍历

class Solution {
public:
    char firstUniqChar(string s) {
        if(s.empty())
            return ' ';
        unordered_map<char,int>m;
        for(int i=0;i<s.size();i++)
        {
            m[s[i]]++;
        }

        for(int i=0;i<s.size();i++)
        {
            if(m[s[i]]==1)
                return s[i];
        }
        return ' ';

    }
};

按照插入顺序排序的哈希表

class Solution:
    def firstUniqChar(self, s: str) -> str:
        dic = collections.OrderedDict()
        for c in s:
            dic[c] = not c in dic
        for k, v in dic.items():
            if v: return k
        return ' '

# 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
暴力

class Solution {
public:
    int reversePairs(vector<int>& data) {
        int count=0;
        if(data.empty()||data.size()==1)
            return count;
        

        for(int i=0;i<data.size()-1;i++)
        {
            for(int j=i+1;j<data.size();j++)
                if(data[i]>data[j])
                    count++;
        }
        
        return count%1000000007;       

    }
};

归并

法1:

class Solution {
public:
    int mergeSort(vector<int>& nums, int l, int r) {
        if (l >= r) {
            return 0;
        }

        int mid = (l + r) >> 1;
        int inv_count = mergeSort(nums, l, mid) + mergeSort(nums, mid + 1, r);
        static vector<int> tmp;
        tmp.clear();
        int i = l, j = mid + 1;
        //把两个排序好的数组合为一个数组
        while (i <= mid && j <= r) 
        {
            if (nums[i] <= nums[j]) 
            {
                tmp.push_back(nums[i++]);
                //tmp[pos++] = nums[i++];//tmp记录归并排序后的数组
                inv_count += (j - mid - 1);//有nums[i]>nums[j-1],计算逆序对的个数
            }
            else //nums[i]>nums[j]
            {
                tmp.push_back(nums[j++]);
                //tmp[pos++] = nums[j++];
            }
        }
        //左边数组有剩余的,后面几个大的数
        while(i <= mid)
        {
            tmp.push_back(nums[i++]);
            inv_count += (j - (mid + 1));
        }
        //右边数组有剩余的,后面几个大的数
        while(j <= mid)
        {
            tmp.push_back(nums[j++]);
        }
        
        // //左边数组有剩余的,后面几个大的数
        // for (int k = i; k <= mid; ++k) {
        //     tmp.push_back(nums[k]);
        //     inv_count += (j - (mid + 1));
        // }
        // //右边数组有剩余的,后面几个大的数
        // for (int k = j; k <= r; ++k) {
        //     tmp.push_back(nums[k]);
        // }

        for(int i : tmp)
            nums[l++] = i;//写回原数组
        return inv_count;
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        return mergeSort(nums, 0, n - 1);
    }
};

法2:

class Solution {
public:
    int mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {
        if (l >= r) {
            return 0;
        }

        int mid = (l + r) >> 1;
        int inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r);
        int i = l, j = mid + 1, pos = l;
        //把两个排序好的数组合为一个数组
        while (i <= mid && j <= r) 
        {
            if (nums[i] <= nums[j]) 
            {
                tmp[pos++] = nums[i++];//tmp记录归并排序后的数组
                inv_count += (j - mid - 1);//有nums[i]>nums[j-1],计算逆序对的个数
            }
            else //nums[i]>nums[j]
            {
                tmp[pos++] = nums[j++];
            }
        }
        //左边数组有剩余的,后面几个大的数
        for (int k = i; k <= mid; ++k) {
            tmp[pos++] = nums[k];
            inv_count += (j - (mid + 1));
        }
        //右边数组有剩余的,后面几个大的数
        for (int k = j; k <= r; ++k) {
            tmp[pos++] = nums[k];
        }
        copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);
        return inv_count;
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        vector<int> tmp(n);
        return mergeSort(nums, tmp, 0, n - 1);
    }
};

参考:

class Solution {
public:
    int mergeSort(vector<int>& nums, vector<int>& tmp, int l, int r) {
        if (l >= r) {
            return 0;
        }

        int mid = (l + r) / 2;
        int inv_count = mergeSort(nums, tmp, l, mid) + mergeSort(nums, tmp, mid + 1, r);
        int i = l, j = mid + 1, pos = l;
        while (i <= mid && j <= r) {
            if (nums[i] <= nums[j]) {
                tmp[pos] = nums[i];
                ++i;
                inv_count += (j - (mid + 1));
            }
            else {
                tmp[pos] = nums[j];
                ++j;
            }
            ++pos;
        }
        for (int k = i; k <= mid; ++k) {
            tmp[pos++] = nums[k];
            inv_count += (j - (mid + 1));
        }
        for (int k = j; k <= r; ++k) {
            tmp[pos++] = nums[k];
        }
        copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);
        return inv_count;
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        vector<int> tmp(n);
        return mergeSort(nums, tmp, 0, n - 1);
    }
};

剑指 Offer 52.两个链表中的第一个公共结点

输入两个链表,找出它们的第一个公共结点。

双指针+2次遍历

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int len1=ListNodeLength(pHead1);
        int len2=ListNodeLength(pHead2);
        ListNode* first=pHead1;
        ListNode* second=pHead2;
        int len=0;
        if(len1>len2)
        {
            len=len1-len2;
            while(len--)
            {
                first=first->next;
            }
        }
        else
        {
            len=len2-len1;
            while(len--)
            {
                second=second->next;
            }
        }
        
        while(first!=second)
        {
            first=first->next;
            second=second->next;
        }
        
        return first;
        
    }
    
    int ListNodeLength(ListNode* pHead)
    {
        int len=0;
        while(pHead)
        {
            len++;
            pHead=pHead->next;
        }
        
        return len;
    }
};

剑指 Offer 53 - II. 0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

示例 1:

输入: [0,1,3]
输出: 2

示例 2:

输入: [0,1,2,3,4,5,6,7,9]
输出: 8

限制:

1 <= 数组长度 <= 10000
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        if(nums[0]!=0)
            return 0;
        for(int i=1;i<nums.size();i++)
        {
            if(nums[i]!=nums[i-1]+1)
                return nums[i-1]+1;
        }

        return nums.size();

    }
};

剑指 Offer 53 - II. 0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

示例 1:

输入: [0,1,3]
输出: 2

示例 2:

输入: [0,1,2,3,4,5,6,7,9]
输出: 8

限制:

1 <= 数组长度 <= 10000
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        if(nums[0]!=0)
            return 0;
        for(int i=1;i<nums.size();i++)
        {
            if(nums[i]!=nums[i-1]+1)
                return nums[i-1]+1;
        }

        return nums.size();

    }
};

剑指 Offer 54. 二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第k大的节点。

示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 4

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 4

限制:

1 ≤ k ≤ 二叉搜索树元素个数

中序遍历

递归

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    vector<int>res;
public:
    int kthLargest(TreeNode *root, int k) {
        helper(root);
        return res[res.size()-k];
    }
    //右根左
    void helper(TreeNode *root){
        if(root==NULL){
            return;
        }
        helper(root->left);
        res.push_back(root->val);
        helper(root->right); 
    }
};

迭代

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int kthLargest(TreeNode* root, int k) {
        if(root==NULL)
            return 0;
        vector<int>res;
        stack<TreeNode*>st;
        TreeNode* node=root;
        while(node || !st.empty())
        {
            while(node)
            {
                st.push(node);
                node=node->left;
            }

            node=st.top();
            st.pop();
            res.push_back(node->val);
            node=node->right;
        }

        return res[res.size()-k];
    }
};

反中序遍历

递归

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
    int count=0,num=0;
public:
    int kthLargest(TreeNode *root, int k) {
        helper(root,k);
        return num;
    }
    //右根左
    void helper(TreeNode *root,int k){
        if(root==NULL){
            return;
        }
        helper(root->right,k);
        count++;
        if(count==k){
            num=root->val;
            return;
        }
        helper(root->left,k);
    
    }
};

迭代

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int kthLargest(TreeNode* root, int k) {
        if(root==NULL)
            return 0;
        int res=0;
        stack<TreeNode*>st;
        TreeNode *node=root;
        while(node || !st.empty())
        {
            while(node)
            {
                st.push(node);
                node=node->right;
            }

            res++;
            node=st.top();
            st.pop();
            if(res==k)
                return node->val;
            
            node=node->left;
        }

        return res;
    }
};

二叉树深度

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {
        if(pRoot==NULL)
            return 0;
        
        int nleft=TreeDepth(pRoot->left);
        int nright=TreeDepth(pRoot->right);
        
        return (nleft>nright)?(nleft+1):(nright+1);
    
    }
};

# 平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isBalanced(TreeNode* root) {
        if(!root)
            return true;
        if(abs(depthOfBalanced(root->left) - depthOfBalanced(root->right)) > 1)
            return false;
        return isBalanced(root->left) && isBalanced(root->right);


        
    }

    int depthOfBalanced(TreeNode* root)
    {
        if(root==NULL)
            return 0;
        //不需要考虑空,前面判断了哦
        // if(!root->left)
        //     return depthOfBalanced(root->right)+1;
        // if(!root->right)
        //     return depthOfBalanced(root->left)+1;
        return max(depthOfBalanced(root->left),depthOfBalanced(root->right))+1;
    }
};

剑指 Offer 56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]

示例 2:

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

限制:

  • 2 <= nums.length <= 10000
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        //异或结果至少有一位为1
        int ret = 0;
        for (int n : nums)
            ret ^= n;
        int div = 1;
        //获得k中最低位的1
        while ((div & ret) == 0)
            div <<= 1;
        int a = 0, b = 0;
        for (int n : nums)
            if (div & n)
                a ^= n;
            else
                b ^= n;
        return vector<int>{a, b};
    }
};

# 数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> counts;
        int N = (int)nums.size();
        for (int i = 0; i < N; i++) {
            ++counts[nums[i]];
        }
        for (int i = 0; i < N; i++) {
            if (counts[nums[i]] == 1) {
                return nums[i];
            }
        }
        return nums[0];
    }
};


# 和为s的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:

对应每个测试案例,输出两个数,小的先输出。

哈希set

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int>res;
        unordered_set<int>m;
        for(auto v:nums)
        {
            if(m.find(target-v)==m.end())
                m.insert(v);
            else
            {
                res={target-v,v};
                break;
            }

        }
        return res;
    }
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWMwjz4Q-1598374982571)(/home/xiaohu/.config/Typora/typora-user-images/image-20200725215305025.png)]

双指针

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int>res;
        int left=0,right=nums.size()-1;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[left]+nums[right]==target)
            {
                res={nums[left],nums[right]};
                break;
            }
                
            else if(nums[left]+nums[right]<target)
                left++;
            else
                right--;
            
        }
        return res;
    }
};
和为s的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

暴力

class Solution {
    vector<vector<int>>res;
public:
    vector<vector<int>> findContinuousSequence(int target) {
        for(int i=1;i<=target/2;i++)
            ContinuousSequence(i,target);
        return res;
    }

    void ContinuousSequence(int i,int target)
    {
        int count=0;
        vector<int>temp;
        while(i<=target/2+1)
        {
            count+=i;
            temp.push_back(i);
            if(count==target)
            {
                res.push_back(temp);
                return;
            }          
            if(count>target)
                break;
            i++;
        }
        return;
    }
};

双指针

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>>vec;
        vector<int> res;
        for (int l = 1, r = 2; l < r;){
            int sum = (l + r) * (r - l + 1) / 2;
            if (sum == target){
                res.clear();
                for (int i = l; i <= r; ++i) res.emplace_back(i);
                vec.emplace_back(res);
                l++;//只能增大l
            }
            else if (sum < target) 
                r++; //l不变,增大r,即增大个数
            else l++;//r不变,增大l,即减小个数
        }
        return vec;
    }
};

# 翻转单词顺序

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

class Solution {
public:
    void Reverse(string &s,int start,int end)
    {
        char temp;
        while(start<end)
        {
            temp=s[start];
            s[start]=s[end];
            s[end]=temp;
            start++;
            end--;
        }
    }
    
    string ReverseSentence(string str) {
        int len = str.length();
        string &s=str;
        Reverse(s,0,len-1);
        int m= -1;
        for(int i=0;i<len;i++){
            if(str[i] == ' '){
                Reverse(s,m+1,i-1);
                m = i;
            }
        }
        Reverse(s,m+1,len-1);
        return str;
    }
};

istringstream

class Solution {
public:
    string reverseWords(string s) {
        //stringstream 
        //ss 输出单词
        //res 存储结果
        istringstream ss(s);
        string res,cur;
        while(ss>>cur){
            res = cur+' '+res;
        }
        return res.substr(0,res.size()-1);
    }
};
class Solution {
public:
    string reverseWords(string s) {
        stack<string> stk;
        string res,str;
        istringstream ss(s);
        while (ss >> str)//处理行内的单个单词
        {
            stk.push(str);
            stk.push(" ");
        } 

        //去除最后压入的一个空格呀
        if (!stk.empty()) 
            stk.pop();
        while (!stk.empty()) 
        {
            res += stk.top();
            stk.pop();          
        }

        return res;
    }
};

# 左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

substr

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        return s.substr(n)+s.substr(0,n);

    }
};
class Solution {
public:
    string LeftRotateString(string str, int n) {
        string s1,s2;
        
        for(int i=0;i<n;i++)
        {
            s1+=str[i];
        }
        
        for(int i=n;i<str.size();i++)
        {
            s2+=str[i];
        }
        
        return s2+s1;

    }
};

# 滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

暴力

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        if(nums.empty()||k>nums.size() ||k==0)
            return result;
        for(int i=0;i<=nums.size()-k;i++)
        {
            int m=nums[i];
            for(int j=i+1;j<i+k;j++)
            {
                m=max(nums[j],m);
            }
            result.push_back(m);
        }
        
        return result;

    }
};

单调队列

class Solution {
deque<int>window;
public:
    void push(int n)
    {
        //把前面比新元素小的元素都删掉
        while(!window.empty() && window.back() < n)//队列的元素保持单调递减
            window.pop_back();
        window.push_back(n);
    }
    
    int max()
    {
        return window.front();
    }
        

    
    void pop(int n)
    {
        //要删除的元素不是窗口内最大元素,就不用删除
        if(!window.empty() && window.front()==n)
        {
            window.pop_front();
        }
    }

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;    //用来存结果的vector   
        for(int i=0; i<nums.size(); ++i)
        {
            if(i<k-1)//先填满窗口的k-1个值
                push(nums[i]);
            else//窗口向前移动
            {
                push(nums[i]);
                res.push_back(max());
                pop(nums[i-k+1]);
            }          
        }
        return res;
    }
};



push 操作中含有 while 循环,时间复杂度不是 O(1) 呀,那么本算法的时间复杂度应该不是线性时间吧?

单独看 push 操作的复杂度确实不是 O(1),但是算法整体的复杂度依然是 O(N)线性时间。要这样想,nums 中的每个元素最多被 push_back 和 pop_back 一次,没有任何多余操作,所以整体的复杂度还是 O(N)

剑指 Offer 59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_valuepush_backpop_front均摊时间复杂度都是O(1)。

若队列为空,pop_frontmax_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1] 

限制:

  • 1 <= push_back,pop_front,max_value的总操作数 <= 10000
  • 1 <= value <= 10^5

队列+双端队列

class MaxQueue {
    queue<int>que;
    deque<int> deq; //辅助双端队列,随着每次push记录队列中相应最大值、第二大、第三大.....单调非递增
public:
    MaxQueue() {
    }
    
    int max_value() {
        if(que.empty())
            return -1;
        return deq.front();//最大值在队头

    }
    
    void push_back(int value) {
        que.push(value);
        if(deq.size()==0)
            deq.push_back(value);
        else if(value>deq.front())
        {
            deq.clear();
            deq.push_back(value);//更新最大值
        }
        else{
            while(deq.back() < value)
                deq.pop_back();
            deq.push_back(value);
        }
    }
    
    int pop_front() {
        if(que.empty())
            return -1;
        int res=que.front();
        que.pop();
        if(res==deq.front())
            deq.pop_front();//若弹出的元素为最大值,需弹出双端队列队头元素
        return res;
    }
};

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue* obj = new MaxQueue();
 * int param_1 = obj->max_value();
 * obj->push_back(value);
 * int param_3 = obj->pop_front();
 */

剑指 Offer 60. n个骰子的点数

难度简单81收藏分享切换为英文关注反馈

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

限制:

1 <= n <= 11

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CaaFdWkz-1598374982573)(/home/xiaohu/.config/Typora/typora-user-images/image-20200726154114885.png)]

class Solution {
    public double[] twoSum(int n) {
        int [][]dp = new int[n+1][6*n+1];
        //边界条件
        for(int s=1;s<=6;s++)dp[1][s]=1;
        for(int i=2;i<=n;i++){
            for(int s=i;s<=6*i;s++){
                //求dp[i][s]
                for(int d=1;d<=6;d++){
                    if(s-d<i-1)break;//为0了
                    dp[i][s]+=dp[i-1][s-d];
                }
            }
        }
        double total = Math.pow((double)6,(double)n);
        double[] ans = new double[5*n+1];
        for(int i=n;i<=6*n;i++){
            ans[i-n]=((double)dp[n][i])/total;
        }
        return ans;
    }
}

扑克牌中的顺子

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。

排序+遍历

不能有重复元素

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int i=0;
        while(i<nums.size() && nums[i]==0)
        {
            i++;//i指向第一个非零元素,没非零元素,为5
        }
        if(i==nums.size())
            return true;//全为0时,为true
        for(int j=i;j<4;j++)//注意i,j不要写错了,不然会堆栈溢出
        {
            if(nums[j]==nums[j+1])
                return false;//判断其中是否有大小相同的牌
        }

        return nums[4]-nums[i]<=4;//最大的牌和非0最小牌不能超过4
    }
};

## 法2

  1. 首先对数组排序,统计0的个数,如果有重复数字出现,不连续。
  2. 统计排序后数组中相邻数字的空缺总数。
  3. 如果空缺总数小于等于0的个数,那么此数组是连续的
class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.size()<5)
            return false;
        
        sort(numbers.begin(),numbers.end());
        
        int number_zero=0;
        int number_gap=0;
        
        for(int i=0;i<numbers.size() && numbers[i]==0;i++)
            number_zero++;
        
        //统计数组中的间隔数目
        //前number_zero为0;
        int small=number_zero;
        int big=small+1;
        while(big<numbers.size())
        {
            if(numbers[small]==numbers[big])
                return false;
            
            number_gap+=numbers[big]-numbers[small]-1;
            small=big;
            big++;
        }
        
        return (number_zero<number_gap)?false:true;
    }
};

# 圆圈中最后剩下的数字

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

如果没有小朋友,请返回-1

动态规划

参考 :https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/huan-ge-jiao-du-ju-li-jie-jue-yue-se-fu-huan-by-as/

递归求解规律:

约瑟夫环2.png

在这里插入图片描述

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n<1 || m<1)
            return -1;
        
        int last=0;
        for(int i=2;i<=n;i++)
            last=(last+m)%i;
        
        return last;
    }
};

股票的最大利润

可以有一次买入和一次卖出,那么买入必须在前。求最大收益。

使用贪心策略,假设第 i 轮进行卖出操作,买入操作价格应该在 i 之前并且价格最低。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int min_price=INT_MAX;
        int max_profit=0;
        for(int i=0;i<prices.size();i++)
        {
            min_price=min(prices[i],min_price);
            max_profit=max(prices[i]-min_price,max_profit);
        }

        return max_profit;

    }
};

# 求1+2+…+n

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

class Solution {
public:
    int sumNums(int n) {
        return n == 0 ? 0 : n + sumNums(n - 1);
    }
};
class Solution {
public:
    int sumNums(int n) {
        n && (n += sumNums(n-1));
        return n;
    }
};

# 不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

位运算

https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/solution/mian-shi-ti-65-bu-yong-jia-jian-cheng-chu-zuo-ji-7/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-967Qoe3s-1598374982577)(/home/xiaohu/.config/Typora/typora-user-images/image-20200726174911596.png)]

Picture1.png

class Solution {
public:
    int add(int a, int b) {
        //循环直到b等于0
        while(b)
        {
            int temp=a^b;//不用进位的部分
            b=(unsigned int)(a & b)<<1;//进位的部分
            a=temp;
        }

        return a;
    }
};

构建乘积数组

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。
构建乘积数组

下三角和上三角想乘得到B

Picture1.png

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cvE9Qeel-1598374982584)(/home/xiaohu/.config/Typora/typora-user-images/image-20200726181051764.png)]

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        if(a.empty())
            return vector<int>();
        vector<int> B(a.size(),1); 
        int temp=1;
        for(int i=1;i<a.size();i++)
        {
            B[i]=B[i-1]*a[i-1];//下三角
        }  
        for(int i=a.size()-2;i>-1;i--)
        {
            temp*=a[i+1];//上三角
            B[i]*=temp;//下三角 * 上三角
        } 

         return B;      
    }
};

剑指 Offer 67. 把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 1:

输入: "42"
输出: 42

示例 2:

输入: "   -42"
输出: -42
解释: 第一个非空白字符为 '-', 它是一个负号。
     我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:

输入: "4193 with words"
输出: 4193
解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。

示例 4:

输入: "words and 987"
输出: 0
解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
     因此无法执行有效的转换。

示例 5:

输入: "-91283472332"
输出: -2147483648
解释: 数字 "-91283472332" 超过 32 位有符号整数范围。 
     因此返回 INT_MIN (−231) 。

注意:本题与主站 8 题相同:https://leetcode-cn.com/problems/string-to-integer-atoi/

class Solution {
public:
    int strToInt(string str) {
        long res=0;
        int flag=1;
        while(str[0]==' ')
            str=str.substr(1);
        if(str[0]=='+' || str[0]=='-')
        {
            if(str[0]=='+')
                flag=1;
            else
                flag=-1;
            str=str.substr(1);
            while(str[0]-'0'>=0 && str[0]-'0'<=9)
            {
                res=10*res+str[0]-'0';
                if(flag*res>INT_MAX )
                    return INT_MAX ;
                else if(flag*res<INT_MIN)
                    return INT_MIN;
                str=str.substr(1);
            }

            return flag*res;
        }
        else
        {
            while(str[0]-'0'>=0 && str[0]-'0'<=9)
            {
                res=10*res+str[0]-'0';
                if(res>INT_MAX )
                    return INT_MAX ;
                str=str.substr(1);
            }
            return res;
        }

        return 0;    
    }
};

面试题68 - I. 二叉搜索树的最近公共祖先

难度简单43

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

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

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]

img

示例 1:

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

示例 2:

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

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

递归

  • 既然是BST,如果p和q都比root.val小,则递归左子树,如果都比root.val大,则递归右子树,否则,root 即为所求
  • 因为 root 为 p,q 的最近公共祖先,只可能是下面的情况
    • p,q 分居root的两个子树
    • p 为 root ,q 在root的左或右子树中
    • q 为 root , p 在root 的左或右子树中
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (p->val < root->val && q->val < root->val) {
            return lowestCommonAncestor(root->left, p, q);
        }
        if (p->val > root->val && q->val > root->val) {
            return lowestCommonAncestor(root->right, p, q);
        }
    
        return root;  
    }
};

通用方法:使用与二叉树

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==NULL)
            return NULL;
        if(root==p || root==q)
            return root;
        //root不等于p或q
        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(p && q)
            return root;
        return NULL;
        
    }
};

面试题68 - II. 二叉树的最近公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

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

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]

img

示例 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。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==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;
        //left、right为空
        if(p && q)
            return root;
        return NULL;
    }
};

迭代:

  • while 循环,当 root 为null时退出
  • 如果p q都在root的左子树中,则遍历到root.left
  • 如果p q都在root的右子树中,则遍历到root.right
  • 否则,说明当前root就是最近公共祖先,找到了,break
  • 最后返回 root 即为所求
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //二叉搜索树
        while (root) {
            if (p->val < root->val && q->val < root->val) 
            {
                root = root->left;
            } 
            else if (p->val > root->val && q->val > root->val) 
            {
                root = root->right;
            } 
            else {
                break;
            }
        }
        return root;
    }
};

迭代

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //二叉搜索树
        while (root) {
            if (p->val < root->val && q->val < root->val) {
                root = root->left;
            } 
            else if (p->val > root->val && q->val > root->val) {
                root = root->right;
            } 
            else 
            {
                break;
            }
        }
        return root;
    }
};
  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值