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

image.png

  1. class Solution {
  2. public:
  3. std::set<int> s;
  4. int findRepeatNumber(vector<int>& nums) {
  5. for(int i=0;i<nums.size();i++){
  6. pair<set<int>::iterator,bool> p = s.insert(nums[i]);// 注意s.insert的返回值
  7. if(p.second==false) return nums[i];
  8. }
  9. return -1;
  10. }
  11. };
  12. // 时间复杂度: O(n), 空间复杂度: O(n)
  13. // set方法,插入重复元素判断返回值是否为fasle,进而返回重复元素
  1. class Solution {
  2. public:
  3. int findRepeatNumber(vector<int>& nums) {
  4. for(int i=0;i<nums.size();i++){
  5. while(nums[i]!=i){
  6. if(nums[nums[i]]==nums[i]) return nums[i];
  7. swap(nums[i],nums[nums[i]]);
  8. }
  9. }
  10. return -1;
  11. }
  12. };
  13. // 时间复杂度: O(n), 空间复杂度: O(1)
  14. // 但是会改原数组

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

image.png

  1. class Solution {
  2. public:
  3. bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
  4. if(matrix.size()==0) return false;
  5. //行、列 从0开始
  6. int rows =matrix.size()-1;
  7. int clos = matrix[0].size()-1;
  8. int i=0;
  9. while(clos>=0&&i<=rows){
  10. if(matrix[i][clos]==target) return true;
  11. else if(matrix[i][clos] < target) i++;
  12. else clos--;
  13. }
  14. return false;
  15. }
  16. };
  17. // 时间复杂度: O(n+m) 空间复杂度: O(1)
  18. //右上角开始,比当前值小,移动到左边一列,比当前大,移动到下一行

剑指 Offer 05. 替换空格

image.png

class Solution {
public:
    string replaceSpace(string s) {
        string res;
        for(auto c:s){
            if(c==' ') res+="%20";
            else res+=c;
        }
        return res;
    }
};
//时间复杂度: O(n) 空间复杂度: O(n)

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

image.png

// 思路   依次入栈,出栈即反转
class Solution {
public:
    vector<int> res;
    vector<int> reversePrint(ListNode* head) {
        stack<int> s;
        while(head!=NULL){
            s.push(head->val);
            head=head->next;
        }
        //出栈
        while(!s.empty()){
            res.push_back(s.top());
            s.pop();
        }
        return res;
    }
};
//时间复杂度: O(n) 空间复杂度: O(n)
//思路 遍历链表将数据存入数组中,再反转数组
class Solution {
public:
    vector<int> res;
    vector<int> reversePrint(ListNode* head) {
        while(head!=NULL){
            res.push_back(head->val);
            head=head->next;
        }
        reverse(res.begin(),res.end());
        return res;
    }
};
//时间复杂度: O(n) 空间复杂度: O(n)
// 思路: 反转整个链表  背过反转模板
class Solution {
public:
    //反转链表模板  O(n)
    ListNode * reverseList(ListNode *head){
         if(head== NULL || head ->next == NULL) return head; //如果为空,则返回
        ListNode *p = head;
        ListNode* q = head->next;
        while(q){
            ListNode *tmp = q->next;
            q->next = p;
            p=q;
            q=tmp;
        }
        head->next =NULL;
        return p;
    }
    //打印
    vector<int> reversePrint(ListNode* head) {
       ListNode* p =  reverseList(head);
       while(p){
           res.push_back(p->val);
           p=p->next;
       }
       return res;
    }
private:
    vector<int> res;    
};

剑指 Offer 07. 重建二叉树

image.png

class Solution {
public:
    unordered_map<int,int> pos;//用以存储中序遍历中数字x的位置,这样可以使用O(1) 的时间查找
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        for(int i=0;i<inorder.size();i++){
            pos[inorder[i]] = i;
        }
        return dfs(preorder,inorder,0,preorder.size()-1,0,inorder.size()-1);
    }
    TreeNode* dfs(vector<int> &preorder,vector<int> & inorder,int pl,int pr,int il,int ir){
        if(pl>pr) return NULL;
        TreeNode *root = new TreeNode(preorder[pl]);
        int k = pos[preorder[pl]];
        root->left = dfs(preorder,inorder,pl+1,pl+k-il,il,k-1);
        root->right = dfs(preorder,inorder,pl+k-il+1,pr,k+1,ir);
        return root;
    }
};
//时间复杂度 O(n)

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

image.png

class CQueue {
public:
stack<int> s1,s2;
    CQueue() {


    }

    void appendTail(int value) {
        if(s1.empty()) s1.push(value);
        else {
            while(!s1.empty()){
                s2.push(s1.top());
                s1.pop();
            }
            s1.push(value);
            while(!s2.empty()){
                s1.push(s2.top());
                s2.pop();
            }
        }
    }

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

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

image.png

class Solution {
public:
    int a=0,b=1;
    int fib(int n) {
        if(n==0) return 0;
        if(n==1) return 1;
        for(int i=2;i<=n;i++){
            int tmp =b;
            b=b+a;
            a=tmp; 
            if(b>1e9+7) b=b%1000000007;
        }
        return b;
    }
};

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

image.png

// 青蛙跳台阶问题: f(0)=1f(0)=1 , f(1)=1f(1)=1 , f(2)=2f(2)=2 ;
// 斐波那契数列问题: f(0)=0f(0)=0 , f(1)=1f(1)=1 , f(2)=1f(2)=1 。

class Solution {
public:
    int a=1,b=1; // 起始点不同
    int numWays(int n) {
        if(n==0) return 1;
        if(n==1) return 1;
        for(int i=2;i<=n;i++){
            int tmp =b;
            b+=a;
            a =tmp;
            if(b>1e9+7) b=b%1000000007;
        }
        return b;
    }
};

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

image.png

二分法

class Solution {
public:3
    int minArray(vector<int>& numbers) {
        if(!numbers.size()) return -1;
        int n= numbers.size()-1; //闭区间
        while(n>0&&numbers[n]==numbers[0]) n--;
        if(numbers[n]>numbers[0]) return numbers[0];
        int l=0,r=n;
        while(l<r){
            int mid = l+r>>1;
            if(numbers[mid] < numbers[0] ) r=mid;
            else l=mid+1;
        }
        return numbers[l];
    }
};

剑指 Offer 12. 矩阵中的路径

image.png

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        for(int i=0;i<board.size();i++){
            for(int j=0;j<board[i].size();j++){
                if(dfs(board,word,0,i,j)) return true;
            }
        }
         return false;
    }


    bool dfs(vector<vector<char>>& board,string &word,int u,int x, int y){
        if(board[x][y]!=word[u]) return false;
        if(u==word.size()-1) return true;

        int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
        char t = board[x][y];
        board[x][y] = '.';
        for(int i=0;i<4;i++){
            int a=x+dx[i],b=y+dy[i];
            if(a>=0&&a<board.size()&&b>=0&&b<board[a].size()){
                if(dfs(board,word,u+1,a,b)) return true;
            }
        }
        board[x][y]=t;
        return false;
    }
};

剑指 Offer 13. 机器人的运动范围

机器人路径 (宽搜问题)
宽搜模板
image.png

//BFS使用队列,把每个还没有搜索到的点一次放入队列,然后再弹出队列的头部元素当做当前遍历点。
//如果不需要确定当前遍历到了哪一层,BFS模板如下

压入初始状态
while queue 不空:
    cur = queue.pop()
    for 节点 in cur的所有相邻节点:
        if 该节点有效且未访问过:
            queue.push(该节点)


