LeetCode学习计划——剑指Offer

37 篇文章 2 订阅
5 篇文章 0 订阅

Day1 栈与队列(简单)

1、用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

class CQueue {
/**
将先进后出改为先进先出:
定义两个栈,每次向栈1里存数据,将栈1里的数据再次存入栈2,每次删除时返回的值应是栈2的顶部元素
**/
    stack<int> stack1, stack2;//定义栈,先进后出
public:
    CQueue() {//初始化
        while(!stack1.empty()){
            stack1.pop();
        }

        while(!stack2.empty()){
            stack2.pop();
        }

    }
    
    void appendTail(int value) {//向栈1里加数据
        stack1.push(value);
    }
    
    int deleteHead() {
        if(stack2.empty()){//如果栈2是空的,将栈1的数据压入栈2
            while(!stack1.empty()){
                stack2.push(stack1.top());
                stack1.pop();
            }           
        }

        if(stack2.empty()){//如果栈2依然是空的,栈1为空,返回-1
            return -1;
        }
        else{//定义栈2的栈顶元素并返回,即为栈1要删除的栈底元素即为头元素
            int deleteItem = stack2.top();
            stack2.pop();
            return deleteItem;    
        }

    }
};

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

2、包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。

class MinStack {
/**
定义两个栈,栈1存的是原始数据,栈2应确保大小和栈1一致,并且栈2的栈顶元素始终为当前所有元素中最小的值
每次删除时也要将两个栈的元素都进行删除
**/
    stack<int> stack1, stack2;
public:
    /** initialize your data structure here. */
    MinStack() {
        while(!stack1.empty()){
            stack1.pop();
        }
        while(!stack2.empty()){
            stack2.pop();
        }

    }
    
    void push(int x) {
        stack1.push(x); 

        if(stack2.empty() || x < stack2.top()){
            stack2.push(x);//stack2的栈顶始终是最小值
        }
        else{
            stack2.push(stack2.top());//确保stack2和stack1大小相等,同时pop
        }
                                
    }
    
    void pop() {
        stack1.pop();
        if(!stack2.empty())
        stack2.pop();

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

    }
    
    int min() {
        return stack2.top();
    }
};

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

Day2 链表(简单)

1、从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

/**
 * 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) {
        //使用容器数组和栈
        stack<int> s;
        vector<int> res;

        //遍历链表存入栈
        ListNode *cur = head;
        while(cur){
            s.push(cur->val);
            cur = cur->next;
        }
        //出栈存入容器
        while(!s.empty()){
            res.push_back(s.top());
            s.pop();
        }

        return res;

    }
};

2、反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
/**
定义一个空节点(虚拟节点)pre,一个临时节点tmp,一个用作循环的cur节点,初始值指向头部节点,
每次先在tmp中存储cur->next,再让cur->next等于pre节点即指向上一个节点,
pre节点移动到cur节点,cur节点等于tmp节点(即之前存储的cur->next节点)
当循环结束,cur节点为空,pre节点为原始链表的尾部节点,此时返回pre节点即可返回返转后的链表
**/
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *temp;
        ListNode *cur = head;
        ListNode *pre = NULL;

        while(cur){
            temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }

        return pre;

    }
};

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

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
/**
定义哈希表,键和值都为Node*,从链表头部开始循环向哈希表中存储,每次存储时相当于构造节点(建立 “原节点--> 新节点” 的 Map 映射),使用new,
先复制原链表中的值,之后再次让cur指向头部节点,开始复制指针域到哈希表,
最后返回m[head]即可
**/
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;

        unordered_map<Node*, Node*> m;
        Node* cur = head; //定义头结点

        //复制值
        while(cur){
            m[cur] = new Node(cur->val); //
            cur = cur->next;
        }

        cur = head;//回到头结点
        //复制指针域
        while(cur){
            m[cur]->next = m[cur->next];
            m[cur]->random = m[cur->random];
            cur = cur->next;
        }

        cur = head;//回到头结点
        return m[cur];
    
    }
};

Day3 字符串(简单)

1、替换空格
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

class Solution {
/**
遍历s将字符依次存入res,若遇到空格则将‘%’‘2’‘0’存进res,返回res即可
**/
public:
    string replaceSpace(string s) {
        string res;

        for(int i = 0;i < s.size(); i++){
            if(s[i] == ' '){
                res.push_back('%');
                res.push_back('2');
                res.push_back('0');
            }
            else{
                res.push_back(s[i]);
            }
        }
        return res;

    }
};

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

class Solution {
/**
先从第n个字符开始依次存入字符串res,再将0——n即前面的字符串依次存入res即可
**/
public:
    string reverseLeftWords(string s, int n) {
        string res;

        for(int i = n;i < s.size(); i++){
            res.push_back(s[i]);
        }
        for(int j = 0; j < n;j++){
            res.push_back(s[j]);
        }

        return res;

    }
};

Day4 查找算法(简单)

1、数组中重复的数字
找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

class Solution {
/**
建立哈希表,键为数组元素,值为该元素出现次数,
再次遍历数组,返回哈希表中值大于等于2的对应元素
**/
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int, int> m;

        for(int i = 0; i < nums.size(); i++){
            m[nums[i]]++;
        }
        for(int j = 0; j < nums.size(); j++){
            if(m[nums[j]] >= 2){
                return nums[j];
            }
        }

        return 0;
    }
};

2、I. 在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。

class Solution {
/**
建立哈希表,键为数组元素,值为该元素出现次数,返回对应元素在哈希表中的值即可
**/
public:
    int search(vector<int>& nums, int target) {
        unordered_map<int, int> m;

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

        for(int j = 0; j < nums.size(); j++){
            if(nums[j] == target){
                return m[nums[j]];
            }
        }
        return 0;

    }
};

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

class Solution {
/**
二分查找:两个指针,如果nums[mid]的值不等于mid,则说明要查找的缺失数字在前半部分或者就是mid,否则在后半部分,
最后返回right即可。
**/
public:
    int missingNumber(vector<int>& nums) {
       int left = 0, right = nums.size();

        while(left < right){
            int mid = (left + right) / 2;
            if(nums[mid] != mid){
                right = mid; //
            }
            else{
                left = mid + 1;
            }
        }
        return right;

    }
};

Day5 查找算法(中等)

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

class Solution {
/**
通过观察可知,二维数组中对于左下角元素而言,类似于二叉搜索树,行数往上走值越来越小,列数往右走值原来越大,
以此开始搜索整个二维数组,当前值比目标值大,行数上移,当前值比目标值小,列数右移
**/
public:
//以左下角为起点,往上小,往右大
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(matrix.size() == 0 || matrix[0].size() == 0) { //矩阵为空
            return false;
        }

        int rows = matrix.size(), cols = matrix[0].size();
        int row = rows - 1, col = 0;

        while(row >= 0 && col <= cols - 1){ //
            if(matrix[row][col] > target) --row;
            else if(matrix[row][col] < target) ++col;
            else return true;
        }

        return false;
    }
};

2、旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。

注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

class Solution {
/**
法一:直接查找,定义res,循环遍历找出最小值。
法二:二分查找,如果nums[mid]大于nums[r],说明要找的最小值在后半部分,如果nums[mid]小于nums[r],说明要找的最小值在前半部分或者就是nums[mid],如果nums[mid]等于nums[r],则左指针不动,右指针前移继续循环寻找。
**/
public:
    int minArray(vector<int>& numbers) {
        /*int res = numbers[0];

        for(int i = 0; i < numbers.size(); i++){
            res = min(res, numbers[i]);
        }

        return res;*/
        
        int left = 0, right = numbers.size() - 1;
        
        while(left < right){    
            int mid = (right + left) / 2;
            if(numbers[mid] > numbers[right]){ //
                left = mid + 1;
            }
            else if(numbers[mid] < numbers[right]){ //
                right = mid;
            }
            else{
                right--; //
            }
        }

        return numbers[left];

    }
};

3、第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

class Solution {
/**
建立哈希表,键为字符,值该字符出现的次数,再次遍历字符串,返回次数为1的字符
**/
class Solution {
public:
    char firstUniqChar(string s) {
        if(s.size() == 0) return ' ';

        unordered_map<char, int> m;
        for(int i = 0; i < s.size(); i++){
            m[s[i]]++;
        }

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

        return ' ';
    }
};

Day6 搜索与回溯算法(简单)

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

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
/**
广度优先搜索:
建立队列,先将根存入,
开始循环递归
**/
public:
//层序遍历
    vector<int> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        vector<int> res;

        if(root != NULL) que.push(root);

