《剑指offer》75题 C++详细题解

目录

简单:

剑指 Offer 03. 数组中重复的数字

map:

unordered_map:

 原地交换

剑指 Offer 05. 替换空格

剑指 Offer 06. 从尾到头打印链表

出栈入栈

双指针

 reverse库函数

剑指 Offer 09. 用两个栈实现队列

剑指 Offer 10- I. 斐波那契数列

剑指 Offer 10- II. 青蛙跳台阶问题

剑指 Offer 11. 旋转数组的最小数字

剑指 Offer 18. 删除链表的节点

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

剑指 Offer 22. 链表中倒数第k个节点

​编辑

剑指 Offer 24. 反转链表

法1:双指针

法2:栈

剑指 Offer 25. 合并两个排序的链表

法1:迭代

法2:递归 

剑指 Offer 27. 二叉树的镜像

法1:递归前序 

 法2:迭代深度优先遍历(前序)

 法3:迭代广度优先遍历(层序)

剑指 Offer 28. 对称的二叉树

 法1:递归

法2:迭代队列、栈

剑指 Offer 29. 顺时针打印矩阵

模拟 

剑指 Offer 30. 包含min函数的栈

法1:bfs

法2:dfs

剑指 Offer 39. 数组中出现次数超过一半的数字

 法1:排序

法2:Boyer-Moore 投票算法 

法2:基于快排思想,递归版本

剑指 Offer 42. 连续子数组的最大和

​编辑

动态规划子序列

法1:unordered_map直接插入

法2:遍历map 

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

剑指 Offer 53 - I. 在排序数组中查找数字 I

二分查找

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

二分查找

二分补充笔记:

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

剑指 Offer 55 - I. 二叉树的深度

 剑指 Offer 55 - II. 平衡二叉树

剑指 Offer 57. 和为s的两个数字

剑指 Offer 57 - II. 和为s的连续正数序列

暴力

滑动窗口

剑指 Offer 58 - I. 翻转单词顺序

剑指 Offer 58 - II. 左旋转字符串

剑指 Offer 62. 圆圈中最后剩下的数字

剑指 Offer 65. 不用加减乘除做加法

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

剑指 Offer 68 - II. 二叉树的最近公共祖先

面试题61. 扑克牌中的顺子

中等

剑指 Offer 04. 二维数组中的查找

二分:

Z型查找

剑指 Offer 07. 重建二叉树

剑指 Offer 12. 矩阵中的路径

剑指 Offer 14- I. 剪绳子

回溯

动归

完全背包 

 贪心

剑指 Offer 14- II. 剪绳子 II

贪心

剑指 Offer 16. 数值的整数次方

二分快速幂

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

剑指 Offer 26. 树的子结构

剑指 Offer 31. 栈的压入、弹出序列

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

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

剑指 Offer 33. 二叉搜索树的后序遍历序列

递归

剑指 Offer 34. 二叉树中和为某一值的路径 

剑指 Offer 35. 复杂链表的复制

 哈希

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

中序递归

剑指 Offer 38. 字符串的排列

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

找规律 

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

动归

递归回溯 

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

剑指 Offer 48. 最长不含重复字符的子字符串

滑动窗口

剑指 Offer 49. 丑数

 动态规划

最小堆

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

滑动窗口 

unorsered_map 

 位运算

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

动态规划

剑指 Offer 63. 股票的最大利润

剑指 Offer 64. 求1+2+…+n

递归

剑指 Offer 66. 构建乘积数组

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

面试题13. 机器人的运动范围

回溯 dfs

面试题45. 把数组排成最小的数

 自定义排序

面试题59 - II. 队列的最大值

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

剑指 Offer 37. 序列化二叉树

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

优先级队列

 力扣

剑指 Offer 43. 1~n 整数中 1 出现的次数

剑指 Offer 51. 数组中的逆序对

暴力双层for循环-->归并排序 

剑指 Offer 59 - I. 滑动窗口的最大值


 


简单:


剑指 Offer 03. 数组中重复的数字

map:

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        map<int,int> a;
        for(auto& i:nums)
        {
            auto w=a.insert(make_pair(i,1));
            if(w.second==false)
            {
                w.first->second++;
            }
        }
        for(auto &w:a)
        {
            if(w.second>1)
            {
                return w.first;
            }
        }
        return 1;
    }
};

unordered_map:

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int,bool> a;
        for(auto &i:nums)
        {
            if(a[i])
                return i;
            a[i]=true;
        }
        return 1;
    }
};

 原地交换

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        int i=0;
        while(i<nums.size())
        {
            if(nums[i]==i)
            {
                i++;
                continue;
            }
            if(nums[i]==nums[nums[i]])
                return nums[i];
            swap(nums[i],nums[nums[i]]);
        }
        return 1;
    }
};


剑指 Offer 05. 替换空格

class Solution {
public:
    string replaceSpace(string s) {
        int res=0;
        for(int i=0;i<s.size();i++)
        {
            if(s[i]==' ')
                res++;
        }

        int len=s.size();
        s.resize(len+res*2);
        for(int i=len-1,j=s.size()-1;i<j;i--,j--)
        {
            if(s[i]!=' ')
                s[j]=s[i];
            else
            {
                s[j]='0';
                s[j-1]='2';
                s[j-2]='%';
                j-=2;
            }
        }
        return s;
    }
};

 可以记住这个库函数,面试时不推荐使用!

return s.replace(" ", "%20") ;

剑指 Offer 06. 从尾到头打印链表

出栈入栈

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    vector<int>res;
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int> a;
        while(head)
        {
            a.push(head->val);
            head=head->next;
        }
        while(!a.empty())
        {
            res.push_back(a.top());
            a.pop();
            
        }
        return res;

    }
};

双指针

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
    vector<int>a;
public:
    vector<int> reversePrint(ListNode* head) {
        ListNode *i=NULL;
        ListNode *j=head;
        ListNode*next;
        while(j)
        {
            next=j->next;
            j->next=i;
            i=j;
            j=next;

        }
        
       while(i)
       {
           a.push_back(i->val);
           i=i->next;
       }
        return a;
    }
};

 reverse库函数

        vector<int> res;   
        while(head){
            res.push_back(head->val);
            head = head->next;
        }
        //algorithm中的reverse反转res
        reverse(res.begin(),res.end());
        return res;


剑指 Offer 09. 用两个栈实现队列

 一个入栈,一个出栈。

尾插负责入栈

头删:栈后进先出,先把入栈里面的元素调出到出栈里,此时在出栈的第一个元素就是队列要删的。

class CQueue {
private:
    stack<int> a;
    stack<int> b;
public:
    CQueue() {    
        while (!a.empty()) {
            a.pop();
        }
        while (!b.empty()) {
            b.pop();
        }
    }
    
    void appendTail(int value) {
        a.push(value);
       
    }
    
    int deleteHead() {
        if(b.empty())
        {
            while(!a.empty())
            {
                b.push(a.top());
                a.pop();
            }
        }

        if(b.empty())
        {
            return -1;
        }
        else
        {
            int x=b.top();
            b.pop();
             return x;
        }
       return -1;

    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

剑指 Offer 10- I. 斐波那契数列


剑指 Offer 10- II. 青蛙跳台阶问题

class Solution {
public:
    int numWays(int n) {
        int a=1,b=1,c=2;
        
        for(int i=0;i<n;i++)
        {
            c=(a+b)%1000000007;
            a=b;
            b=c;
        }
        return a;
    }
};


剑指 Offer 11. 旋转数组的最小数字

分析:

两边都是升序,左区间都比右区间大

答案在左右区间的分界点上,查找最小元素-->优化为二分

可以与右区间端点比较,如果numbers[mid]==numbers[ r ],r--,因为无法确定旋转点x在哪个区间,所以减小区间。(滑动窗口)

或者使用线性查找该区间,因为此时区间 [ l,m ] 内所有元素相等 或 区间 [ m,r ] 内所有元素相等(或两者皆满足)

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int l=0,r=numbers.size()-1,mid=0;
        while(l<r)
        {
            mid=l+((r-l)>>1);
            if(numbers[mid]>numbers[r])
            {
                l=mid+1;
            }
            else if(numbers[mid]<numbers[r])
            {
                r=mid;
            }
            else 
                r--;
            // else//numbers[mid]==numbers[r]
            // {
            //     int x=l;
            //     for(int k=l+1;k<r;k++)
            //     {
            //         if(numbers[k]<numbers[x])
            //             x=k;
            //     }
            //     return numbers[x];
            // }
        }
        return numbers[r];

    }
};

时间O( \log _{2}N )  ,全是重复数字的数组时O(N)

空间O(1)


法1:位运算

按位与1:都是1才等于1,右移一位获取每一位的二进制

法2:位运算优化

32位二进制,右移31次即可

法3: n&(n−1)

n:

n-1:把n最右边的1的右边的0,都变成1;这个1变成0

n&(n−1):把n-1最右边的1变成0,其余不变。相当于把n最右边的1变成0

循环数一数要消去几个1

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res=0;
        // while(n)
        // {
        //     if(n&1)
        //         res++;
        //     n=n>>1;
        // }

        // for(int i=0;i<32;i++)
        // {
        //     if((n>>i)&1)
        //         res++;
            
        // }

        while(n)
        {
            n=n&(n-1);
            res++;
        }
        return res;
    }
};

时间 O(N) ,N为1的个数;O(1),第三种方法

空间 O(1)


法1:暴力全排列

问题:如果出现大数,超出int32的整数范围,无法存储

class Solution {
private:
    vector<int> a;
public:
    vector<int> printNumbers(int n) {
        int k=1,x=0;
        
        while(n--)
        {
            k=k*10;
        }
        while(x<k-1)
        {
            x++;
            a.push_back(x);
        }
        return a;
    }
};

 法2:递归实现排列型枚举(没想到哇!)

排列问题,{1,2}、{2,1}是两种答案。每一层都要从0号下标元素开始,不需要startindex记录下一层从第几个数字开始搜索。

此题需要出现{1,0},{1,1}这种答案,一个答案中每个数字可以重复,不需要used数组来标记已经选择的元素。

层数:n层

每个节点的子树个数(宽度):0~9种情况