/*如果要确定当前遍历到了哪一层,BFS模板如下。这里增加了level表示当前遍历到二叉树中的哪一层了,也可以理解
为在一个图中,现在已经走了多少步了。size表示在开始遍历新的一层时,队列中有多少个元素,即有多少个点需要向
前走一步。*/

level = 0 
压入初始状态
while queue 不空:
    size = queue.size()
    while (size --) {
        cur = queue.pop()
        for 节点 in cur的所有相邻节点:
            if 该节点有效且未被访问过:
                queue.push(该节点)
    }
    level ++;

作者:fuxuemingzhu
链接:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/bfsmo-ban-yi-ci-bei-hui-chu-chu-shi-yong-by-fuxuem/
class Solution {
public:
    //单个数字的数位和
    int singleSum(int num){
        int res =0;
        while(num){
            res+=num%10;
            num = num/10;
        }
        return res;    
    }
    //坐标是数位和
    int doubleSum(int a ,int b){
        return singleSum(a)+singleSum(b);
    }

    int movingCount(int m, int n, int k) {
        vector<vector<bool>> visted(m,vector<bool>(n,false)); // 标记数组
        queue<pair<int,int>> q; //宽搜需要队列
        int count=0;//计数器
        int dx[4]={0,1,0,-1},dy[4]={-1,0,1,0};
        q.push({0,0});///压入初始状态
        while(q.size()){ // 队列不为空
            pair<int,int > c=q.front();//点的坐标,取出头部元素
            q.pop();
            if(doubleSum(c.first,c.second)>k||visted[c.first][c.second]) continue;    
            visted[c.first][c.second] = true;
            count++;
            for(int i=0;i<4;i++){ // 该点的上下左右
                int a =c.first+dx[i],b=c.second+dy[i];
                if(a>=0&&a<m&&b>=0&&b<n){
                    q.push({a,b});
                }
            }
        }
        return count;
    }
};

剑指 Offer 14- I. 剪绳子

image.png

// 找规律 尽可能找到更多的3可以使得结果最大
class Solution {
public:
    int cuttingRope(int n) {
        if(n<=3) return 1*(n-1);
        int res =1;
        if(n%3==1) res*=4,n-=4;
        if(n%3==2) res*=2,n-=2;
        while(n){
            res*=3;
            n-=3;  
        }
        return res;
    }
};
// 时间复杂度:O(1)
// 空间复杂度:O(1)

剑指 Offer 14- II. 剪绳子 II

image.png

剑指 Offer 15. 二进制中1的个数

image.png

class Solution {
public:
    int lowbit(uint32_t n){
        return n&-n; //返回x的最后一位1  例如 10100 --> 100   1010110 --> 10
    }
    int hammingWeight(uint32_t n) {
        int res=0;
        while(n){
            n-=lowbit(n);
            res++;
        }
        return res;
    }
};

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

image.png

/*方法一
*超出了时间限制
*/
class Solution {
public:
    double myPow(double x, int n) {
        double res=1;
        for(int i=0;i<abs(n);i++){
            res*=x;
        }
        if(n<0) return 1/res;
        return res;
    }
};

/*方法二
* 
*/

class Solution {
public:
    double myPow(double x, int n) {
        if(x==1||n==0) return 1;
        double res=1;
        long  _n = abs(n);// leetcode中有超过int范围的数,用long定义

        while(_n){
            if(_n&1){
                res*=x;
            }
            x*=x;
            _n>>=1;//_n=_n>>1;
            //cout << _n;
        }
        if(n<0) return 1/res;
        return res;
    }
};

剑指 Offer 17. 打印从1到最大的n位数

image.png

/*
*简单解法
*/

class Solution {
public:
    vector<int> printNumbers(int n) {
        vector<int> res;
        if(n==0) return res;
        for(int i=1;i<pow(10,n);i++){
            res.push_back(i);
        }
        return res;
    }
};

// 大数

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

image.png

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head==NULL|| head->next==NULL) return NULL;
        ListNode *pNode =head; // 对头节点单独判断
        if(pNode->val==val) {
            return pNode->next;
        }
        while(pNode!=NULL&&pNode->next->val!=val) pNode=pNode->next;
        pNode->next=pNode->next->next; 
        return head;
    }

};


//双指针
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode *pCur =head->next;
        ListNode *pPre = head;
        if(head->val == val ) return head->next; // 对头节点单独判断
        while(pCur!=NULL&&pCur->val!=val){
            pPre=pCur;
            pCur=pCur->next;

        }
        pPre->next = pCur->next;
        return head;

    }
};

18.2在O(1)时间删除链表结点
假设链表一定存在,并且该节点一定不是尾节点。

class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val =node->next->val;  // 有假设条件,不是尾节点,所以可以让下一个节点的值覆盖当前节点
        node ->next = node->next->next;//再删除下一个节点
    }
};

18.3.1 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。leetcode 83

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head==NULL) return NULL;
        ListNode *pCur = head;
        while(pCur->next){
            if(pCur->next->val==pCur->val) pCur->next = pCur->next->next;
            else pCur=pCur->next;
        }
        return head;
    }
};

//定义一个指针,判断当前节点的值是否与当前节点下一个节点的值相同,如果相同,删除下一个节点,如果不同,
//指针移向下一个节点
//遍历整个链表

//另外,由于头节点不会被删除,所以不需要定义

18.3.2 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留 leetcode 82

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* xhead = new ListNode(-1); // 有可能删除头节点,定义虚拟头节点将问题一般化
        xhead->next =head;
        ListNode *p = xhead;
        while(p->next){
            ListNode *q = p->next;
            while(q&&q->val==p->next->val) q=q->next;
            if(p->next->next==q) p =p->next; //如果区间长度为1
            else p->next=q; //区间长度不是1,则删除
        }
        return xhead->next;
    }
};

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

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

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

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //头指针
        if(l1==NULL&&l2==NULL) return NULL;
        if(l1==NULL) return l2;
        if(l2==NULL) return l1;

        ListNode *xhead =new ListNode(-1);
        ListNode * cur = xhead;

        ListNode *p1 =l1, *p2 =l2;
        while(p1!=NULL&&p2!=NULL){
            if(p1->val<=p2->val){
                cur->next = p1;
                p1 = p1->next;
                cur=cur->next;
            } 
            else{
                cur->next = p2;
                p2 =p2->next;
                cur=cur->next;
            }
        }
        if(p1==NULL) cur->next = p2;
        if(p2==NULL) cur->next =p1;
        return xhead->next;
    }
};

剑指 Offer 26. 树的子结构

class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if(!A||!B) return false; //递归出口
        if(A->val == B->val){  // 遍历A,找到与B的头节点相同的点
            if(isSub(A,B)) return true; // 头节点相同再递归判断AB左子树、右子树是否相同
        }
        return (isSubStructure(A->left,B)|| isSubStructure(A->right,B));
    }
    // 遍历到B的叶节点 真
    // 遍历到A的叶节点而B不为空,返回假
    // 如果AB根节点的值不相同的话,返回假
    bool isSub(TreeNode* a,TreeNode *b){
        if(!b) return true;// 如果b的节点遍历到叶节点,说明是子串
        if((!a&& b) || (a->val!=b->val)) return false;// 如果a为空,b不为空,返回false,相当于根节点
        return isSub(a->left,b->left)&&isSub(a->right,b->right);
    }
};

剑指 Offer 27. 二叉树的镜像