        while(!que.empty()){
            int size = que.size();
            for(int i= 0; i < size; i++){
                TreeNode *node = que.front(); //将队列中的队首元素拿出来赋给一个节点 以方便后面取值
                que.pop();
                res.push_back(node->val);

                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            
        }
        return res;

    }
};

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

/**
 * 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 {
/**
广度优先搜索:
建立队列,先将根存入,
开始循环递归,每次for循环执行完(即把当前队列中的元素处理完)即代表处理了“一行”,
将这一行存入最终结果
**/
public:
//层序遍历
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> que;
        vector<vector<int>> res;

        if(root != NULL) que.push(root);

        while(!que.empty()){
            vector<int> vec;
            int size = que.size();

            for(int i = 0; i < size; i++){
                TreeNode *node = que.front();//定义指针 //将队列中的队首元素拿出来赋给一个节点 以方便后面取值
                que.pop();
                vec.push_back(node->val);

                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            res.push_back(vec);
        }

        return res;

    }
};

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

/**
 * 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 {
/**
广度优先搜索:
定义计数变量,记录当前遍历的“行数”,
建立队列,先将根存入,
开始循环递归,每次for循环执行完(即把当前队列中的元素处理完)即代表处理了“一行”,
对这一行判断奇偶后(以决定是否反转),再存入最终结果
**/
public:
//在层序遍历基础上加上行数的奇偶判断以决定是否反转输出
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        queue<TreeNode*> que;
        int count = 0; //计数

        if(root != nullptr) que.push(root);

        while(!que.empty()){
            vector<int> vec;
            int size = que.size();
            for(int i = 0; i < size; i++){
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);

                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            count++;

            if(count % 2 == 1){
                res.push_back(vec);
            }
            else{
                reverse(vec.begin(), vec.end()); //反转
                res.push_back(vec);
            }
        }

        return res;
    }
};

Day7 搜索与回溯算法(简单)

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

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

/**
 * 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 {
/**
判断节点A和B(子函数),判断A->left和B,判断A->right和B;
子函数中:
如果B为空,匹配完毕,返回true;
如果A为空或者A->val和B->val不相等,返回false;
否则返回 子函数(A->left,B->left) && (A->right,B->right)
**/
public:
    bool isSub(TreeNode* A, TreeNode* B){
        if(B == nullptr) return true; //当节点 B 为空:说明树 B 已匹配完成
        else if(A == nullptr || A->val != B->val) return false; //当节点 A 为空 / 当节点 A 和 B 的值不同
        else return isSub(A->left, B->left) && isSub(A->right, B->right);//判断 A 和 B 的左子节点是否相等 && 判断 A 和 B 的右子节点是否相等
    }

    bool isSubStructure(TreeNode* A, TreeNode* B) {
        //节点 A 为根节点的子树 包含树 B
        //树 B 是 树 A 左子树 的子结构
        //树 B 是 树 A 右子树 的子结构
        return (A != nullptr && B != nullptr && (isSubStructure(A->left, B) || isSubStructure(A->right, B) || isSub(A, B))); //
    }
};

2、二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
/**
深度优先搜索:
当前层交换左右节点的值;
递归左节点;
递归右节点。
**/
public:
//深度优先搜索
    TreeNode* mirrorTree(TreeNode* root) {
        if(root == nullptr) return nullptr;

        swap(root->left, root->right);
        if(root->left != nullptr) mirrorTree(root->left);
        if(root->right != nullptr) mirrorTree(root->right);

        return root;
    }
};

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

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
/**
要满足对称二叉树,则要左右子树都不为空并且左子树的左子树和右子树的右子树(外圈)&&左子树的右子树和右子树的左子树(内圈)都要相等,此处可以递归。
**/
public:
    bool isSym(TreeNode* left, TreeNode* right){
        if(left == nullptr && right == nullptr) return true;
        else if(left != nullptr && right == nullptr) return false;
        else if(left == nullptr && right != nullptr) return false;
        else if(left->val != right->val) return false;

        //左子树的左节点和右子树的右节点 // 左子树的右节点和右子树的左节点
        else{
            bool outSide = isSym(left->left, right->right);
            bool inSide = isSym(left->right, right->left);
            bool isSame = outSide && inSide;
            return isSame;
        }
    }

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

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

Day8 动态规划(简单)

1、I. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

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

class Solution {
/**
DP:
递推公式:dp[i] = (dp[i -1] + dp[i -2]) 
**/
public:
//动态规划
    int fib(int n) {
        const int MOD = 1000000007;
              
        if(n <= 1) return n;
        /*
        return (fib(n - 1) + fib(n - 2)) % MOD;
        */

        vector<int> dp(n + 1); //n + 1 为size()
        dp[0] = 0;
        dp[1] = 1;

        for(int i = 2; i <= n; i++){
            dp[i] = (dp[i -1] + dp[i -2]) % MOD;
        }

        return dp[n] % MOD;

        /*int fn[2];
        fn[0] = 0;
        fn[1] = 1;

        for(int i = 2; i <= n; i++){
            int sum = (fn[0] + fn[1])% MOD;
            fn[0] = fn[1];
            fn[1] = sum;
        }

        return fn[1] % MOD;
       */

    }
};

2、II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

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

class Solution {
/**
DP:
递推公式:dp[i] = (dp[i -1] + dp[i -2]) 
**/
public:
//动态规划
    int numWays(int n) {
        //dp[i] = dp[i - 1] + dp[i - 2] 
        int MOD = 1000000007;
        if(n == 0) return 1;
        if(n == 1) return 1;

        vector<int> dp(n + 1);
        dp[0] = 1;
        dp[1] = 1;

        for(int i = 2; i <= n;i++){
            dp[i] = (dp[i -1] + dp[i - 2]) % MOD;
        }

        return (dp[n]) % MOD;
    }
};

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

class Solution {
/**
两个变量分别为最大利润和最低价格(初始化为1e9),
遍历所有价格更新即可
**/
public:
//动态规划
    int maxProfit(vector<int>& prices) {
        /*int n = prices.size();
        int ans = 0;

        for(int i = 0; i < n;i++){
            for(int j = i + 1; j < n;j++){
                ans = max(ans, prices[j] - prices[i]);
            }
        }

        return ans;*/

        /*for(int i = 0; i < prices.size(); i++){
            if(prices[i] < minPrice){
                minPrice = prices[i];
            }
            else if(prices[i] - minPrice > maxProfit){
                maxProfit = prices[i] - minPrice;

            }
        }
        return maxProfit;*/
        
        if(prices.size() == 0) return 0;

        int maxprofit = 0;
        int minprice = 1e9; //

        /*for(int i = 0; i < prices.size(); i++){
            minprice = min(prices[i], minprice);
            maxprofit = max(prices[i] - minprice, maxprofit);
        }*/
        for(auto price : prices){
            minprice = min(minprice, price);
            maxprofit = max(maxprofit, price - minprice);
        }
        return maxprofit;
    }
};

Day9 动态规划(中等)

1、连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

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

class Solution {
/**
DP:
建立数组存放第i位为止的最大和,建立结果res保存每次dp数组的最大值,
数组中第i位的值应为数组前一位与nums[i]之和 和 nums[i]本身的较大值,
返回res即可。
**/
public:
//动态规划
    int maxSubArray(vector<int>& nums) {
        //dp[i] = max(dp[i - 1] + nums[i], nums[i])

        int n = nums.size();
        vector<int> dp(n);//存放最大和的数组    
        dp[0] = nums[0];
        int res = dp[0];

        for(int i = 1; i < n; i ++){
            dp[i] = max(dp[i - 1] + nums[i], nums[i]);//
            res = max(dp[i], res);//res保存最大值
        }

        return res;

    }
};

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

class Solution {
/**
DP:
更新grid矩阵,第一行和第一列就是该行和该列一直相加,
中间grid[i][j]的元素等于
它的上一行同一列元素grid[i - 1][j] 和 
它的前一列同一行元素grid[i][j - 1]的 最大值 与 它本身 的和,
最后返回右下角元素即可。
**/
public:
//动态规划
    int maxValue(vector<vector<int>>& grid) {
        //更新矩阵为每走一步的拿到的最大价值,返回右下角元素
        if(grid.empty()) return 0;

        int rows = grid.size();
        int cols = grid[0].size();

        for(int i = 0; i < rows; i++){
            for(int j = 0; j < cols; j++){
                if(i == 0 && j == 0) grid[i][j] = grid[i][j];                
                else if(i == 0 && j > 0) grid[i][j] += grid[i][j - 1]; //第一行
                else if(i > 0 && j == 0) grid[i][j] += grid[i - 1][j]; //第一列

                else{
                    grid[i][j] += max(grid[i][j - 1], grid[i - 1][j]);
                }
            }
        }

        return grid[rows - 1][cols - 1];

    }
};