使用stoi是将字符串转成整数,只是为了通过本题。大数的情况下返回字符串即可。

 

class Solution {
   vector<int>ans;
   string b;
   //int n;
   
public:
    void dfs(int x,int n)
    {
        if(b.size()==n)
        {
            int num=stoi(b);
            if(num!=0)
                ans.push_back(num);
            return;
        }

        for(int i=0;i<10;i++)
        {
            b+=to_string(i);
            dfs(x+1,n);
            b.pop_back();
        }
    }
    vector<int> printNumbers(int n) {
        //this.n=n;
        ans.clear();
        b.clear();
        dfs(0,n);
       
       return ans;
    }

};

时间:O(10^n)递归深度

空间:O(10^n)ans结果数组大小为10^n-1


剑指 Offer 18. 删除链表的节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head==NULL)
            return NULL;

        ListNode*newh=new ListNode(0);
        ListNode*c=newh;

        newh->next=head;
        while(c->next)
        {
            if(c->next->val==val)
            {
                c->next=c->next->next;
               
                break;
            }
            else
            {
                c=c->next;                
            }
        }
        head=newh->next;
        delete newh;
        return head;

    }
};

 时间:O(N), N 为链表长度,删除操作平均需循环 N/2 次,最差 N 次。

空间:O(1)


剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

补充 快排双指针法的思想:

prev指向起点,cur指向起点+1,key在最左侧,如果cur<=key的值,prev++,交换prev和cur,cur++。否则cur++

最后cur已经越界,交换prev和最左侧key

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int i=-1,j=0;
        while(j<nums.size())
        {
            if(nums[j]%2!=0)
            {
                i++;
                swap(nums[i],nums[j]);
                j++;
            }
            else
            {
                j++;
            }
        }
        return nums;
    }
};

时间:O(N)

空间:O(1)


剑指 Offer 22. 链表中倒数第k个节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        if(head==NULL)
            return head;
        
        ListNode*i=head;
        ListNode*j=head;

         while(k--&&j!=NULL)
        {
                j=j->next;
        }
        while(j)
        {
           
            j=j->next;
            i=i->next;

        }
        return i;
    }
};


剑指 Offer 24. 反转链表

法1:双指针

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==NULL)
            return NULL;
        
        ListNode*p=NULL;
        ListNode*c=head;
        
        while(c)
        {
            ListNode*n=c->next;
            c->next=p;

            p=c;
            c=n;
           
        }
        return p;



    }
};

法2:栈

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
   
public:
    ListNode* reverseList(ListNode* head) {
         stack<int>a;
        while(head)
        {
            a.push(head->val);
            head=head->next;
        }
        ListNode* b=new ListNode(0);
        ListNode*c=b;
        while(!a.empty())
        {
            c->next=new ListNode(a.top());;
            a.pop();
            c=c->next;
        }

        return b->next;

    }
};

时间:O(N)

空间:O(1)、O(N)


剑指 Offer 25. 合并两个排序的链表

法1:迭代

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {

        if(l1==NULL&&l2!=NULL)
            return l2;
        if(l2==NULL&&l1!=NULL)
            return l1;
        if(l1==NULL&&l2==NULL)
            return l1;

        ListNode*i=l1;
        ListNode*j=l2;

        ListNode*head=new ListNode(0);
        ListNode*cur=head;

        while(i!=NULL&&j!=NULL)
        {
            
            if(i->val<=j->val)
            {
                cur->next=i;
                i=i->next;

            }
            else
            {
                cur->next=j;
                j=j->next;
            }
            cur=cur->next;
                       
        }

        if(i==NULL)
        {
            cur->next=j;
            return head->next;
        }
        else
        {
            cur->next=i;
            return head->next;
        }
        
    }
};

法2:递归 

mergeTwoLists(l1, l2)返回的是两个链表头中值小的那个,如果l1 小, 就返回l1, 在返回l1之前会去找l1的下一个节点和l2 哪个更小来当刚才要返回的l1的下一个节点。这样直到两个有一个空了就找完了,然后一层一层倒着返回,最后返回最开始的l1

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {

        if(l1==NULL&&l2!=NULL)
            return l2;
        if(l2==NULL&&l1!=NULL)
            return l1;
        if(l1==NULL&&l2==NULL)
            return l1;

       if(l1->val<=l2->val)
       {
           l1->next=mergeTwoLists(l1->next,l2);
           return l1;
       }
       else
       {
           l2->next=mergeTwoLists(l1,l2->next);
           return l2;
       }
        
    }
};

时间:O(N+M) 两个链表的长度

空间:O(1)、O(M+N)


剑指 Offer 27. 二叉树的镜像

法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:
    TreeNode* mirrorTree(TreeNode* root) {
        if(root==NULL)
            return root;
        swap(root->left,root->right);//先交换节点
        mirrorTree(root->left);//再交换左右子树
        mirrorTree(root->right);
        return root;
    }
};

                                            其中 n 是二叉树的结点数。空间复杂度主要是递归调用的栈空间,取决于二叉树的高度,最坏情况下二叉树的高度是 O(n)

 法2:迭代深度优先遍历(前序)

可以用中序,用栈来遍历,而不是靠指针来遍历,避免了递归法中翻转了两次的情况。

/**
 * 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* mirrorTree(TreeNode* root) {
        stack<TreeNode*> st;
        if(root)
            st.push(root);
        while(!st.empty())
        {
            TreeNode*node=st.top();
            if(node!=NULL)
            {
                st.pop();//弹出该节点

                if(node->right!=NULL)
                    st.push(node->right);
                if(node->left!=NULL)
                    st.push(node->left);
                st.push(node);
                st.push(NULL);//访问了根节点但还没处理
            }
            else
            {
                //遇到了空节点,将下一个节点放进输出结果里(处理)
                st.pop();//删掉空节点
                node=st.top();
                st.pop();
                swap(node->left,node->right);
            }
            
        }

        return root;
    }
};

 法3:迭代广度优先遍历(层序)

/**
 * 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* mirrorTree(TreeNode* root) {
        queue<TreeNode*> q;
        if(root)
            q.push(root);
        while(!q.empty())
        {
            int size=q.size();
            for(int i=0;i<size;i++)
            {
                TreeNode*node=q.front();
                q.pop();//队头出队列
                swap(node->left,node->right);//左右子节点已经交换了
                if (node->left) q.push(node->left);//左子节点
                if (node->right) q.push(node->right);//右子节点
            }
        }
        return root;
    }
};

剑指 Offer 28. 对称的二叉树

 法1:递归

先比较外侧,再比较内侧。类似中序:左右中

1.确定递归参数:递归遍历外侧的一对、内侧的一对,传进去左节点,右节点

2.递归出口:一个空一个不空,都空,都不空且不相同

3.单层逻辑:左树的左节点,右树的右节点;左树的右节点,右树的左节点

/**
 * 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 dp(TreeNode*left,TreeNode*right)
    {
        if(left&&!right)
            return false;
        else if(!left&&right)
            return false;
        else if(!left&&!right)//都空
            return true;
        else if(left->val != right->val)
            return false;
        
        //相同
       return  dp(left->left,right->right) && dp(left->right,right->left);

    }
    bool isSymmetric(TreeNode* root) {
        if(root==NULL)  return true;

        return dp(root->left,root->right);
    }
};

时间:O(N),二叉树节点个数,一对一对遍历,最多N/2次dp方法

空间:O(N),最差时成为链表,高度N,递归深度为N。只在左右外侧有节点,O(N)大小的栈空间

法2:迭代队列、栈

栈的代码只用把队列换成栈即可

/**
 * 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 isSymmetric(TreeNode* root) {
        if(!root) return true;
        queue<TreeNode*> q;
        q.push(root->left);
        q.push(root->right);

        while(!q.empty()) 
        {
            TreeNode*l=q.front();
            q.pop();
            TreeNode*r=q.front();
            q.pop();

            if(!l && !r) continue;
            if ((!l || !r|| (l->val != r->val))) 
                return false;

            q.push(l->left);
            q.push(r->right);
            q.push(l->right);
            q.push(r->left);
        }

        return true;
        
    }
};


剑指 Offer 29. 顺时针打印矩阵

模拟 

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {

       vector<int> str;
        if (matrix.size() == 0 || matrix[0].size() == 0) 
            return {};
        int t=0;
        int b=matrix.size()-1;
        int l=0;
        int r=matrix[0].size()-1;
        int i,j;
        while(true)
        {

            for(i=l;i<=r;i++)
                str.push_back(matrix[t][i]);
            if(++t>b)//上面一行已经遍历了
                break;
            for(i=t;i<=b;i++)
                str.push_back(matrix[i][r]);
            if(--r<l)//右边一列
                break;
            
            for(i=r;i>=l;i--)
                str.push_back(matrix[b][i]);
            if(--b<t)//下面一行
                break;
            
            for(i=b;i>=t;i--)
                str.push_back(matrix[i][l]);
            if(++l>r)//左边一列
                break;


            
        }
        return str;

    }
};

剑指 Offer 30. 包含min函数的栈

辅助栈

(以为是自己是实现栈……) 

class MinStack {
public:
    /** initialize your data structure here. */
    
    stack<int> st;
    stack<int> mst;
    MinStack(){
        while(!st.empty())
            st.pop();
         while(!mst.empty())
            mst.pop();
        mst.push(INT_MAX);
    }
    
    void push(int x) {
        st.push(x);
        int m=std::min(mst.top(),x);
        mst.push(m);
    }
    
    void pop() {
        st.pop();
        mst.pop();
    }
    
    int top() {
        return st.top();
    }
    
    int min() {

        return mst.top();
    }
};

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


法1:bfs