class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(!root) return nullptr;
        mirrorTree(root->left);
        mirrorTree(root->right);
        swap(root->left, root->right);
        return root;
    }
};

class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        stack<TreeNode*> stk;
        if(root) stk.push(root);
        while(stk.size()) {
            TreeNode* tr = stk.top();
            stk.pop();
            swap(tr->left, tr->right);
            if(tr->left) stk.push(tr->left);
            if(tr->right) stk.push(tr->right);
        }
        return root;
    }
};

class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        queue<TreeNode*> q;
        if(root) q.push(root);
        while(q.size()) {
            TreeNode* tr = q.front();
            q.pop();
            swap(tr->left, tr->right);
            if(tr->left) q.push(tr->left);
            if(tr->right) q.push(tr->right);
        }
        return root;
    }
};

剑指 Offer 28. 对称的二叉树

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root==NULL) return true;
        return dfs(root->left,root->right);
    }

    bool dfs(TreeNode* mleft, TreeNode* mright){
        if(mleft==NULL&& mright==NULL) return true;
        if(mleft==NULL||mright==NULL||mright->val!=mleft->val) return false;
        return dfs(mleft->left,mright->right) && dfs(mleft->right,mright->left);       
    }
};

题解
image.png

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

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;
        if(matrix.size()==0||matrix[0].size()==0) return res;
        int rows = matrix.size(), cols = matrix[0].size();

        int l=0,r=cols-1,t=0,b=rows-1;
        while(l<=r&&t<=b){
            for(int i=l;i<=r;i++){
                res.push_back(matrix[t][i]);
            }
            for(int i=t+1;i<=b;i++){
                res.push_back(matrix[i][r]);
            }
            if(l<r&&t<b){
            for(int i=r-1;i>=l;i--){
                res.push_back(matrix[b][i]);
            }
            for(int i=b-1;i>t;i--){
                res.push_back(matrix[i][l]);
            }
            }
            l++;
            t++;
            r--;
            b--;
        }

        return res;
    }
};

题解
image.png

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

class MinStack {
public:
    /** initialize your data structure here. */

    MinStack() {

    }
    void push(int x) {
        s1.push(x);
        if(s2.empty()||x<=s2.top()){
            s2.push(x);
        }
        else{
            s2.push(s2.top());
        }
    }
    void pop() {
        if(!s1.empty()&& !s2.empty()){
            s1.pop();
            s2.pop();
        }
    } 
    int top() {
        return s1.top();
    }

    int min() {
        return s2.top();
    }
private:
    stack<int> s1,s2;
};

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

image.png

class Solution {
public:
    vector<int> levelOrder(TreeNode* root) {
        vector<int> res;
        if(!root) return res;
        queue<TreeNode*>  q;
        q.push(root);
        while(q.size()){
            TreeNode *tmp = q.front();
            q.pop();
            res.push_back(tmp->val);
            if(tmp->left) q.push(tmp->left);
            if(tmp->right) q.push(tmp->right);
        }
        return res;
    }
};

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

image.png

class Solution {
    // 标准的 bfs 方法
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(root==NULL) return res;
        queue<TreeNode*> q;
        q.push(root);
        while(q.size()){
            vector<int> tmp; //定义在此处,每次会更新为空
            int t= q.size();
            for(int i=0;i<t;i++){
                TreeNode *p= q.front();
                q.pop();
                tmp.push_back(p->val);
                if(p->left) q.push(p->left);
                if(p->right) q.push(p->right);
            }
            res.push_back(tmp);
        }
        return res;
    }
};

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

image.png

class Solution {

    //双端队列
    //bfs
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if(root == NULL) return res;

        deque<TreeNode*> d;//双端队列
        bool flag = true;
        d.push_back(root);
        while(d.size()){
            vector<int> v;
            int n = d.size();
            TreeNode *p;
            for(int i=0;i<n;i++){

                 if(flag){
                     p = d.front();
                     d.pop_front();
                     v.push_back(p->val);
                     if(p->left) d.push_back(p->left);
                     if(p->right) d.push_back(p->right);
                 }
                 else{
                     p=d.back();
                     d.pop_back();
                     v.push_back(p->val); 
                     if(p->right) d.push_front(p->right);
                     if(p->left) d.push_front(p->left);

                 }
            }
            flag=!flag;
            res.push_back(v);
        }
        return res;

    }
};

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

image.png

思路: 递归
二叉搜索树的左子树都小于根节点,右子树都大于根节点。因此需要找到分界点,分成左子树和右子树再分别遍历
步骤:

  1. 如果大小小于等于1的话,可以直接返回,因为空树也是二叉搜索树
  2. 递归函数,输入边界l和r
  • 递归结束条件 ;l<=r
  • 根节点一定位于数组最后一个元素
  • 找到分界点,即找到第一个大于根节点的数,它的左边是左子树,右边是右子树
  • 左边的数肯定成立了,因为这是循环结束的条件,所以判断右边是否成立,遍历比较,一旦有小于根节点的数,直接返回false
  • 否则,本层成立,递归遍历左子树与右子树

    // 递归解法
    class Solution {
    public:
    vector<int> v; // 为了减少递归传递一个参数,定义成类的成员变量
      bool verifyPostorder(vector<int>& postorder) {
          v=postorder;
          if(postorder.size()<=1) return true;
          return dfs(0,v.size()-1);
      }
      bool dfs(int l,int r){
          if(l>=r) return true;// 递归结束条件,说明全部成立
          int k =l;
          int root = v[r];
          while(k<r&&v[k]<root) k++; // 到k,没有=
          for(int i=k;i<r;i++){
              if(v[i]<root) return false;
          }
    
          return dfs(l,k-1)&&dfs(k,r-1);
    
      }
    };
    // 时间复杂度 O(n^2)
    

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

image.png
思路:
dfs遍历每一条路径
结束条件

  • 遍历到根节点,即左右子树都为空
  • __sum减为0

    class Solution {
    public:
      vector<vector<int>> res;
      vector<int> path; // 定义为类成员变量
    
      vector<vector<int>> pathSum(TreeNode* root, int sum) {
          if(!root) return res;
          dfs(root,sum);
          return res;
      }
      void dfs(TreeNode* root,int sum){
          if(!root) return ;
    
          sum-=root->val;
          path.push_back(root->val);
          // 如果到达了叶子节点即该节点的左右子树都为空且sum==0了,说明这条路径是满足的,保存到res中
          if(!root->left&&!root->right&&sum==0){
              res.push_back(path);
          }
          dfs(root->left,sum);
          dfs(root->right,sum);
          // 注意: 要回溯,恢复原状,因为sum是传递进去的参数,在递归中不满足返回上一层时不会影响sum,因此sum不用回溯,只需将path数组回溯
          path.pop_back();
      }
    };
    

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

    image.png

  • 方法一 (最优解) 直接在原链表进行改造,时间复杂度O(n),空间复杂度O(1) ```cpp / // 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) return head;
      Node* p =head;
      // 第一次遍历,复制每个节点并插入
      while(p){
          Node* q = new Node(p->val);
          Node *tmp = 
          q->next = p->next;
          p->next=q;
          p=p->next->next;
      }
      //第二次遍历,给random指针赋值,p->next->random=p->random->next;
      p = head;
      while(p){
          if(p->random){
              p->next->random=p->random->next;
          }
          p=p->next->next;
      }
      // 第三次遍历 ,将链表从中拆分处理
      Node *xhead = new Node(-1);
      p=head;
      Node *cur = xhead;
      while(p){
          cur->next= p->next;
          cur =cur->next;
          p->next=p->next->next;// 将原链表恢复原状
          p=p->next;
      }
      return xhead->next;
    

    } };

// 时间复杂度 三次遍历 所以为O(n) ,空间复杂度 O(1)


- 方法二 哈希表 需要额外的空间,时间复杂度O(n),空间复杂度O(n)
```cpp