Day10 动态规划(中等)

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

class Solution {
/**
//动态规划
每次取后两位做判断,递归进行可能性的求和
**/
public:
    int translateNum(int num) {
    /*//参考牛客上的同名题
        string tmp = to_string(num);
        if(tmp == "0") return 1;
        if(tmp == "10" || tmp == "20") return 2;

        vector<int> dp(tmp.size() + 1, 0); //到第i个位置有多少种翻译方法
        dp[0] = 1; dp[1] = 1;
        for(int i = 2; i <= tmp.size(); i++){
            if((tmp[i - 2] == '1') || (tmp[i - 2] == '2' && tmp[i - 1] >= '0' && tmp[i - 1] < '6')){
                //在10-25之间的情况
                dp[i] = dp[i - 1] + dp[i - 2]; //对于满足两种译码方式(10,20不能)
            }
            else{
                dp[i] = dp[i - 1]; //对于只有一种译码方式的
            }
        }

        return dp[tmp.size()];*/

        if(num < 10) return 1;
        else if((num % 100) <= 25 && (num % 100) >= 10){//每次取后两位做判断
            return translateNum(num / 10) + translateNum(num / 100); //  /10和/100都可得出不同的解法//两个求和
        }
        else{
            return translateNum(num / 10); // 只能/10才可以得出不同的解法
        }

    }
};

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

class Solution {
/**
滑动窗口:
初始化一个unordered_set集合和一个右边界,一个结果res,
左边界的for循环,右边界的while循环依次往集合中添加元素,如果当前添加的元素不在集合中,
右边界加一,否则删除左边界的元素,res为 res 和 右边界减去左边界 的较大值。
**/
public:
    int lengthOfLongestSubstring(string s) {
        /*
        //哈希表  动态规划
        if(s == "") return 0;
        if(s.length() == 1) return 1;

        unordered_map<char, int> position;//记录字符对应所处的位置
        vector<int> dp(s.length(), 0);//dp[i]表示第i个字符时最长字符串的长度
        int res = 0;//结果是res和dp[i]的最大值

        for(int i = 0; i < s.length(); i++){
            if(i == 0){
                dp[i] = 1;
            }

            else if(i != 0 && position.find(s[i]) == position.end()){ //寻找字符s[i]所对应的值 为空
                dp[i] = dp[i - 1] + 1;
            }

            else{
                int j = position[s[i]];//s[i]首次出现的位置
                if(i- j > dp[i - 1]){ //s[i]首次出现的位置 在最大长度的外面
                    dp[i] = dp[i - 1] + 1;
                }
                else{
                    dp[i] = i - j;
                }
            }

            position[s[i]] = i;
            res = max(res, dp[i]);
        }

        return res;
        */
        
		//滑动窗口
		 if(s.empty()) return 0;

        unordered_set<char> us;//哈希集合
        int res = 0;
        int right = 0; //滑动窗口右边界

        for(int i = 0; i < s.size(); i++){
            if(i != 0){
                us.erase(s[i - 1]); //
            }
            //右边界没走到尾,并且当前字符不在集合中
            while(right < s.size() && us.count(s[right]) == 0){ // 
                us.insert(s[right]);
                right++;
            }

            res = max(res, right - i);
        }

        return res;

    }
};

Day11 双指针(简单)

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

返回删除后的链表的头节点。

/**
 * 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* tmp;
        //删除头结点
        while(head != NULL && head->val == val){   //
            tmp = head;
            head = head->next;
            //delete tmp;
        }

        //删除非头结点
        ListNode* cur = head;
        while(cur != NULL && cur->next != NULL){
            if(cur->next->val == val){
                tmp = cur->next;
                cur->next = cur->next->next;
                //delete tmp;
            }
            else{
                cur = cur->next;
            }
        }        

        return head;*/

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;//虚拟头结点
        ListNode* cur = dummyHead;

        while(cur != NULL && cur->next != NULL){
            if(cur->next->val == val){ //方便删除
                ListNode* tmp = cur->next;
                cur->next =cur->next->next;
                //delete tmp;
            }
            else{
                cur = cur->next;
            }
        }
        head = dummyHead->next;
        //delete dummyHead;

        return head;

    }
};

2、链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        //双指针
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;//虚拟头结点
        
        ListNode* left = dummyHead;
        ListNode* right = dummyHead;
        while(k-- && right != NULL){
            right = right->next;
        }

        right = right->next;//多向前移一位 方便输出

        while(right){
            left = left->next;
            right = right->next;
        }

        return left->next; //

    }
};

Day12 双指针(简单)

1、合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
/**
类似合并两个有序数组,创建空节点,之后依次比较l1和l2。
**/
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* preHead = new ListNode(0);
        ListNode* pre = preHead;

        while(l1 != nullptr && l2 != nullptr){
            if(l1->val > l2->val){
                pre->next = l2;
                l2 = l2->next;
            }
            else{
                pre->next = l1;
                l1 = l1->next;
            }
            pre = pre->next;
        }
        pre->next = l1 == nullptr ? l2 : l1;

        return preHead->next;

    }
};

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
/**
思路:确保两个指针节点的初始位置到末位位置长度相等(末尾对齐)。
首先计算两个链表的长度,求出差值poor,让较长的链表指针先走poor步,
之后开始依次遍历比较,相等则返回。
**/
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;

        //求两个链表长度
        while(curA != nullptr){
            lenA++;
            curA = curA->next;
        }
        while(curB != nullptr){
            lenB++;
            curB = curB->next;
        }

        //回到起点
        curA = headA;//
        curB = headB;//

        //始终curA指向的是较长的链表 //lenA > lenB
        if(lenA < lenB){
            swap(lenA, lenB); //
            swap(curA, curB); //
        }
        
        //curA移动到和poor相同的位置(末尾位置对齐)
        int poor = lenA - lenB;
        while(poor--){
            curA = curA->next;
        }

        //遍历
        while(curA != nullptr){
            //交点不是数值相等,而是指针相等
            if(curA == curB){
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }

        return nullptr;
    }
};

Day13 双指针(简单)

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

class Solution {
/**
双指针,一个指向头,一个指向尾,
左指针遇到奇数右移,右指针遇到偶数左移,
否则交换,左指针右移,右指针左移。
**/
public:
    vector<int> exchange(vector<int>& nums) {
        /*
        int left = 0; int right = nums.size() - 1;

        while(left < right){
            while(left < right && nums[left] % 2 == 1) left++; //左边是奇数 直接右移
            while(left < right && nums[right] % 2 == 0) right--; //右边是偶数 直接左移
            swap(nums[left++], nums[right--]); //左边为偶 右边为奇 交换并移动双指针
        }

        return nums;
        */

        int l = 0, r = nums.size() - 1;

        while(l < r){
            if(nums[l] % 2 == 1 && nums[r] % 2 == 0) ++l, --r;
            else if(nums[l] % 2 == 1 && nums[r] % 2 == 1) ++l;
            else if(nums[l] % 2 == 0 && nums[r] % 2 == 0) --r;
            else{
                swap(nums[l], nums[r]);
            }
        }

        return nums;
    }
};

2、和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

class Solution {
/**
法一:哈希表,寻找target - nums[i]。
法二:双指针(排序数组),如果大于,右指针左移,如果小于左指针右移。
**/
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        /*unordered_map<int, int> map;//key为值,val为索引

        for(int i = 0; i <nums.size(); i++){
        	//find函数来定位数据出现位置,它返回的一个迭代器,当数据出现时,它返回数据所在位置的迭代器,如果map中没有要查找的数据,它返回的迭代器等于end函数返回的迭代器
            auto iter = map.find(target - nums[i]); //find(k) 
            if(iter != map.end()){
                return {iter->first, nums[i]};
            }

            map[nums[i]] = i;//先找后存 避免找到自己 避免nums[i]和自己匹配
        }
        return {};*/

        int l = 0, r = nums.size() - 1;

        while(l < r){
            if(nums[l] + nums[r] > target) --r;
            else if(nums[l] + nums[r] < target) ++l;
            else{
                return {nums[l], nums[r]};
            }
        }

        return {};
    }
};

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