/**
 * 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) {
        queue<TreeNode*> q;
        vector<vector<int>> res;
        
        if(root!=NULL)
            q.push(root);
        while(!q.empty())
        {
            vector<int> a;
            int size=q.size();
            for(int i=0;i<size;i++)
            {
                TreeNode*node=q.front();
                q.pop();
                a.push_back(node->val);
                if(node->left) q.push(node->left);
                if(node->right) q.push(node->right);
            }
            res.push_back(a);
        }

        return res;
        
    }
};

时间:O(N),每个节点进队出队一次

空间:O(N),队中元素n个

法2:dfs

/**
 * 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:
    void dfs(vector<vector<int>>& res,TreeNode* root,int depth)
    {
        if(root==NULL)
            return;
        if(res.size()==depth)
            res.push_back(vector <int> ());
        res[depth].push_back(root->val);
        dfs(res,root->left,depth+1);
        dfs(res,root->right,depth+1);

    }

    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==NULL)
            return {}; 
        vector<vector<int>> res;
        int depth=0;
        dfs(res,root,depth);

        return res;

    }
};

时间:O(N),遍历n个节点,最多调用n次dfs方法

空间:O(N),递归深度n


剑指 Offer 39. 数组中出现次数超过一半的数字

 

 法1:排序

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int n=(nums.size()-1)/2;//向上取整
        return nums[n];

    }
};

 

法2:Boyer-Moore 投票算法 

有:

众数的票数+1,非众数的票数-1,最后票数和>0

当前面的几个数字票数和=0,后面的票数和依旧>0 ,即后面剩下的数字的众数仍旧是它

所以:

当已用的数字票数和==0,设置下一个数字为众数

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int x=0;
        int sum=0;
        int c=0;
        for(int i=0;i<nums.size();i++)
        {
            if(sum==0)
                x=nums[i];
            if(x==nums[i])
            {
                sum+=1;
            }
            else
                sum-=1;

        }
        for(int a:nums)
        {
            if(a==x)
                c++;
        }

        return c>nums.size()/2? x:0;

    }
};

 时间: O(N) ,遍历n个数字

空间:O(1),都是常数变量


法1:TopK

升序,建大堆。前K个最小的元素一定比大堆的堆顶元素小。如果用小堆,小的元素放在上面,比堆顶大的就进不去。

先初始化堆(优先级队列对象,默认是大堆),把前K个构成大堆。

剩下N-K个,谁比堆顶元素小,就替代堆顶元素。

最后输出K个堆内元素

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        //Top-k问题
        vector<int>res(k,0);
        priority_queue<int>q;
        if(k==0)
            return res;
        for(int i=0;i<k;i++)
        {
            q.push(arr[i]);
        }

        for(int i=k;i<(int)arr.size();i++)
        {
            
            if(arr[i]<q.top())
            {
                q.pop();
                q.push(arr[i]);
            }
        }
        for(int i=0;i<k;i++)
        {
            res[i]=q.top();
            q.pop();
        }
        return res;

    }
};

 时间:O(N*log⁡k)  堆里最多k个数字,插入删除都是O(log⁡k)。总共n个数字,最多n次插入删除。

空间:O(K)  堆里最多k个数字。

法2:基于快排思想,递归版本

快排需要处理mid两边的数据,本题求的是前K个最小的数,只用处理一边的数据即可。

getindex求出了 mid,它对应的数字是当前数组里的第 mid - l +1 个数字,记为index

如果index<k,说明要求的第k个目标在mid右侧,递归参数 k-index

>k,在mid左侧,递归参数 k 

=k,就是第k个

class Solution {
public:
    int getindex(vector<int>&arr,int l,int r)
    {
        
        int mid=l+((r-l)>>1);
        if(arr[l]<arr[mid])
        {
            if(arr[r]>arr[mid])
                return mid;
            else if(arr[r]<arr[l])
                return l;
            else 
                return r;     
        }
        else
        {
            if(arr[r]<arr[mid])
                return mid;
            else if(arr[r]>arr[l])
                return l;
            else
                return r;
        }

        
    }

    int part(vector<int>& arr,int l,int r)
    {
        //快排升序
        int p=l,c=l+1;
        int key=l;
        int mid=getindex(arr,l,r);
        swap(arr[mid],arr[l]);
        while(c<=r)
        {
            if(arr[c]<=arr[key])
            {
                p++;
                swap(arr[c],arr[p]);
            }
            c++;
        }
        swap(arr[p],arr[key]);
        return p;
    }

    void quick(vector<int>&arr,int l,int r,int k)
    {
        //递归出口
        if(l>=r)
            return;
        
        //单层逻辑
        int mid=part(arr,l,r);
        int index=mid-l+1;//数组中从1数第几个
        if(index==k)
            return;
        else if(index<k)//要求的数在mid右边
            quick(arr,mid+1,r,k-index);
        else //要求的数在mid左边
            quick(arr,l,mid-1,k);
    }


    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> res(k,0);
        if(k==0)
            return res;
        
        
        quick(arr,0,arr.size()-1,k);

        for(int i=0;i<k;i++)
        {
            res[i]=arr[i];
        }
        return res;
    }
};

时间:O(N)

空间:O(logN)~O(N),递归调用最深n-1层,划分n次


剑指 Offer 42. 连续子数组的最大和

动态规划子序列

        //dp[i],下标为i的数组和

        //dp[i]=max(dp[i-1]+nums[i],nums[i])

        //dp[0]=nums[0];

        //从左到右

        //答案不一定在最后,每次更新最大值

class Solution {
public:

    int maxSubArray(vector<int>& nums) {
        //动态规划,子序列
        // dp(i)到i的和
        // dp(i)=max(dp(i-1)+numd[i],nums[i])

        vector<int> dp(nums.size(),0);
        dp[0]=nums[0];
        int res=dp[0];
        for(int i=1;i<nums.size();i++)
        {
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            if(dp[i]>res)
                res=dp[i];
        }

        return res;


    }
};


法1:unordered_map直接插入

无法实现第一次在map里出现1就输出

要插入的字符的位置在最后嘛?在最后说明前面没有和他一样的字符,不在说明前面有

class Solution {
public:
    char firstUniqChar(string s) {
        
        unordered_map<char,bool>a;
        for(char x:s)
        {
            a[x]=a.find(x)==a.end();
        }
        for(char c:s)
        {
            if(a[c])
                return c;
        }
        return ' ';

    }
};

 时间:O(N),N个字符插入,遍历N两轮

空间:O(1),常量N放进数组里

法2:遍历map 

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


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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode*i=headA;
        ListNode*j=headB;
        int n1=0,n2=0;
        while(headA!=NULL)
        {
            n1++;
            headA=headA->next;
        }
        while(headB!=NULL)
        {
            n2++;
            headB=headB->next;
        }

        if(n1<n2)
        {
            swap(n1,n2);
            swap(i,j);
        }
        int x=n1-n2;
        while(x--)
        {
                i=i->next;
        }
        while(i!=NULL&&j!=NULL)
        {
            if(i==j)
                return i;
            i=i->next;
            j=j->next;

        }
        return NULL;
        
      




    }
};

时间:O(N+M),两个链表的长度

空间:O(1),常量


剑指 Offer 53 - I. 在排序数组中查找数字 I

二分查找

 有序,单调,查找

class Solution {
public:
    int search(vector<int>& nums, int target) {

        if(nums.size()==0)
            return 0;
        int l=0,r=nums.size()-1;
        int m=0;
        int res=0;
        //>=target的最左边界
        while(l<r)
        {
            m=(l+r)>>1;
            if(nums[m]>=target)
            {
                r=m;
            }
            else 
            {
                l=m+1;
            }

        }
        
        if( nums[r] != target) return 0;
        int a=r;

        //l=0;
        //右端点一定在【左端点,n-1】之间
        r=nums.size()-1;
        //<=target的最右边界
            while(l<r)
            {
                m=(r+l+1)>>1;
                if(nums[m]<=target)
                    l=m;
                else
                    r=m-1;
            }

        return r-a+1;
        
    }
};


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

二分查找

单调有序数组,查找

分为左数组都nums[m]==m,右数组nums[m]!=m

找的是nums[m]>m的最左端点,也就是第一个>m的值

如果nums[m]==m,说明目标(右子数组的首位元素)在【m+1,j】

nums[m]!=m,目标(左子数组的末尾元素)在【i,m-1】

i>j时,i==右子数组的首位元素,j==左子数组的末尾元素

考虑nums只有一个数字,i==j时的输出

[0,n-1]的范围,不能r=size-1并且while(l<r)

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        
        int i=0,j=nums.size()-1;
        while(i<=j)
        {
            int m=(i+j)>>1;
            if(nums[m]==m)
                i=m+1;
            else
                j=m-1;
        }
        return i;
    }
};

 放几个大佬的答案:

class Solution {
public:
    int missingNumber(vector<int>& nums) {

        //显然用二分
        //找到第一个nums[i]>i的数即为结果
        int n=nums.size();
        int l = 0,r=n;  //结果在 [0,n-1]因此r=n_1
        while(l<r){
            
            int mid = l+(r-l)/2;
            if(nums[mid]<=mid){
                //当nums[mid]<=mid的时候,mid必不是结果,结果只能在mid右边
                //因此选择区间[mid+1,r]
                l=mid+1;
            }else{
                //当nums[mid]>mid的时候,mid可能是结果,结果也可能在mid的左边
                //因此选择区间[l,mid]
                r=mid;
            }

        }
        
        return l;

    }
};

public int missingNumber(int[] nums) {
        int len = nums.length;
        int left = 0, right = len - 1, mid = 0;
        while (left < right) {
            mid = left + right + 1 >> 1;
            if (nums[mid] == mid) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        if (nums[left] != left) {
            return left;
        }
        return left + 1;
    }

二分补充笔记:

数组的长度 len 也有可能是问题的答案,

       // 特殊判断
        if (nums[len - 1] < target) {
            return len;
        }

        // 程序走到这里一定有 nums[len - 1] >= target,插入位置在区间 [0..len - 1]
        int left = 0;
        int right = len - 1;

        while (left < right) {……}

len 也有可能是答案,可以在初始化的时候,把 right 设置成 len,在一开始的时候就不需要特殊判断了。

        int left = 0;
        int right = len;
        while (left < right) {……}

退出循环的时候有 left == right


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

二叉搜索树,每个节点都符合,左孩子<根,右孩子>根。

中序遍历为递增序列,那么倒序中序序列,也就是递减序列,能够找到第k个最大的

题目给了1<=k<=二叉树的元素个数,如果没说,可以在中序遍历完判断k>0?,如果>0,说明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 {
    private:
    int res;//每个dfs共用
public:
    void dfs(TreeNode*root,int &k)//这里的k要求每个递归共用
    {
        if(root==NULL)
            return ;
        dfs(root->right,k);
        k--;
        if(k==0)//找到了
            res=root->val;
        dfs(root->left,k);
    }
    int kthLargest(TreeNode* root, int k) {
        if(root==NULL)
            return 0;
        dfs(root,k);
        return res;
    }
};

时间:O(N),递归遍历二叉树,二叉树变成只有右节点时,递归深度N

空间:O(N),二叉树变成只有右节点时,O(N)大小的栈空间


剑指 Offer 55 - I. 二叉树的深度

/**
 * 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 maxDepth(TreeNode* root) {
        if(root==NULL)
            return 0;
        return max(maxDepth(root->left),maxDepth(root->right))+1;
    }
};

 时间:O(N),每个节点遍历一次

空间:O(树的高度),递归函数需要栈空间,而栈空间取决于递归的深度。如果使用层序遍历,则O(N),因为队列长度最大为N


 剑指 Offer 55 - II. 平衡二叉树

一棵二叉树是平衡二叉树,当且仅当其所有子树也都是平衡二叉树,因此可以使用递归的方式判断二叉树是不是平衡二叉树。

遍历顺序是前序,判断每一个根节点左子树,右子树深度之差是否<=1。

要求根节点左右子树之差<=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:
    int height(TreeNode*root)
    {
        if(root==NULL)
            return 0;
        return max(height(root->left),height(root->right))+1;
    }
    bool isBalanced(TreeNode* root) {
        if(root==NULL)
            return true;
        return abs(height(root->left)-height(root->right))<=1&&isBalanced(root->left)&&isBalanced(root->right);

    }
};

剑指 Offer 57. 和为s的两个数字

对撞指针:

递增排序的数组。

一个指针在头,一个指针在尾。相加大于target,右指针--;小于target,左指针++

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int>res;
        int l=0,r=nums.size()-1;
        while(l<r)
        {
            if(nums[l]+nums[r]<target)
            {
                l++;
            }
            else if(nums[l]+nums[r]>target)
                r--;
            else
            {
                res.push_back(nums[l]);
                res.push_back(nums[r]);
                break;
            }


        }
        return res;
    }
};

剑指 Offer 57 - II. 和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

示例 1:

输入:target = 9
输出:[[2,3,4],[4,5]]

示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

限制:

  • 1 <= target <= 10^5

暴力

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> res;
        
        for(int i=1;i<target;i++)
        {
            int sum=0;
            vector<int> a;
            for(int j=i;j<target;j++)
            {
                
                sum+=j;
                if(sum>target)
                    break;
                if(sum==target)
                {
                    for(int k=i;k<=j;k++)
                        a.push_back(k);
                    res.push_back(a);
                }

            }
                      
        }
        
        return res;
    }
};

滑动窗口

因为题目要求最少有两个数加和为target,所以到一半结束就可以了

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> res;

        int i=1,j=1,sum=0;
        while(i<=target/2)
        {
            vector<int>a;

            if(sum>target)
            {
                sum-=i;
                i++;
            }
            else if(sum<target)
            {
                sum+=j;
                j++;
            }
            else
            {
                for(int k=i;k<j;k++)
                    a.push_back(k);
                res.push_back(a);

                //找新的答案
                sum-=i;
                i++;
            }

        }
        return res;
    }
};

 答案区间左闭右开:

如:target=15

 


剑指 Offer 58 - I. 翻转单词顺序

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。

示例 1:

输入: "the sky is blue"
输出: "blue is sky the"

示例 2:

输入: "  hello world!  "
输出: "world! hello"
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

示例 3:

输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

说明:

  • 无空格字符构成一个单词。
  • 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
  • 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

class Solution {

public:
    void Deletes(string &s)
    {
        int slow=0;
        for(int i=0;i<s.size();i++)
        {
            
            if(s[i]!=' ')//新的单词开始了
            {
                if(slow!=0) 
                    s[slow++]=' ';
                while(i<s.size()&&s[i]!=' ')
                {    
                    s[slow++]=s[i++];
                }
            }
        }
        s.resize(slow);
    }
   void reverse(string& s, int start, int end){ //翻转,区间写法:左闭又闭 []
        for (int i = start, j = end; i < j; i++, j--) {
            swap(s[i], s[j]);
        }
    }
    string reverseWords(string s) {

        Deletes(s);
        reverse(s,0,s.size()-1);
        int start=0;
        for(int i=0;i<=s.size();i++)
        {
            if(s[i]==' '||i==s.size())
            {
                reverse(s,start,i-1);
                start=i+1;
            }
            
        }
        return s;

    }
};

剑指 Offer 58 - II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:

输入: s = "abcdefg", k = 2
输出: "cdefgab"

示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

限制:

  • 1 <= k < s.length <= 10000
class Solution {
public:
    void reverse(string &s,int a,int b)
    {
        for(int i=a,j=b;i<j;i++,j--)
        {
            swap(s[i],s[j]);
        }
    } 
    string reverseLeftWords(string s, int n) {

        reverse(s,0,s.size()-1);
        reverse(s,0,s.size()-1-n);
        reverse(s,s.size()-n,s.size()-1);
        return s;
    }
};

剑指 Offer 62. 圆圈中最后剩下的数字

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

示例 1:

输入: n = 5, m = 3
输出: 3

示例 2:

输入: n = 10, m = 17
输出: 2

限制:

  • 1 <= n <= 10^5
  • 1 <= m <= 10^6

 数学问题,约瑟夫环

删掉一个 (m-1) 的数字之后, 从接下来的数开始重新编号,相当于把所有的位置左移了m位。 所以我们要返回上一层的时候,把删掉的数字补回去之后,再右移m位,即所有坐标 +m 然后 %n ,(n是上一层的个数)。

最后要有一个取余操作,相当于把溢出部分的元素再填充到最前面。

pos就是最后剩下的数字的初始位置

class Solution {
public:
    int lastRemaining(int n, int m) {

        int pos=0;
        for(int i=2;i<=n;i++)
        {
            pos = (pos + m) % i;
        }
        return pos;
    }
};

剑指 Offer 65. 不用加减乘除做加法

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

示例:

输入: a = 1, b = 1
输出: 2

提示:

  • ab 均可能是负数或 0
  • 结果不会溢出 32 位整数

位运算:

进位:c=(a&b)<<1

不进位:a=a^b

将进位c赋值给b:b=c

使用unsigned防止溢出

class Solution {
public:
    int add(int a, int b) {
        while(b!=0)
        {
        unsigned c=unsigned(a&b)<<1;
        a=a^b;
        b=c;
        }
        return a;
    }
};

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

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

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

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

示例 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 为不同节点且均存在于给定的二叉搜索树中。

 不用回溯,因为二叉搜索树自带方向性,不用自底向上,如果是二叉树找公共祖先就得回溯。

要找cur在[p,q]或者[q,p]这个区间内。

如果cur比他俩都大,得往小找,去左子树。

比他俩都小,去右子树,往大找。

最后的else就是答案。

此时迭代比递归写起来更简单

/**
 * 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(root->val<p->val&&root->val<q->val)
            {
                root=root->right;
            }
            else if(root->val>p->val&&root->val>q->val)
            {
                root=root->left;
            }
            else
                return root;
        }
        return nullptr;//题目说了一定存在,其实可以不写
    }
};

剑指 Offer 68 - II. 二叉树的最近公共祖先

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

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

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

示例 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 为不同节点且均存在于给定的二叉树中。

 回溯,后序遍历。

递归:

确定参数,返回值:root,p,q。 TreeNode*

终止条件:root==p或者root==q或者root==nullptr,return root

单层逻辑:   l=递归左树,r=递归右树

                   if l和r都不空,说明root是

                   if l和r都空,就是没找到

                   if l或者r为空,返回不空的那个,它就是公共祖先

递归是把根的左右子树都遍历了,加入pq在左树,那么右树返回的就是nullptr       

/**
 * 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==nullptr||root==p||root==q)
            return root;
        TreeNode*l=lowestCommonAncestor(root->left,p,q);
        TreeNode*r=lowestCommonAncestor(root->right,p,q);

        if(l!=nullptr&&r!=nullptr) return root;
        if(l==nullptr&&r!=nullptr) return r;
        else if(l!=nullptr&&r==nullptr) return l;
        else
            return l;
    }
};


面试题61. 扑克牌中的顺子

若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

示例 1:

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

示例 2:

输入: [0,0,1,2,5]
输出: True

限制:

数组长度为 5 

数组的数取值为 [0, 13] .

题目都没看明白……

0 是大小王,可以看作任意牌,因此可以 12005 -> 12345

先记录0的个数,非0情况下不能有重复

只要最大数-最小数<5,true

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int s=0;
        
        for(int i=0;i<4;i++)
        {   
            if(nums[i]==0)
                s++;
            else if(nums[i+1]==nums[i])
                return false;
        }
        
        return nums[4]-nums[s]<5;
            
    }
};


中等


剑指 Offer 04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右 非递减 的顺序排序,每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true

给定 target = 20,返回 false

限制:

0 <= n <= 1000

0 <= m <= 1000

题目提到了高效,且数据较大,暴力法可以优化

二分:

每行都是升序,对每行求二分,一行二分O(logM),N行就是O(N*logM)

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        for(auto x:matrix)
        {
            auto it=lower_bound(x.begin(),x.end(),target);
            if(it!=x.end()&&*it==target)
                return true;
        }
        return false;
    }
};

Z型查找

发现数组目标比左下角 大,目标就在左下角右边的列;

比左下角小,目标就在左下角上面的行

不断缩小范围

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        int i=matrix.size()-1,j=0;
        while(i>=0&&j<matrix[0].size())
        {
            if(matrix[i][j]>target)
                i--;
            else if(matrix[i][j]<target)
                j++;
            else
                return true;
        }
        return false;
    }
};

剑指 Offer 07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

示例 1:

Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]

示例 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]

限制:

0 <= 节点个数 <= 5000

 

/**
 * 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* res(vector<int>& inorder,int ibegin,int iend ,vector<int>& preorder, int pbegin,int pend)
    {
        if(pbegin==pend) return NULL;//9
        int rootv=preorder[pbegin];
        TreeNode*root=new TreeNode(rootv);//每次的根节点
        if(pend-pbegin==1) return root;//15 7

        //在中序里寻找根节点下标
        int iroot;
        for(iroot=ibegin;iroot<iend;iroot++)
        {
            if(inorder[iroot]==rootv)
                break;
        }

        //中序
        int ileftbegin=ibegin;
        int ileftend=iroot;
        int irightbegin=iroot+1;
        int irightend=iend;

        //前序
        int pleftbegin=pbegin+1;
        int pleftend=pbegin+1+iroot-ibegin;//终止位置是起始位置加上中序左区间的大小size
        int prightbegin=pbegin+1+iroot-ibegin;
        int prightend=pend;

        root->left=res(inorder,ileftbegin,ileftend,preorder,pleftbegin,pleftend);
        root->right=res(inorder,irightbegin,irightend,preorder,prightbegin,prightend);

        return root;

    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {

        if(inorder.size()==0||preorder.size()==0)   return NULL;

        //左闭右开
        return res(inorder,0,inorder.size(),preorder,0,preorder.size());

    }
};


剑指 Offer 12. 矩阵中的路径

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

示例 2:

输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false

提示:

  • m == board.length
  • n = board[i].length
  • 1 <= m, n <= 6
  • 1 <= word.length <= 15
  • board word 仅由大小写英文字母组成

看起来像动归,再想想发现是回溯。

排列问题,不能重复选择已用的字母,需要used数组记录已用的字母。

组合问题,比如{1,2}和{2,1}是一种答案,需要startindex记录下一次该从哪个字母开始 

class Solution {
private:
    int rows,cols;
public:
    bool dfs(vector<vector<char>>& board, string word,int i,int j,int startindex)
    {
        if(i<0||i>=rows||j<0||j>=cols||board[i][j]!=word[startindex])  return false;
        if(startindex==word.size()-1)   return true;
        //把已经用过的字母改成特殊字符,相当于使用了used数组
        board[i][j]='!';
        //去上下左右四个方向递归遍历
        int dx[4] = {-1,0,1,0}, dy[4] = {0,1,0,-1}; //方向数组
        for(int k=0;k<4;k++)
        {
            int a = i + dx[k], b = j + dy[k];
            if(a < 0 || a >= board.size() || b < 0 || b >= board[0].size() || board[a][b] == '!')  
                continue;
            if(dfs(board,word,a,b,startindex+1))
                return true;
        }
        board[i][j]=word[startindex];//回溯
        return false;

        // bool res=dfs(board,word,i+1,j,startindex+1)
        // ||dfs(board,word,i,j+1,startindex+1)
        // ||dfs(board,word,i-1,j,startindex+1)
        // ||dfs(board,word,i,j-1,startindex+1)

    }
    bool exist(vector<vector<char>>& board, string word) {
        rows=board.size();
        cols=board[0].size();

        for(int i=0;i<rows;i++)
        {
            for(int j=0;j<cols;j++)
            {               
                //遍历矩阵上的所有字母
                if(dfs(board,word,i,j,0))   return true;

            }
        }
        return false;
    }
};


剑指 Offer 14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

提示:

  • 2 <= n <= 58

回溯

class Solution {
private:
    int res;
public:
    void dfs(int sum,unsigned long long x,int startindex, int n)
    {
        if(sum>n) 
            return;
        else if(sum==n)//符合条件才计算乘积
        {
            res=res>x?res:x;
            return;
        }

        for(int i=startindex;i<n;i++)
        {
            dfs(sum+i,x*i,i,n);
            startindex-=1;
        }
        return ;
    }
    int cuttingRope(int n) {
        dfs(0,1,1,n);
        return res;
    }
};

动归

1dp数组:dp[i] 拆开i的最大乘积

2公式:dp[i]=max( dp[i-j]*j,  (i-j)*j ]):分成i-j,j之后,i-j这段继续剪;分成i-j,j之后,不再剪

3初始化:dp[2]=1

4遍历顺序:j 的取值范围是 1 到 i−1,前往后

//背包倒序,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。

class Solution {
public:
    int cuttingRope(int n) {
        vector<int>dp(n+1);
        dp[2]=1;
        for(int i=3;i<=n;i++)
        {
            for(int j=1;j<i-1;j++)
            {
                dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
            }
        }
       
        return dp[n];
    }
};

完全背包 

class Solution {
public:
    int cuttingRope(int n) {
        vector<int>dp(n+1);
        dp[0]=1;
        for(int i=1;i<n;i++)
        {
            for(int j=i;j<=n;j++)
            {
                dp[j] = max(dp[j-i]*i,dp[j]);
            }
        }
       
        return dp[n];
    }
};

 贪心

想到了,细想不出来了。

尽可能把绳子分成长度为3的小段,这样乘积最大。

n=2,返回1

n=3,返回2

n=4,返回4

n>4,n-3,乘积*3,返回 *小于等于4的最后一段


剑指 Offer 14- II. 剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

提示:

  • 2 <= n <= 1000

贪心

和上题一样,不过res每次求得时要取余数

最后n的值只有可能是:2、3、4。而2、3、4能得到的最大乘积恰恰就是自身值
因为2、3不需要再剪了(剪了反而变小);4剪成2x2是最大的,2x2恰巧等于4

class Solution {
public:
    int cuttingRope(int n) {
        if(n<4) return n-1;
        long long res=1;
        while(n>4)
        {
            res*=3;
            res%=1000000007;
            n-=3;
        }
        return res*n %1000000007;
    }
};

剑指 Offer 16. 数值的整数次方

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000

示例 2:

输入:x = 2.10000, n = 3
输出:9.26100

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25

二分快速幂

long:

 因为32位int是补码形式,正数是和原码相同,范围是0到2的32次方-1,但是对于负数,需要反码+1,范围是2的32次方到0,负数要比正数多一个数字。如果传进来的int刚好是负2的32次方,取相反数之后就超过int32类型的取值范围了,所以需要用long来扩大取值范围

class Solution {
public:
    double myPow(double x, int n) {
        if(x==0)   return 0;
        double res=1.0;
        long b=n;
        if(b<0)
        {
            x=1/x;
            b=-b;
        }

        while(b>0)
        {
            if((b&1)==1)//二进制最后一位是1
                res*=x;//单独乘一次x
            x=x*x;//x^2
            b=b>>1;// b÷2,二进制右移一位
        }
        return res;
    }
};

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

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。

数值(按顺序)可以分成以下几个部分:

  1. 若干空格
  2. 一个 小数 或者 整数
  3. (可选)一个 'e' 或 'E' ,后面跟着一个 整数
  4. 若干空格

小数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+' 或 '-'
  2. 下述格式之一:
    1. 至少一位数字,后面跟着一个点 '.'
    2. 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字
    3. 一个点 '.' ,后面跟着至少一位数字

整数(按顺序)可以分成以下几个部分:

  1. (可选)一个符号字符('+' 或 '-'
  2. 至少一位数字

部分数值列举如下:

  • ["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]

部分非数值列举如下:

  • ["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4"]

示例 1:

输入:s = "0"
输出:true

示例 2:

输入:s = "e"
输出:false

示例 3:

输入:s = "."
输出:false

示例 4:

输入:s = "    .1  "
输出:true

提示:

  • 1 <= s.length <= 20
  • s 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。
class Solution {
public:
    bool scanIn(const string s,int&index)
    {
        if(s[index]=='+'||s[index]=='-')
            ++index;
        return scui(s,index);
    }
    bool scui(const string s,int &index)
    {
        int befor=index;
        while(index!=s.size()&&s[index]>='0'&&s[index]<='9')
            ++index;

        return index>befor;//有数字
    }
    bool isNumber(string s) {
        if(s.size()==0)
            return false;
        int index=0;

        while(s[index]==' ')
            ++index;
        bool num=scanIn(s,index);//数字部分

        //符号部分
        if(s[index]=='.')
        {
            index++;
            num=scui(s,index)||num;//小数部分的规则可以有也可以没有
        }
        if(s[index]=='e'||s[index]=='E')
        {
            index++;
            num=num&&scanIn(s,index);//指数必须符合规则 
        }

        while(s[index]==' ')//字符串结尾有空格可以返回true: "    .1  "
        {
            ++index;
        }

        return num&&index==s.size();
    }
};


剑指 Offer 26. 树的子结构

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

B是A的子结构, 即 A中有出现和B相同的结构和节点值

例如:
给定的树 A:

     3
    / \
   4   5
  / \
 1   2

给定的树 B:

   4 
  /
 1

返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false

示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true

限制:

0 <= 节点个数 <= 10000

前序遍历 

/**
 * 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;
        
        //b的根节点也就是起点是A的根节点
        //||在A的左子树里
        //||在A的右子树里
        return recur(A,B)||isSubStructure(A->left,B)||isSubStructure(A->right,B);
        
    }

    bool recur(TreeNode* A,TreeNode* B)
    {
        if(B==NULL)
            return true;
        if(A==NULL||A->val!=B->val)
            return false;
        //a和b值相等,还得递归判断b的子树是否是a的子结构
        return recur(A->left,B->left)&&recur(A->right,B->right);
    }
};


剑指 Offer 31. 栈的压入、弹出序列

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

示例 1:

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

示例 2:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。

提示:

  1. 0 <= pushed.length == popped.length <= 1000
  2. 0 <= pushed[i], popped[i] < 1000
  3. pushed 是 popped 的排列。
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> res;
        int i=0;
        for(auto x:pushed)
        {
            res.push(x);
              
        while(!res.empty()&&res.top()==popped[i])
        {
            res.pop();
            i++;
        }
        }
        return res.empty();

    }
};


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

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

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

    3
   / \
  9  20
    /  \
   15   7

返回:

[3,9,20,15,7]

提示:

  1. 节点总数 <= 1000
/**
 * 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<int> levelOrder(TreeNode* root) {
       queue<TreeNode*>q;
       vector<int>res;
       if(root==NULL)
            return res;
        q.push(root);
        while(!q.empty())
        {
            int size=q.size();
            for(int i=0;i<size;i++)
            {
                TreeNode*cur=q.front();
                q.pop();
                res.push_back(cur->val);
                if(cur->left)
                    q.push(cur->left);
                if(cur->right)
                    q.push(cur->right);

            }
        }
        return res;
    }
};


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

中等

259

相关企业

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

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

    3
   / \
  9  20
    /  \
   15   7

返回其层次遍历结果:

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

提示:

  1. 节点总数 <= 1000
/**
 * 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) {
       queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<vector<int>> result;
        bool is=true;
        while (!que.empty()) {
            int size = que.size();
            vector<int> vec(size);
            for (int i = 0; i < size; i++) {//一层
                TreeNode* node = que.front();
                que.pop();
                //奇数层正序,偶数层倒序
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);               
                vec[is? i:size-1-i]=node->val;//1正序,0倒序
            }
            //下一层的顺序要变
            is=!is;
            result.push_back(vec);
        }
        return result;
    }
};

剑指 Offer 33. 二叉搜索树的后序遍历序列

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

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3

示例 1:

输入: [1,6,3,2,5]
输出: false

示例 2:

输入: [1,3,2,6,5]
输出: true

提示:

  1. 数组长度 <= 1000

递归

后序:左右根 

左树小于根,右树大于根,则左树都小于右树

找到第一个比已给数组起点大的数字,以此划分左右子树,继续向下递归

class Solution {
public:
    bool  order(vector<int>& postorder, int i, int j)
    {
        if(i>=j)
            return true;//结束了
        int p=i;
        while(postorder[p]<postorder[j])
        {
            p++;
        }
        int m=p;//i,m-1
        while(postorder[p]>postorder[j])
        {
            p++;
        }//m,j-1
        return p==j && order(postorder,i,m-1) && order(postorder,m,j-1);
        
    }
    bool verifyPostorder(vector<int>& postorder) {
        
       return order(postorder,0,postorder.size()-1);
    }
};


剑指 Offer 34. 二叉树中和为某一值的路径 

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

提示:

  • 树中节点总数在范围 [0, 5000] 内
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

 递归,深度遍历

int dfs(cur,sum)

if(sum==0||!cur->left || !cur->right) 答案  ;

if(!root)return ;

单层:

if(cur->left),if(cur->right)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
    vector<int>a;
    vector<vector<int>>res;
public:
    void dfs(TreeNode* root, int target)//值传递,函数结束,target的数值没有改变。
    {
        if(!root) return ;
        a.push_back(root->val);
        target-=root->val;
        if(!root->left&&!root->right&&target==0)
            res.push_back(a);
        dfs(root->left,target);
        dfs(root->right,target);
        a.pop_back();//回溯
        target+=root->val;
    }
    vector<vector<int>> pathSum(TreeNode* root, int target) {
        dfs(root,target);
        return res;
    }
};


剑指 Offer 35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

 哈希

复制,再处理next,random节点,最后返回哈希的head节点

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
    
public:
    Node* copyRandomList(Node* head) {
        if(head==NULL)
            return NULL;
        unordered_map<Node*,Node*>um;
        Node*cur=head;
        while(cur)
        {
            um[cur]=new Node(cur->val);
            cur=cur->next;
        }
        cur=head;
        while(cur)
        {
            um[cur]->next=um[cur->next];
            um[cur]->random=um[cur->random];
            cur=cur->next;
        }
        return um[head];
    }
};


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

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

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

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

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

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

中序递归

class Solution {
    Node head, pre;
    public Node treeToDoublyList(Node root) {
        if(root==null) return null;
        dfs(root);

        pre.right = head;
        head.left =pre;//进行头节点和尾节点的相互指向,这两句的顺序也是可以颠倒的

        return head;

    }

    public void dfs(Node cur){
        if(cur==null) return;
        dfs(cur.left);

        //pre用于记录双向链表中位于cur左侧的节点,即上一次迭代中的cur,当pre==null时,cur左侧没有节点,即此时cur为双向链表中的头节点
        if(pre==null) head = cur;
        //反之,pre!=null时,cur左侧存在节点pre,需要进行pre.right=cur的操作。
        else pre.right = cur;
       
        cur.left = pre;//pre是否为null对这句没有影响,且这句放在上面两句if else之前也是可以的。

        pre = cur;//pre指向当前的cur
        dfs(cur.right);//全部迭代完成后,pre指向双向链表中的尾节点
    }
}


剑指 Offer 38. 字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]

限制:

1 <= s 的长度 <= 8

回溯

全排列,字母不能够重复,used记录已用过的字母。 排序后去重,可以使用set

class Solution {
private:
    string res="";
    vector<string>a;
    
public:
    void dfs(string s,vector<bool>used)
    {
        if(res.size()==s.size())
        {
            a.push_back(res);
            return ;
        }
        
        for(int i=0;i<s.size();i++)
        {
            if(used[i]==true) continue;//剪
            if(i>0&&used[i-1]==false&&s[i]==s[i-1]) continue;
            //同层去重,112,同层前一个使用了11,同层下一个就不能再使用第二个1,第一个1了

            
                res.push_back(s[i]);
                used[i]=true;
            
            dfs(s,used);
            res.pop_back();
            used[i]=false;
            
        }
    }
    vector<string> permutation(string s) {
        vector<bool>used(s.size(),0);
        sort(s.begin(),s.end());
        dfs(s,used);
        return a;
    }
};

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

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

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

示例 1:

输入:n = 3
输出:3

示例 2:

输入:n = 11
输出:0

限制:

  • 0 <= n < 2^31

找规律 

1.先确定目标是几位数中的第几个,start是每种数位的开始:10,100

所求数位 在从数字 start开始的第 [(n−1)/digit] 个 数字 中( start为第 0 个数字)。

2.确定目标是哪个数字

3.确定n是数字里的第几位,数字首位是第0位

class Solution {
public:
    int findNthDigit(int n) {
        int digit=1;
        long start=1;
        long count =9;
        while(n>count)
        {
            n-=count;
            digit+=1;
            start*=10;
            count=digit*start*9;
        }

        long num=start+(n-1)/digit;
        string s=to_string(num);
        return s[(n-1)%digit]-'0';

    }
};


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

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

示例 1:

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

提示:

  • 0 <= num < 231

动归

如果第 i−1位和第 i 位组成的数字在 10 到 25 之间,可以把这两位连起来翻译

青蛙跳台的经典问题(青蛙跳台阶,一次可以跳1级,也可以跳2级,问n级台阶多少种跳法)

f(n) = f(n-1) + f(n-2)

所以动态规划,只需要知道dp[0]和dp[1],然后递推就好了。

这里的变化是,能不能跳两个需要判断一下.

dp[i]:以i结尾的数字能被组成方式的个数

dp[0]=dp[1]=1 ,即 “无数字” 和 “第 1位数字” 的翻译方法数量均为 1

dp[i]=dp[i-1]+dp[i-2],两个数字连起来,还得判断是否在10~25

dp[i]=dp[i-1],使用一个数字

返回dp[len],dp数组len+1

class Solution {
public:
    int translateNum(int num) {
        string str = to_string(num);
        int len = str.size();
        if(len < 2) return len;
        vector<int> dp(len+1);
        dp[1] = 1;
        dp[0] = 1;
        for(int i = 2;i <= len;i++){
            if(str[i-2] == '1' || (str[i-2] == '2' && str[i-1] <= '5')) dp[i] = dp[i-2]+dp[i-1];
            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

动归

1确定dp: dp[i][j],到达(i ,j)时礼物的价值,就是最大值 

2.dp[i][j] = max(dp[i-1][j], dp[i][j-1])+grid[i][j]

3.初始化:dp[0][j]=第一行,dp[i][0]=第一列

4.左右,上下的顺序遍历

5.返回右下角

 如果多开一行一列,就不用从1开始了,也不用单独初始化第一行第一列了

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int m=grid.size(),n=grid[0].size();
        vector<vector<int>>dp(m,vector<int>(n,0));
        dp[0][0] = grid[0][0];
        for(int i=1;i<m;i++)
        {
            dp[i][0]=dp[i-1][0]+grid[i][0];      
        }
        for(int i=1;i<n;i++)
        {
            dp[0][i]=dp[0][i-1]+grid[0][i];  
        }

        for(int i=1;i<m;i++)
        {
            for(int j=1;j<n;j++)
            {
                dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }

        return dp[m-1][n-1];

    }
};


剑指 Offer 48. 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • s.length <= 40000

滑动窗口

r去遍历字符串,碰到了重复的,l 就跳到此处。 

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int size=s.size();
        if(size==0) return 0;
        unordered_map<char,int>res;
        int r=0,l=0,sum=0;
        int a=0;
        while(r<size)
        {
            if(res.find(s[r])!=res.end())//重复了
            {
                l=max(l,res[s[r]]+1);//l跳到新的重复的字母上
            }
            res[s[r++]]=r;//更新map里的字母对应的序号,从1开始
            a=max(r-l,a);
        }
        return a;
    }
};

剑指 Offer 49. 丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

说明:  

  1. 1 是丑数。
  2. n 不超过1690。

 动态规划

一个丑数乘以 2, 3, 5 之后, 一定还是一个丑数。每一个序列都各自维护一个指针, 然后比较指针指向的元素的值, 将最小的放入最终的合并数组中, 并将相应指针向后移动一个元素,索引+1。可能会出现重复的,比如nums2[2] == 3*2nums3[1] == 2*3,

 当比较到元素 6 时, if (dp[i] == dp[p2] * 2)...if (dp[i] == dp[p3] * 3)... 可以同时指向 nums2, nums3 中 元素 6 的下一个元素。

力扣

// 1, 2, 3, 4, 5, 6, 8, 9, 10, 12
class Solution {
public:
	int nthUglyNumber(int n) {
		vector<int> dp(n, 0);
		dp[0] = 1;
		int p2 = 0, p3 = 0, p5 = 0;
		for (int i = 1; i < n; i++) {
			dp[i] = min(min(dp[p2] * 2, dp[p3] * 3), dp[p5] * 5);
			if (dp[i] == dp[p2] * 2)
				p2++;
			if (dp[i] == dp[p3] * 3)
				p3++;
			if (dp[i] == dp[p5] * 5)
				p5++;
		}
		return dp[n - 1];
	}
};

最小堆

从小到大的第 n 个丑数,升序

现将最小的丑数加入堆,每次取得堆顶元素,堆顶就是当前最小的丑数, 2x,3x,5x也是丑数,因此将 2x,3x,5x 加入堆。去重,如果和堆顶元素相同,那么移除子节点

class Solution {
public:
    int nthUglyNumber(int n) {
        priority_queue<long,vector<long>,greater<long>>q;
        long ans=1;
        for(int i=1;i<n;i++)
        {
            q.push(ans*2);
            q.push(ans*3);
            q.push(ans*5);
            ans=q.top();
            q.pop();
            while(!q.empty()&&ans==q.top())//将使用过的相同元素移除队列
            {    
                q.pop();
            }

        }
        return ans;
    }
};


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

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

示例 1:

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

示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

限制:

  • 1 <= nums.length <= 10000
  • 1 <= nums[i] < 2^31

滑动窗口 

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        if(nums.size()==1)
            return 1;
        int i=0,j=1;
        sort(nums.begin(),nums.end());

        for(j=1;j<nums.size();j++)
        {
            if(nums[i]==nums[j])
                continue;
            if(j>=i&&nums[i]!=nums[j])
            {
                if(j-i==3&&j==nums.size()-1 )
                    return nums[j];
                if(j-i==1)  return nums[i];
                i=j;
            }
        }
        return -1;
        
    }
};

unorsered_map 

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        if(nums.size()==1)
            return 1;
        unordered_map<int,int>hs;
        for(auto x:nums)
        {
            hs[x]++;
        }
        for(auto [k,v]:hs)
        {
            if( v==1)
                return k;
        }
        return -1;
    }
};

 位运算

二进制求和,某一个数字如果出现一次,则最终和的对应位置不能被3整除

力扣

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        if(nums.size()==1)
            return 1;
        int ans=0;
        for(int i=0;i<32;i++)//遍历int二进制 32位
        {
            int cnt=0;
            for(int n:nums)
            {
                if(n&(1<<i))//该位是1
                    cnt++;
            }
            if(cnt%3==1)//该位不能整除3
                ans^=(1<<i);//采用异或的方法生成二进制中的每一位
        }        
        return ans;
    }
};


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

把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

动态规划

1. 确定dp:dp[i][j],投i个骰子,和为j的总次数

2.少一个骰子,同时减一个对应的点数,dp[i][j]+=dp[i-1][j-cur],  cur是少的那个骰子对应的点数(1~6)

3.左->右

4.初始化:dp[1][j],都为1,j=1~6

5.计算概率 dp[n][i]

getCount(3,4),意思就是 先定下第一个骰子是1,剩下的骰子必然和为3, 所以getCount(3,4) 第一种情况是1 * getCount(2, 3),

第二种情况就是第一个骰子为2, getCount(2, 2),

第三种情况是 第一个骰子为 3, getCount(2, 1) = 0,

优化空间使用一维数组的时候,从高到低是因为如果从低到高的话,那边后面使用的就是本轮已经改变的值,不再是上一轮的值了。

class Solution {
public:
    vector<double> twoSum(int n) {
//定义一个数组保存所有状态,一维是第n次时,二维为第n次时所有可能情况
        int dp[15][70]={0};
//我们只能知道第一次的情况,也是边界条件
        for(int i=1;i<=6;i++)
        {
            dp[1][i]=1;
        }
//一层循环,每多一个色子的情况
        for(int i=2;i<=n;i++)
        {
//二层循环是这么多色子情况下的所有可能和
            for(int j=i;j<=6*i;j++)
            {
//三层循环是为了找到能得到这种可能和时,对于少一个色子时的所有可能情况,相加即是当前可能总和
                for(int cur=1;cur<=6;cur++)
                {
//i-1,即是对于只有对于少一个色子时,最小和最少为i-1,不可能再小了
                    if(j-cur<i-1) break;
                     dp[i][j]+=dp[i-1][j-cur];
                }
            }
        }
//第n次的所有可能情况
        int all=pow(6,n);
        vector<double> res;
//push 到res中
        for(int i=n;i<=6*n;i++)
        {
        //实现整形到浮点型的转换
        res.push_back(dp[n][i]*1.0/all);
        }        
        return res;  
    }
};


剑指 Offer 63. 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

限制:

0 <= 数组长度 <= 10^5

1.dp[i][0]:第i天有股票,i-1天就有了,dp[i][0]=dp[i-1][0],i天买入了,现金一开始是0,所以是个负数, = -values[i];

dp[i][1]:第i天没有股票,,i-1天也没有,=dp[i-1][1],i天买入了,dp[i][0]+values[i];

2.初始化:dp[0][0],第0天有股票,-values[0]

dp[0][1],第0天没有股票,0

3.左到右

4.输出最后一天的现金,dp[n][1],本题中不持有股票状态所得金钱一定比持有股票状态得到的多!

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n=prices.size();
        if(n==0)    return 0;
        vector<vector<int>> dp(n, vector<int>(2));
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        for(int i=1;i<n;i++)
        {
            dp[i][0]=max(dp[i-1][0],-prices[i]);
            dp[i][1]=max(dp[i-1][1],dp[i][0]+prices[i]);
        }
        return dp[n-1][1];
    }
};


剑指 Offer 64. 求1+2+…+n

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

示例 1:

输入: n = 3
输出: 6

示例 2:

输入: n = 9
输出: 45

限制:

  • 1 <= n <= 10000

递归

if(n == 1) return 1; //递归出口
    n += sumNums(n - 1);

 逻辑短路,n>1&&   如果n==1就不再继续右边的表达式了

class Solution {
public:
    int sumNums(int n) {
        //递归
        bool b= n>1&&(n+=sumNums(n-1));
        return n;
    }
};


剑指 Offer 66. 构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

示例:

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

提示:

  • 所有元素乘积之和不会溢出 32 位整数
  • a.length <= 100000

 其左边所有元素的乘积 * 其右边所有元素的乘积 

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        vector<int>left(a.size(),1);
        vector<int>right(a.size(),1);
        vector<int> res;
        for(int i=1;i<a.size();i++)//右边界i不算
        {
            left[i]=left[i-1]*a[i-1];
        }
        for(int i=a.size()-2;i>=0;i--)//左边界i不算
        {
            right[i]=right[i+1]*a[i+1];
        }
        for(int i=0;i<a.size();i++)
        {
            res.push_back(left[i]*right[i]);
        }

        return res;
    }
};


剑指 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) 。

class Solution {
public:
    int strToInt(string str) {
        int flag=1;
        long res=0;
        int i=0;
        while(str[i]==' ')
        {
            i++;
        }
        if(str[i]=='-')
            flag=-1;
        if(str[i]=='-'||str[i]=='+')
            i++;
        for(;i<str.size();i++)
        {
            if(str[i]<'0'||str[i]>'9')
                break;
            res=res*10+(str[i]-'0');
            if (res >= INT_MAX && flag == 1) return  INT_MAX;
            if (res > INT_MAX && flag == -1) return  INT_MIN;
        }
        return flag*res;
    }
};
int number = Integer.MAX_VALUE / 10

    

这个条件的意思为,因为题目要求不能超过int范围,所以需要判断结果是否越界,
因为res每次都会 * 10 ,所以外面定义了一个int最大值除以10的数字,
                    此时只需要保证本次循环的res * 10 + chars[j] 不超过 int 即可保证不越界,
                    res > number 意思是,此时res已经大于number了,他 * 10 一定越界;
                    res == number && chars[j] > '7' 的意思是,当res == number时,即:214748364;
                    此时res * 10 变成 2147483640 此时没越界,但是还需要 + chars[j],
                    而int最大值为 2147483647,所以当chars[j] > 7 时会越界

class Solution {
public:
    int strToInt(string str) {
        int i = 0, flag = 1;
        int res = 0; //默认flag = 1,正数
        while (str[i] == ' ') i ++;
        if (str[i] == '-') flag = -1;
        if (str[i] == '-' || str[i] == '+') i ++;
        for (; i < str.size() && isdigit(str[i]); i ++)  {
            if (res > INT_MAX / 10 || (res == INT_MAX / 10 && str[i] - '0' > 7)) //溢出判定
                  return flag == 1 ? INT_MAX : INT_MIN;
            res = res * 10 + (str[i] - '0');
        } 
        return flag * res;
    }
};


面试题13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:

输入:m = 2, n = 3, k = 1
输出:3

示例 2:

输入:m = 3, n = 1, k = 0
输出:1

提示:

  • 1 <= n,m <= 100
  • 0 <= k <= 20

回溯 dfs

机器人的起点固定,从原点出发只有两个方向,而上一道题(矩阵的路径)起点是多个,就有四个方向

class Solution {
public:
    int sum(int i,int j )
    {
        int a=0,b=0;
        while(i)
        {
            a+=i%10;
            i=i/10;
        }
        while(j)
        {
            b+=j%10;
            j=j/10;
        }
        return a+b; 
    }
    int dfs(int m,int n,int i,int j,int k,vector<vector<bool>>&used)
    {
        if(i>=m||j>=n||sum(i,j)>k||used[i][j]==true)//走过了
            return 0;
        
        used[i][j]=true;//处理当前
        return dfs(m,n,i+1,j,k,used)+dfs(m,n,i,j+1,k,used)+1;
        
    }
    int movingCount(int m, int n, int k) {
        vector<vector<bool> > used(m, vector<bool>(n, false)); 
        return dfs(m,n,0,0,k,used);
    }
};


面试题45. 把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

示例 1:

输入: [10,2]
输出: "102"

示例 2:

输入: [3,30,34,5,9]
输出: "3033459"

提示:

  • 0 < nums.length <= 100

说明:

  • 输出结果可能非常大,所以你需要返回一个字符串而不是整数
  • 拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0

 自定义排序

先转成一个个字符串:“3”,“30”……
自定义排序:因为要先放入第一位最小的数字,相同再比较第二位。字符串比较是‘5’>'30',所以采用转成字符串。我们要的是305。
比较3,30,303<330,所以30要更小
可以用lamda,或者自定义sort():s1+s2<s2+s1 s1<s2 返回true
 

class Solution {
public:
    string minNumber(vector<int>& nums) {
        vector<string>a;
        string res;
        for(auto x:nums)
        {
            a.push_back(to_string(x));
        }
        //"10","2"
        sort(a.begin(),a.end(),[](string &r1,string &r2){
            return r1+r2<r2+r1;// 为真说明r1更小
        });
        for(int i=0;i<a.size();i++)
        {
            res+=a[i];
        }
        return res;

    }
};

class Solution {
public:
    struct cmp{
        bool operator()(string s1, string s2){
            return s1 + s2 < s2 + s1;
        }
    };
    string minNumber(vector<int>& nums) {
        vector<string> strs;
        string ans;
        for(int i = 0; i < nums.size(); i++){
            strs.push_back(to_string(nums[i]));
        }

        sort(strs.begin(), strs.end(), cmp());

        for(int i = 0; i < strs.size(); i++){
            ans += strs[i];
        }

        return ans;
    }
};

面试题59 - II. 队列的最大值

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

若队列为空,pop_front 和 max_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>a;
    deque<int>b;//递减队列,队头始终是queue里面的最大值,以实现O(1)
public:
    MaxQueue() {
        
    }
    
    int max_value() {
        return b.empty()?-1:b.front();
    }
    
    void push_back(int value) {
        a.push(value);
        while(!b.empty()&&b.back()<value)
        {
            b.pop_back();//queue中出现了次大值,那么更新deque里的对应的次大值
        }
        b.push_back(value);
    }
    
    int pop_front() {
        if(a.empty()) return -1;
        int res=a.front();
        if(res==b.front())//相同才能删,否则把别人的最大值删掉了
            b.pop_front();
        a.pop();
        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 19. 正则表达式匹配

请实现一个函数用来匹配包含'. ''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含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 的小写字母以及字符 . 和 *,无连续的 '*'

当 p[j - 1] != '*' 时, dp[i][j] 在当以下任一情况为 true 时等于 true :

dp[i - 1][j - 1] 且 s[i - 1] = p[j - 1]:字符串 s 的前 i-1 个字符和 p 的前 j -1个字符匹配, 且s的第i个字符等于p的第j个字符;

dp[i - 1][j - 1] 且 p[j - 1] = '.': 字符串 s 的前 i-1 个字符和 p 的前 j -1个字符匹配, 且p的第j个字符


剑指 Offer 37. 序列化二叉树
 

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

你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

提示:输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

示例:

输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]

 层序遍历,加个逗号处理

/**
 * 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 "";
        queue<TreeNode*> q;
        string s="";      
        q.push(root);

        while(!q.empty())
        {
            int size=q.size();
            
            for(int i=0;i<size;i++)
            {
                TreeNode*node=q.front();
                q.pop();
                if(node==NULL)
                {
                    s.push_back('$');
                }
                else
                    s.append(to_string(node->val));
                s.push_back(',');
                if(node)
                {
                    q.push(node->left);
                    q.push(node->right);
                }
            }
           
        }
        s.pop_back();//最后一个逗号
        return s;
    }

    
   
    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data.size()==0) return nullptr;
        int len=data.size();
        //data.erase(remove(data.begin(),data.end(),','),data.end());
        int i=0;
        vector<TreeNode*>vec;
        while(i<len)
        {
            string str="";
            
            while(i<len&&data[i]!=',')
            {
                str.push_back(data[i]);
                i++;
            }
            //根节点
            if(str=="$")
            {
                TreeNode*tmp=NULL;
                vec.push_back(tmp);
            }
            else
            {
                int temp=std::stoi(str);
                TreeNode*cur=new TreeNode(temp);
                vec.push_back(cur);
            }
            i++;
        }
        int j = 1;
        for(int i=0;j<vec.size();i++)
        {
            if(vec[i]==NULL) continue;
            if(j<vec.size()) vec[i]->left = vec[j++];
            if(j<vec.size()) vec[i]->right = vec[j++];
           
        }
        return vec[0];



        

    }
};

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

剑指 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、findMedian 进行 50000 次调用。

优先级队列

分为较小的一边和较大的一边
较小的一边放到大顶堆B,堆顶是最大值,较大的一边放到小顶堆A,堆顶是最小值

当两边个数相同时,说明m+n=N是偶数,中位数就是(两边堆顶相加)/2,此时要插入的元素放到A里:将新元素 num 插入至 B ,再将 B 堆顶元素插入至 A ;
不同,m+n=N奇数,中位数是a1即小顶堆的堆顶,此时要插入的元素放到B里:将新元素 num 插入至 A ,再将 A 堆顶元素插入至 B ;

取中位数的时候,若两堆数量相等,则各取堆顶取平均,若小顶比大顶多一,则多的那一个就是中位数。

 力扣

偶数和奇数时往哪边插入可以改变,相应的求中位数时就是取大顶堆了

 

 

class MedianFinder {
    
public:
    /** initialize your data structure here. */
    priority_queue<int,vector<int>,greater<int>> minHeap;
    priority_queue<int, vector<int>, less<int>>maxHeap;
    MedianFinder() {
        
    }
    //小顶堆的最大值小于或等于大顶堆的最小值
    void addNum(int num) {
        if(maxHeap.size()==minHeap.size())//偶数,插入小顶堆
        {
            maxHeap.push(num);
            int a=maxHeap.top();
            maxHeap.pop();
            minHeap.push(a);
        }
        else//奇数,插入大顶堆
        {
            minHeap.push(num);
            int a=minHeap.top();
            minHeap.pop();
            maxHeap.push(a);
             
        }
    }
    
    double findMedian() {
        if(maxHeap.size()==minHeap.size())
            return (maxHeap.top()+minHeap.top())/2.0;
        else
            return minHeap.top()*1.0;
    }
};

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