//key 与 value 都为Node 节点
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(!head) return head;
        unordered_map<Node*,Node*> mhash;
        Node* p=head;
        while(p){
            mhash[p] = new Node(p->val);
            p=p->next;
        }
        p=head;
        while(p){
            if(p->next) mhash[p]->next= mhash[p->next];
            if(p->random) mhash[p]->random = mhash[p->random];
            p=p->next;
        }
        return mhash[head];
    }
};

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

image.png
image.png
整体思路

  1. 要求有序,二叉树又是搜索二叉树,因此采用中序遍历就可以排好序
  2. 需要记录上次访问的节点pre,这样可以直接pre->right = cur; cur->left =pre; 完成双向链表的创建
  3. 循环链表特殊处理 head->left = tail; tail ->right =head;

细节描述:

  • 关于头节点head和尾节点的获取,当递归到左子树的叶节点时返回,此时root即位头节点,我们可以通过判断头节点是否为空,只赋值一次。尾节点即为最后的pre

    class Solution {
    public: 
      Node *pre =NULL;//定义一个node记录先前节点
      Node *head =NULL;
      Node* treeToDoublyList(Node* root) {
          if(!root) return root;
          dfs(root);
          pre->right=head;
          head->left=pre;
          return head;
      }
      void dfs(Node *root){
          if(!root) return;
          dfs(root->left);
          if(!head){ // 初始化头节点和记录当前节点
              head=root;
              pre =root;
          }
          else{
              pre->right=root;
              root->left=pre; // 关键部分
              pre =root;
          }
          dfs(root->right);
      }
    };
    

    剑指 Offer 37. 序列化二叉树

    思路:
    序列化二叉树,可以用不同的遍历方法,只需要在恢复时按同样的顺序即可。
    先序递归遍历:

    class Codec {
    public:
      string res;
      // Encodes a tree to a single string.
      string serialize(TreeNode* root) {
          if(!root) return res;
          dfs_s(root);
          return res;
      }
      void dfs_s(TreeNode* root){
          if(!root){
              res+="null ";//加空格,空格分割
              return ;
          }
          res+= to_string(root->val)+" ";
          dfs_s(root->left);
          dfs_s(root->right);
      }
    
      // Decodes your encoded data to tree.
      TreeNode* deserialize(string data) {
          int u =0;
          return dfs_d(data,u);
    
      }
      //u是引用,遍历过程中会依次后移
      TreeNode* dfs_d(string data,int &u){
          if(u==data.size()) return NULL;
          int k=u;
          bool flag=false;
          int sum;
          while(data[k]!=' ')k++;// 取得该字段末尾 比如说 "123"
          if(data[u]=='n'){
              u=k+1;
              return NULL;
          }
          for(int i=u;i<k;i++){
              //判断负数
              if(data[i]=='-'){
                  flag=true;
                  continue;
              }
              sum=sum*10+(data[i]-'0');
          }
          if(flag){
              flag=!flag;
              sum =-sum;
          }
          u=k+1;
          TreeNode* root = new TreeNode(sum);
          root->left=dfs_d(data,u);
          root->right=dfs_d(data,u);
          return root;
      }
    };
    

    剑指 Offer 38. 字符串的排列

    image.png
    思路:

  • dfs() 深搜

  • 为了去除重复元素,我们可以先排序将相同的元素挨在一起,对于相同的元素,认为定义后一个元素只能排在上一个相同元素的后面,从而保证不重复。

    class Solution {
    public:
      vector<string> res;
      string path;
      vector<string> permutation(string s) {
          if(!s.size()) return res;
          path.resize(s.size());
          sort(s.begin(),s.end());
          dfs(s,0,0,0);
          return res;
      }
      // u :  s 中的第u个元素
      // start : 为了去重而设置,假如和前一个元素不相等,则从第一个位置开始枚举,如果相同,则只能从当前元素后面开始
      // flag : 也可以设置成bool数组,标志该位是否为空,但设置为数组时,记得回溯
      void dfs(string &s,int u,int start,int flag){
          if(u==s.size()){
              res.push_back(path);
              return;
          }
          if(!u||s[u]!=s[u-1]) start =0;
          for(int i=start;i<s.size();i++){
              if(!(flag>>i&1)){ // 第i为是否为空
                  path[i]=s[u];
                  dfs(s,u+1,i+1,flag+(1<<i));
          }
      }
      }
    };
    

    数字排列
    image.png

    class Solution {
    public:
      vector<vector<int>> res;
      vector<int> path;
      vector<vector<int>> permutation(vector<int>& nums) {
          path.resize(nums.size());
          sort(nums.begin(),nums.end());
          dfs(nums,0,0,0);
          return res;
      }
      void dfs(vector<int>& nums,int u,int start,int flag){
          if(u==nums.size()){
              res.push_back(path);
              return;
          }
          if(!u||nums[u]!=nums[u-1]) start=0;
          for(int i=start;i<nums.size();i++){
              if(!(flag>>i & 1)){
                  path[i]=nums[u];
                  dfs(nums,u+1,i+1,flag+(1<<i));
    
              }
          }
    
      }
    };
    

    dfs问题
    image.png
    写法1:
    采用和上述方法一致的写法,即标志采用标志位的写法
    注意:
    关于回溯的问题,因为我们将标志位传入了形参,因此不用再另行进行回溯。回溯见下一种写法,不过思想是一致的。 ```cpp

    include

    include

    using namespace std; vector path; vector> res; int n; void dfs(int u,int flag){ if(u==n) {

      res.push_back(path);
      return;
    

    } for(int i=0;i<n;i++){

      if(!(flag>>i &1)){
          path[u]=i+1; //因为i是从0开始的
          dfs(u+1,flag+(1<<i));
    

    } } } int main(){ cin >> n; path.resize(n); dfs(0,0); for(auto v:res){

      for(auto x:v)
          cout << x << ' ';
      cout << endl;
    

    } return 0; }

/ 3 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1 /

**写法二**<br />**
```cpp

#include <iostream>
#include <vector>
using namespace std;
vector<int> path;
vector<vector<int>> res;
int n;
vector<bool> flag;
void dfs(int u){
    if(u==n){
        res.push_back(path);
        return;
    }
    for(int i=0;i<n;i++){
        if(!flag[i]){ // 未被访问
            path[u]=i+1;
            flag[i]=true; // 已访问
            dfs(u+1);
            flag[i]=false; //回溯
        }
    }
}
int main(){
    cin >> n;
    path.resize(n);
    flag=vector<bool>(n,false);
    dfs(0);
    for(auto v:res){
        for(auto x:v)
            cout << x << ' ';
        cout << endl;
    }
    return 0;
}
/*
3
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*/

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

image.png

//排序方法 时间复杂度O(nlgn),空间复杂度O(1)
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        if(nums.size()==1) return nums[0];
        sort(nums.begin(),nums.end());
        return nums[nums.size()/2];
    }
};
// 哈希表 时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int,int> mhash;
        for(auto x:nums){
            mhash[x]++;
        }
        for(auto a:mhash){
            if(a.second>nums.size()/2)
            return a.first;
        }
        return 0;
    }
};

