• 剑指Offer


    剑指Offer

    面试题14- I. 剪绳子

    代码1 dp

    集合:f[i]表示 绳子长度为i时所有方案集合中 最大乘积

    集合划分:考虑f[i]可以由哪些变过来, f[i-1] * f[1] , f[i-2] *f[2] ...

    f[i-k] * f[k];

    O(n)枚举k,总复杂度O(n^2)

    class Solution {
    public:
        int f[105];
        int cutRope(int number) {
            //必须分成两段
            if(number == 2) return 1; 
            else if(number == 3) return 2;
            //f[i] 长度为i的升子的最大乘积
            f[1] = 1;
            f[2] = 2; //1和2和3特例
            f[3] = 3;
            for(int i=4;i<=number;i++){
                f[i] = max(f[i],f[i-1]);
                for(int j=2;j<i;j++){
                    f[i] = max(f[i-j]*f[j],f[i]);
                }
            }
            return f[number];
        }
    };
    

    代码2 贪心

    贪心,分成2和3最佳,能分成3就不分成2

    class Solution {
    public:
        int cutRope(int number) {
            //必须分成两段
            if(number == 2) return 1;
            else if(number == 3) return 2;
            int thirdNum = number / 3;
            int thirdOther = number % 3;
            if(thirdOther == 0){
                return pow(3,thirdNum);
            }else if(thirdOther == 1){
                return pow(3,thirdNum-1) * 4;
            }else{
                return pow(3,thirdNum) * 2;
            }
        }
    };
    

    剑指 Offer 14- II. 剪绳子 II

    这道题再用dp,取mod后又取max就不准确了,一定要用dp的话只能使用java大数

    思路:贪心 + 快速幂(普通求幂的方法也可以的)

    划分成2和3的乘积,结果值最大。先计算能分成3的最大个数,再取余计算2的数量,不够2的整数倍就减去一个3分给2。

    class Solution {
    public:
        int mod = 1e9+7;
        int cuttingRope(int n) {
            if(n == 0) return 0;
            if(n == 1) return 0;
            if(n == 2) return 1;
            if(n == 3) return 2;
            //分成2和3最佳 3的数量尽量多
            int a = n/3;
            int b = n%3;
            if(b == 2){
                return (int) (quickPow(3,a) * b) % mod;
            }else if(b == 0){
                return (int) quickPow(3,a) % mod;
            }else{
                //取模注意 要在int转型前取模
                return (int) ((quickPow(3,a-1) * 4) % mod);
            }
        }
    
        //快速幂
        long long quickPow(long long a,long long n){
            long long ans = 1;
            while(n){
                if(n&1){
                    ans = (ans * a) % mod;
                }
                a = (a * a) % mod;
                n >>= 1;
            }
            return ans;
        }
    };
    

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

    坐标dfs,判满足条件。

    注意走不到的要直接退出递归,防止走到其它序列,导致结果不正确了。

    class Solution {
    public:
        int cnt = 0;
        int dr[4][2] = {{0,1},{1,0},{-1,0},{0,-1}};
        int vis[1000][1000];
    
        void dfs(int x ,int y,int m,int n,int k){
            if(x >= m || y >= n || x < 0 || y < 0) return ;
            if(vis[x][y]) return ;
            vis[x][y] = 1;
            if(x / 10 + x % 10 + y / 10 + y % 10 <= k) {
                cnt++;
            }else return; //走不到的要退出不能继续往下
            for(int i=0;i<4;i++){
                int nx = x + dr[i][0];
                int ny = y + dr[i][1];
                dfs(nx,ny,m,n,k);
            }
        }
    
        int movingCount(int m,int n,int k){
            dfs(0,0,m,n,k);
            return cnt;
        }
    };
    

    面试题03. 数组中重复的数字

    哈希表判重

    class Solution {
    public:
        int findRepeatNumber(vector<int>& nums) {
            map<int,int> mp;
            for(int i=0;i<nums.size();i++){
                if(mp.count(nums[i])){
                    return nums[i];
                }
                mp[nums[i]] = 1;
            }
            return -1;
        }
    };
    

    面试题04. 二维数组中的查找 / 240. 搜索二维矩阵 II

    二分lower_bound

    class Solution {
    public:
        bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
            //nlogn二分
            if(matrix.size() == 0) return false;
            int n = matrix.size();
            int m = matrix[0].size();
            for(int i=0;i<n;i++) {
                int index = lower_bound(matrix[i].begin(),
                matrix[i].end(),target) - matrix[i].begin();
                if(index < m && index >= 0 && matrix[i][index] == target) 
                    return true;
            }
            return false;
        }
    };
    

    方法2:根据题目性质,从右上角开始

    具体可看题解

    class Solution {
    public:
        bool searchMatrix(vector<vector<int>>& matrix, int target) {
            //nlogn二分
            if(matrix.size() == 0) return false;
            int n = matrix.size() - 1;
            int m = matrix[0].size() - 1;
            if(n < 0 || m < 0) return false;
            int row = 0,col = m;
            while(row <= n && col >= 0){
                if(matrix[row][col] == target){
                    return true;
                }else if(matrix[row][col] < target){
                    row++;
                }else if(matrix[row][col] > target){
                    col--;
                }
            }
            return false;
        }
    };
    

    面试题05. 替换空格

    这题用java写吧,StringBuilder减少开销,用C++和Java的String,字符串+拼接都会拷贝原字符串形成新的字符串。

    class Solution {
        public String replaceSpace(String s) {
            StringBuilder sb = new StringBuilder();
            for(int i=0;i<s.length();i++){
                char c = s.charAt(i);
                if(c == ' ') sb.append("%20");
                else sb.append(c);
            }
            return sb.toString();
        }
    }
    

    面试题06. 从尾到头打印链表

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode(int x) : val(x), next(NULL) {}
     * };
     */
    class Solution {
    public:
        vector<int> reversePrint(ListNode* head) {
            vector<int> results;
            ListNode *p = head;
            while(p){
                results.push_back(p->val);
                p = p->next;
            }
            reverse(results.begin(),results.end());
            return results;
        }
    };
    

    面试题10- I. 斐波那契数列

    dp

    class Solution {
    public:
        int fib(int n) {
            if(n == 0) return 0;
            int dp[110];
            int mod = 1e9+7;
            dp[0] = 0;
            dp[1] = 1;
            dp[2] = 1;
            for(int i=3;i<=n;i++){
                dp[i] = (dp[i-1] + dp[i-2])%mod;
            }
            return dp[n];
        }
    };
    

    面试题10- II. 青蛙跳台阶问题

    这道题注意特判,台阶是0的时候默认一种方案,1的时候也是1种方案

    dp 滚动一维

    class Solution {
        public int numWays(int n) {
            if(n == 0) return 1; //特判
            if(n == 1) return 1; //特判
            //从2开始  第二阶可以由第0个跳1次2或者第1个跳2次1
            int f0 = 1,f1 = 1,f2 = f1 + f0;
            int mod = 1000000007;
            for(int i=2;i<=n;i++){
                f2 = (f1 + f0)%mod;;
                f0 = f1;
                f1 = f2;
            }
            return f2%mod;
        }
    }
    

    面试题07. 重建二叉树

    前序中序序列建树

    前序确定根,中序划分左右子树

    /**
     * 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:
        map<int,int> mp;
        TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
            if(preorder.size() == 0 || inorder.size() == 0) return NULL;
            for(int i=0;i<inorder.size();i++){
                mp[inorder[i]] = i;
            }
            TreeNode * Root = build(preorder,inorder,0,inorder.size()-1,0);
            return Root;
        }
    
        TreeNode* build(vector<int>& preorder,vector<int>& inorder,
        int il,int ir,int rootIndex){
            if(il > ir) return NULL;
            TreeNode* Root = new TreeNode();
            Root->val = preorder[rootIndex];
            //在中序数组中找到根所在的位置 划分左右子树
            int pos = mp[preorder[rootIndex]];
            Root->left = build(preorder,inorder,il,pos-1,rootIndex+1);
            Root->right = build(preorder,inorder,pos+1,ir,(pos-il+1)+rootIndex);
            return Root;
        }
    };
    

    面试题09. 用两个栈实现队列

    第一个栈push就可以

    要删除时,需要将第一个栈的除第一个元素外的所有元素移动到临时栈中,再弹出栈首,并返回栈首值。再将临时栈所有元素移动到第一个栈中。

    class CQueue {
    public:
        stack<int> stk,stkTemp;
        CQueue() {
    
        }
        
        void appendTail(int value) {
            stk.push(value);
        }
        
        int deleteHead() {
            if(stk.size() == 0) return -1;
            while(stk.size() > 1){
                stkTemp.push(stk.top());
                stk.pop();
            }
            int front = stk.top();
            stk.pop();
            while(stkTemp.size()){
                stk.push(stkTemp.top());
                stkTemp.pop();
            }
            return front;
        }
    };
    
    /**
     * Your CQueue object will be instantiated and called as such:
     * CQueue* obj = new CQueue();
     * obj->appendTail(value);
     * int param_2 = obj->deleteHead();
     */
    

    优化,插入都查到stk栈中。删除时,当stkTemp有值的时候返回stkTemp的首元素;当stkTemp没有值时,将stk栈中元素全部移动到stkTemp(FIFO就是倒序进栈了,先进的后出,后出即到了stkTemp栈顶),栈顶就是本轮删除的队首,pop它。

    class CQueue {
    public:
        stack<int> stk,stkTemp;
        CQueue() {
    
        }
        
        void appendTail(int value) {
            stk.push(value);
        }
        
        int deleteHead() {
            if(stkTemp.size()==0 && stk.size() == 0) return -1;
            int front;
            if(stkTemp.size()){
                front = stkTemp.top();
                stkTemp.pop();
                return front;
            }
            while(stk.size()){
                stkTemp.push(stk.top());
                stk.pop();
            }
            front = stkTemp.top();
            stkTemp.pop();
            return front;
        }
    };
    
    /**
     * Your CQueue object will be instantiated and called as such:
     * CQueue* obj = new CQueue();
     * obj->appendTail(value);
     * int param_2 = obj->deleteHead();
     */
    

    面试题11. 旋转数组的最小数字

    同leetCode154

    不同于leetCode153

    本题数组中可能有相等的元素,需要特判nums[mid] == nums[r]时,让r -= 1

    class Solution {
    public:
        int minArray(vector<int>& nums) {
            if(nums.size() == 0) return 0;
            int l = 0,r = nums.size()-1;
            while(l < r){
                int mid = (l + r) >> 1;
                if(nums[mid] < nums[r]) r = mid;
                else if(nums[mid] > nums[r]) l = mid + 1;
                else r = r - 1; //特判相等
            }
            return nums[r];
        }
    };
    

    面试题12. 矩阵中的路径

    dfs+回溯

    坐标上搜索和回溯,搜索到正解返回true,非正解继续搜索

    class Solution {
    public:
        int dr[4][2] = {{0,1},{1,0},{-1,0},{0,-1}};
        int length;
        bool vis[210][210];
        int n,m;
        bool exist(vector<vector<char>>& board, string word) {
            length = word.size();
            n = board.size();
            m = board[0].size();
            for(int i=0;i<n;i++){
                for(int j=0;j<m;j++){
                    if(board[i][j] == word[0]){
                        if(dfs(i,j,0,board,word)) return true;
                    }
                }
            }
            return false;
        }
    
        bool dfs(int x,int y,int index,vector<vector<char>>& board,
        string word){
            if(index == length-1){
                return true;
            }
            if(vis[x][y]) return false;
            vis[x][y] = 1;
            for(int i=0;i<4;i++){
                int nx = x + dr[i][0];
                int ny = y + dr[i][1];
                if(vaild(nx,ny) && !vis[nx][ny] && 
                board[nx][ny] == word[index+1]){
                    if(dfs(nx,ny,index+1,board,word))
                    	return true;
                }
            }
            vis[x][y] = 0;
            return false;
        }
    
        bool vaild(int x,int y){
            if(x < 0 || y < 0 || x >= n || y >=m) return false;
            return true;
        }
    };
    

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

    位运算,与1作&运算,如果结果值为1,表示当前为数字是1,计数器+1;右移一位。

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

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

    首先看到n的范围,枚举肯定不行

    分治,每次计算一半,递归往下,最大32层。

    class Solution {
    public:
        double myPow(double x, int n) {
            if(n == 0) return 1;
            if(n == 1) return x;
            if(n == -1) return 1.0/x;
            double half = myPow(x,n/2);
            double mod = myPow(x,n%2);
            return half*half*mod;
        }
    };
    

    方法二:快速幂

    先将幂为负数的转成整数 x也要变成分数

    二分快速幂O(logn)

    class Solution {
    public:
        double myPow(double x, int n) {
            if(n == 0) return 1;
            if(n == 1) return x;
            if(x == 1) return 1;
            //快速幂 先将幂为负数的转成整数 x也要变成分数 
            long num = n;
            if(num < 0){
                num = -num;
                x = 1/x;
            }
            double ans = 1;
            while(num){
                if(num&1) ans*=x;
                x *= x;
                num >>= 1;
            }
            return ans;
        }
    };
    

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

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

    剑指 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) {
            //建立虚拟头节点 使得首尾结点删除更方便
            ListNode *Head = new ListNode(-1);
            Head->next = head;
            ListNode *p = Head;
            //如果最后1个是要被删除的
            //那么只会遍历到倒数第二个 所以无bug
            while(p->next){
                if(p->next->val == val){
                    p->next = p->next->next;
                    break;
                }
                p = p->next;
            }
            return Head->next;
        }
    };
    

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

    条件大模拟

    首先要删除字符串首尾空格

    1..前面不能出现.和e

    2.e前面不能出现e,也不能没有数字

    3.1e-3 是正确的,+和-只能出现在首部,或者e后

    4.不能出现 - + e 数字 外的字符

    class Solution {
    public:
        bool isNumber(string str) {
            if(str.length() == 0) return false;
            bool numSeen = false;
            bool dotSeen = false;
            bool eSeen = false;
            string s = str;
            s.erase(0,s.find_first_not_of(" "));
            s.erase(s.find_last_not_of(" ") + 1);
            for(int i=0;i<s.length();i++){
                if(s[i] >= '0' && s[i] <= '9') numSeen = true;
                else if(s[i] == '.'){
                    //.前面不能出现.和e
                    if(dotSeen || eSeen) return false;
                    dotSeen = true;
                }else if(s[i] == 'e' || s[i] == 'E'){
                    //e前面不能出现e 不能没有数字
                    if(eSeen || !numSeen) return false;
                    eSeen = true;
                    numSeen = false; //重置e后面的num了
                }else if(s[i] == '-' || s[i] == '+'){
                    //1e-3 是正确的
                    if(i != 0 && s[i-1] != 'e' && s[i-1] != 'E') return false;
                }else return false;
            }
            return numSeen;
        }
    };
    

    方法二,DFA自动机转移,读入字符转移,看最终能否到达终态

    参考题解

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

    class Solution {
    public:
        vector<int> exchange(vector<int>& nums) {
            if(nums.size() == 0) return nums;
            int n = nums.size();
            vector<int> result,temp;
            for(int i = 0;i<nums.size();i++){
                if(nums[i] % 2 == 1) result.push_back(nums[i]);
                else temp.push_back(nums[i]);
            }
            for(int i=0;i<temp.size();i++) result.push_back(temp[i]);
            return result;
        }
    };
    

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

    建立虚拟头节点

    建立两个指针,第二个指针提前移动k个距离,即两个指针最后相距k

    同时移动两个指针,当第二个指针为空时(到了链表末尾),次数第一个指针指向的位置就是倒数第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* list, int k) {
            ListNode *head = new ListNode(-1);
            head->next = list;
            ListNode* p = head;
            ListNode* q = head;
            int pos = 0;
            while(pos < k){
                q = q->next;
                pos++;
            }
            while(q){
                q = q->next;
                p = p->next;
            }
            return p;
        }
    };
    

    剑指 Offer 24. 反转链表

    /**
     * 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 head;
            ListNode* a = head;
            ListNode* b = a->next;
            while(b){
                ListNode* c = b->next;
                b->next = a;
                a = b,b = c;
            }
            head->next = NULL;
            head = a;
            return head;
        }
    };
    

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

    /**
     * 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) {
            ListNode *p = l1;
            ListNode *q = l2;
            ListNode *head = new ListNode(-1);
            ListNode *dummy = head;
            while(p && q){
                if(p->val <= q->val){
                    head->next = p;
                    head = head->next;
                    p = p->next;
                }else{
                    head->next = q;
                    head = head->next;
                    q = q->next;
                }
            }
            if(p) head ->next = p,p = p->next;
            if(q) head ->next = q,q = q->next;
            return dummy->next;
        }
    };
    

    剑指 Offer 27. 二叉树的镜像

    交换左右节点,递归子树

    /**
     * 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) {
            dfs(root);
            return root;
        }
        void dfs(TreeNode* root){
            if(root == NULL) return;
            swap(root->left,root->right);
            if(root->left) dfs(root->left);
            if(root->right) dfs(root->right);
        }
    };
    

    剑指 Offer 28. 对称的二叉树

    一个空一个不同,false

    两个都空,true

    1.比较当前两结点的值 是否相等

    2.比较p结点左子树和q结点右子树

    3.比较p结点右子树和q结点左子树

    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    class Solution {
    public:
        bool isSymmetric(TreeNode* root) {
            if(root == NULL) return true;
            return dfs(root->left,root->right);
        }
        bool dfs(TreeNode* p,TreeNode* q){
            if(p && !q || q && !p) return false;
            if(!p && !q) return true;
            return p->val == q->val && dfs(p->left,q->right) && dfs(p->right,q->left);
        }
    };
    

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

    按右下左上到底的顺序模拟

    class Solution {
    public:
        vector<int> spiralOrder(vector<vector<int>>& matrix) {
            bool vis[110][110];
            memset(vis,false,sizeof(vis));
            vector<int> ans;
            int n = matrix.size();
            if(n == 0) return ans;
            int m = matrix[0].size();
            int x = 0,y = 0;
            vis[x][y] = 1;
            ans.push_back(matrix[x][y]);
            while(1){
                //右 下 左 上
                //右
                bool flag = false;
                while(y+1 < m && vis[x][y+1] == 0) {
                    y++;
                    vis[x][y] = 1;
                    ans.push_back(matrix[x][y]);
                    flag = true;
                }
                //下
                while(x+1 < n && vis[x+1][y] == 0){
                    x++;
                    vis[x][y] = 1;
                    ans.push_back(matrix[x][y]);
                    flag = true;
                }
                //左
                while(y-1>=0 && vis[x][y-1] == 0){
                    y--;
                    vis[x][y] = 1;
                    ans.push_back(matrix[x][y]);
                    flag = true;
                }
                //上
                while(x-1>=0 && vis[x-1][y] == 0){
                    x--;
                    vis[x][y] = 1;
                    ans.push_back(matrix[x][y]);
                    flag = true;
                }
                if(!flag) break;
            }
            return ans;
        }
    };
    

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

    两个栈模拟,维护栈顶最小的栈push的时候,让x与栈顶(最小)比较,插入小的

    class MinStack {
    public:
        stack<int> stk;
        stack<int> minStk;
        /** initialize your data structure here. */
        MinStack() {
    
        }
        
        void push(int x) {
            stk.push(x);
            if(minStk.size() && minStk.top() < x){
                minStk.push(minStk.top());
            }else minStk.push(x);
        }
        
        void pop() {
            if(stk.size()){
                stk.pop();
                minStk.pop();
            }
        }
        
        int top() {
            return stk.top();
        }
        
        int min() {
            if(minStk.size())
                return minStk.top();
            else return -1;
        }
    };
    
    /**
     * 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();
     */
    

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

    ​ 辅助栈 用于模拟后进先出的pushed栈

    ​ 遍历pushed 每次加入i值

    ​ 当stk栈顶与popped栈顶值匹配时 while循环pop匹配

    ​ 最后比较pos下标到达m 表示完全匹配上

    class Solution {
    public:
        bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
            int pos = 0;
            int n = pushed.size(),m = popped.size();
            //辅助栈 后进先出
            stack<int> stk; 
            if(n != m) return false;
            //遍历pushed 每次加入i值
            //当stk栈顶与popped栈顶值匹配时 while循环pop匹配
            //最后比较pos下标到达m 表示完全匹配上
            for(int i = 0;i<n;i++){
                stk.push(pushed[i]);
                while(pos < m && stk.size() 
                && stk.top() == popped[pos]){
                    stk.pop();
                    pos++;
                }
            }
            return pos == m;
        }
    };
    

    剑指 Offer 26. 树的子结构

    思路:

    先bfs搜索到所有与B树的根节点值相同的节点。

    然后遍历所有与B根相同的根节点,dfs递归判断是否有子结构与B相同的子树。

    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:
        bool isSubStructure(TreeNode* A, TreeNode* B) {
            if(A==NULL || B==NULL) return false;
            queue<TreeNode*> q;
            vector<TreeNode*> roots; 
            q.push(A);
            while(!q.empty()){
                TreeNode* top = q.front();
                if(top->val == B->val) roots.push_back(top);
                q.pop();
                if(top->left) q.push(top->left);
                if(top->right) q.push(top->right);
            }
            for(int i=0;i<roots.size();i++){
                if(dfs(roots[i],B)) return true;
            }
            return false;
        }
    
        bool dfs(TreeNode *p,TreeNode *q){
            if(p == NULL && q != NULL) return false;
            if(p == NULL && q == NULL) return true;
            if(p != NULL && q == NULL) return true;
            // if(q == NULL) return true;
            //下面if成立条件 q != NULL && p == NULL
            // if(p == NULL) return false;
            return p->val == q->val && 
            dfs(p->left,q->left) && dfs(p->right,q->right);
        }
    };
    

    思路二

    把思路一中的bfs找子树,换成递归判断A的左右子树与B比较

    class Solution {
    public:
        bool isSubStructure(TreeNode* A, TreeNode* B) {
            if (A == nullptr || B == nullptr) {
                return false;
            }
            if(A->val != B->val) return false;
            return hasSubStructure(A, B) 
            || isSubStructure(A->left, B) 
            || isSubStructure(A->right, B);
        }
        bool hasSubStructure(TreeNode* A, TreeNode* B) {
            if (B == nullptr) {
                return true;
            }
            if (A == nullptr) {
                return false;//在不满足b为nullptr但是A变成了空
            }
            if (A->val != B->val) {
                return false;
            }
            return hasSubStructure(A->left, B->left) 
            && hasSubStructure(A->right, B->right);
        }
    };
    

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

    bfs层序遍历

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

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

    dfs深度优先遍历,以深度depth为参数

    class Solution {
    public:
        
        vector<vector<int>> levelOrder(TreeNode* root) {
            vector<vector<int>> result(1001,vector<int>(0));
            vector<vector<int>> ans;
            if(!root) return ans;
            dfs(root,0,result);
            for(int i=0;i<result.size();i++){
                if(result[i].size() == 0) break;
                ans.push_back(result[i]);
            }
            return ans;
        }
        void dfs(TreeNode* root,int depth,
        vector<vector<int>>& result){
            if(!root) return;
            result[depth].push_back(root->val);
            dfs(root->left,depth+1,result);
            dfs(root->right,depth+1,result);
        }
    };
    

    bfs层序也行,但与32-I题区别是,bfs每次要把上一层队列的值出队完。以保证能把下一层的节点都入队

    class Solution {
    public:
        vector<vector<int>> levelOrder(TreeNode* root) {
            vector<vector<int>> ans;
            if(root == nullptr)     return ans;
            queue<TreeNode*> myQue;
            myQue.push(root);
            while(!myQue.empty()){
                vector<int> tmp;
                int size = myQue.size();
                for(;size--;myQue.pop()){
                    auto node = myQue.front();
                    if(node->left)  myQue.push(node->left);
                    if(node->right) myQue.push(node->right);               
                    tmp.push_back(node->val);
                }
                ans.push_back(tmp);
            }
            return ans;
        }
    };
    

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

    比32-II多一次奇数层,反转

    class Solution {
    public:
        vector<vector<int>> levelOrder(TreeNode* root) {
            vector<vector<int>> ans;
            if(root == nullptr)     return ans;
            queue<TreeNode*> myQue;
            myQue.push(root);
            while(!myQue.empty()){
                vector<int> tmp;
                int size = myQue.size();
                for(;size--;myQue.pop()){
                    auto node = myQue.front();
                    if(node->left)  myQue.push(node->left);
                    if(node->right) myQue.push(node->right);               
                    tmp.push_back(node->val);
                }
                ans.push_back(tmp);
            }
            for(int i=0;i<ans.size();i++){
                if(i&1) reverse(ans[i].begin(),ans[i].end());
            }
            return ans;
        }
    };
    

    思路二,用java的ArrayList,偶数层尾插,奇数层头插

    原作者:麦宇恒

    class Solution {
        List<List<Integer>> res = new ArrayList<>();
        public List<List<Integer>> levelOrder(TreeNode root) {
            helper(root,0);
            return res;
        }
    
        private void helper(TreeNode root,int level){
            if(root==null)
                return;
            if(res.size()==level){
            	//加入新的一层 初始化 这个if直得学习
                res.add(new ArrayList<>());
            }
            if(level%2==0){
                res.get(level).add(root.val);
            }else{
                res.get(level).add(0,root.val);
            }
            helper(root.left,level+1);
            helper(root.right,level+1);
        }
    }
    

    同样也可以用C++的deque双端队列

    push_back 尾插

    push_front 头插

    明天要把32-II的dfs vector >的初始化给优化一下啊

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

    class Solution {
    public:
        vector<vector<int>> results;
        int cnt;
        vector<vector<int>> pathSum(TreeNode* root, int sum) {
            if(root==NULL) return results;
            cnt = sum;
            vector<int> result;
            dfs(root,0,result);
            return results;
        }
        void dfs(TreeNode* root,int sum,vector<int> result){
            if(root == NULL) 
                return;
            result.push_back(root->val);
            sum += root->val;
            if(sum == cnt){
                if(!root->left && !root->right){
                    results.push_back(result);
                    result.pop_back();
                    return;
                }
            }
            if(root->left){
                dfs(root->left,sum,result);
            }
            if(root->right){
                dfs(root->right,sum,result);
            }
            result.pop_back();
        }
    };
    

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

    方法一:递归、分治。

    后序遍历序列的倒数第一个元素尾根。

    二叉搜索树的根,能把子树 划分为两部分,一部分的值都小于根,另一部分的值都大于根,否则就不满足二叉搜索树的定义(即序列不能划分为左边一端小于根的、右边一端都大于根的两部分)

    参考题解

    class Solution {
    public:
        bool verifyPostorder(vector<int>& postorder) {
            return dfs(postorder,0,postorder.size()-1);
        }
    
        bool dfs(vector<int>& postorder,int l,int r){
            //子树 只有1个节点 说明到了叶结点 返回true
            if(l >= r) return true;
            int pos = l;
            //左子树 都比根小 
            //根就是后序序列的r右编辑postorder[r]) pos++;
            int mid = pos - 1;
            //右子树 都比根大
            while(pos<=r && 
            postorder[pos] > postorder[r]) pos++;
            //最终pos一定要等于r  否则说明pos~r之间还有小于根对应值的
            //即根能划分成左右两部分 一份小于根 一份大于根
            //否则就不满足 二叉搜索树 右子树
            return pos == r 
            && dfs(postorder,l,mid) && dfs(postorder,mid+1,r-1);
        }
    };
    

    方法二:单调栈

    参考地址

    class Solution {
    public:
        bool verifyPostorder(vector<int>& postorder) {
            stack<int> stk;
            int pre = INT_MAX;
            for(int i=postorder.size()-1;i>=0;i--){
                //pre表示当前i的根 左子树必须要小于
                if(postorder[i] > pre) return false;
                //满足while 就进入左子树了 要找到根 就要把之前比i大的都弹出
                while(stk.size() && postorder[i] < stk.top()){
                    pre = stk.top();
                    stk.pop();//弹出右边的树
                }
                stk.push(postorder[i]);
            }
            return true;
        }
    };
    

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

    哈希表

    class Solution {
    public:
        int majorityElement(vector<int>& nums) {
            int n = nums.size();
            map<int,int> mp;
            for(int i = 0; i< nums.size();i++){
                mp[nums[i]]++;
                if(mp[nums[i]] > n/2) return nums[i];
            }
            return -1;
        }
    };
    

    学习摩尔投票法

    class Solution {
    public:
        vector<int> getLeastNumbers(vector<int>& arr, int k) {
            sort(arr.begin(),arr.end());
            return vector<int>(arr.begin(),arr.begin()+k);
        }
    };
    

    学习topK大根堆

    //升序队列,小顶堆
    priority_queue <int,vector<int>,greater<int> > q;
    //降序队列,大顶堆
    priority_queue <int,vector<int>,less<int> >q;
    
    //greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
    

    控制大根堆的size始终保持k,如果新进入堆中一个元素,并且队头(最大值)比当前元素小,就pop队头,即始终保持k个最小

    class Solution {
    public:
        vector<int> getLeastNumbers(vector<int>& arr, int k) {
            vector<int> result;
            if(k == 0) return result;
            //大根堆
            priority_queue<int> heap;
            for(int i=0;i<arr.size();i++){
                if(heap.size() < k){
                    heap.push(arr[i]);
                }else{
                    if(heap.top() > arr[i]){
                        heap.pop();
                        heap.push(arr[i]);
                    }
                }
            }
            while(!heap.empty()){
                result.push_back(heap.top());
                heap.pop();
            }
            return result;
        }
    };
    

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

    dp[i]: 数组中前i个连续子数组的最大和

    dp[i] 只可以由 dp[i-1]转移 所以思考得出状态转移方程

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

    class Solution {
    public:
        int maxSubArray(vector<int>& nums) {
    
            int n = nums.size();
            if(n == 0) return 0;
            vector<int>dp(n);
            dp[0] = nums[0];//初始化
            int ans = nums[0];
            for(int i=1;i<n;i++){
                dp[i] = max(dp[i-1] + nums[i],nums[i]);
                ans = max(ans,dp[i]);
            }
            return ans;
        }
    };
    

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

    哈希表存储旧节点----新节点的映射

    遍历一原链表,查map找到映射的新节点,建立新的链表

    /*
    // 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 head;
            map<Node*,Node*> mp;
            //存储新旧节点映射 node 映射 node'
            for(Node* cur = head;cur != NULL;cur = cur->next)
                mp[cur] = new Node(cur->val);
            //拷贝节点
            for(Node* cur = head;cur != NULL;cur = cur->next){
                mp[cur]->next = mp[cur->next];
                mp[cur]->random = mp[cur->random];
            }
            //返回新节点的头部
            return mp[head];
        }
    };
    

    剑指 Offer 50. 第一个只出现一次的字符

    哈希表

    class Solution {
    public:
        char firstUniqChar(string s) {
            char ans = ' ';
            map<char,int> mp;
            for(char c : s){
                mp[c]++;
            }
            for(char c : s){
                if(mp[c] == 1){
                    return c;
                }
            }
            return ans;
        }
    };
    

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

    思路:树状数组 + 离散化

    逆序: 前i-1个中比nums[i]小的数的个数

    树状数组维护每个数字出现的次数

    当前这个时间点i下的逆序数个数 = 已经有的个数i - 前面出现过的数字中比nums[i]小的个数getsum(nums[i])

    因为出现了负值,所以要对原数据离散化

    class Solution {
    public:
        int c[50010];
    
        int lowbit(int x){
            return x & -x;
        }
    
        void update(int x,int v,int n){
            while(x <= 50000){
                c[x] += v;
                x += lowbit(x);
            }
        }
    
        int getSum(int x){
            int res = 0;
            while(x>=1){
                res += c[x];
                x -= lowbit(x);
            }
            return res;
        }
    
        long long getSum(long long x){
            long long res = 0;
            while(x){
                res += c[x];
                x -= lowbit(x);
            }
            return res;
        }
    
        int reversePairs(vector<int>& nums) {
            int n = nums.size();
            vector<int> temp(nums);
            //离散化开始
            sort(temp.begin(),temp.end());
            int num = unique(temp.begin(),temp.end()) - temp.begin();
            for(int i=0;i<n;i++){
                nums[i] = lower_bound(temp.begin(),temp.begin()+num,
                nums[i]) - temp.begin();
            	nums[i] += 1;
    		}
            //离散化结束
            int ans = 0;
            //树状数组开始
            for(int i=0;i<n;i++){
                update(nums[i],1,n);
                //查询nums[i]前的前缀和个数
                int ttt = getSum(nums[i]);
                //逆序: 前i-1个中比nums[i]小的数的个数
           //当前这个时间点下的逆序数 = 已经有的个数 - 比nums[i]小的个数
                ans += (i+1) - ttt;
            }
            return ans;
        }
    };
    

    160. 相交链表

    两个链表头指针都走完,公共部分 + A的非公共部分 + B的非公共部分 时会重合

    1当头节点到末尾时,把指针指向另一个链表的头节点。

    2跑到公共部分 + A的非公共部分 + B的非公共部分 时会重合

    class Solution {
    public:
        ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
            //当快的链表先到达时末尾 移动到另一个链表开头 交换重新开始移动
            ListNode* p = headA;
            ListNode* q = headB;
            while(p && q){
                p = p->next;
                q = q->next;
            }
            //都跑a+b+c段距离会重合
            if(!p){
                p = headB;
                while(q){
                    q = q->next;
                    p = p->next;
                }
                q = headA;
            }else{
                q = headA;
                while(p){
                    p = p->next;
                    q = q->next;
                }
                p = headB;
            }
            while(p && q && p!=q){
                p = p->next;
                q = q->next;
            }
            return p;
        }
    };
    

    简单写法

    class Solution {
    public:
        ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
            auto tempHeadA = headA;
            auto tempHeadB = headB;
            while(tempHeadA != tempHeadB){
                if(tempHeadA) tempHeadA = tempHeadA->next;
                else tempHeadA = headB;
                if(tempHeadB) tempHeadB = tempHeadB->next;
                else tempHeadB = headA;
            }
            return tempHeadB;
        }
    };
    

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

    STL二分

    class Solution {
    public:
        int search(vector<int>& nums, int target) {
            return upper_bound(nums.begin(),nums.end(),target) 
            - lower_bound(nums.begin(),nums.end(),target);
        }
    };
    

    手写二分

    找满足(==target)最大值最小的下标

    找满足(>target)最大值最小的下标

    二者相减

    class Solution {
    public:
        int search(vector<int>& nums, int target) {
            int n = nums.size();
            if(n == 0) return 0;
            if(n == 1) {
                if(nums[0] == target) return 1;
                return 0;
            }
            //找满足(==target)最大值最小的下标
            int l = 0, r = n;
            while(l < r){
                int mid = (l + r) >> 1;
                if(nums[mid] >= target){
                    r = mid;
                }else{
                    l = mid + 1;
                }
            }
            int lowPos = r;
            //找满足(>target)最大值最小的下标
            l = 0,r = n;
            while(l < r){
                int mid = (l + r) >> 1;
                if(nums[mid] > target){
                    r = mid;
                }else{
                    l = mid + 1;
                }
            }
            int highPos = r;
            return highPos - lowPos;
        }
    };
    

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

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

    二分

    左半段mid都等于nums[mid],右半段都不等于

    所以二分最大的最小满足的点

    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
            int left = 0, right = nums.size();
            while(left < right){
                int mid = (left + right)/2;
                if(mid == nums[mid]) left = mid + 1;
                else right = mid;
            }
            return left;
        }
    };
    
    

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

    递归max(左右子树的深度) + 1

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

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

    思路:二叉搜索树的中序遍历就是有序的递增序列,所以只要中序遍历得出序列,并且统计出节点个数cnt,返回第k大也就是下标为cnt-k小的数

    class Solution {
    public:
        int cnt = 0;
        int kthLargest(TreeNode* root, int k) {
            if(root == NULL) return 0;
            if(k == 0) return root->val;
            vector<int> inOrder;
            dfs(root,inOrder);
            return inOrder[cnt - k];
        }
    
        void dfs(TreeNode* root,vector<int>& inOrder){
            if(root == NULL) return;
            if(root->left) dfs(root->left,inOrder);
            cnt++;
            inOrder.push_back(root->val);
            if(root->right) dfs(root->right,inOrder);
        }
    };
    

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

    判断当前节点是否满足,左子树深度 与 右子树深度差为1

    递归判断左右子树是否满足

    中序遍历,时间复杂度O(n)

    class Solution {
    public:
        bool isBalanced(TreeNode* root) {
            if(root == NULL) return true;
            if(abs(dfs(root->left) - dfs(root->right) ) <= 1){
                return isBalanced(root->left)
                && isBalanced(root->right);
            }
            return false;
        }
        int dfs(TreeNode* root){
            if(root == NULL) return 0;
            return max(dfs(root->left)+1,dfs(root->right)+1);
        }
    };
    

    思路二:中序遍历的倒序序列,第k个才是第k大;正序的中序遍历是第k小。所以为了倒序先遍历右子树,再遍历根,再遍历左子树

    class Solution {
    public:
        int cnt = 0;
        int ans = 0;
        int kthLargest(TreeNode* root, int k) {
            if(root == NULL) return 0;
            if(k == 0) return root->val;
            dfs(root,k);
            return ans;
        }
    
        void dfs(TreeNode* root,int k){
            if(root == NULL) return;
            if(root->right) dfs(root->right,k);
            if(++cnt == k) {
                ans = root->val;
                return;
            }
            if(root->left) dfs(root->left,k);
        }
    };
    

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

    先把中序序列存下来

    然后建立双向链表

    因为先存下中序序列,然后开辟新结点,所以空间复杂度较高

    class Solution {
    public:
        Node* treeToDoublyList(Node* root) {
            if(!root) return NULL;
            vector<int> result;
            dfs(root,result);
            Node * head = new Node(result[0]);
            Node * last = head;
            for(int i=1;i<result.size();i++){
                Node *cur = new Node(result[i]);
                cur->left = last;
                last->right = cur;
                last = last->right;
            }
            last->right = head;
            head->left = last;
            return head;
        }
        void dfs(Node* root,vector<int>& result){
            if(!root) return ;
            if(root->left) dfs(root->left,result);
            result.push_back(root->val);
            if(root->right) dfs(root->right,result);
        }
    };
    

    思路二:递归实现,在中序遍历的过程中维护前驱,以及前驱结点的后继,类似于线索二叉树

    class Solution {
    public:
        Node* head,*pre = NULL;
        Node* treeToDoublyList(Node* root) {
            if(root == NULL) return NULL;
            dfs(root);
            pre->right = head;
            head->left = pre;
            return head;
        }
    
        void dfs(Node *root){
            if(!root) return;
            if(root->left) dfs(root->left);
            
            if(pre == NULL){
                //中序序列的第一个为头节点
                head = root;
                pre = root;
            }else{
                //1.维护当前结点的前驱
                //2.维护当前结点前驱的后继
                root->left = pre;
                pre->right = root;
                pre = root;
            }
            if(root->right) dfs(root->right);
        }
    };
    

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

    二分查找

    class Solution {
    public:
        vector<int> twoSum(vector<int>& nums, int target) {
            int n = nums.size();
            int ans = 0;
            for(int i=0;i<n;i++){
                if(nums[i] > target) break;
                int pos = lower_bound(nums.begin(),
                nums.end(),target-nums[i]) - nums.begin();
                if(pos < n && nums[i] + nums[pos] == target){
                    ans = nums[i];
                    break;
                }
            }
            return {ans,target-ans};
        }
    };
    

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

    class Solution {
    public:
        string reverseWords(string s) {
            vector<string> result;
            string buffer = "";
            for(int i = 0; i < s.length();i++){
                if(s[i] == ' '){
                    if(buffer != ""){
                        result.push_back(buffer);
                        buffer = "";
                    }
                }else{
                    buffer += s[i];
                }
            }
            if(buffer != "") result.push_back(buffer);
            reverse(result.begin(),result.end());
            string ans = "";
            int n = result.size();
            for(int i=0;i<n;i++){
                ans += result[i];
                if(i != n - 1) ans += " ";
            }
            return ans;
        }
    };
    

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

    二进制运算详细专题

    重复的数字进行分组,很简单,只需要有一个统一的规则,就可以把相同的数字分到同一组了。例如:奇偶分组。因为重复的数字,数值都是一样的,所以一定会分到同一组!

    对 i & -i 的位进行分组,

    class Solution {
    public:
        vector<int> singleNumbers(vector<int>& nums) {
            int n = nums.size();
            int sum = 0;
            for(int i=0;i<n;i++) sum ^= nums[i];
    
            //第pos位为1
            int pos = sum & -sum;
    
            int a = 0,b = 0;
            for(int i=0;i<n;i++){
                //与pos作& 分成两组
                if(nums[i] & pos){
                    a ^= nums[i];
                }else{
                    b ^= nums[i];
                }
            }
            return {a,b};
        }
    };
    

    nums[i] & pos理解不了了啊

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

    字符串模拟

    class Solution {
    public:
        string reverseLeftWords(string s, int k) {
            int n = s.length();
            string temp = "";
            for(int i=0;i<k;i++){
                temp += s[i];
            }
            string ans = "";
            for(int i=k;i<n;i++){
                ans += s[i];
            }
            return ans + temp;
        }
    };
    

    库函数

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

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

    to_string 将数字转换为字符串

    对字符串组合排序 a+b < b + a 小的排在前面

    class Solution {
    public:
        string minNumber(vector<int>& nums) {
            vector<string> vec;
            for(int i=0;i<nums.size();i++){
                vec.push_back(to_string(nums[i]));
            }
            //对字符串组合排序 a+b < b + a 小的排在前面
            sort(vec.begin(),vec.end(),
            [](string& s1,string &s2){return s1 + s2 < s2 + s1;});
            string ans = "";
            for(int i=0;i<vec.size();i++){
                ans += vec[i];
            }
            return ans;
        }
    };
    

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

    思路:dp

    集合:dp[i] 表示前i个不同的方案数

    状态转移:考虑dp[i]可以有哪些字符串转移过来

    1.由dp[i-1]转移过来

    2.当num[i-1] == 1 时 可以由dp[i-2] 转移过来

    3.当num[i-1] == 2 且 num[i] <=5 时 可以由dp[i-2]转移过来

    dp[i] = dp[i-1];

    if(arr[i-1] == 1 || (arr[i-1] == 2 && arr[i] <= 5) dp[i] += dp[i-2];

    判断边界

    初始化dp[0] = 1;

    class Solution {
    public:
        int translateNum(int num) {
            //dp[i] 表示前i个不同的方案数
            //考虑dp[i]可以有哪些字符串转移过来
            //1.由dp[i-1]转移过来
            //2.当num[i-1] == 1 时 可以由dp[i-2] 转移过来
            //3.当num[i-1] == 2 且 num[i] <=5 时 可以由dp[i-2]转移过来
            if(num == 0) return 1;
            int len = 0;
            int temp = num;
            vector<int> arr;
            while(temp){
                arr.push_back(temp%10);
                temp /= 10;
            }
            reverse(arr.begin(),arr.end());
            int n = arr.size();
            vector<int> dp(n,0);
            dp[0] = 1;
            for(int i=1;i<n;i++){
                dp[i] = dp[i-1];
                if(arr[i-1] == 1 || (arr[i-1] == 2 
                	&& arr[i] <= 5)) {
                    if(i >= 2){
                        dp[i] += dp[i-2];
                    }else{
                        dp[i] += 1;
                    }
                }
            }
            return dp[n-1];
        }
    };
    

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

    坐标dp

    dp(i,j)表示在坐标(i,j)下能拿到礼物的所有集合 中的 礼物的最大价值

    dp(i,j)可以由dp(i-1,j) 和 dp(i,j-1)推出来,判边界

    初始化dp(0,0) = grid(0,0)

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

    想办法再优化成滚动数组,空间复杂度O(N)的

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

    0表示可以不连续的间隔+1

    统计不连续数字的间隔和,与0比较

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

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

    字符串大模拟

    我的代码混乱,要改!

    class Solution {
    public:
        int strToInt(string str) {
            long long ans = 0;
            bool flag = false;
            bool symbol = true;
            int len = str.length();
            for(int i=0;i<len;i++){
                if(i>0 && str[i-1] >= '0' && str[i-1] <= '9' && (str[i] < '0' || str[i] > '9')) break;
                if(str[i] == ' ' && !flag) continue;
                if(str[i] == ' ') return 0;
                if(!flag && (str[i] == '-' || str[i] == '+')){
                    flag = true;
                    if(str[i] == '-') symbol = false;
                    continue;
                }
                if(str[i] < '0' || str[i] > '9') break;
                if(flag && (str[i] < '0' || str[i] >'9')) break;
                if(flag || (str[i] >= '0' && str[i] <='9') ){
                    ans = ans * 10 + (str[i]-'0');
                    if(symbol){
                        if(ans >= INT_MAX) return INT_MAX;
                    }else{
                        if(ans * -1 <= INT_MIN) return INT_MIN;
                    }
                }
            }
            return symbol==false?-1*ans:ans;
        }
    };
    

    剑指 Offer 38. 字符串的排列

    使用next_permutation对字符串全排列,函数将每次返回下一次的字典序。所以需要先对字符串s字符进行字典序排序

    class Solution {
    public:
        vector<string> permutation(string s) {
            sort(s.begin(),s.end());
            vector<string> ans;
            do{
                ans.push_back(s);
            }while(next_permutation(s.begin(),s.end()));
            return ans;
        }
    };
    

    dfs搜索+set去重

    class Solution {
    public:
        vector<string> res;
        set<string> se;
        vector<string> permutation(string s) {
            sort(s.begin(),s.end());
            vector<char> temp;
            for(int i=0;i<s.length();i++) temp.push_back(s[i]);
            dfs(temp,0);
            for(auto str : se){
                res.push_back(str);
            }
            return res;
        }
    
        void dfs(vector<char>& temp,int left){
            if(left == temp.size()-1){
                string ans;
                for(int i=0;i<temp.size();i++) ans += temp[i];
                se.insert(ans);
                return;
            }
            //交换回溯 防重复
            for(int i=left;i<temp.size();i++){
                if(i!=left && temp[left] == temp[i]) continue;
                swap(temp[left],temp[i]);
                dfs(temp,left+1);
                swap(temp[left],temp[i]);
            }
        }
    };
    

    dfs交换 不需要去重的写法

    1.不用&

    2.不用回溯

    class Solution {
    public:
        vector<string> res;
        vector<string> permutation(string s) {
            sort(s.begin(),s.end());
            vector<char> temp;
            for(int i=0;i<s.length();i++) temp.push_back(s[i]);
            dfs(temp,0);
            return res;
        }
    
        void dfs(vector<char> temp,int left){
            if(left == temp.size()-1){
                string ans;
                for(int i=0;i<temp.size();i++) ans += temp[i];
                res.push_back(ans);
                return;
            }
            //交换回溯 防重复
            for(int i=left;i<temp.size();i++){
                if(i!=left && temp[left] == temp[i]) continue;
                swap(temp[left],temp[i]);
                dfs(temp,left+1);
            }
        }
    };
    

    去重关键:如果pos和i之间有字符等于s[i],则跳过。

    class Solution {
    public:
        vector<string> result;
        vector<string> permutation(string s) {
            dfs(s, 0);
            return result;
        }
        void dfs(string& s, int pos) {
            if (pos >= s.size()) {
                result.push_back(s);
                return;
            }
            for (int i = pos; i < s.size(); ++i) {
            	// 如果pos和i之间有字符等于s[i],则跳过。
                if (judge(s, pos, i)) continue;   
                swap(s[pos], s[i]);
                dfs(s, pos+1);
                swap(s[pos], s[i]);
            }
        }
    
        bool judge(string& s, int start, int end) {
            for (int i = start; i < end; ++i) {
                if (s[i] == s[end]) return true;
            }
            return false;
        }
    };
    
    作者:yuexiwen
    链接:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/solution/c-dfs-mian-shi-ti-38-zi-fu-chuan-de-pai-lie-by-yue/
    

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

    题解

    数位dp思想,模拟出来

    int getRest(stack<int> s){
        s.pop();
        int res = 0;
        while(!s.empty()){
            res = res * 10 + s.top();
            s.pop();
        }
        return res + 1;
    }
    
    int countDigitOne(int n) {
        // 计算f(1~10)
        vector<long>help = vector<long>(11);
        help[1] = 1;
        for(int i = 2; i < help.size(); i++){
            help[i] = pow(10, i - 1) + 10 * help[i - 1];
        }
        
        //用栈来记录数字的各个位数上的值
        stack<int> s = stack<int>();
        while(n){
            s.push(n % 10);
            n /= 10;
        }
        //res 记录结果
        long res = 0;
        while(!s.empty()){
            int size = s.size();
            int top = s.top();
            //判断个位
            if(size == 1){
                // 如果个位为0则不++  如果个位不为0 则增加一个1个数
                if(top != 0)    res += help[1];
            }
            else{
                //判断除了个位的其他位
                if(top > 1)   res += pow(10, s.size() - 1);
                else if(top == 1)   res += getRest(s);
                res += top * help[size - 1];
            }
            s.pop();
        }
        return res;
    }
    

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

    找规律

    题解

        int findNthDigit(int n){
            long int base = 9;
            int p = 1;
            while((n - base * p) > 0){
                n -= base * p;
                base *= 10;
                p += 1;
            }
            int number = 1;
            for(int i = p; i > 1; --i){
                number *= 10;
            }
            number += n / p;
            int re;
            if(n % p == 0){
                re = (number - 1) % 10;
            }
            else {
                for(int i = 0; i < p - n % p; ++i){
                    number /= 10;
                }
                re = number % 10;
            }
            return re;
        }
    

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

    双指针,保证l~r区间内无重复下移动r++扩大区间,如果有重复值了让左指针l移动到重复的下一个(当重复位置大于L时才移动),更新区间最大长度答案

    class Solution {
    public:
        int lengthOfLongestSubstring(string s) {
            map<char,int> mp;
            int ans = 0,l = 0,r = 0;
            while(r < s.length()){
                if(mp.find(s[r]) != mp.end()){
                    l = max(l,mp[s[r]] + 1);
                }
                mp[s[r]] = r;
                ans = max(r-l+1,ans);
                r++;
            }
            return ans;
        }
    };
    

    剑指 Offer 49. 丑数

    优先队列 每次从丑数队列中选出最小的,并分别与2、3、5相乘加入新值到队列,有可能计算会重复所以使用set去重, 第n次从队头出队的就是答案

    class Solution {
    public:
        int nthUglyNumber(int n) {
            priority_queue<long long,vector<long long>,greater<long long> > queue;
            set<long long> se;
            long long  prime[3] = {2,3,5};
            queue.push(1);
            se.insert(1);
            long long ans = 1;
            for(int i=1;i<=n;i++){
                ans = queue.top();
                queue.pop();
                for(int j=0;j<3;j++){
                    if(se.find(ans*prime[j]) == se.end()){
                        se.insert(ans*prime[j]);
                        queue.push(ans*prime[j]);
                    }
                }
            } 
            return ans;
        }
    };
    

    dp 题解

    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(dp[p2]*2,min(dp[p3]*3,dp[p5]*5));
                if(dp[p2] * 2 == dp[i]) p2++;
                if(dp[p3] * 3 == dp[i]) p3++;
                if(dp[p5] * 5 == dp[i]) p5++;
            }
            return dp[n-1];
        }
    };
    

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

    作者地址

    class Solution {
    public:
        int singleNumber(vector<int>& nums) {
            int cnt[32];
            for(int i=0;i<32;i++) cnt[i] = 0;
            for(int i=0;i<nums.size();i++){
                for(int j=0;j<32;j++){
                    cnt[j] += nums[i] & 1;
                    nums[i]>>=1;
                }
            }
            int ans = 0;
            for(int i=0;i<31;i++){
                ans = ans + (cnt[i]%3) * pow(2,i);
            }
            return ans;
        }
    };
    

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

    每次使用i个数字 target表示这一次最小的是多少

    下一次使用i+1个数,各元素相差增加了i,

    题解参考作者xuelimin

    class Solution {
    public:
        vector<vector<int>> findContinuousSequence(int target) {
            vector<vector<int>> result;
            int i = 1;//几个数
            //每次使用i个数字  target表示这一次最小的是多少
            while(target > 0){
                target -= i;
                i++;
                if(target > 0 && target%i == 0){
                    vector<int> temp;
                    for(int j=0;j<i;j++) 		
                      temp.push_back(target/i+j);
                    result.push_back(temp);
                }
            }
            reverse(result.begin(),result.end());
            return result;
        }
    };
    

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

    单调队列,滑动窗口保持窗口大小小于k

    class Solution {
    public:
        vector<int> maxSlidingWindow(vector<int>& nums, int k) {
            vector<int> result;
            deque<int> queue;
            for(int i=0;i<nums.size();i++){
                //控制滑动窗口的大小
                if(queue.size() && i-queue.front()+1 > k)   
                    queue.pop_front();
                //控制单调性 找到当前元素的合适位置
                while(queue.size() 
                && nums[queue.back()] < nums[i])
                    queue.pop_back();
                //把当前元素加入到队尾
                queue.push_back(i);
                //保存这一轮队头 就是最大值答案
                if(queue.size() && i - k + 1 >= 0){
                    result.push_back(nums[queue.front()]);
                }
            }
            return result;
        }
    };
    

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

    如果当前价格i小于前面出现过的最小价格,就更新最小价格;

    否则,就更新最大利润

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

    思路二:dp

    dp[i] 表示第i天所有方案中的最大利润,注意是第i天,不是前i天

    转移方程:

    1.当第i天价格比第i-1天大,说明我第i天卖出去能多卖prices[i]-prices[i-1]。

    2.否则,只能与前面的minPrice作比较来判断最大利润,且差值不能小于0

    每一轮都要更新minPrice

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

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

    使用两个队列:一个队列正常入队出队;再用一个双端队列来辅助作为单调队列,维护队头最大值。这样O(1)查询最大值就是队头的值。

    入队的时候维护单调性,注意这里是可以有相同元素进入单调队列的。

    出队时,如果普通队列的队头元素等于单调队列的队头,那么单调队列队头也出队

    class MaxQueue {
    public:
        queue<int> queue;
        deque<int> help;
        MaxQueue() {
    
        }
        
        int max_value() {
            return help.size() ? help.front() : -1;
        }
        
        void push_back(int value) {
            queue.push(value);
            while(help.size() && help.back() < value){
                help.pop_back();
            }
            help.push_back(value);
        }
        
        int pop_front() {
            if(!queue.size()) return -1;
            int topValue = queue.front();
            queue.pop();
            if(help.size() && help.front() == topValue) 
            	help.pop_front();
            return topValue;
        }
    };
    

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

    如果两个结点都在左子树上,就递归搜索左子树

    如果两个结点都在右子树上,就递归搜索右子树

    否则就是一个结点在左子树,另一个结点在右子树,那么root就是它们的公共祖先

    class Solution {
    public:
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            if(root == NULL) return root;
            if(p->val < root->val && q->val < root->val){
                return lowestCommonAncestor(root->left,p,q);
            }else if(p->val > root->val && q->val > root->val){
                return lowestCommonAncestor(root->right,p,q);
            }
            return root;
        }
    };
    

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

    1.递归出口root==NULL 或则 root == 左右孩子其中一个

    2.递归找左子树上有没有两个结点,递归找右子树有没有两个结点,

    3.三种情况,左子树null,两个结点都在右子树;右子树null,两个结点都在左子树;左右子树各有一个,root就是最近公共祖先

    /**
     * Definition for a binary tree node.
     * struct TreeNode {
     *     int val;
     *     TreeNode *left;
     *     TreeNode *right;
     *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
     * };
     */
    class Solution {
    public:
        TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
            if(root == NULL) return NULL;
            if(root == p || root == q) return root;
            TreeNode* left = 
            	lowestCommonAncestor(root->left,p,q);
            TreeNode* right = 
            	lowestCommonAncestor(root->right,p,q);
            if(!left) return right;
            if(!right) return left;
            return root;
        }
    };
    

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

    Java使用ArrayList模拟

    pos = (pos + m - 1) % n 表示每一次位置上加上m对新的任务n取余

    class Solution {
        public int lastRemaining(int n, int m) {
            ArrayList<Integer> list = new ArrayList(n);
            for(int i=0;i<=n-1;i++) list.add(i);
            int pos = 0;
            while(n > 1){
                pos = (pos + m - 1) % n;
                list.remove(pos);
                n--;
            }
            return list.get(0);
        }
    }
    

    C++使用队列模拟,超时

    class Solution {
    public:
        int lastRemaining(int n, int m) {
            queue<int> q;
            for(int i=0;i<=n-1;i++) q.push(i);
            int num = 0;
            while(q.size() > 1){
                num++;
                int top = q.front();
                q.pop();
                if(num == m){
                    num = 0;
                }else{
                    q.push(top);
                }
                n--;
            }
            return q.front();
        }
    };
    

    数学方法约瑟夫环

    https://blog.csdn.net/u011500062/article/details/72855826

    剑指 Offer 66. 构建乘积数组

    思路本质是构造一个矩阵,让下三角和上三角相乘。

    下三角从0往下推用到i-1;上三角从n往上推用到i+1;

    参考题解原作者

    class Solution {
    public:
        vector<int> constructArr(vector<int>& a) {
            int n = a.size();
            if(n == 0) return a;
            vector<int> result(n);
            vector<int> left(n,0);
            vector<int> right(n,0);
            left[0] = 1;
            right[n-1] = 1;
            for(int i=1;i<n;i++) left[i] = left[i-1] * a[i-1];
            for(int i=n - 2;i>=0;i--) right[i] = right[i+1]*a[i+1];
            for(int i=0;i<n;i++){
                result[i] = left[i] * right[i];
            }
            return result;
        }
    };
    
  • 相关阅读:
    利用HttpModule做流量记录
    VS2010 调试出现 asp.net development server 错误
    利用win7自带的虚拟WIFI网卡,与其他设备共享网络
    关于 ASP 中使用 Server.CreateObject("ADODB.Stream") 上传文件报错
    lightweight jobs
    YOLO v3 包括Tiny-Yolo 训练自己的数据集(Pytorch版本)以及模型评价指标的介绍
    Sublime Text3 下载安装与激活使用
    QT中自定义封装控件笔记
    19_7_25-7_27 暑假学校收获
    数字图像处理基础知识2
  • 原文地址:https://www.cnblogs.com/fisherss/p/13193893.html
Copyright © 2020-2023  润新知