剑指 Offer 43. 1~n 整数中 1 出现的次数

输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

示例 1:

输入:n = 12
输出:5

示例 2:

输入:n = 13
输出:6

限制:

  • 1 <= n < 2^31

力扣

class Solution {
   public:
    int countDigitOne(int n) {
        // "1"出现的次数 = sum ("1"在各个计数位上出现的次数)
        // 从个位开始向最高位统计
        // 3101592
        // 将数字拆分为[a...][cur][b...]
        // cur 为当前位
        long base = 1;
        int res = 0;
        while (base <= n) {
            // 计算 a..., cur, b...
            int a, cur, b;
            a = n / base / 10;
            cur = (n / base) % 10;
            b = n % base;
            // 将当前位设为1,考察其他部分的变化范围
            if (cur > 1) {
                // 一、cur > 1,
                //          [3101 ] 5 [92]
                // 变化范围:[0-3101] 1 [0-99]
                // 总个数:   (a+1)  *  base
                res += (a + 1) * base;
            } else if (cur == 1) {
                // 二、cur == 1,
                //             [310] 1 [592]
                // 1、变化范围 [0-309] 1 [0-999]
                //              a    *  base
                // 2、变化范围 [310]   1 [0-592]
                //               1   *   (b+1)
                // 总个数:a *base + (b + 1)
                res += a*base + b + 1;

            } else {
                // 三、cur < 1,
                //           [31] 0 [1592]
                // 变化范围 [0-30] 1 [0-9999]
                // 总个数    a     *   base
                res += a * base;
            }
            // 统计更高一位
            base *= 10;
        }
        return res;
    }
};

 