摩尔投票法
思路:
image.png

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int count =0;
        int val ;
        for(auto x:nums){
            if(!count) val=x,count=1;  // 如果count 为0 
            else{ // 如果count 不为0
                if(val==x) count++;
                else count--;
            }
        }
        return val;
    }
};
/*************************************************/
class Solution {
    public int majorityElement(int[] nums) {
        int vote = 0;   int target = nums[0];
        for(int i =0;i<nums.length;i++){
            if(nums[i]==target) vote++;
            if(nums[i]!=target) vote--;
            if(vote==0&&(i+1)<nums.length) target=nums[i+1];
        }
        return target;
    }
}

剑指 Offer 40. 最小的k个数

image.png

// 大根堆
// 大根堆在stl是以优先级队列的形式存在的
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        priority_queue<int> heap;
        for(auto x:arr){
            heap.push(x);
            if(heap.size()>k) heap.pop();
        }
        vector<int> res;
        while(heap.size()){
            res.push_back(heap.top());
            heap.pop();
        }
        reverse(res.begin(),res.end()); // 大根堆,顺序是反的,所以得反转
        return res;
    }
};
// 排序
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> res;
        if(arr.size()==0) return res;
        sort(arr.begin(),arr.end());
        for(int i=0;i<k;i++){
            res.push_back(arr[i]);
        }
        return res;
    }
};

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

image.png
思路:
image.png

class MedianFinder {
public:
    priority_queue<int,vector<int>,greater<int>> mheap;// 小顶堆
    priority_queue<int> lheap; // 大顶堆
    // mheap 当为奇数时,多存放一个元素比lheap,为偶数时,存放元素个数相等
    /** initialize your data structure here. */
    MedianFinder() {

    }

    void addNum(int num) {
        if(mheap.size()!=lheap.size()){
            mheap.push(num);
            lheap.push(mheap.top());
            mheap.pop();
        }
        else{
            lheap.push(num);
            mheap.push(lheap.top());
            lheap.pop();
        }
    }

    double findMedian() {
        if(mheap.size()!=lheap.size()) return mheap.top();
        else return static_cast<double>((lheap.top()+mheap.top())/2.0);
    }
};

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

DP

image.png

class Solution {
    //动态规划的思想
    // 假设当前项前一项的和为s, 如果s<0 , 则s=x;如果s>0,则s=s+x; 记录s出现的最大值
public:
    int maxSubArray(vector<int>& nums) {
        int res = INT_MIN;
        int s=0;
        for(auto x:nums){
            if(s<0) s=0;
            s+=x;
            res= max(res,s);
        }
        return res;
    }
};

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

思路:
假设一个数 32 4 134
当前指向的数为 4 , left =32 right = 134

  • left: 00-31 后三位 000-999 即t=1000; res+= left t = 32 1000;
  • 当前两位为32时, 当前位置分情况讨论,
    • cur=0时, 没有1的情况
    • cur=1时,000-134 即 res+ = right+1
    • cur>1时,000-999 即 res+= t;

步骤:

  • 先分解
  • 遍历每一位数,再求出 左侧和右侧两个参数
  • 按规律分情况讨论
    class Solution {
    public:
      int countDigitOne(int n) {
          int res;
          vector<int> num;
          while(n){
              num.push_back(n%10);
              n/=10;
          }
          for(int i=num.size()-1;i>=0;i--){
              int left=0,right=0;
              int t=1;
              for(int j=i-1;j>=0;j--) right=right*10+num[j],t*=10;
              for(int j=num.size()-1;j>i;j--) left=left*10+num[j];
              res+= left*t;
              if(num[i]==1) res+=right+1;
              else if(num[i]>1) res+=t;
          }  
          return res;
      }
    };
    // 时间复杂度 O(logn) 空间复杂度 O(1)
    

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

格外注意 n与 i
image.png
image.png
image.png

class Solution {
public:
    int findNthDigit(int n) {
        int i =1;
        while(n>0.9*pow(10,i)*i) n-=0.9*pow(10,i)*i,i++; // 求出在几位数的区间 i表示,n表示在次区间的多少位
        int num = pow(10,i-1) + (n-1)/i; // base+ 
        string res = to_string(num);
        return res[(n-1)%i]-'0';
    }
};

剑指 Offer 45. 把数组排成最小的数

image.png
思路:
image.png

class Solution {
public:
// 注意添加static
static bool cmp(string &st1,string &st2){
        return st1+st2 < st2+st1;
    }
    string minNumber(vector<int>& nums) {
        vector<string> vst;
        for(int i=0;i<nums.size();i++){
            vst.push_back(to_string(nums[i]));
        }
        sort(vst.begin(),vst.end(),cmp);
        //拉姆达 表达式写法
        //sort(vst.begin(),vst.end(),[](string &st1,string &st2){return st1+st2<st2+st1;});
        string res;
        for(auto x:vst) res+=x;
        return res;
    }    
};
class Solution {
public:
    static bool cmp(int a, int b ){
        string sa =to_string(a),sb= to_string(b);
        return sa+sb < sb+sa;
    }
    string minNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end(),cmp);
        string res;
        for(auto x:nums) res+=to_string(x);
        return res;
    }
};

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

image.png
思路
image.png

class Solution {
public:
    int translateNum(int num) {
        //转换成字符串
        string s= to_string(num);
        int n =s.size();
        vector<int> dp(n+1,0);
        dp[0] =1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i] =dp[i-1];
            int t = (s[i-2] -'0')*10+(s[i-1]-'0');
            if(t>=10&&t<=25) dp[i]+=dp[i-2];
        }
        return dp[n];
    }
};

//时间复杂度 :O(log(num))  这是转换成字符串的长度
//空间复杂度 : O(n) 主要是dp数组, 当然还有字符串长度 O(lognum) 10 为底
class Solution {
public:
    int translateNum(int num) {
        //转换成字符串
        string s= to_string(num);
        int n =s.size();
        if(n<2) return 1;
        int a =0;
        int b=1;
        int c=1;
        for(int i=2;i<=n;i++){
            a=b; 
            int t = (s[i-2] -'0')*10+(s[i-1]-'0');
            if(t>=10&&t<=25) a+=c;
            c=b; b=a;//记录 i-1和i-2 的值
        }
        return a;
    }
};

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

image.png

思路:

  • 状态 : 剑指offer - 图46
  • 状态转移 : 剑指offer - 图47 的值只能从 左边剑指offer - 图48 或者 上边剑指offer - 图49 中得到,选择二者中的最大值。