class Solution {
/**
字符串操作的模板。具体见代码。(或另一篇博客——字符串操作相关)
**/
public:
    string reverseWords(string s) {
        s += " ";//最后补个空格 不会漏掉最后一个单词
        string tmp = "";//临时字符串
        vector<string> res;//存放字符串的数组

        for(char ch : s){
            if(ch == ' '){
                if(!tmp.empty()){ //如果前置和后置有空格 则要加这行 否则会压进去空字符串
                    res.push_back(tmp); 
                    tmp.clear();
                }
            }
            else{ //
                tmp += ch;//拼接
            }
        }

        s.clear();//清空当前字符串 好在最后返回新的字符串
        reverse(res.begin(), res.end()); //反转容器中的元素 a b c --> c b a
        for(string &str : res){
            s += str + ' '; //补成句子的形式
        }
        s.pop_back(); //把最后一个空格推出去

        return s;
    }
};

Day14 搜索与回溯算法(中等)

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

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

class Solution {
/**
深度优先搜索:
选定一个位置,深度的比较word[k]和当前位置是否相等(递归)
**/
public:
//深度优先搜索
    int dx[4] = {1, 0, -1, 0}; int dy[4] = {0, 1, 0, -1};
    bool dfs(vector<vector<char>>& board, string word, int x, int y, int k){  //k表示当前枚举到了单词的第k个位置
        if(board[x][y] != word[k]) return false;
        if(k == word.size() - 1) return true; //k到了单词的最后一个位置
        
        char t = board[x][y];//记录当前矩阵中的字母 为后面标记之后的复原做准备
        board[x][y] = '.';//矩阵中该字母已被使用过 标记为 ‘.’
        //搜索
        for(int i = 0; i < 4; i++){
            int mx = x + dx[i]; int my = y + dy[i];

            if(mx < 0 || mx >= board.size() || my < 0 || my >= board[0].size() || board[mx][my] == '.') continue;//越界或被标记为‘.’进行下一个方向的判断
            if(dfs(board, word, mx, my, k + 1)) return true;//递归 回溯
        }
        board[x][y] = t;//还原被标记的字母

        return false;
    }

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

        for(int i = 0; i < rows; i++){
            for(int j = 0; j < cols; j++){
                if(dfs(board, word, i, j, 0)) return true;
            }
        }

        return false;

    }
};

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

class Solution {
/**
广度优先搜索:
构建队列,从(0, 0)位置开始,只要队列不为空,进行广度优先搜索,判断是否能走到和是否走过,结果是否加一。
**/
public:
//BFS 不需要四个方向
    // 向右和向下的方向数组  从[0, 0]开始
    int dx[2] = {0, 1}; int dy[2] = {1, 0};

    //求数位和
    int getSum(int x){
        int s = 0;
        while(x != 0){
            s += x % 10; //个位数
            x /= 10;//去掉个位数字
        }
        return s;
    }

    int movingCount(int m, int n, int k) {
        if(k == 0) return 1;

        queue<pair<int, int>> que;
        vector<vector<int>> visit(m, vector<int>(n, 0)); //标记矩阵

        que.push(make_pair(0, 0));
        visit[0][0] = 1;//已经走过 标记为1
        int res = 1;//结果 格子数 初始化为1

        //搜索
        while(!que.empty()){ //
            int x = que.front().first; int y = que.front().second;
            que.pop();

            //两个方向 向下和向右
            for(int i = 0; i < 2; i++){
                int mx = x + dx[i]; int my = y + dy[i];

                if(mx < 0 || mx >= m || my < 0 || my >= n || visit[mx][my] || getSum(mx) + getSum(my) > k) continue; //下一个方向

                que.push(make_pair(mx, my));
                visit[mx][my] = 1; //已经走过 标记为1
                res++;
            }
        }

        return res;
    }
};

Day15 搜索与回溯算法(中等)

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

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

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
/**
DFS+回溯:
每次让target的值减去root->val,之后进行左右节点的分别递归,记得回溯将上一次压入的元素推出去。
**/
public:
//DFS
    vector<vector<int>> res;
    vector<int> path;

    void dfs(TreeNode* root, int target){
        if(root == nullptr) return;

        path.emplace_back(root->val); //
        target -= root->val;

        //终止条件
        if(root->left == nullptr && root->right == nullptr && target == 0){ //
            res.push_back(path); //
        }

        dfs(root->left, target);
        dfs(root->right, target);
        path.pop_back(); // //回溯 推出此次压入的元素    
    }

    vector<vector<int>> pathSum(TreeNode* root, int target) {
        res.clear();
        path.clear();

        dfs(root, target);

        return res;

    }
};

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

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

    Node() {}

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

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
/**
使用中序遍历,每次对根节点进行操作,
如果上一个点不为空,上一个节点的右指针指向当前节点,
让当前节点的左指针指向上一个节点,之后pre移动到当前节点。
最后记得首尾的指针域设置。
**/
public:
    Node *head, *pre; //

    //中序遍历
    void dfs(Node* cur){
        if(cur == nullptr) return;

        dfs(cur->left);

        if(pre != nullptr) pre->right = cur; //
        else head = cur; //
        
        cur->left = pre;
        pre = cur;

        dfs(cur->right);

    }

    Node* treeToDoublyList(Node* root) {
        if(root == nullptr) return nullptr;

        dfs(root);
		//首尾的指针域设置
        head->left = pre;
        pre->right = head;
        
        return head;
    }
};

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

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
/**
中序遍历,得到排序数组,之后反转,返回数组中的值即res[k - 1]。
**/
public:
    void inOrder(TreeNode* root, vector<int> &res){
        if(root == nullptr) return;

        inOrder(root->left, res);
        res.push_back(root->val);
        inOrder(root->right, res);
    }

    int kthLargest(TreeNode* root, int k) {
        vector<int> res;
        inOrder(root, res);

        reverse(res.begin(), res.end());

        return res[k - 1];
    }
};

Day16 排序(简单)

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

class Solution {
/**
自定义排序规则,之后依次拼接字符串即可。
**/
public:
    /*static bool sortRole(string &x, string &y){ //比较函数的返回值一定是bool型
        return x + y < y + x;//排序完成后,数组中 x 应在 y 左边  //这种情况下返回的值是true
    }

    string minNumber(vector<int>& nums) {
        vector<string> str;
        string res;

        for(int i = 0; i < nums.size(); i++){
            str.push_back(to_string(nums[i]));
        }

        sort(str.begin(), str.end(), sortRole);//[](string &x, string &y){return x + y < y + x;});
     
        for(int i = 0; i < str.size(); i++){
            res.append(str[i]);
        }

        return res;*/

    struct cmp{
        bool operator () (string& x, string& y){//比较函数的返回值一定是bool型
            return x + y < y + x;//排序完成后,数组中 x 应在 y 左边  //这种情况下返回的值是true
        }
    };

    string minNumber(vector<int>& nums) {
        vector<string> str;
        string res;

        for(int i = 0; i < nums.size(); i++){
            str.push_back(to_string(nums[i]));
        }

        sort(str.begin(), str.end(), cmp()); //

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

        return res;
    }
};

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

class Solution {
/**
先从小到大进行排序,从前往后找到牌面为0级joker的位置,
寻找过程中,如果有连续两张牌相等,则直接返回false,
最终只要排序后数组的最后一个牌面减去joker位置的值(0)能够小于5,那么可以构成顺子。
**/
public:
    bool isStraight(vector<int>& nums) {
        int joker = 0;
        sort(nums.begin(), nums.end());

        for(int i = 0; i < 4; i++){
            if(nums[i] == 0) joker++; //大小王个数
            else if(nums[i + 1] == nums[i]) return false;//连续两张牌相等 返回false
        }

        return nums[4] - nums[joker] < 5; // 最大牌 - 最小牌 < 5 则可构成顺子   joker位置即为最小值
    }
};

Day17 排序(中等)

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

class Solution {
/**
先进行数组的排序,再将前k个数返回即可。
**/
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        sort(arr.begin(), arr.end());

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

        return res;
    }
};

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

例如,

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

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

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

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

class MedianFinder {
/**
输入的时候将数字分为两半,
小的一半放在大根堆big中,大的一半放在小根堆的small中。(顶部即为中间值)
输入的同时保证两堆的大小之差不超过一,如果超过,则将数量多的堆弹出堆顶元素放到另一个堆中。
取中位数的时候,奇数返回数量多的堆顶元素;偶数返回两堆的堆顶平均数即可。
**/
public:
//priority_queue<Type, Container, Functional> Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式
//priority_queue <int,vector<int>,greater<int> > q;//升序队列(自动排序),小顶堆
//priority_queue <int,vector<int>,less<int> >q;//降序队列(自动排序),大顶堆
//push 插入元素到队尾 (并排序)