剑指 Offer 51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]
输出: 5

限制:

0 <= 数组长度 <= 50000

暴力双层for循环-->归并排序 

并且此时左边数组中的从i到m的所有数都是大于nums[j]的(因为m左右的数组都是已经排好序的
即此时有m-i+1个逆序对,加到res上即可
class Solution {
    vector<int>res;
    int ans=0;
public:
    void mergesort(int l,int r,vector<int>& nums)
    {
        if(l>=r)    return ;
        int m=(l+r)/2;

        mergesort(l,m,nums),mergesort(m+1,r,nums);
        int i=l,j=m+1,k=0;
        while(i<=m&&j<=r)
        {
            if(nums[i]<=nums[j])
            {
                res[k++]=nums[i++];
            }
            else
            {
                res[k++]=nums[j++];
                ans+=m-i+1;
            }
        }
        while(i<=m)
                res[k++]=nums[i++];
        while(j<=r)
                res[k++]=nums[j++];
            
        for(int i=l,j=0;i<=r;i++,j++)
                nums[i]=res[j];
        
    }
    int reversePairs(vector<int>& nums) {
        res.resize(nums.size());

        mergesort(0,nums.size()-1,nums);
        return ans;
    }
};

 


剑指 Offer 59 - I. 滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

提示:

你可以假设 总是有效的,在输入数组 不为空 的情况下,1 ≤ k ≤ nums.length

双端队列deque,类似于最小栈

力扣 

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size() == 0 || k == 0) return {};
        deque<int> dq;//双端队列
        vector<int> res;
        int left = 1-k,right = 0; //左边界的范围是[1-k,n+1-k] 有边界的范围是[0,n-1]

        for(;right < nums.size();left++,right++) {
        //1.dq队头如果和已经离开窗口的元素相等就删除
            if(left > 0 && nums[left-1] == dq[0]) {
                dq.pop_front();
              //如果上一轮被删掉的滑动窗口左边界 就是队首元素(最大值)
              //则要删掉它的王者地位,因为上一轮它已经out了。
              //由它的直系小弟接替它的王者地位
            }

        //2.看本次新来的右边界在dq中什么地位,它会把比它小的都干掉,
        //比nums[right]小的都删除,dq始终递减
            while(!dq.empty() && dq[dq.size()-1] < nums[right]) {
                dq.pop_back();
             //把dp中所有小于nums[right]的小啰啰干掉,保持它王者的地位。
             //若没有比它小的,则它不敢造次,自己自觉的站在大佬的后面,
            }
        //3.//自己自觉的站在大佬的后面
            dq.push_back(nums[right]);
        
        //4.把本次滑动窗口的大佬(队首元素)为最大值,放入答案数组中
            if(left >= 0) {
                res.push_back(dq[0]);
            }
            
        }
       return res;
    }
};

 

  • 10
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值