剑指offer - 图50

  • 边界条件: 代码注释

    class Solution {
    public:
      int maxValue(vector<vector<int>>& grid) {
          int n = grid.size(),m=grid[0].size();
          vector<vector<int>> dp(n+1,vector<int>(m+1)); // dp[i][j]  从1开始, dp[0][0~m]=0 dp[0~n][0] =0 边界条件
          for(int i=1;i<=n;i++){
              for(int j=1;j<=m;j++){
                  dp[i][j] = max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
              }
          }
          return dp[n][m];
      }
    };
    

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

    请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
    思路:
    双指针算法 O(2n)
    image.png

    //双指针算法模板
    for(int i=0,j=0,i<n;i++){
      while(j<i&&check(i,j)) j++;
      res=max(res,i-j+1);
    }
    
    class Solution {
    public:
      int lengthOfLongestSubstring(string s) {
          unordered_map<char,int> mhash;
          int n=s.size();
          int res =0;
          for(int i=0,j=0;i<n;i++){
              mhash[s[i]]++;
              while(mhash[s[i]]>1){
                  mhash[s[j]]--;
                  j++;
              }
              res=max(res,i-j+1);
          }
          return res;
      }
    };
    

    相似的题目
    最长序列
    题目描述
    给定一个长度为的整数序列,请找出最长的不包含重复数字的连续子序列,输出它的长度。
    输入格式
    第一行包含整数。
    第二行包含个整数(均在0~100000范围内),表示整数序列。
    输出格式共一行,包含一个整数,表示最长的不包含重复数字的连续子序列的长度。
    数据范围
    样例

    输入样例:
    5
    1 2 2 3 5
    输出样例:
    3
    
    using namespace std;
    const int N =100010;
    int a[N],s[N];
    int main(){
      int n;
      int res=0;
      cin >> n;
      for(int i=0;i<n;i++){
          cin >> a[i];
      }
      for(int i=0,j=0;i<n;i++){
          s[a[i]]++;
          while(s[a[i]]>1) {
              s[a[j]]--;
              j++;
          }
          res = max(res,i-j+1);
    
      }
      cout << res <<endl;
      return 0;
    }
    

剑指 Offer 49. 丑数 ——DP

image.png
思路:
image.png
简单来说,就是当前的状态由前面三个数分别承 2 3 5 中的最小值所确定。

class Solution {
public:
    int mmin(int a,int b,int c){
        if(a<b) return min(a,c);
        else return min(b,c);
    }
    int nthUglyNumber(int n) {
        int a=0,b=0,c=0;
        vector<int> dp(n+1,0);
        dp[0] =1;
        for(int i=1;i<=n;i++){
            dp[i] = mmin(dp[a]*2,dp[b]*3,dp[c]*5);
            if(dp[i]== dp[a]*2) a++;
            if(dp[i]== dp[b]*3) b++;
            if(dp[i]== dp[c]*5) c++;
        }
        return dp[n-1];
    }
};

剑指 Offer 50. 第一个只出现一次的字符 ——哈希表

image.png
思路:

  • 先将所有元素压入哈希表中
  • 在重新遍历,但是要按原字符顺序遍历(哈希表是无序的)找到第一个 value为1的数返回key 即可。
    class Solution {
    public:
      char firstUniqChar(string s) {
          char res;
          if(s.size()==0) return ' ';
          unordered_map<char,int> mhash;
          for(auto x:s) mhash[x]++;
          for(auto a:s) if(mhash[a]==1) return a;
           return ' ';
      }
    };
    

剑指 Offer 51. 数组中的逆序对 ——归并排序

image.png-

  • 数据范围分析, 假设最坏情况 n个数全部为逆序对,则 res= (n+ n-1 +n-2 + … +1) = (1+n)*n/2= n^2/2

数据是50000,n^2/2 = 25亿/2 =12亿,int 类型 的最大值大概为21亿,所以采用的Int 类型返回。

  • 归并排序的顺序:
    • 找中点作为分界点,其他也可以
    • 递归分解
    • 归并 (开辟新的空间保存中间值,末尾需要重新赋给原数组,这样才会将原数组排序)
  • 特点:

    • 在归并的过程,左右都是有序的,而这也是可以用作逆序对的原因
    • 时间复杂度 O(nlogn) 空间复杂度 O(n)

      class Solution {
      public:
      // 归并排序
      int res=0;
      int reversePairs(vector<int>& nums) {
         if(!nums.size()) return 0;
         int n = nums.size();
         vector<int> tmp(n,0);
         merge_sort(nums,tmp,0,n-1);
         return res;
      }
      void merge_sort(vector<int>& nums,vector<int> &tmp, int l,int r ){
         if(l>=r) return ;       
         int mid =(l+r) >>1;
         merge_sort(nums,tmp,l,mid);
         merge_sort(nums,tmp,mid+1,r);
         int i=l,j=mid+1;
         int k =0;
         while(i<=mid&&j<=r){
             if(nums[i]<=nums[j]) tmp[k++]=nums[i++];
             else {
                 res+= mid-i+1;
                 tmp[k++]= nums[j++];
             }           
         }
         while(i<=mid) tmp[k++]=nums[i++];
         while(j<=r) tmp[k++] =nums[j++];
      
         for(i=l,j=0;i<=r;i++,j++) nums[i] = tmp[j];
      }
      };
      

      归并排序模板

image.png

//归并排序
#include<iostream>
using namespace std;
const int N =100000;
int q[N],temp[N];
long long res=0; // 10^10/2 = 5*10^9  > 21亿
void merge_sort(int q[],int l,int r){
    if(l>=r) return;
    int mid = l+r>>1;
    merge_sort(q,l,mid);
    merge_sort(q,mid+1,r);
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r){
        if(q[i]<=q[j]) temp[k++]=q[i++];
        else { 
            res += mid-i+1;
            temp[k++]=q[j++];
        }
    }
    while(i<=mid)temp[k++]=q[i++];
    while(j<=r) temp[k++]=q[j++];

    for(i=l,j=0;i<=r;i++,j++) q[i]=temp[j]; // 本段q的范围:l~r
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&q[i]);
    }
    merge_sort(q,0,n-1); // 闭区间
    cout << res<<endl;
    for(int i=0;i<n;i++)printf("%d ",q[i]);
}

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

image.png

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* p1 =headA,*p2 =headB;
        while(p1!=p2){
            if(p1==NULL) p1=headB;
            else p1=p1->next;
            if(p2==NULL) p2=headA;
            else p2=p2->next;
        }
        return p1;
    }
};
  1. 求长度再对齐 ```cpp class Solution { public: ListNode getIntersectionNode(ListNode headA, ListNode *headB) {
     int lengthA = ComputeLengthLN(headA);
     int lengthB = ComputeLengthLN(headB);
    
    if(lengthA>=lengthB) {
        int d = lengthA-lengthB;
          goahead(headA,d);//前进
    }
    else{
        int d=lengthB-lengthA;
         goahead(headB,d);
    }  

    ListNode *p1=headA,*p2 =headB;
    while(p1&&p2){
        if(p1==p2) return p1;

        p1=p1->next;
        p2=p2->next;
    }
    return NULL;

}

int ComputeLengthLN(ListNode* head){
    int res=0;
    ListNode* node=head;
    while(node){
        res++;
        node=node->next;
    }
    return res;
}
void goahead(ListNode* &head,int d){
    while(head&&d){
        d--;
        head=head->next;
    }
}

};

<a name="s0Kim"></a>
#### [剑指 Offer 53 - I. 在排序数组中查找数字 I](https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/) ----二分

![image.png](https://cdn.nlark.com/yuque/0/2020/png/474935/1594449239594-e8f3da11-10b5-4791-a24d-157f53582d1a.png#align=left&display=inline&height=609&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1218&originWidth=1241&size=147696&status=done&style=none&width=620.5)

![image.png](https://cdn.nlark.com/yuque/0/2020/png/474935/1594449273971-13c94d5c-092f-426e-ad59-97c83a344564.png#align=left&display=inline&height=255&margin=%5Bobject%20Object%5D&name=image.png&originHeight=511&originWidth=875&size=26385&status=done&style=none&width=437.5)

```cpp
class Solution {
public:
    int search(vector<int>& nums, int target) {
        if(nums.empty()) return 0;

        int l=0,r=nums.size()-1;
        while(l<r){
            int mid =l+r>>1;
            if(nums[mid] < target) l=mid+1;
            else r=mid;
        }
        if(nums[l]!=target) return 0;
        int a=l;
        l=0,r=nums.size()-1;
        while(l<r){
            int mid =(l+r+1)>>1;
            if(nums[mid ]<= target) l=mid;
            else r=mid-1;
        }
            return l-a+1;
    }
};

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