    /** initialize your data structure here. */
    priority_queue<int, vector<int>, less<int>> big; //降序 大顶堆
    priority_queue<int, vector<int>, greater<int>> small; //升序 小顶堆
    int n;//数据流中的总的数字个数
    MedianFinder() {
        n = 0; //初始化
    }
    
    void addNum(int num) {
        if(big.empty()){ //第一个数存入大顶堆 记为最小值
            big.push(num);
            n++;
        }
        else if(num <= big.top()){ 小于 往大顶堆存 即存入“小的一半”
            big.push(num);
            n++;
        }
        else{ //大于 往小顶堆存 即存入“大的一半”
            small.push(num);
            n++;
        }

        //两个堆的size之差不能大于2
        if(big.size() - small.size() == 2) {
            small.push(big.top());
            big.pop();
        }
        if(small.size() - big.size() == 2) {
            big.push(small.top());
            small.pop();
        }

    }
    
    double findMedian() {
        if(n % 2){ //奇数个数字
            if(big.size() > small.size()) return big.top();
            else return small.top();
        }
        else{
            return (big.top() + small.top()) * 0.5;
        }

    }
};

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

Day18 搜索与回溯算法(中等)

1、 I. 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
/**
一个判断根节点为空的返回值0,
递归左子树和右子树的较大值+1即可。
**/
public:
    int maxDepth(TreeNode* root) {
        if(root == nullptr) return 0;

        return max(maxDepth(root->left) + 1, maxDepth(root->right) + 1);

    }
};

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

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
/**
返回值应为:
左子树为平衡树,且右子树为平衡树,且左右子树深度差(要取绝对值)不大于1。
此处计算深度可用上一题的思路。
**/
public:
    int maxDepth(TreeNode* root){
        if(root == nullptr) return 0;

        return max(maxDepth(root->left) + 1, maxDepth(root->right) + 1);
    }

    bool isBalanced(TreeNode* root) {
        if(root == nullptr) return true;
        else{
            return abs(maxDepth(root->left) - maxDepth(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right); //
        }

    }
};

Day19 搜索与回溯算法(中等)

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

class Solution {
/**
法一:使用“短路” 改变递归的终止条件
法二:计算二维矩阵bool类型所占的内存大小
**/
public:
/*
//需要一个短路来终止递归。
    int res = 0;
    int sumNums(int n) {
        bool flag = (n > 1) && (sumNums(n - 1)); //前面的条件满足执行后面的
        res += n;
        return res;
    }
    */
    int sumNums(int n) {
        bool matrix[n][n + 1]; //计算内存
        //cout<<sizeof(matrix)<<endl;
        return ((sizeof(matrix)) >> 1);
    }
};

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

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

/**
 * 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 == p || root == q || root == nullptr) return root;

        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);

        if(left != nullptr && right != nullptr) return root;
        else if(left != nullptr && right == nullptr) return left;
        else if(left == nullptr && right != nullptr) return right;
        else{
            return nullptr;
        }*/

        if(root->val > p->val && root->val > q->val) return lowestCommonAncestor(root->left, p, q);
        else if(root->val < p->val && root->val < q->val) return lowestCommonAncestor(root->right, p, q);
        else return root;
        
    }
};

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

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

/**
 * 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 {
/**
递归:
使用left和right分别“接住”左边和右边递归的返回值,之后进行判断。
**/
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == p || root == q || root == nullptr) return root;

        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);

        if(left != nullptr && right != nullptr) return root;
        else if(left != nullptr && right == nullptr) return left;
        else if(left == nullptr && right != nullptr) return right;
        else{
            return nullptr;
        }
    }
};

Day20 分治算法(中等)

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

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

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
/**
根据规律进行切割和递归
**/
public:
    /*TreeNode* traversal(vector<int>& preorder, vector<int>& inorder){
        if(preorder.size() == 0) return nullptr;

        //前序遍历的第一个值是根节点
        int rootVal = preorder[0];
        TreeNode* root = new TreeNode(rootVal);
        if(preorder.size() == 1) return root;

        //在中序遍历中寻找根节点的值 以 进行切割
        int delimiterIndex;
        for(delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++){
            if(inorder[delimiterIndex] == rootVal) break;
        }
        vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
        vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
      
        //删除前序遍历的第一个值
        preorder.erase(preorder.begin());
     
        //按照中序遍历的切割长度 切割前序遍历的数组
        vector<int> leftPreorder(preorder.begin(), preorder.begin() + leftInorder.size());
        vector<int> rightPreorder(preorder.begin() + leftInorder.size(), preorder.end());
       
        //对左边和右边进行递归
        root->left = traversal(leftPreorder, leftInorder);
        root->right = traversal(rightPreorder, rightInorder);

        return root;
    }*/

    TreeNode* traversal(vector<int>& preorder, int preorderBegin, int preorderEnd, vector<int>& inorder, int inorderBegin, int inorderEnd){
        if(preorderBegin == preorderEnd) return nullptr;

        int rootVal = preorder[preorderBegin]; //
        TreeNode* root = new TreeNode(rootVal);
        if(preorderEnd - preorderBegin == 1) return root;

        int delimiterIndex;
        for(delimiterIndex = 0; delimiterIndex < inorderEnd; delimiterIndex++){
            if(inorder[delimiterIndex] == rootVal) break;
        }
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = delimiterIndex;
        int rightInorderBegin = delimiterIndex + 1;
        int rightInorderEnd = inorderEnd;

        int leftPreorderBegin = preorderBegin + 1;
        int leftPreorderEnd = preorderBegin + 1 + (leftInorderEnd - leftInorderBegin);// 终止位置是起始位置加上中序左区间的大小size
        int rightPreorderBegin = leftPreorderEnd; //preorderBegin + 1 + (leftInorderEnd - leftInorderBegin); //
        int rightPreorderEnd = preorderEnd;

        root->left = traversal(preorder, leftPreorderBegin, leftPreorderEnd, inorder, leftInorderBegin, leftInorderEnd);
        root->right = traversal(preorder, rightPreorderBegin, rightPreorderEnd, inorder, rightInorderBegin, rightInorderEnd);

        return root;
    }

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size() == 0 || inorder.size() == 0) return nullptr;

        /*return traversal(preorder, inorder);*/


        //相同思路 进行优化
        return traversal(preorder, 0, preorder.size(), inorder, 0, inorder.size()); //
    }
};

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

class Solution {
/**
先进行特殊条件的判断输出(0,1,-1),之后开始递归,每次让n减少一半,res *= res,
最后要对n进行奇偶判断,若为奇数,要多乘一次x。
**/
public:
//用右移运算代替/2、用位与运算代表求余运算来判断一个数是奇数还是偶数 n&1 == 1 ==> n为奇数
    double myPow(double x, int n) {
        if(n == 0) return 1;
        if(n == 1) return x;
        if(n == -1) return 1 / x;

        double res = myPow(x, n >> 1); //右移是向下取整 ; /2是向零取整
        res *= res;

        if(n & 1 == 1) res *= x; //n为奇数 再乘一次x

        return res;

    }
};

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

class Solution {
/**
倒着遍历,先判断再存,具体见代码注释
**/
public:
//左右中
//栈        
//反向遍历 挨着的两个数如果arr[i]<arr[i+1],那么arr[i+1]一定是arr[i]的右子节点
//如果arr[i]>arr[i+1],那么arr[i+1]一定是arr[0]……arr[i]中某个节点的左子节点,并且这个节点值是大于arr[i+1]中最小的
    bool verifyPostorder(vector<int>& postorder) {
        stack<int> st;
        int root = INT_MAX;

        for(int i = postorder.size() - 1; i >= 0; i--){
            if(postorder[i] > root) return false; //如果元素(左节点)还比根节点大了  那肯定不是正确的顺序
     
            //当前元素小于栈顶元素,说明是倒序的,
            //说明当前元素是某个节点的左子节点,
            //要找到这个左子节点的父节点,就让栈顶元素出栈,直到栈为空或者栈顶元素小于当前值为止,其中最后一个出栈的就是当前元素的父节点
            while(!st.empty() && postorder[i] < st.top()){ //
                root = st.top();
                st.pop();
            }
            //如果栈不为空,并且当前元素大于栈顶元素,说明是升序的,
            //那么就说明当前元素是栈顶元素的右子节点,就把当前元素压栈,如果一直升序,就一直压栈
            st.push(postorder[i]);
        }

        return true;

    }
};

Day21 位运算(简单)

1、二进制中1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。

class Solution {
/**
n & (n - 1)即可得1的个数。
**/
public:
    int hammingWeight(uint32_t n) {
        int res = 0;

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

        return res;
        
    }
};

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

class Solution {
/**
用异或拿到非进位和,相与左移一位拿到进位,循环异或即可。
**/
public:
//n=a⊕b  非进位和:异或运算
//c=a&b<<1  进位:与运算+左移一位
//s=a+b⇒s=n+c
    int add(int a, int b) {

        while(b){ //直到进位为0
            int tmp = a ^ b; //非进位和
            b = (unsigned int)(a & b) << 1; //进位 //unsigned int
            a = tmp;
        }

        return a;

    }
};

Day22 位运算(中等)

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

class Solution {
/**
//先对所有数字进行一次异或,得到两个出现一次的数字的异或值。
//在异或结果中找到任意为 1 的位。
//根据这一位对所有的数字进行分组。
//在每个组内进行异或操作,得到两个数字
**/
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int tmp = 0;
        for(int num : nums){
            tmp ^= num;
        }

        int dev = 1;
        while((dev & tmp) == 0){ //找异或结果中1的位(该位是 最终结果中 两个数的不同位置)
            dev <<= 1;
        }

        int a = 0; int b = 0;
        for(int num : nums){
            //分组 进行异或
            if(dev & num){ //
                a ^= num;
            }
            else{
                b ^= num;
            }
        }
        
        return {a, b};
    }
};

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

class Solution {
/**
//对于出现三次的数 各个二进制位出现的次数都是三的倍数
//把数组中所有数的二进制的每一位相加,与 3 取模,要么等于0 要么等于1 得到的结果就是那个只出现一次的数。
//模3后只会剩下只出现一次的数字二进制的情况
**/
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;

        for(int i = 0; i < 32; i++){
            int count = 0; //最后一位是1的个数
            for(auto num : nums){
                if(num >> i & 1) count++; //如果该数的最后一位是1,count+1    //相当于求每一个num的二进制中每一位1的个数           
            }

            if(count % 3 != 0){ //count对3取模若不为0,说明该位并没有出现三次 即为最终结果中可用的二进制位 即找到了某一位取余为 1 的数
                res = res | 1 << i;//1 << i即是将改变后的二进制数还原   相或即可得最终结果
            }
        }
        
        return res;
    }
};

Day23 数学(简单)

1、数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

class Solution {
/**
法一:哈希表
法二:“投票”,votes为0时刷新x,如果num==x,votes加一,否则减一,
遍历结束后,votes不为0(出现次数超过一半)的那个x就是结果,最后返回x即可。
**/
public:
    int majorityElement(vector<int>& nums) {
        /*unordered_map<int, int> m;

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

        for(int i = 0; i < nums.size(); i++){
            if(m[nums[i]] > nums.size() / 2) return nums[i];
        }

        return -1;*/

        int x = 0, votes = 0;
        for(int num : nums){
            if(votes == 0) x = num;
            votes += num == x ? 1 : -1;
        }

        return x;
    }
};

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

class Solution {
/**
//结果集中任何一个元素 = 其左边所有元素的乘积 * 其右边所有元素的乘积
//左右两个数组存放左边和右边所有元素的乘积
// left[i]表示左边前 i 位的乘积   right[i]表示右边后 a.size() - i 位的乘积
**/
public:
    vector<int> constructArr(vector<int>& a) {
        /*会超时
        vector<int> res;
        int tmp = 1;

        for(int j = 0; j < a.size(); j++){
            for(int i = 0; i < a.size(); i++){
                if(i == j) continue;              
                tmp *= a[i];               
            }
            res.push_back(tmp);
            tmp = 1;
        }

        return res;*/


        if(a.size() == 0) return a;

        vector<int> left(a.size(), 1);
        for(int i = 1; i < a.size(); i++){
            left[i] = left[i - 1] * a[i - 1]; 
        }

        vector<int> right(a.size(), 1);
        for(int i = a.size() - 2; i >= 0; i--){
            right[i] = right[i + 1] * a[i + 1]; 
        }

        vector<int> res(a.size(), 1);
        for(int i = 0; i < a.size(); i++){
            res[i] = left[i] * right[i];
        }

        return res;
    }
};

Day24 数学(中等)

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

class Solution {
//动态规划
//dp[i]表示长度为i的绳子剪出来的最大乘积
//j表示剪第一段出来的长度是j(j>=2 && j<n),剩下i - j 可以剪:j*dp[i-j],可以不剪:j*(i-j)
//dp[i] = max(dp[i], max(j*dp[i-j], j*(i-j)))
public:
    int cuttingRope(int n) {
        vector<int> dp(n + 1, 0);
        dp[2] = 1;
        for(int i = 3; i <= n; i++){ //绳子长度 //每一次的绳子长度都经过从 剪2到剪i-1的过程 以计算最大值
            for(int j = 2; j < i; j++){ //第一次剪出来的长度
                dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]));
            }
        }

        return dp[n];
    }
};

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

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

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        /*//从1开始枚举计算
        vector<vector<int>> res;
        vector<int> tmp;
        int sum = 0; int limit = target / 2;
        for(int i = 1; i <= limit; i++){ //第一位从1开始枚举
            for(int j = i; ; j++){
                sum += j;
                if(sum > target){ //如果大于 则清空sum,跳出此次枚举 枚举下一次(首位加1)
                    sum = 0;
                    break;
                }
                else if(sum == target){
                    sum = 0;
                    for(int k = i; k <= j; k++){ //i是首位,j是末位
                        tmp.push_back(k);
                    }
                    res.push_back(tmp);
                    tmp.clear();
                    break;
                }
            }

        }
        return res;*/


        //滑动窗口
        vector<vector<int>> res;
        vector<int> tmp;
        int left = 1; int right = 1;//滑动窗口的左右边界
        int sum = 0;//滑动窗口内 两个边界移动前 的和

        while(left <= target / 2){
            if(sum < target){
                sum += right;
                right++; //右边界右移
            }
            else if(sum > target){
                sum -= left;
                left++; //左边界右移
            }
            else{
                for(int k = left; k < right; k++){
                    tmp.push_back(k);                  
                }
                res.push_back(tmp);
                tmp.clear();
                
                sum -= left;
                left++; //左边界继续右移 寻找下一种可能
            }
        }

        return res;
    }
};

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

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

class Solution {
public:
//递归
    int f(int n, int m){
        //终止条件
        if(n == 1) return 0; //只有一个人 返回的下标必是0  
        //递归
        return (f(n - 1, m) + m) % n; //f(n - 1, m)表示长度为n-1时返回的结果,每删除一次,相当于将数组前移了m位,%n因为当+ m后超过当前的总人数n时,需要回到队伍头计数
    }

    int lastRemaining(int n, int m) {
        return f(n, m);

        /*//优化
        int x = 0;//最后剩下一个人的情况时胜利者的下标是0
        for(int i = 2; i <= n; i++){
            x = (x + m) % i;
        }

        return x;*/

    }
};

Day25 模拟(中等)

1、顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

class Solution {
//按层模拟
//可以将矩阵看成若干层,首先打印最外层的元素,其次打印次外层的元素,直到打印最内层的元素
//定义四个边界
//根据边界打印 : 从左往右 从上往下 从右往左 从下往上
//边界向内收缩
//比较上下边界和左右边界大小 判断打印是否完毕
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        //if(matrix.size() == 0 || matrix[0].size()) return {};vector<vector<bool>> used(rows, vector<bool>(rows, false));
        if(matrix.empty()) return {};
        int rows = matrix.size(); int cols =matrix[0].size();
        vector<int> res;
        
        //定义四个边界
        int l = 0; //左边界
        int r = cols - 1; //右边界
        int t = 0; //上边界
        int b = rows - 1; //下边界
     
        while(true){
            //从左往右
            for(int i = l; i <= r; i++){
                res.push_back(matrix[t][i]);//根据边界打印
            }
            if(++t > b) break;//++t先执行加1再进行判断   边界向内收缩 比较边界大小 判断打印是否完毕

            //从上往下
            for(int i = t; i <= b; i++){
                res.push_back(matrix[i][r]);
            }
            if(--r < l) break;

            //从右往左
            for(int i = r; i >= l; i--){
                res.push_back(matrix[b][i]);
            }
            if(--b < t) break;

            //从下往上
            for(int i = b; i >= t; i--){
                res.push_back(matrix[i][l]);
            }
            if(++l > r) break;
        }

        return res;      
    }
};

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

class Solution {
//模拟
//栈 先进后出
//初始化:辅助栈 stack  ;
//遍历压栈序列:各元素记为 num ;
//元素 num 入栈,每入栈一次便进行判断;
//循环出栈:若 stack 的栈顶元素 == 弹出序列的元素 popped[i] ,则执行出栈与 i++ ;
//返回值:若 stack 为空,则此弹出序列合法。
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> st;
        int i = 0;