image.png
image.png

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int l=0,r=nums.size()-1;
        while(l<r){
            int mid =(l+r) >>1;
            if(nums[mid]!=mid) r=mid; // 如果不相等,说明错位的地方在当前位置的左边
            else l =mid+1;
        }
        if(nums[r]==r)  return r+1;;
        return l;
    }
};

取巧的方法

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int n = nums.size()+1;
        int sum= (0+n-1)*n/2;
        for(auto x:nums)  sum-=x;
        return sum;
    }
};

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

做此题之前,先做一个 二叉搜索树的第k个结点

思路:

  • 二叉搜索树,所以左子树< 根节点 < 右子树
  • 中序遍历即位从小到大
class Solution {
public:
    TreeNode *res;
    TreeNode* kthNode(TreeNode* root, int k) {
        dfs(root,k);
        return res;
    }
    void dfs(TreeNode* root,int &k){
        if(!root) return;
        dfs(root->left,k);
        k--;
        if(!k) res =root;
        if(k>0) dfs(root->right,k); // 此处加上判断,可以提前结束,提高效率
    }
};

再来看本题思路:

  • 调整顺序,右根左 即是从大到小的顺序
  • 也是中序遍历的问题
    class Solution {
    public:
      int res;
      int kthLargest(TreeNode* root, int k) {
          dfs(root,k);
          return res;
      }
      void dfs(TreeNode *root,int &k){
          if(!root) return ;
          dfs(root->right,k);
          k--;
          if(!k) res=root->val;
          if(k>0) dfs(root->left,k);
      }
    };
    

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

    输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
    class Solution {
    public:
      int maxDepth(TreeNode* root) {
          if(!root) return 0;
          int m =maxDepth(root->left); // 递归遍历左子树
          int n =maxDepth(root->right); // 递归遍历右子树
          return max(m,n)+1;  //返回左子树和右子树中最长路径 +1 根节点
      }
    };
    

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

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
思路:

  • 同上题, 比较左右子树的最大深度,比较是否相差>1
    class Solution {
    public:
      bool res=true;
      bool isBalanced(TreeNode* root) {
          dfs(root);
          return res;
      }
      int dfs(TreeNode* root){
          if(!root) return 0;
          int mleft = dfs(root->left);
          int mright =dfs(root->right);
          if(abs(mleft-mright)>1) res=false;
          return max(mleft,mright)+1;
      }
    };
    

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

    image.png
    思路:
    image.png
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int sum=0;
        for(auto x:nums) sum^=x;//x^=y
        int k=0;
        while(!((sum>>k)&1)) k++;
        int first=0;
        int second=0;
        for(auto x:nums) {
            if((x>>k)&1) first^=x;
            else second^=x;
        }
        return vector<int>{first,second};
    }
};

// 优化一部分
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int sum=0;
        for(auto x:nums) sum^=x;//x^=y
        int k=0;
        while(!((sum>>k)&1)) k++;
        int first=0;
        for(auto x:nums) if((x>>k)&1) first^=x;  
        return vector<int>{first,sum^=first}; // sexond = sum^=first;
    }
};

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

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

  • 进行位运算,逐位数出数组中该位为1的个数,为1的位为3的倍数

如果个数刚好是3的倍数,说明,唯一的那个数在该位上为0。
如果个数不是3的倍数,说明,唯一的那个数在该位上为1。
遍历每一位之后我们可以得出唯一的那个数

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;  
        for(int i=0;i<32;i++){
            int count =0;
            for(auto x:nums){
                if((x>>i)&1) count++;
            }
            if(count%3!=0) res|= (1<<i);
        }
        return res;
    }
};

有限状态机 : 推导[https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/solution/mian-shi-ti-56-ii-shu-zu-zhong-shu-zi-chu-xian-d-4/]
没看懂 :(

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ones=0,twos=0;
        for(auto x:nums){
            ones=(ones^x)& ~twos;
            twos= (twos^x)& ~ones;
        }
        return ones;
    }
};

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

image.png
方法一: 哈希表 unordered_set;

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

方法二
思路:

  • 可以先用二分法找到第一个大于target的数,因次右指针可以指向它 // logn
  • 双指针算法,>target j— < target i++ 因为是有序数组。
    class Solution {
    public:
      vector<int> twoSum(vector<int>& nums, int target) {
          //二分法把大于target的第一个数找到
          int l=0,r=nums.size()-1;
          while(l<r){
              int mid = (l+r)>>1;
              if(nums[mid] <=target ) l=mid+1;
              else r=mid;
          }
          cout << nums[l]<<endl;
          //双指针算法
          int i=0,j=r;
          while(i<j){
              int sum = nums[i]+nums[j];
              if(sum >target) j--;
              else if(sum<target) i++;
              else if(sum==target) return vector<int>{nums[i],nums[j]};
          }       
          return vector<int> ();
      }
    };
    

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

    image.png
    链接
    image.png
class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        //双指针算法
        int i=1,j=1;
        int sum =1; // 小区间中的和
        vector<vector<int>> res;
        // 左指针小于中值  (上取整)
        while(i<((target+1)/2)){
            if(sum<target) {
                j++;
                sum+=j;
            }
            else if(sum>target){             
                sum-=i; 
                i++;
            }
            else if(sum==target){
                vector<int> path;
                for(int m=i;m<=j;m++){
                    path.push_back(m);
                }
                res.push_back(path);
                sum-=i;
                i++;
            }
        }
        return res;
    }
};

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

image.png
思路1: 双指针算法
时间复杂度 O(n) ,空间复杂度O(n);

class Solution {
public:
    string reverseWords(string s) {
        string res;
        if(s.empty()) return res;
        // 去除首位空格
        int n=s.size()-1;
        int end=n;
        while(end>=0&&s[end]==' ')end--;
        if(end<0) return ""; // 做一个特判,防止全都为空格
        int start=0;
        while(s[start]==' ') start++;

        for(int i=end,j=end;i>=start;i--){
            while(i>=start&&s[i]==' ') i--;
            j=i;
            while(i>=start&&s[i]!=' ') i--;
            for(int m=i+1;m<=j;m++){
                res+=s[m];
            }
            res+=' ';
        }
        res.pop_back();
        return res;
    }
};

假设中间不存在空格问题的话
(存在空格的话还没写)
思路:整体翻转+ 局部反转
时间复杂度O(n), 空间复杂度 O(1)

class Solution {
public:
    string reverseWords(string s) {
        reverse(s.begin(),s.end());
        for(int i=0,j=0;i<s.size();i++){
            j=i;
            while(i<s.size()&&s[i]!=' ')i++;
            reverse(s.begin()+j,s.begin()+i);
        }
        return s;
    }
};

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

image.png
思路:
“abcdefg” 翻转 “gfedcba” 从n处分割开 gfedc ba
我们的期望 是 “cdefgab” 从n处分割开 cdefg ab
我么可以发现,刚好是各自翻转

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        reverse(s.begin(),s.end());
        reverse(s.begin(),s.end()-n);
        reverse(s.end()-n,s.end());
        return s;
    }
};

剑指 Offer 59 - I. 滑动窗口的最大值 —-单调队列