        for(int num : pushed){
            st.push(num);//一直入栈

            while(!st.empty() && st.top() == popped[i]){
                st.pop(); //出栈
                i++;
            }
        }

        return st.empty();
    }
};

Day26 字符串(中等)

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

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

若干空格
一个 小数 或者 整数
(可选)一个 ‘e’ 或 ‘E’ ,后面跟着一个 整数
若干空格
小数(按顺序)可以分成以下几个部分:

(可选)一个符号字符(‘+’ 或 ‘-’)
下述格式之一:
至少一位数字,后面跟着一个点 ‘.’
至少一位数字,后面跟着一个点 ‘.’ ,后面再跟着至少一位数字
一个点 ‘.’ ,后面跟着至少一位数字
整数(按顺序)可以分成以下几个部分:

(可选)一个符号字符(‘+’ 或 ‘-’)
至少一位数字
部分数值列举如下:
[“+100”, “5e2”, “-123”, “3.1416”, “-1E-16”, “0123”]
部分非数值列举如下:
[“12e”, “1a3.14”, “1.2.3”, “±5”, “12e+5.4”]

class Solution {
/**
使用变量idx表示当前遍历到的索引位置,
先去除前面的空格,
设bool变量isNum,判断是否是正负号,返回 判断是否是数字,
之后判断小数点,如果是 更新isNum,使用判断是否是数字和之前的isNum相或;
之后判断e和E,如果是 更新isNum,使用判断是否是正负号和之前的isNum相与;
最后返回isNum&&idx == s.size()
**/
public:
//A.BeC
    //判断0~9
    bool isUnsignedInterNum(string s, int& idx){
        int before = idx; //
        while(idx != s.size() && s[idx] >= '0' && s[idx] <= '9'){
            ++idx;
        }
        return idx > before; //
    }

    //判断A和C(有正负号)
    bool isInterNum(string s, int& idx){
        if(s[idx] == '+' || s[idx] == '-'){
            ++idx;
        }
        return isUnsignedInterNum(s, idx);
    }

    bool isNumber(string s) {
        if(s.empty()) return false;
        int idx = 0;

        //字符串开始有空格,可以返回true
        while(s[idx] == ' '){
            ++idx;
        }
        
        bool isNum = isInterNum(s, idx);

        // 如果出现'.',接下来是数字的小数部分
        if(s[idx] == '.'){
            ++idx;

            // 下面一行代码用||的原因:
            // 1. 小数可以没有整数部分,例如.123等于0.123;
            // 2. 小数点后面可以没有数字,例如233.等于233.0;
            // 3. 当然小数点前面和后面可以有数字,例如233.666
            isNum = isUnsignedInterNum(s, idx) || isNum; //没有正负号
        }

        // 如果出现'e'或者'E',接下来跟着的是数字的指数部分
        if(s[idx] == 'e' || s[idx] == 'E'){
            ++idx;

            // 下面一行代码用&&的原因:
            // 1. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
            // 2. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4
            isNum = isInterNum(s, idx) && isNum; //有正负号
        }

        //字符串结尾有空格,可以返回true
        while(s[idx] == ' '){
            ++idx;
        }

        return isNum && idx == s.size(); 
    }
};

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

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

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

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

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

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

说明:

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

class Solution {
/**
去除前面的空格并截取为新的字符串,
设置返回结果,开始处理正号标志,开始处理负号标志,开始处理数字标志,
开始遍历:
遇到正号,更新处理正号标志,continue,否则break;
遇到负号,更新处理负号标志,continue,否则break;
遇到数字,更新处理数字标志,计算res,否则break;
最后使用负号标志决定res的正负;
再用res和INT的最大值和最小值进行比较,返回res即可。
**/
public:
    int strToInt(string str) {
        if(str.size() == 0) return 0;

        //去除前面的空格
        int l = 0;
        while(str[l] == ' '){
            l++;
        }
        string s = str.substr(l);
        //cout<<s<<endl;

        long long res = 0; //避免溢出
        int posFlag = 0; //正号标志
        int negFlag = 0; //负号标志
        int numFlag = 0; //开始处理数字的标志

        for(int i = 0; i < s.size(); i++){
            if(s[i] == '+'){
                if(numFlag == 0 && posFlag == 0 && negFlag == 0) {
                    posFlag = 1;
                    continue; //
                }
                else break; //
            }
            if(s[i] == '-'){
                if(numFlag == 0 && posFlag == 0 && negFlag == 0) {
                    negFlag = 1;
                    continue; //
                }
                else break; //
            }

            if(isdigit(s[i])){ //如果是数字
                if(res > INT_MAX) break; //
                res = res * 10 + (s[i] - '0');
                numFlag = 1; //开始处理数字
            }
            else break; //
        }

        //判断是否有正负号决定输出
        if(negFlag == 1) res = -res;
        //判断是否超出范围
        res = min(res, (long long)INT_MAX); //
        res = max(res, (long long)INT_MIN); //

        return res;
    }
};

Day27 栈与队列(困难)

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

class Solution {
/**
滑动窗口
整体思路是先选出窗口,在对窗口内的数字进行大小的比较
**/
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        /*if(nums.size() < k || k <= 0) return {};
        int left = 0; int right = left + k - 1;
        vector<int> res;

        while(right < nums.size()){
            stack<int> st;
            int start = left, end = right; //滑动窗口中的双指针

            st.push(nums[start]);
            while(start <= end){              
                if(!st.empty() && nums[start] > st.top()){
                    st.push(nums[start]);
                }
                start++;
            }
            res.push_back(st.top()); //st的栈顶为滑动窗口中的最大值

            while(!st.empty()){
                st.pop(); //清空栈
            }
            left++;
            right++;
            
			/*
			int head = l, tail = r;
			int tmp = nums[head];
			
			while(head <= tail){
			    tmp = max(tmp, nums[head++]);
			}
			res.push_back(tmp);
			
			l++;
			r++;
			*/
        }

        //return res;*/

        if(nums.size() < k || k <= 0) return {};
        priority_queue<pair<int, int>> prique;//默认大顶堆 一个存值,一个存索引
        vector<int> res;

        for(int i = 0; i < k; i++){
            prique.emplace(nums[i], i);
        }
        res.push_back(prique.top().first);
        
        for(int i = k; i < nums.size(); i++){
            prique.emplace(nums[i], i);
            while(!prique.empty() && prique.top().second <= i - k){//队列中的最大值不能在滑动窗口内部
                prique.pop();
            }
            res.push_back(prique.top().first);
        }

        return res;
    }
};

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

若队列为空,pop_front 和 max_value 需要返回 -1

class MaxQueue {
/**
两个队列,普通队列存放每一个推进来的值,双端队列维护最大值
**/
public:
    queue<int> q;//存放每一个推进来的值
    deque<int> d;//双端队列 队首维护最大值
    MaxQueue() {

    }
    
    int max_value() {
        if(d.empty()){
            return -1;
        }
        return d.front();
    }
    
    void push_back(int value) {
        while(!d.empty() && d.back() <= value){ //将后面的 比此次推进来的值 小的全推出去
            d.pop_back();
        }
        d.push_back(value);
        q.push(value);
    }
    
    int pop_front() {
        if(q.empty()){ //
            return -1;
        }
        int res = q.front();
        if(res == d.front()){ //如果此次要推出去的值就是最大值,则d也要推出去
            d.pop_front(); //
        }
        q.pop();

        return res;
    }
};

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

Day28 搜索与回溯算法(困难)

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

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

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

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
/**
使用队列进行层序遍历。(注意判断"null")
**/
public:
//层序遍历
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root == nullptr) return "[]";

        string res = "[";
        queue<TreeNode*> que;
        que.push(root);

        while(!que.empty()){
            TreeNode* cur = que.front();
            que.pop();

            if(cur == nullptr){
                res += "null";
            }
            else{
                res += to_string(cur->val);
                que.push(cur->left);
                que.push(cur->right);
            }

            if(!que.empty()){
                res += ",";
            }
           
        }

        res += "]";//cout<<res<<endl;
        return res;        
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data == "[]") return nullptr;

        queue<TreeNode*> que;
        stringstream iss(data.substr(1, data.length() - 2)); //去除首尾的 []
        string str = "";
        
        getline(iss, str, ','); //获取iss以','截止 存入到str里
        TreeNode* root = new TreeNode(atoi(str.c_str()));
        que.push(root);

        while(!que.empty()){
            TreeNode* cur = que.front();
            que.pop();

            getline(iss, str, ',');
            if(str != "null"){
                TreeNode* left = new TreeNode(atoi(str.c_str()));
                cur->left = left;
                que.push(left);
            }

            getline(iss, str, ',');
            if(str != "null"){
                TreeNode* right = new TreeNode(atoi(str.c_str()));
                cur->right = right;
                que.push(right);
            }
            
        }

        return root;
    }
};

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

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

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