image.png

  1. 暴力解法

    class Solution {
    public:
     vector<int> maxSlidingWindow(vector<int>& nums, int k) {
         // 暴力做法
         vector<int> res;
         if(nums.empty()) return res;    cout << nums.size()<<" "<< nums.size()-k;    
         for(int i=0;i<=nums.size()-k;i++){
             int xmax=INT_MIN; // 值要刷新
             for(int j=i;j<i+k;j++){
                 xmax=max(xmax,nums[j]);
             }
             res.push_back(xmax);
         }
         return res;
     }
    };
    
  2. 单调队列

思路
image.png
步骤:

  • 先删除不属于当前区间的元素
  • 关键,队列的末尾如果小于当前元素,弹出
  • 为了判断区间内首元素是否还在,存入的是索引值
    class Solution {
    public:
      vector<int> maxSlidingWindow(vector<int>& nums, int k) {
          vector<int> res;
          deque<int> q;
          for(int i=0;i<nums.size();i++){
              // 先删除不属于当前区间的元素
              while(q.size()&&q.front()<=i-k) q.pop_front();
              //关键,队列的末尾如果小于当前元素,弹出
              while(q.size()&&nums[q.back()]<=nums[i]) q.pop_back();
              // 为了判断区间内首元素是否还在,存入的是索引值
              q.push_back(i);
              if(i>=k-1) res.push_back(nums[q.front()]);
          }
          return res;
      }
    };
    

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

    image.png

思路:
维护有一个辅助双端队列,队首一直存放着最大值

class MaxQueue {
public:
    queue<int> q;
    deque<int> maxq; // 双端队列, 维护一个队首一直是最大值的双端队列
    MaxQueue() {
    }   
    int max_value() {
        if(q.empty()) return -1;
        return maxq.front();
    }

    void push_back(int value) {
        q.push(value);
        while(maxq.size()&&value>maxq.back()) maxq.pop_back();
        maxq.push_back(value);
    }

    int pop_front() {
        if(q.empty()) return -1;
        if(maxq.front()==q.front()) maxq.pop_front();
        int res = q.front();
        q.pop();
        return res;
    }
};

剑指 Offer 60. n个骰子的点数 —-DP(分组背包问题)

image.png
题解

class Solution {
public:
    vector<double> twoSum(int n) {
        vector<double> res;
        vector<vector<int>> dp(n+1,vector<int>(6*n+1,0));// 1开始
        //初始化
        for(int i=1;i<=6;i++) dp[1][i] = 1;
        for(int i=2;i<=n;i++){
            for(int j=i;j<=i*6;j++){
                for(int tmp=1;tmp<=6;tmp++){
                    if(j-tmp<0) break;
                    dp[i][j]+=dp[i-1][j-tmp];
                }
            }
        }
        int sum =pow(6,n);
        for(int i=n;i<=6*n;i++){
            res.push_back(dp[n][i]*1.0/sum);
        }
        return res;
    }
};
// 状态表示 dp(n,j)  n个骰子出现的点数
// 状态转移: dp(n,j) = d(n-1,j-1)+ d(n-1,j-2)+d(n-1,j-3)+d(n-1,j-4)+d(n-1,j-5)+d(n-1,j-6) = d(n-1,j-i) i=1~6
// 初始条件 dp(1,j)=1;

初始条件不同

class Solution {
public:
    vector<double> twoSum(int n) {
        vector<double> res;
        vector<vector<int>> dp(n+1,vector<int>(6*n+1));
        dp[0][0] =1;
        for(int i=1;i<=n;i++){
            for(int j=i;j<=6*i;j++){
                for(int tmp =1;tmp<=min(j,6);tmp++){
                    dp[i][j] += dp[i-1][j-tmp];
                }
            }
        }
        int sum =pow(6,n);
        for(int i=n;i<=6*n;i++) res.push_back(dp[n][i]*1.0/sum);
        return res;
    }
};
// 初始条件  当投0次时,s=0 的方案数为1个

剑指 Offer 61. 扑克牌中的顺子

image.png
思路:

  • 数组中除0外,不能有相同元素,有相同元素不能构成顺子
  • 数组中除去0后,最大值-最小值<5 ,则能构成顺子

步骤:

  • 排序 方便去0 和判断重复元素和差值
  • 去除0
  • 查看是否有相同元素
  • 判断差值是否 <5
    class Solution {
    public:
      bool isStraight(vector<int>& nums) {
          if(nums.empty()) return false;
          sort(nums.begin(),nums.end());
          int k=0;
          while(nums[k]==0) k++;// 指向第一个不为0的元素
          for(int i=k+1;i<nums.size();i++){
              if(nums[i]==nums[i-1]) return false;
          }
          return nums.back()-nums[k]<5;
      }
    };
    

    剑指 Offer 62. 圆圈中最后剩下的数字 —-约瑟夫问题

    image.png
    题解:
class Solution {
public:
    int lastRemaining(int n, int m) {
        if(n==1) return  0;
        return (lastRemaining(n-1,m)+m)%n;
    }
};

// 递归做法
class Solution {
public:
    int lastRemaining(int n, int m) {
        int res=0;
        for(int i=2;i<=n;i++){
            res=(res+m)%i;
        }
        return res;
    }
};
// 迭代做法,还没想明白

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

image.png
思路:

  • 维护一个最小值的变量
  • 遍历数组,将与最小值的差记录,更新当前的最大值
    class Solution {
    public:
      int maxProfit(vector<int>& prices) {
          if(prices.empty()) return 0;
          int res=0,mmin=prices[0];
          for(int i=1;i<prices.size();i++){
              if(prices[i]-mmin>0)
              res=max(res,prices[i]-mmin);
              mmin=min(mmin,prices[i]);
          }
          return  res;
      } 
    };
    

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

    1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
    思路:
    A&& B 当A不满足条件时,将不会执行B,因此可以替换 if 判断语句
    class Solution {
    public:
      int sumNums(int n) {
          int res=n;
          //if(n>0) res+= sumNums(n-1);
          (n>0)&& (res+=sumNums(n-1));
          return res;
      }
    };
    

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

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

class Solution {
public:
    int add(int a, int b) {
        while(b){
             int sum = a^b;//不带进位的数
             int carry=(unsigned int)(a&b)<<1; // 进位
             a=sum;
             b=carry;
        }
       return a;
    }
};

剑指 Offer 66. 构建乘积数组

image.png
image.png

class Solution {
public:
    vector<int> constructArr(vector<int>& a) {
        int n =a.size();
        vector<int> res(n);
        if(!n) return res;

        for(int i=0,p=1;i<n;i++){
            res[i]=p;
            p*=a[i];
        }
        for(int i=n-1,p=1;i>=0;i--){
            res[i]*=p;
            p*=a[i];
        }
        return res;
    }
};

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

image.png

class Solution {
public:
    int strToInt(string str) {
        int k=0;
        while(k<str.size()&&str[k]==' ')k++;
        bool flag=false;// 负数标志
        if(k<str.size()&&str[k]=='+') k++;
        else if(k<str.size()&&str[k]=='-') k++,flag=true;
        long long num =0;
        while(k<str.size()&& str[k]>='0'&&str[k]<='9') {
            num=num*10+(str[k]-'0'); 
            if(num>INT_MAX&&(!flag)) return INT_MAX;
            if(num>INT_MAX&&flag) return INT_MIN;
            k++;
        }
        if(flag) num=num*(-1);  
        return (int)num;
    }
};