class Solution {
/**
//回溯
//里面不能有重复元素==>使用过的字母加标记flag  //不止一个字母 使用vector<bool> used
**/
public:
    vector<string> res;
    string path;

    void backtracking(string& s, vector<bool>& used){
        //终止条件
        if(path.size() == s.size()){
            res.push_back(path);
            return;
        }

        //单层逻辑
        for(int i = 0; i < s.size(); i++){
            //先判断该字母是否已使用过 //该字母和前一个字母相等 且 前一个字母已经使用过 继续
            if(used[i] == true || (i > 0 && s[i] == s[i-1] && used[i - 1] == true)) continue;

            used[i] = true;//将该位的字母标识为true 即使用过
            path.push_back(s[i]);
            backtracking(s, used);
            path.pop_back(); //再推出去 //回溯
            used[i] = false; //改变标志位 为未使用
        }

    }

    vector<string> permutation(string s) {
        res.clear();
        path.clear();

        //首先对原字符串排序,保证相同的字符都相邻
        sort(s.begin(), s.end());

        vector<bool> used(s.size(), false);//标识位 数组 记录每一个字母的使用情况
        backtracking(s, used);

        return res;
    }
};

Day29 动态规划(困难)

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

class Solution {
public:
//dp[i][j] 代表字符串 s 的前 i 个字符和 p 的前 j 个字符能否匹配。
//dp[0][0] 代表的是空字符的状态, 因此 dp[i][j] 对应的添加字符是 s[i - 1] 和 p[j - 1] 
    bool isMatch(string s, string p) {
        int m = s.size() + 1; int n = p.size() + 1;
        vector<vector<bool>> dp(m, vector<bool>(n, false));
        dp[0][0] = true;

        //初始化首行
        for(int j = 2; j < n; j += 2){
            dp[0][j] = dp[0][j - 2] && p[j - 1] == '*'; //即空字符串和空字符串匹配
        }

        for(int i = 1; i < m; i++){
            for(int j = 1; j < n; j++){
                if(p[j - 1] == '*'){
                    if(dp[i][j - 2]){//将字符组合p[j-2]* 看作出现0次时,能否匹配;
                        dp[i][j] = true;
                    }
                    if(dp[i - 1][j] && s[i - 1] == p[j - 2]){//让字符p[j-2]多出现1次时,能否匹配;
                        dp[i][j] = true;
                    }
                    if(dp[i - 1][j] && p[j - 2] == '.'){//让字符 '.' 多出现 1 次时,能否匹配;
                        dp[i][j] = true;
                    }
                }
                else{
                    if(dp[i - 1][j - 1] && s[i - 1] == p[j - 1]){
                        dp[i][j] = true;  
                    } 
                    if(dp[i - 1][j - 1] && p[j - 1] == '.'){
                        dp[i][j] = true;
                    }                
                }
            }
        }

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

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

class Solution {
/**
dp[i - 1]表示第i个丑数
**/
public:
//丑数只包含因子 2,3,5 ,因此有 “丑数 = 某较小丑数 × 某因子” (例如:10=5×2)。
    int nthUglyNumber(int n) {
        int a = 0, b = 0, c = 0;
        vector<int> dp(n, 0);

        dp[0] = 1;
        for(int i = 1; i < n; i++){
            int num2 = dp[a] * 2, num3 = dp[b] * 3, num5 = dp[c] * 5;

            dp[i] = min(min(num2, num3), num5);

            if(dp[i] == num2) a++;
            if(dp[i] == num3) b++;
            if(dp[i] == num5) c++;
        }

        return dp[n - 1];
    }
};

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

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

class Solution {
public:
    vector<double> dicesProbability(int n) {
        vector<double> dp(6, 1.0 / 6.0);

        for(int i = 2; i <= n; i++){
            vector<double> tmp(5 * i + 1, 0.0);//两个骰子便有11中可能性 n个骰子有5*n+1种可能性
            for(int j = 0; j < dp.size(); j++){
                for(int k = 0; k < 6; k++){ //新增加的骰子的点数可能性
                    tmp[j + k] += dp[j] / 6.0; //
                }
            }
            dp = tmp;
        }

        return dp;
    }
};

Day30 分治算法(困难)

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

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

        return res;

    }
};

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

class Solution {
public:
//归并排序(合并两个有序数组)
    int mergeSort(vector<int>& nums, int l, int r){
        if(l >= r) return 0; //

        int mid = l + ((r - l) >> 1); //注意括号
        // 递归 mergeSort 左右
        int left = mergeSort(nums, l, mid);
        int right = mergeSort(nums,mid + 1, r);

        // 计算横跨 left、right 的逆序对
        vector<int> help;
        int count = 0;
        int i = l, j = mid + 1, tmp = 0;
        while(i <= mid || j <= r){
            if(i == mid + 1){
                tmp = nums[j++];
            }
            else if(j == r + 1){
                tmp = nums[i++];
            }
            else if(nums[i] <= nums[j]){ //相等时要压左边
                tmp = nums[i++];
            }
            else{
                tmp = nums[j++];
                count += mid -i + 1;//左子数组当前元素 至 末尾元素 与 右子数组当前元素 构成了若干 「逆序对」
            }

            help.push_back(tmp);
        }

        // 复制回原数组
        for(int i = 0; i < help.size(); i++){
            nums[l + i] = help[i]; //
        }

        return left + count + right;
    }

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

Day31 数学(困难)

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

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

class Solution {
public:
//尽可能将绳子以长度 3 等分为多段时,乘积最大
//贪心
    int cuttingRope(int n) {
        int num = (int)1e9+7;
        if(n <= 3) return n - 1;

        long res = 1;//
        while(n > 4){
            res = (res * 3) % num; //循环取余 /取余避免溢出
            n -= 3;
        }

        //到这里的n只能为2 3 4
        return (int)(res * n % num);
    }
};

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

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

class Solution {
//见https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/mian-shi-ti-43-1n-zheng-shu-zhong-1-chu-xian-de-2/
/**
求出每一位上1的个数,根据当前位的值的大小求出该位上1的个数
**/
public:
    int countDigitOne(int n) {
        /*超时
        string str = "";
        for(int i = 1; i <= n; i++){
            str += to_string(i);
        }

        int res = 0;
        for(char c : str){
            if(c == '1'){
                res++;
            }
        }

        return res;*/

        //分别计算每一位上的1的个数
        int res = 0;
        long digit = 1; //个位
        int high = n / 10, cur = n % 10, low = 0;

        while(high != 0 || cur != 0){ //当 high 和 cur 同时为 0 时,说明已经越过最高位,因此跳出
            if(cur == 0) res += high * digit; //
            else if(cur == 1) res += high * digit + low + 1; //
            else res += (high + 1) * digit; // cur=2,3,⋯,9 

            low += cur * digit; //将 cur 加入 low ,组成下轮 low
            cur = high % 10; //下轮 cur 是本轮 high 的最低位
            high = high / 10; //将本轮 high 最低位删除,得到下轮 high
            digit *= 10; //位因子每轮 × 10
        }

        return res;
    }
};

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

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

class Solution {
// 1. 确定n所在那一段的数字的位数
// 2. 确定n所在的数字
// 3. 确定n所在数字中 要求的n 是第几位 
public:
    int findNthDigit(int n) {
        int digit = 1; //位数
        long long start = 1; //位数的起始数字
        long long cnt = 9; //位数为digit 那一段总的数位

        //比如n = 11时,cnt = 9,经过while循环后 n=2,start=10,digit=2,cnt=180;
        //说明 n所在的那一段的位数为digit = 2
        while(n > cnt){
            n -= cnt; //从n中将前面的总的数位减去
            start *= 10; //每减一次 起始位的数字增加十倍(1 -- 10 -- 100 -- ...)
            ++digit; //位数加一   // 1. 确定n所在那一段的位数
            cnt = 9 * start * digit; //总的数位的变化 
        }

        int num = start + (n - 1) / digit;   // 2. 确定n所在的数字 //num = 10

        int numDigit = (n - 1) % digit;    // 3. 确定n所在数字中 要求的n 是第几位  (左边首位是第0位)  //numDigit = 1

        int res = to_string(num)[numDigit] - '0'; //字符串num的第numDigit个字符  再转换为int型

        return res;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wrdoct

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值