【题解】剑指offer

1. #include<bits/stdc++.h> //万能头文件

文章目录

剑指offer题解

1 二维数组中的查找

描述

在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
给定 target = 7,返回 true。
给定 target = 3,返回 false。
0 <= array.length <= 500
0 <= array[0].length <= 500

//暴力解法,遍历每一个元素
class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        if(array.size()==0 || array[0].size()==0) return false ;
        for(const auto& vec : array) {
            for(const int val : vec) {
                if(target == val)
                return true;
            }
        }
        return false;
    }
};
//二分查找
class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        //判断数组是否为空
        int m = array.size();
        if(m == 0) return false;
        int n = array[0].size();
        if(n == 0) return false;
        
        int row = 0, col = n-1; //二分值定在右上角(也可以是左下角)
        while(row < m && col >= 0){
            if(target == array[row][col]) {
                return true;
            }
            else if(target  < array[row][col]) { // 左走
                --col;
            }
            else if(target  < array[row][col]) { // 下走
                ++row;
            } 
        }
        return false;
 
};

2 替换空格

描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

class Solution {
public:
    string replaceSpace(string s) {
        string res;
        for(int i=0; i<s.length(); ++i){
            if(s[i] != ' ')
                res+=s[i];
            else
                res+="%20";
        }
        return res;
    }    
};

3 从尾到头打印链表

描述
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
如输入{1,2,3}的链表如下图:
在这里插入图片描述

返回一个数组为[3,2,1]
0 <= 链表长度 <= 10000

class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        if(head == nullptr) return vector<int>(); //临时对象
        vector<int> vec;
        while(head != nullptr){
            vec.push_back(head->val);
            head=head->next;
        }
        //reverse(vec.begin(), vec.end());//反转算法
        //反向迭代器创建临时对象
        return vector<int> (vec.rbegin(), vec.rend());
    }  
};

4 重建二叉树

描述
给定某二叉树的前序遍历和中序遍历,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
在这里插入图片描述

提示:
1.0 <= pre.length <= 2000
2.vin.length == pre.length
3.-10000 <= pre[i], vin[i] <= 10000
4.pre 和 vin 均无重复元素
5.vin出现的元素均出现在 pre里
6.只需要返回根结点,系统会自动输出整颗树做答案对比

class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if(pre.size() == 0 || vin.size() == 0) return nullptr;
         
        //首先在中序找到根节点的位置,返回迭代器
        auto rootNode_loc = find(vin.begin(), vin.end(), pre[0]);
        //求出根节点到起始的距离
        int mid = distance(vin.begin(), rootNode_loc);
          
        //把中序和前序分为左右两组,调用拷贝构造函数
        vector<int> vin_left (vin.begin(), rootNode_loc);
        vector<int> vin_right (rootNode_loc+1, vin.end());
        vector<int> pre_left (pre.begin()+1, pre.begin()+mid+1);
        vector<int> pre_right (pre.begin()+mid+1, pre.end());
         
        //得自己定义一颗树
        TreeNode* treenode = new TreeNode(pre[0]);//重建二叉树初始化为pre[0]
        treenode->left = reConstructBinaryTree(pre_left, vin_left);
        treenode->right = reConstructBinaryTree(pre_right, vin_right);
        return treenode;
    }
};

5 用两个栈实现队列

描述
用两个栈来实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

示例:
输入:
[“PSH1”,“PSH2”,“POP”,“POP”]
返回:
1,2
解析:
“PSH1”:代表将1插入队列尾部
“PSH2”:代表将2插入队列尾部
"POP“:代表删除一个元素,先进先出=>返回1
"POP“:代表删除一个元素,先进先出=>返回2

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }
    int pop() {
      //如果stack2为空,那么需要将stack1中的数据转移到stack2中,
      //然后在对stack2进行pop,如果stack2不为空,直接pop就ok。
        if(stack2.empty()){ //入口
            while(!stack1.empty()){ //结束条件
                stack2.push(stack1.top());
                stack1.pop();
            }
        }
        int ret = stack2.top();
        stack2.pop();
        return ret;
    }
 
private:
    stack<int> stack1;//保存元素
    stack<int> stack2;//辅助栈
};

6 旋转数组的最小数字

描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。

有一个长度为 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

// 利用STL算法
class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size() == 0) return -1;
        sort(rotateArray.begin(), rotateArray.end());//从小到大排序
        return rotateArray[0];
    }
};
class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
      
        if( rotateArray.size() == 0) return -1;
        if( rotateArray.size() == 1) return rotateArray[0];
        for(int i = 0; i < rotateArray.size()-1; ++i){
            if( rotateArray[i] > rotateArray[i + 1] ) return rotateArray[i+1]; 
        }
        return rotateArray[0];//走到这一步了,就说明整个数组都是递增或者都是非递减的
    }
};
// 改进二分查找(非有序)
class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        //二分查找,把右端点看为target
        if(rotateArray.size() == 0) return -1;
        int low = 0, high = rotateArray.size()-1;
        while(low < high) {
            if(rotateArray[low] < rotateArray[high]) {
                return rotateArray[low];
            }
            //找中间值
            int mid = low + (high - low)/2;
            if(rotateArray[mid] > rotateArray[high]) {
                low = mid+1;
            }
            else if(rotateArray[mid] < rotateArray[high]) {
                high = mid;
            }
            else if(rotateArray[mid] == rotateArray[high]) {
                --high;//慢慢缩小空间
            }
        }
        return rotateArray[low];
    }
};

7 斐波那契数列

描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
n≤39

// 暴力解法
class Solution {
public:
    int Fibonacci(int n) {
        if(n==0) return 0;
        if(n==1) return 1;
        return Fibonacci(n-1)+Fibonacci(n-2);
    }
};
// 自顶向下,备忘录法
class Solution {
public:
    int Fibonacci(int n) {
       if(n<=0) return 0;
       if(n==1) return 1;
        int FibOne = 0;
        int FibTwo = 1;
        int FibN;
        
        for(int i=2; i<=n; ++i) { //n要等于2
             
            FibN = FibOne + FibTwo;
            FibOne = FibTwo;
            FibTwo = FibN;
        }
        return FibN;
    }
};
// 动态规划
class Solution {
public:
	int Fibonacci(int n) {
		if (n < 0) return -1;
		vector<int> dp(n+1, 0);
		dp[0] = 0;
		dp[1] = 1;
		for (int i = 2; i <= n; ++i) {
			dp[i] = dp[i - 1] + dp[i - 2];
		}
		return dp[n];
	}
};

8 跳台阶(动态规划)

描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

// 自顶向下,备忘录法
class Solution {
public:
    int jumpFloor(int number) {
        //现实版的斐波拉写数列
        //递归解法
        //if(number == 1) return 1;
        //if(number == 2) return 2;
        //return jumpFloor(number-1) + jumpFloor(number-2);
         
        //迭代解法
        if(number == 1) return 1;
        if(number == 2) return 2;
         
        int first = 1;
        int second = 2;
        int third;
        for(int i=3; i<=number; ++i){
            third = first + second;
            first = second;
            second = third;
        }
        return third;
    }
};
// 动态规划
class Solution {
public:
    int jumpFloor(int number) {
        //第n阶台阶:可以从n-1阶跳一次得到,也可以从n-2阶跳两次得到
        vector<int> dp(number+1, 0);
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i <= number; ++i){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[number];
    }
};

9 跳台阶扩展问题(动态规划)

描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。

class Solution {
public:
    int jumpFloorII(int number) {
        //找规律,递推法
        if(number == 1) return 1;
        //return 2*jumpFloorII(number-1);//迭代
        return pow(2, number-1);//乘方计算
    }
};
// 自顶向下,备忘录法
class Solution {
public:
    int jumpFloorII(int number) {         
        //迭代法
        if(number <= 1) return 1;
        int first = 1, jumpN;   
        for(int i=2; i<=number; ++i){        
            jumpN = 2*first;
            first = jumpN;
        }
        return jumpN;
    }
};
//动态规划
class Solution {
public:
    int jumpFloorII(int number) {
        vector<int> dp(number+1, 0);
        dp[0] = 0;
        dp[1] = 1;
        for(int i = 2; i <= number; ++i){
            dp[i] = 2*dp[i-1];
        }
        return dp[number];
    }
};

10 矩形覆盖(动态规划)

描述
我们可以用2 * 1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2 * 1的小矩形无重叠地覆盖一个2 * n的大矩形,从同一个方向看总共有多少种不同的方法?
比如n=3时,2 * 3的矩形块有3种不同的覆盖方法(从同一个方向看):

在这里插入图片描述

class Solution {
public:
    int rectCover(int number) {
        //斐波那契数列
        //递归
        if(number < 1) return 0;
        if(number == 1) return 1;
        if(number == 2) return 2;       
        return rectCover(number-1) + rectCover(number-2);
    }
};
// 自顶向下,备忘录法
class Solution {
public:
    int rectCover(int number) {
        //斐波那契数列
        //递归
        //if(number < 1) return 0;
        //if(number == 1) return 1;
        //if(number == 2) return 2;
         
        //return rectCover(number-1) + rectCover(number-2);
         
        //迭代
        if(number < 1) return 0;
        if(number == 1) return 1;
        if(number == 2) return 2;
         
        int first = 2;
        int second = 1;
        int res;
        for(int i=3; i<=number; ++i){
            res = first + second;
            second = first;
            first = res;
        }
        return res;
    }
};
// 动态规划
class Solution {
public:
    int rectCover(int number) {
        vector<int> dp(number+1, 0);
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3; i <= number; ++i){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[number];
    }
};

11 二进制中1的个数

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

class Solution {
public:
     int  NumberOf1(int n) {
         return bitset<32> (n).count();
     }
};

分析一下代码: 这段小小的代码,很是巧妙。
如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

class Solution {
public:
     int  NumberOf1(int n) {
         int count = 0;
         while(n!=0){
              ++count;
             n=n&(n-1);   
         }
         return count;
     }
};

12 数值的整数次方

描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0。不得使用库函数,同时不需要考虑大数问题,也不用考虑小数点后面0的位数。

// 分类讨论
class Solution {
public:
    double Power(double base, int exponent) {
        double ans;//一定要是双精度,否则小数点后被清除
        if(base == 0 && exponent == 0) return 0;
        if(base == 0) return 0;
        if(exponent == 0) return 1;
        
        if(exponent > 0) {
            if(exponent == 1) return base;
            ans = base;
            for(int i=2; i<=exponent; ++i){
                ans = ans*base;
            }
        }
        if(exponent < 0) {
            if(exponent == -1) return 1/base;
            ans = 1/base;
            for(int i=-2; i>=exponent; --i){
                ans = ans*(1/base);
            }
        }
        return ans;
    }
};
// 递归解法
class Solution {
public:
    double powfun(double x, int e){
        if(e == 0) return 1.0;//递归的终止条件
        double res = powfun(x, e/2);
        if(e&1 == 1)   //e为奇数
            return res*res*x;
        else 
            return res*res;
    }
    double Power(double base, int exponent) {
        if(exponent < 0) {
            base = 1/base;
            exponent *=-1;
        }
        return powfun(base,exponent);
    }
};

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

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

class Solution {
public:
    vector<int> reOrderArray(vector<int>& array) {
        //另外开辟一个数组
        int len = array.size();
        int index = 0;
        vector<int> vec(len, 0);//虽然数组支持动态扩展,但自己设置可节约内存
        for(int i=0; i<len; ++i){ //奇数
            if((array[i]&1) == 1)  vec[index++] = array[i]; 
        }
        for(int i=0; i<len; ++i){ //偶数
            if((array[i]&1) == 0)  vec[index++] = array[i]; 
        }
        return vec;
    }
};

14 链表中倒数最后k个结点(快慢指针)

描述
输入一个链表,输出一个链表,该输出链表包含原链表中从倒数第k个结点至尾节点的全部节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。

//** * struct ListNode {
// *	int val;
// *	struct ListNode *next;
// *	ListNode(int x) : val(x), next(nullptr) {}
// * };
// */
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        if(pHead == nullptr || k <= 0) return nullptr;
        //int len = pHead.size();//只知道头指针,不能获取大小size()
        ListNode* slow = pHead, *fast = pHead;
        while(k--){
            if(fast != nullptr){
                fast = fast->next;
            }
            else return nullptr;//如果该链表长度小于k,请返回一个长度为 0 的链表
        }
        while(fast != nullptr){
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

15 反转链表

描述
输入一个链表,反转链表后,输出新链表的表头。

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        //三指针方法
        ListNode* cur = pHead;
        ListNode* pre = nullptr;
        while(cur != nullptr){
            ListNode* nex = cur->next;//需要保存cur后面的节点
            cur->next = pre; //把pre指向的值赋给他
            pre = cur;
            cur = nex;
        }
        return pre; //pre就是头指针
    }
};
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        ListNode* pre = nullptr, *cur = pHead, *nex = pHead->next;
        while(nex != nullptr){
            ListNode* temp = nex->next;
            cur->next = pre;
            nex->next = cur;
            pre = cur;
            cur = nex;
            nex = temp;
        }
        return cur;
    }
};

16 合并两个排序的链表

描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

// 迭代
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        //迭代
        if(pHead1 == nullptr) return pHead2;
        if(pHead2 == nullptr) return pHead1;
        
        ListNode* newHead = new ListNode(-1);//在堆区构造一个链表,且初始化为-1,头节点
        ListNode* cur = newHead;//用cur遍历,newHead记录头指针
        while(pHead1 != nullptr && pHead2 != nullptr) {
            if(pHead1->val < pHead2->val){
                cur->next = pHead1;
                pHead1 = pHead1->next;
            }
            else {
                cur->next = pHead2;
                pHead2 = pHead2->next;
            }
               cur = cur->next;
        }
        cur->next = (pHead1 ? pHead1 : pHead2);
        return newHead->next;
    }
};
//递归
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        //递归方法
        if(pHead1 == nullptr) return pHead2;
        if(pHead2 == nullptr) return pHead1;
         
        if(pHead1->val <= pHead2->val) {
            pHead1->next = Merge(pHead1->next, pHead2);
            return pHead1;
        }
        else
            pHead2->next = Merge(pHead1, pHead2->next);
            return pHead2;
    }     
};

17 树的子结构

描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
输入:
{8,8,#,9,#,2,#,5},{8,9,#,2}
返回值:
true

// 两次递归
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        // 递归解法
        if(pRoot1 == nullptr || pRoot2 == nullptr) return false;
        bool leftNode = HasSubtree(pRoot1->left, pRoot2); //判断A左节点是否为B的子结构
        bool rightNode = HasSubtree(pRoot1->right, pRoot2);//判断A右节点是否为B的子结构
        //判断当前节点是否为其子结构
        bool CurNode = CurHasSubtree(pRoot1, pRoot2);
        return leftNode || rightNode || CurNode; //左、右、当前有一个满足即可
    }
    //判断当前节点是否为其子结构
    bool CurHasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        //细节注意:先判断2为空,再判断1
        if(pRoot2 == nullptr) return true; //说明树B已匹配完成(越过叶子节点),因此返回true
        if(pRoot1 == nullptr) return false;//说明已经越过树 A叶子节点,即匹配失败,返回 false
        if(pRoot1->val != pRoot2->val) return false;//当节点A和B的值不同:说明匹配失败,返回false;
        bool leftMatch = CurHasSubtree(pRoot1->left, pRoot2->left);//左匹配
        bool rightMatch = CurHasSubtree(pRoot1->right, pRoot2->right);//右匹配
        return leftMatch && rightMatch;//左右匹配同时满足
    }
};

18 二叉树的镜像

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

比如:    源二叉树   
            8  
           /  \  
          6   10  
         / \  / \
        5  7 9 11  
        镜像二叉树  
            8  
           /  \  
          10   6  
         / \  / \  
        11 9 7  5
///**
// * struct TreeNode {
// *  int val;
// *  struct TreeNode *left;
// *  struct TreeNode *right;
//*  TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
//* };
//*/
class Solution {
public:
    TreeNode* Mirror(TreeNode* pRoot) {
        // 递归解法
        if(pRoot == nullptr) return nullptr;
        swap(pRoot->left, pRoot->right);
        pRoot->left = Mirror(pRoot->left);
        pRoot->right = Mirror(pRoot->right);
        return pRoot;
    }
};

19 顺时针打印矩阵

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

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> vec;
        if(matrix.empty()) return vec;
        int rl = 0, rh = matrix.size()-1; // 行
        int cl = 0, ch = matrix[0].size()-1; //列
        
        while(1){
            for(int i = cl; i <= ch; ++i){
                vec.push_back(matrix[rl][i]);
            }
            rl = rl +1;
            if(rl > rh) break;
            for(int i = rl; i <= rh; ++i){
                vec.push_back(matrix[i][ch]);
            }
            ch = ch -1;
            if(cl > ch) break;
            for(int i = ch; i >= cl; --i){
                vec.push_back(matrix[rh][i]);
            }
            rh = rh -1;
            if(rl > rh) break;
            for(int i = rh; i >= rl; --i){
                vec.push_back(matrix[i][cl]);
            }
            cl = cl + 1;
            if(cl > ch) break;
        }
        return vec;
    }
};

20 包含min函数的栈

描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,并且调用 min函数、push函数 及 pop函数 的时间复杂度都是 O(1)
push(value):将value压入栈中
pop():弹出栈顶元素
top():获取栈顶元素
min():获取栈中最小元素

class Solution {
public:
    stack<int> normal, assist; //正常栈,辅助栈
    // 插入删除操作看不到,正常与辅助栈同步插入
    void push(int value) {  
        if(normal.size() == 0 && assist.size() == 0){
            normal.push(value);
            assist.push(value);
        }
        else{
            normal.push(value);
            if(value <= assist.top()){
                assist.push(value);
            }
            else
                assist.push(assist.top());
        }
    }
    void pop() {
        normal.pop();
        assist.pop();
    }
    int top() {
        return normal.top();
    }
    int min() {
        return assist.top(); // 在插入阶段,就在辅助栈中进行了排序
    }
};

21 栈的压入、弹出序列

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

// 判断栈不为空很重要,防止j越过数组的边界,同时防止栈溢出(s.top()溢出)  


// 模拟栈的实现过程
class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.empty() || popV.empty() || pushV.size() != popV.size()) {
            return false;
        }
        // 不能共用一个i,指针速度不一样
        stack<int> s;
        int j = 0;
        for(int i = 0; i < pushV.size(); ++i){
                s.push(pushV[i]); //压入
            // 判断栈不为空很重要,防止j越过数组的边界,同时防止栈溢出(s.top()溢出)
            while(!s.empty() && s.top() == popV[j]) { // 判断栈顶与弹出序列是否相等
                s.pop();
                ++j; 
            }
        }
        if(s.empty()) // 最后判断压入弹出后栈是否为空
            return true;
        else 
            return false;
    }
};

22 从上往下打印二叉树(打印一行层序遍历)

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

//层序遍历解法,bfs
class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        //层序遍历,借助辅助队列
         vector<int> vec;
        if(root == nullptr) return vec; //边界条件
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()) {
            int len = q.size(); //队列的大小是动态变化的
            for(int i = 0; i < len; ++i){
                root = q.front();
                vec.push_back(root->val);
                q.pop(); //先删除再插入 
                if(root->left != nullptr) q.push(root->left);
                if(root->right != nullptr) q.push(root->right);
            }
        }
        return vec;
    }
};

23 二叉搜索树的后序遍历序列

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

输入:
[4,8,6,12,16,14,10]
复制
返回值:
true
//递归解法,数组划分
class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        // 递归解法
        if(sequence.empty()) return false; //空树不是二叉搜索树
        if(sequence.size() == 1) return true; 
        bool isBst = isBST(sequence, 0, sequence.size()-1);
        return isBst;
    }
    // 必须重新定义一个函数,涉及到对数组两端点的递归
    bool isBST(vector<int> &sequence, const int start, const int end) { //对本身进行检测,不是拷贝
        if(start >= end) return true; //数组只有一个元素时,是二叉搜索树
        int pivot ;//基准点
        //二叉搜索树,左右根,左子树全部小于根
        //右子树全部打大于根,找到第一个大于根的元素,那么在他之前都是左子树,之后都是右子树
        for (pivot = start; sequence[pivot] < sequence[end]; ++pivot); //只是为了找到分界点
        for(int i= pivot; i < end; ++i) { 
            if(sequence[i] <= sequence[end]) return false;//右子树必须全部大于根,否则就是假
        } //注意细节:是i不是pivot
        bool leftPivot = isBST(sequence, start, pivot-1);//判断当前节点的其右子树,基准值左侧是否BST,sequence就是要用引用的原因
        bool rightPivot = isBST(sequence, pivot, end-1); //判断当前节点的其右子树,基准值右侧是否BST,不含根节点
        return leftPivot && rightPivot;
    }
};

24 二叉树中和为某一值的路径

描述
输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

输入:
{10,5,12,4,7},22
复制
返回值:
[[10,5,7],[10,12]]

输入:
{10,5,12,4,7},15
复制
返回值:
[]
// 回溯法
class Solution {
public:
    vector<vector<int> > ans;
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        
        vector<int> path;
        int sum = 0;
        backtrack(root, expectNumber, path, sum);
        sort(ans.begin(), ans.end()); //按字典序进行排序
        return ans;
    }
    
    void backtrack(TreeNode* root, int& expectNumber, vector<int>& path, int& sum) {
        if(root == nullptr) {
            //if(sum != expectNumber) return;  // 剪枝与结束条件合在一起
            //ans.push_back(path); //完成一条路径后记录
            return;
        }
        
        // 剪枝 , 要先判断能不能剪
        path.push_back(root->val); //做选择
        sum = sum + root->val;
        if(root->left == nullptr && root->right == nullptr && sum == expectNumber) {
            ans.push_back(path);
        } 
        
        backtrack(root->left, expectNumber, path, sum);
        backtrack(root->right, expectNumber, path, sum);
        
        path.pop_back();  //撤销选择
        sum = sum - root->val;
    }
};
class Solution {
public:
    vector<vector<int>> ans;
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
        if(root == nullptr && expectNumber == 0) return ans;
        vector<int> path;
        backtrack(root, path, expectNumber);
        sort(ans.begin(), ans.end()); //按字典序进行排序
        return ans;
    }
    
    void backtrack(TreeNode* root, vector<int>& path, int sum){
        if(root == nullptr) {
            if(sum == 0) ans.push_back(path);
            return;
        }
        path.push_back(root->val);
        if(root->left == nullptr && root->right == nullptr){
            backtrack(root->left, path, sum - root->val);
        }
        else {
            backtrack(root->left, path, sum - root->val);
            backtrack(root->right, path, sum - root->val);
        }
        path.pop_back();
    }
};

25 复杂链表的复制

描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
在这里插入图片描述

///*
//struct RandomListNode {
//    int label;
//    struct RandomListNode *next, *random;
//    RandomListNode(int x) :
//            label(x), next(NULL), random(NULL) {
//    }
//};
//*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
        //对链表进行深拷贝,防止堆区的内存重复释放
        //哈希表的迭代
         if(pHead == nullptr) return nullptr;
        unordered_map<RandomListNode*, RandomListNode*> hash_map;
        for(RandomListNode* p = pHead; p != nullptr; p = p->next){
            hash_map[p] = new RandomListNode(p->label);
        }
        for(RandomListNode* p = pHead; p != nullptr; p = p->next){
            hash_map[p]->next = hash_map[p->next];
            hash_map[p]->random = hash_map[p->random];
        }
        return hash_map[pHead];
    }
};

26 二叉搜索树与双向链表

描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示
在这里插入图片描述

注意:
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
4.你不用输出或者处理,示例中输出里面的英文,比如"From left to right are:"这样的,程序会根据你的返回值自动打印输出

示例:  
输入: {10,6,14,4,8,12,16}  
输出:From left to right are:4,6,8,10,12,14,16;From right to left   are:16,14,12,10,8,6,4;  

解析:  
输入就是一棵二叉树,如上图,输出的时候会将这个双向链表从左到右输出,以及从右到左输出,确保答案的正确
// 中序遍历的思路
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if(pRootOfTree == nullptr) return nullptr;
        vector<TreeNode*> vec;
        stack<TreeNode*> s;
        //s.push(pRootOfTree);
        while(pRootOfTree != nullptr || !s.empty()){
            if(pRootOfTree != nullptr) {
                s.push(pRootOfTree);
                pRootOfTree = pRootOfTree->left;
            }
            else {
                pRootOfTree = s.top();
                vec.push_back(pRootOfTree);
                s.pop();
                pRootOfTree = pRootOfTree->right;
            }
        }
        for(int i = 0; i < vec.size()-1; ++i) { //i < vec.size()-1 防止数组越界
            vec[i]->right = vec[i+1];
            vec[i+1]->left = vec[i];
        }
        //vec[vec.size()-1]->right = vec[0];
        //vec[0]->left = vec[vec.size()-1];
        return vec[0];
    }
};

27 字符串的排列(回溯法)

描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

// 回溯算法
class Solution {
public:
    
    set<string> s; //插入重复元素,进行忽略处理,同时进行由小到大排序
    
    vector<string> Permutation(string str) {
        string path; //路径
        vector<bool> select(str.size(), false); //选择数组,用于判断字符串str中的元素是否被选择
        backtrack(str, path, select);
        vector<string> res(s.begin(), s.end()); //调用拷贝构造
        return res;
    }
    
    void backtrack(string& str, string& path, vector<bool>& select) { //记录全排列路径
        if(path.size() == str.size()) {  //终止条件,遍历完记录返回
            s.insert(path); //插入重复元素,进行忽略处理,同时进行由小到大排序
            return;
        }
        
        for(int i = 0; i < str.size(); ++i) {
            // 排除不合法的选择(剪枝),被选择除去
            if(select[i]) continue;
            // 递归之前做选择
            path.push_back(str[i]);
            select[i] = true;
            // 进入下一层决策树
            backtrack(str, path, select);  //自顶向下递归
            // 递归完成后撤销选择,回复到默认路径
            path.pop_back();
            select[i] = false;
        }
    }
};

28 数组中出现次数超过一半的数字(哈希)

描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
1<=数组长度<=50000,0<=数组元素<=10000

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty()) return 0;
        
        int len = numbers.size();
        unordered_map<int, int> hash_map;
        for(int i = 0; i < len; ++i){
            hash_map[numbers[i]]++; //对数组重复元素计数,放在value中。构建映射表
            //if(hash_map[numbers[i]] > len/2) return numbers[i]; //这里直接返回,节约时间
        }
        
        for(auto it = hash_map.begin(); it != hash_map.end(); it++){ //遍历哈希表
            if(it->second > len/2) return it->first;
        }
        return 0;
    }
};
class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty()) return 0;
        unordered_map<int, int> hash;
        for(const auto& i : numbers){
            hash[i]++;
            //if(hash[i] > numbers.size()/2) return i;
        }
        for(const auto& i : hash){
            if(i.second > numbers.size()/2) return i.first;
        }
        return 0;
    }
};

29 最小的K个数

描述
给定一个数组,找出其中最小的K个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
0 <= k <= input.length <= 10000
0 <= input[i] <= 10000

//常规方法,排序
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> vec;
        if(k > input.size()) return vec;
        sort(input.begin(), input.end());
        return vector<int>(input.begin(), input.begin() + k); //区间拷贝构造
    }
};
// 优先队列priority_queue
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> vec;
        if(k == 0 || k > input.size()) return vec;
        // 构建优先队列
        priority_queue<int, vector<int>, greater<int> > pq; //默认大顶堆,换成小顶堆
        for(int i = 0; i < input.size(); ++i) {
            pq.push(input[i]);
        }
        while(k--){
            vec.push_back(pq.top());
            pq.pop();
        }
        return vec;
    }
};

30 连续子数组的最大和

描述
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).
输入:
[1,-2,3,10,-4,7,2,-5]
返回值:
18

说明
输入的数组为{1,-2,3,10,—4,7,2,一5},和最大的子数组为{3,10,一4,7,2},因此输出为该子数组的和 18。

// 常规dp算法,类似于连续上升子序列
class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        if(array.size() == 0) return 0; // 表示没有元素
        
        //1.明确dp数组的含义是:以i结尾的连续子数组的最大和
        vector<int> dp(array.size(), 0);
        int maxNum = array[0];
        dp[0] = array[0]; //2. 初始化
        for(int i = 1; i < array.size(); ++i) {
            // 3. 状态转移方程
            dp[i] = max(dp[i-1] + array[i], array[i]); //i=1,防止数组下标越界
            maxNum = max(dp[i], maxNum);
        }
        return maxNum;
    }
};

31 整数中1出现的次数( 从1 到 n 中1出现的次数 )

描述
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数
例如,1~13中包含1的数字有1、10、11、12、13因此共出现6次

// 自己想的双层递归
class Solution {
public:
    int time = 0; //1出现的次数
    int NumberOf1Between1AndN_Solution(int n) {
        NextNum(n);
        return time;
    }
    void NextNum(int n){
        if(n == 1) time = time + 1;
        if(n <= 0) return; //递归结束条件
        if(n >= 10) RestNum(n);
        NextNum(n-1);
    }
    
    void RestNum(int n) {
        
        if(n == 1) time = time + 1; 
        if(n < 10) return;  //递归结束条件

        int temp = n; // 临时存放n
        int weight = 1; // 十进制权重
        //获取n的第一个数字,并判断是否为1
        while((temp/10) != 0) { // 必须是循环体
            temp = temp / 10;
            weight = weight * 10;
            if(temp == 1) time = time + 1; 
        }
        int restNum = n - temp * weight; //获取剩下的数字
        RestNum(restNum); //剩下数字递归
    }
};

32 把数组排成最小的数(可以用回溯、贪心)

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

// 回溯(好无语,剪枝搞反了)
class Solution {
public:
	set<string> s;//插入重复元素,进行忽略处理,同时进行由小到大排序
	string PrintMinNumber(vector<int> numbers) {
		// 字符串比较可以用set(1.去重  2.从小到大排序)
		vector<string> path;

		vector<bool> select(numbers.size(), false);
		backtrack(numbers, path, select);
		vector<string> vec(s.begin(), s.end()); //存放字符串的全排列

		return vec[0];
	}

	void backtrack(vector<int>& numbers, vector<string>& path, vector<bool>& select){
		if (path.size() == numbers.size()){
			string str;
			for (int i = 0; i < path.size(); ++i){
				str += path[i];
			}
            // 不能用vector和string拷贝的方法,会报错
			s.insert(str);//插入重复元素,进行忽略处理,同时进行由小到大排序
			return;
		}

		for (int i = 0; i < numbers.size(); ++i) {
			if (select[i]) continue; // 已选择,剪枝

			path.push_back(to_string(numbers[i])); //做选择
			select[i] = true;

			backtrack(numbers, path, select);

			path.pop_back(); //撤销选择
			select[i] = false;
		}
	}
};
  • lambda表达式
// 贪心算法(局部最优解 -> 全局最优解)
class Solution {
public:
    string PrintMinNumber(vector<int> numbers) {
        vector<string> vec;
        string str;
        for(auto it : numbers) {
            vec.push_back(to_string(it));
        }
        // 具名lambda表达式
        auto lam = [](string& a, string& b) { // lam 类似于函数名
            return a+b < b+a;
        }; // 这是条语句,需要加分号
        sort(vec.begin(), vec.end(), lam);
        // 匿名lambda表达式
        //sort(vec.begin(), vec.end(), [](string& a, string& b) -> bool{return a+b < b+a;});
        for(int i = 0; i < vec.size(); ++i){
            str += vec[i];
        }
        return str;
    }
};

sort函数要定义为静态或者全局函数
sort中的比较函数compare要声明为静态成员函数或全局函数,不能作为普通成员函数,否则会报错。 因为:非静态成员函数是依赖于具体对象的,而std::sort这类函数是全局的,因此无法再sort中调用非静态成员函数。静态成员函数或者全局函数是不依赖于具体对象的, 可以独立访问,无须创建任何对象实例就可以访问。同时静态成员函数不可以调用类的非静态成员。

// 贪心算法(局部最优解 -> 全局最优解)
class Com {
public:
    bool operator() (string a, string b){
        return a+b < b+a;
    }
};

class Solution {
public:
    string PrintMinNumber(vector<int> numbers) {
        vector<string> vec;
        string str;
        for(auto it : numbers) {
            vec.push_back(to_string(it));
        }
        sort(vec.begin(), vec.end(), Com()); // Com () 为类的临时对象
        
        //(静态成员函数不可以调用类的非静态成员)
        //sort(vec.begin(), vec.end(), compare);//sort函数要定义为静态或者全局函数
        for(int i = 0; i < vec.size(); ++i){
            str += vec[i];
        }
        return str;
    }
private:
    //加static的原因:类成员函数有隐藏的this指针,static 可以去this指针
    static bool compare(string a, string b){
        return a+b < b+a;
    }
};

33 丑数

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

// 暴力解法,超时
//我们只需要通过质因数分解,判断他分解2,3,5后,是否为1,如果为1,则说明没有其他的因数,否则则有其他因数,那么他就不是一个丑数
class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        //if(index <= 6) return index; //前6个都是丑数
        vector<int> vec(1, 1);//放入1个1,默认1为丑数
        if(index == 1) return vec[0]; 
        int num = 2; //从(2--无穷)每次+1,一直枚举,直到找到地N个丑数为止
        while(1) {
            int i = num;
            while(i%2 == 0) i /= 2;
            while(i%3 == 0) i /= 3;
            while(i%5 == 0) i /= 5;
            if(i == 1) {
                vec.push_back(num);
                if(vec.size() == index) {
                    return vec[vec.size() - 1];
                    break;
                }
            }
            num++;
        }
    }
};

丑数的排列肯定是1,2,3,4,5,6,8,10… 然后有一个特点是,任意一个丑数都是由小于它的某一个丑数*2,3或者5得到的,那么如何得到所有丑数呢? 现在假设有3个数组,分别是:
A:{1 * 2,2 * 2,3 * 2,4 * 2,5 * 2,6 * 2,8 * 2,10 * 2…}
B:{1 * 3,2 * 3,3 * 3,4 * 3,5 * 3,6 * 3,8 * 3,10 * 3…}
C:{1 * 5,2 * 5,3 * 5,4 * 5,5 * 5,6 * 5,8 * 5,10 * 5…}
那么所有丑数的排列,必定就是上面ABC3个数组的合并结果然后去重得到的,那么这不就转换成了三个有序数组的无重复元素合并的问题了吗?
合并有序数组的一个比较好的方法,就是每个数组都对应一个指针,然后比较这些指针所指的数中哪个最小,就将这个数放到结果数组中,然后该指针向后挪一位。

// 三指针法
class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        vector<int> vec(1,1); // 默认1为丑数
        //分别为指向下一个*2,*3,*5可能成为下一个丑数的数的位置的指针
        int idxTwo = 0, idxThree = 0, idxFive = 0;
        for(int i = 1; i < index; ++i) {
            //三个指针运算的结果中找,下一个丑数
            int minNum = min(min(vec[idxTwo]*2, vec[idxThree]*3), vec[idxFive]*5);
            vec.push_back(minNum);
            if(vec[idxTwo]*2 == minNum) idxTwo++; ;//下一个丑数可以由v[i]*2得到,则2指针后移
            if(vec[idxThree]*3 == minNum) idxThree++;//下一个丑数可以由v[j]*3得到,则3指针后移
            if(vec[idxFive]*5 == minNum) idxFive++;//下一个丑数可以由v[k]*5得到,则5指针后移
            vec[i] = minNum;
        }
        return vec[index-1];
    }
};

34 第一个只出现一次的字符

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

// 哈希来做
class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        if(str.size() == 0) return -1;
        unordered_map<char, int> hash_map;
        for(int i = 0; i < str.size(); ++i) {
            hash_map[str[i]]++; //构建 哈希映射
        }
        for(int i = 0; i <str.size(); ++i) {
            if(hash_map[str[i]] == 1) return i;
        }
        return -1;
    }
};

35 数组中的逆序对(归并排序)

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

对于50%的数据,size≤104
对于100%的数据,size≤105

// 注意:是插入值push_back,不是下标(血的教训)
class Solution {
private:
    const int A = 1000000007;
    
public:
    int count = 0;
    int InversePairs(vector<int> data) {
         mergeSort(data, 0, data.size() - 1);
        return count;
    }
    
    void mergeSort(vector<int>& data, int begin, int end){
        if(begin >= end) return;//结束条件为子序列只有一个元素
        int mid = begin + ((end - begin) >> 1);
        // 递的过程
        mergeSort(data, begin, mid); // 划分为左边
        mergeSort(data, mid + 1, end); // 划分为左边
        // 归的过程
        Merge(data, begin, mid, end);
    }
    
    void Merge(vector<int >& data, int begin, int mid, int end){
        vector<int> temp; //把整理后的元素放在temp
        //不能用指针类型,指针储存的是下标的地址,++后下标与原下标就不同了
        int leftStart = begin;
        int rightStart = mid + 1;
        while(leftStart <= mid && rightStart <= end){
            if(data[leftStart] > data[rightStart]){
                temp.push_back(data[rightStart]);
                rightStart++;
//但是如果区间有序会有什么好处吗?当然,如果区间有序,比如[3,4] 和 [1,2]
//如果3 > 1, 显然3后面的所有数都是大于1, 这里为 4 > 1,
//明白其中的奥秘了吧。所以我们可以在合并的时候利用这个规则。
                count = count + (mid - leftStart + 1);
                count = count % A; // 没理解题目意思,应该是每次都取模
            }
            else{
                temp.push_back(data[leftStart]);
                leftStart++;
            }
        }
        while(leftStart <= mid){
            temp.push_back(data[leftStart]);
            leftStart++;
        }
        while(rightStart <= end){
            temp.push_back(data[rightStart]);
            rightStart++;
        }
        for(int i = 0; i < temp.size(); ++i){
            data[begin++] = temp[i]; // 结束后,temp析构
        }
    }
};

36 两个链表的第一个公共结点

描述
输入两个无环的单链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        //节点的值相等不代表是公共节点
        //双指针法,其实该方法主要就是用链表循环的方式替代了长链表指针先走k步这一步骤。
        //a+b=b+a
        if(pHead1 == nullptr || pHead2 == nullptr) return nullptr;
        ListNode* p1 = pHead1, *p2 = pHead2;
        while(p1 != p2){
            p1 = (p1 == nullptr ? pHead2 : p1->next); //利用a+b = 
            p2 = (p2 == nullptr ? pHead1 : p2->next);
        }
        return p1;
    }
};

37 数字在升序数组中出现的次数

描述
统计一个数字在升序数组中出现的次数。
输入:
[1,2,3,3,3,3,4,5],3
返回值:
4

// 暴力解法(脑残题)
// 查找数组中某个目标,不管数组是否有序,直接遍历一遍即可。
class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        int time = 0;
        for(int i = 0; i < data.size(); ++i){
            if(data[i] == k) ++time;
        }
        return time;
    }
};
  • 二分查找模板解法一
// 二分查找模板解法一
class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.empty()) return 0;
        // 二分查找模板解法一
        int left = 0, right = data.size() - 1;
        while(left <= right) {
            int mid = left + (right - left) / 2;
            if(data[mid] < k)
                left = mid + 1;
            else if(data[mid] > k)
                right = mid - 1;
            // 找到了这个数
            else if(data[mid] == k) {
                int count = 1;//计数
                //统计左边
                int i = mid - 1; //临时存放中间值
                while(i >= 0 && data[i] == k){ //要做数组边界溢出处理
                    ++count;
                    --i;
                }
                //统计右边
                i = mid + 1;
                while(i < data.size() && data[i] == k){
                    ++count;
                    ++i;
                }
                return count;
            }
        }
        return 0; //没有找到这个数
    }
};

38 二叉树的深度

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

class Solution {
public:
    int TreeDepth(TreeNode* pRoot) {
        //递归版本
        if(pRoot == nullptr) return 0;
        int depthLeft = TreeDepth(pRoot->left);
        int depthRight = TreeDepth(pRoot->right);
         
        return 1+max(depthLeft, depthRight);
    }
};

39 平衡二叉树

描述
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

注:我们约定空树是平衡二叉树

class Solution {
public:
        int highCalculate(TreeNode* pRoot) {
            if(pRoot == nullptr) return 0;
            int leftHigh = highCalculate(pRoot->left);
            int rightHigh = highCalculate(pRoot->right);
            return 1+max(leftHigh, rightHigh);
    }
      
    bool IsBalanced_Solution(TreeNode* pRoot) {
        if(pRoot == nullptr) return true;
        bool leftBalanced = IsBalanced_Solution(pRoot->left);
        bool rightBalanced = IsBalanced_Solution(pRoot->right);
        if(leftBalanced && rightBalanced && abs(highCalculate(pRoot->left) - highCalculate(pRoot->right)) <= 1)
            return true;
        else
            return false;
    }
};

40 数组中只出现一次的两个数字(哈希、位运算)

描述
一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

class Solution {
public:
    vector<int> FindNumsAppearOnce(vector<int>& array) {
        //哈希查找的方法
        unordered_map<int, int> hash_map;
        for(int i = 0; i < array.size(); ++i){
            hash_map[array[i]]++; //计数,构建哈希映射
        }
         
        vector<int> vec;
        auto it = hash_map.begin();
        while(it != hash_map.end()){
            if(it->second == 1)
            {
                vec.push_back(it->first);
                break;
            }
            it++;
        }
        
        it++; //it指向下一个不为1的
        while(it != hash_map.end()){
             if(it->second == 1)
            {
                vec.push_back(it->first);
                break;
            }
            it++;
        }
        sort(vec.begin(), vec.end());
        return vec;
    }
};
class Solution {
public:
    vector<int> FindNumsAppearOnce(vector<int>& array) {
        // 位运算:使用异或
        // res = 0^1^4^1^6 = 4^6, 4^6^4=6, 4^6^4=4
        vector<int> vec(2); //初始化2个默认值为0的元素
        int res = 0;
        for(int i = 0; i < array.size(); ++i){
            res = res^array[i]; //得到只出现一次的两个数的异或,eg.4^6
        }
        int k = 1;
        //找到res二进制的第一个1,该位置上两个数的二进制不一样,
        //否则不可能为1.(设该位置为k)为了划分出来两个数
        while((k & res) == 0) k = k<<1;
         
        for(int i = 0;i < array.size(); ++i){
            // 用k来划分之后,然后0重新与划分后的异或
            if((k & array[i]) == 0) vec[0] = vec[0]^array[i];
            else vec[1] = vec[1]^array[i];
        }
        if(vec[0] > vec[1]) swap(vec[0], vec[1]);
        return vec;
         
    }
};

41 和为S的连续正数序列

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

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

// 滑动窗口解法
class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
        vector<vector<int>> res;
        //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
        int left = 1, right = 2; //是实值,不是下标
        while(left < right) { //左=右就结束了
           //由于是连续的,差为1的一个序列,那么求和公式是(a0+an)*n/2
            int arraySum = (left + right) * (right - left + 1) / 2; 
            if(arraySum == sum){ //相等,那么就将窗口范围的所有数添加进结果集
                vector<int> temp;
                for(int i = left; i <= right; ++i){
                    temp.push_back(i);
                }
                res.push_back(temp);
                ++left; //缩小窗口,继续找
            }
            else if(arraySum < sum) ++right;
            else if(arraySum > sum) ++left; //注意:这里不一样
        }
        return res;
    }
};

42 和为S的两个数字

描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,返回两个数的乘积最小的,如果无法找出这样的数字,返回一个空数组即可。
返回值描述
对应每个测试案例,输出两个数,小的先输出。

// 双指针(碰撞指针)
class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> res;
        if(array.size() == 0) return vector<int> ();
        
        int left = 0, right = array.size() - 1; //构建碰撞指针
        //有点类似于二分查找
        while(left < right) {
            if(array[left] + array[right] == sum){
                res.push_back(array[left]);
                res.push_back(array[right]);
                return res;
            }
            else if(array[left] + array[right] < sum)
                ++left; //两数之和小于sum,移动左指针
            else if(array[left] + array[right] > sum)
                --right;//两数之和大于sum,移动右指针
        }
        return vector<int>();
    }
};
// 双指针(碰撞指针)
class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        if(array.size() == 0) return vector<int> ();
        int temp = INT_MAX; //选取乘积最小的
        pair<int, int> ret; //键值对存储下标
        
        int left = 0, right = array.size() - 1; //构建碰撞指针
        //有点类似于二分查找
        while(left < right) {
            if(array[left] + array[right] == sum){
               if(array[left] * array[right] < temp) { //滑动窗口
                   ret = {left, right};
                   temp = array[left] * array[right]; //更新阈值
               } 
                ++left, --right;
            }
            else if(array[left] + array[right] < sum)
                ++left;
            else if(array[left] + array[right] > sum)
                --right;
        }
        if(ret.first >= ret.second) return vector<int>();
        return vector<int>({array[ret.first], array[ret.second]});
    }
};
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
       int low= 0, high = array.size()-1;
       int minResult = INT_MAX;
       vector<int> result;
       while(low <= high){
           int sumTemp = array[low] + array[high];
           if(sumTemp == sum){
               if( array[low] * array[high] < minResult){
                  result.clear();
                  result.push_back(array[low]);
                  result.push_back(array[high]);
//这里其实可以直接返回的,因为同比情况下,
//两个数字相差越远,他们的乘积越小的,约靠近相差的乘积就越大
                  minResult = array[low] * array[high];
               }

               low++;
           }else if(sumTemp > sum) high--;
           else
               low++;
       }
       return result;
    }

43 左旋转字符串

描述
对于一个给定的字符序列 S,请你把其循环左移 K 位后的序列输出(保证 K 小于等于 S 的长度)。例如,字符序列S=”abcXYZdef”,要求输出循环左移 3 位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

//遍历数组
class Solution {
public:
    string LeftRotateString(string str, int n) {
        int len = str.size()-1;
        for(int i = 0; i < n; ++i) {
            int temp = str[0]; //保存第一个元素
            for(int j = 1; j <= len; ++j) {
                str[j-1] = str[j]; //把元素往前移动一个单位
                if(j == len) str[j] = temp;
            }
        }
        return str;
    }
};
//使用标准库,substr
class Solution {
public:
    string LeftRotateString(string str, int n) {
        int len = str.size();
        if(len <= 1) return str; //为空的情况
        if(len < n) n = n%len; //n有可能比len大的情况
        
        str += str; //连续两个str
        str = str.substr(n, len); //获取从n位置的len字符
        return str;
    }
};

44 翻转单词序列

描述
例如,“nowcoder. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a nowcoder.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

// 自己想的,获取子串
class Solution {
public:
	string ReverseSentence(string str) {
		//if(str.empty()) return str;
		// 把子串放入数组中
		vector<string> vec;
		string str1; //获取子串
		int start = 0;
		for (int i = 0; i < str.size(); ++i) {
			if (str[i] == ' ') {
				str1 = str.substr(start, i - start); // 从start开始i-start个字符
				vec.push_back(str1); // 记录子串
				str1.clear(); // 清除子串重新记录
				start = i + 1; // 指向start的下一个
			}
			if (i == str.size() - 1) { //判断是否结束
				string str3 = str.substr(start); // 最后的子串
				vec.push_back(str3); // 记录子串
				str1.clear(); // 清除子串重新记录
			}
		}
		reverse(vec.begin(), vec.end());
		string str2;
		for (int i = 0; i < vec.size(); ++i) {
			if (i == vec.size() - 1) 
				str2 += vec[i];
			else
				str2 = str2 + vec[i] + ' ';
		}
		return str2;
	}
};
// 拷贝拼接思路
class Solution {
public:
    string ReverseSentence(string str) {
        string res, temp;
        for(int i = 0; i < str.size(); ++i) {
            if(str[i] == ' ') {
                res = ' ' + temp + res; 
                temp = "";
            }
            else
                temp += str[i];
        }
        if(!temp.empty()) res = temp + res; //最后一个子串
        return res;
    }
};

45 扑克牌顺子

描述
现在有2副扑克牌,从扑克牌中随机五张扑克牌,我们需要来判断一下是不是顺子。
有如下规则:

  1. A为1,J为11,Q为12,K为13,A不能视为14
  2. 大、小王为 0,0可以看作任意牌
  3. 如果给出的五张牌能组成顺子(即这五张牌是连续的)就输出true,否则就输出false。 >
    例如:给出数据[6,0,2,0,4]
    中间的两个0一个看作3,一个看作5 。即:[6,3,2,5,4]
    这样这五张牌在[2,6]区间连续,输出true
    数据保证每组5个数字,每组最多含有4个零,数组的数取值为 [0, 13]

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

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        if(numbers.size() < 5) return false; //数组不符合要求
        
        sort(numbers.begin(), numbers.end());
        // 双指针
        int zeroNums = 0; // 判断为0的个数
        for(int i = 0; i < numbers.size(); ++i) {
            if(numbers[i] == 0) 
                ++zeroNums; 
            else if(numbers[i] == numbers[i+1])
                return false; //若有重复,提前返回false
        }
        if(numbers[4] - numbers[zeroNums] >= 5) //注意:规律,刚开始没想明白
            return false;
        else
            return true;
    }
};

46 孩子们的游戏(圆圈中最后剩下的数)

描述
首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

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 LastRemaining_Solution(int n, int m) {
        if(n <= 0 || m < 0) return -1; //没有小朋友的情况、或者为负值情况
        // STL自带的为双向循环迭代器,只支持it++、it-- ,不支持 it = it + 1
        //list<int> l;
        deque<int> l; // 用双端数组效率会更高
        
        for(int i = 0; i < n; ++i) {  // 构建双向循环链表
            l.push_back(i);
        }
        //list<int>::iterator it = l.begin(); 链表的迭代器不能循环访问
        while(l.size() != 1){
            int count = 1; // 记录队尾插入对头删除的次数
            while(count < m){
                l.push_back(l.front());
                l.pop_front();
                count++;
            }
            l.pop_front(); // 最后删除所需的节点
        }
        return l.front();
    }
};
//约瑟夫环的问题,背模板吧
class Solution {
public:
    int LastRemaining_Solution(int n, int m) {
        if(n <= 0 || m < 0) return -1;
        int index = 0;  // 当只有一人的时候 胜利者下标肯定为0
        for(int i = 2; i <= n;++i){
           // 每多一人 胜利者下标相当于往右挪动了m位,再对当前人数取模求得新的胜利者下标
            index = (index + m) % i; 
        }
        return index;
    }
};

47 求1+2+3+…+n

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

class Solution {
public:
    int Sum_Solution(int n) {
        //定义一个二维数组再获取数组的size,达到n*(n+1)的目的,然后位运算就是n(n+1)/2
        bool arr[n][n+1]; 
        return sizeof(arr) >> 1;
        //前n项和的公式就是 n * (n+1) / 2,sizeof就是求了一个大小,
        //二维数组可以看成一维的,里面应该是末尾地址 - 首地址
    }
};
//这个特性实际叫做“骤死性评估”,是一种语言特性,即左侧的表达式为假时整个表达式后续将不再进行评估。
//这也就是为什么不要重载&&,||,和逗号操作符的原因,因为重载之后变为函数语义,编译器将不再保证骤死性评估。
class Solution {
public:
    int Sum_Solution(int n) {
        int res = n;
        //逻辑与&&连接符。A&&B,表示如果A成立则执行B,否则如果A不成立,不用执行B
        n && (res += Sum_Solution(n - 1));
        return res;
    }
};

48 不用加减乘除做加法

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

class Solution {
public:
    int Add(int num1, int num2) {
         if(num1 == 0) return num2;
         if(num2 == 0) return num1;
        int sum;
        while(num2 != 0) {
            sum = num1 ^ num2;              //相当于作加法,不进位
            int carry = (num1 & num2) << 1; //相当于求进位
            num1 = sum;
            num2 = carry;
        }
        return sum;
    }
};

49 把字符串转换成整数

描述
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
输入描述:
输入一个字符串,包括数字字母符号,可以为空
返回值描述
如果是合法的数值表达则返回该数字,否则返回0

  1. 字符串为空
  2. 开头为空格,或全部为空格
  3. 有正负号的情况
  4. int溢出的情况
  5. 非法字符的情况
// 字符串边界条件的考察
class Solution {
public:
	int StrToInt(string str) {
		if (str.size() == 0) return 0; //字符串为空的情况
		int sign = 1;//符号位。赋初值1
		int i = 0;
		while (i < str.size()) {
			if (i == str.size()) return 0; // 全为空格的情况
			if (str[i] == '-') sign = -1; //有负号的情况下
			if (str[i] != ' ' && str[i] != '-' && str[i] != '+') //不为空格且符号位时退出
				break;//(逻辑关系容易出错)
            else 
                ++i;//如果为空格,跳过空格的情况
		}

		long res = 0; //用长整型保存结果
		while (i < str.size()) {
			if (str[i] < '0' || str[i] > '9')
				return 0; //非合法数值的情况
			else
				res = res * 10 + str[i] - '0';
			if (res > INT_MAX && sign == 1) return 0; //正数溢出
			if (res < INT_MIN && sign == -1) return 0; //负数溢出
			++i;
		}
		return sign * res;
	}
};

50 数组中重复的数字(哈希)

描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任一一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1

// 哈希解法
class Solution {
public:
    int duplicate(vector<int>& numbers) {
        int len = numbers.size();
        unordered_map<int, int> hash_map;
        for(int i = 0; i < len; ++i){
            if(numbers[i] >= len) return -1; //输入的数字不符合要求
            hash_map[numbers[i]]++; //构建哈希映射
        }
        for(int i = 0; i < len; ++i){ //遍历哈希映射
            if(hash_map[numbers[i]] > 1) 
                return numbers[i];
        }
        return -1;
    }
};

51 构建乘积数组

描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

给定一个数组 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 {
public:
    vector<int> multiply(const vector<int>& A) {
        int len = A.size();
        vector<int> B(len, 0);
        //vector<int> B;
        
        if(A.size() == 1) return B; //A长度为1时,B为空
        for(int i = 0; i < len; ++i) {
            int tempA = 1; //记录乘积中间值
            for(int j = 0; j < len; ++j) {
                if(i != j) tempA = tempA * A[j];
            }
            B[i] = tempA; //如果没有开辟数组空间,不能【】来插入数值
            //B.push_back(tempA);
        }
        return B;
    }
};

52 正则表达式匹配(动态规划)

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

class Solution {
public:
    bool match(string str, string pattern) {
           int m = str.size() + 1, n = pattern.size() + 1;
        vector<vector<bool>> dp(m, vector<bool>(n, false)); // m X n 
        dp[0][0] = true;
        for(int j = 2; j < n; j += 2)
            dp[0][j] = dp[0][j - 2] && pattern[j - 1] == '*';
        for(int i = 1; i < m; i++) {
            for(int j = 1; j < n; j++) {
                dp[i][j] = pattern[j - 1] == '*' ?
                    dp[i][j - 2] || dp[i - 1][j] && (str[i - 1] == pattern[j - 2] || pattern[j - 2] == '.'):
                    dp[i - 1][j - 1] && (pattern[j - 1] == '.' || str[i - 1] == pattern[j - 1]);
            }
        }
        return dp[m - 1][n - 1];
    }
};

53 表示数值的字符串

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

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

  1. (空格)删除空格
  2. (e/E)e的前面有e/E,或者没有有数字;e/E后面必须有数字false
  3. ( . ).之前有e/E,或有点,点后面没有数字,false
  4. (±)±未出现在0位置,且未出现在e/E的后第一个位置;±后面没有数字 false
  5. (数字)有非法字符 ,false(判断非法字符放在末尾)
class Solution {
public:
    bool isNumeric(string str) {
        // 分类讨论
        bool showNum = false; //标记是否出现数字
        bool showDot = false; //标记是否出现点
        bool showE = false;   //标记是否出现E
        
        //把不可能都排除掉
        int len = str.size();
        int index_E;  //记录e的下标位置
        for(int i = 0; i < len; ++i) {
            if(str[i] == ' ') str.erase(i); //删除下标为i的空格
            else if(str[i] == 'e' || str[i] == 'E') {
                if(!showNum || showE) return false; //e的前面有e,或者没有有数字,false
                else if(i == len-1) return false; //e的后面没有数字,false
                showE = true;
                index_E = i; //记录e的下标位置
            }
            else if(str[i] == '.'){
                if(showE || showDot) return false; //点之前有e或有点,false
                if(i == len-1) return false; //点后面没有数字,false
                showDot = true;
            }
            else if(str[i] == '+' || str[i] == '-'){
                // +-未出现在0位置且未出现在e/E的后第一个位置,false
                if(i != 0 && i != index_E + 1) return false; 
                if(i == len-1) return false; //+-后面没有数字,false
            }
            else if(str[i] >= '0' && str[i] <= '9') showNum = true; 
            else if(str[i] < '0' || str[i] > '9') return false; //非法字符
        }
        return true;
    }
};

54 字符流中第一个不重复的字符

描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
返回值描述
如果当前字符流没有存在出现一次的字符,返回#字符。

class Solution
{
public:
    void Insert(char ch) {
         vec.push_back(ch);
         hash_map[ch]++; //构建哈希映射
    }
    char FirstAppearingOnce() {
        for(int i = 0; i < vec.size(); ++i){
            if(hash_map[vec[i]] == 1) return vec[i]; //在整个遍历哈希中是否找到
        }
        return '#'; //没找到
    }
    vector<int> vec;
    unordered_map<char, int> hash_map; //哈希通常与数组联系起来,特别是遍历哈希时
};

55 链表中环的入口结点

描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
输入描述
输入分为2段,第一段是入环前的链表部分,第二段是链表环的部分,后台将这2个会组装成一个有环或者无环单链表
返回值描述
返回链表的环的入口结点即可。而我们后台程序会打印这个节点

// 哈希算法 unordered_map
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        unordered_map<ListNode*, int> hash;
        while(pHead != nullptr){
            hash[pHead]++; //构建哈希映射
            if(hash[pHead] == 2) return pHead;
            pHead = pHead->next;
        }
        return nullptr;
    }
};
// set或 unordered_set 解法
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        //一般用unordered_set可以缩短代码的运行时间
        //set<ListNode*> s;
        unordered_set<ListNode*> s;
        while(pHead != nullptr) {
            pair<unordered_set<ListNode*> :: iterator, bool> ret = s.insert(pHead);
            if(!ret.second) return pHead; //判断是否插入成功
            pHead = pHead->next;
        }
        return nullptr;
    }
};
// set或 unordered_set 解法(变种)
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        //一般用unordered_set可以缩短代码的运行时间
        //set<ListNode*> s;
        unordered_set<ListNode*> s;
        while(pHead != nullptr) {
            if(s.find(pHead) == s.end()){ //在 set 的能否找到元素
                s.insert(pHead);
                pHead = pHead->next;
            }
            else
                return pHead;
        }
        return nullptr;
    }
};
  1. 初始化:快指针fast指向头结点, 慢指针slow指向头结点
  2. 让fast一次走两步, slow一次走一步,第一次相遇在C处,停止
  3. 然后让fast指向头结点,slow原地不动,让后fast,slow每次走一步,当再次相遇,就是入口结点。
// 快慢指针法
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        ListNode* slow = pHead, *fast = pHead; // 定义快慢指针
        
        //其中 fast->next 的检测为了防止链表溢出
        while(fast != nullptr && fast->next != nullptr){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow){
                fast = pHead;
                break;
            }
        }
        if(fast == nullptr || fast->next == nullptr) return nullptr;
        while(fast != slow){
                fast = fast->next;
                slow = slow->next;
        }
        return fast;
    }
};

56 删除链表中重复的结点

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

// 哈希解法
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        vector<ListNode*> vec;
        unordered_map<int, int> hash;
        // 由于pHead非局部变量,会导致其变化
        //while(pHead != nullptr) { 
            //hash[pHead->val]++;
            //pHead = pHead->next;
        //}
        for(ListNode* p = pHead; p != nullptr; p = p->next) {
            hash[p->val]++;
        }
        // 由于已排序,重复节点一定是连续重复的
        ListNode* newHead;
        for(ListNode* p = pHead; p != nullptr; p = p->next) {
            if(hash[p->val] == 1){
                // 进行深拷贝复制节点(next为空),浅拷贝会导致(next指向下一节点)
                newHead = new ListNode(p->val); 
                vec.push_back(newHead);
            }
        }
        if(vec.size() == 0) return nullptr;
        if(vec.size() == 1) return vec[0];
        for(int i = 0; i < vec.size()-1; ++i) { //数组两个以上元素的情况
            // 链接两个链表节点,最后一个节点会带有之间的节点,故1.拷贝节点来杜绝  2.或最后节点置为空
            vec[i]->next = vec[i+1]; 
        }
        //vec[vec.size() - 1]->next = nullptr; //2.或最后节点置为空
        return vec[0];
    }
};

57 二叉树的下一个结点

描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

分析可知
1.二叉树为空,则返回空;
2.节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;
3.右孩子不存在,如果节点不是根节点,如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果。

// 画图分类讨论
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode) {
        if(pNode == nullptr) return nullptr; //子树根节点为空就返回空
        
        if(pNode->right != nullptr) { //子树根节点的右子节点不为空
            pNode = pNode->right;
            while(pNode->left != nullptr){
                pNode = pNode->left;
            }
            return pNode;
        }
        else  //子树根节点的右子树为空的情况下
            while(pNode->next != nullptr) { //子树根节点的上一级根节点不为空
                //判断是否为上一级根节点的左子树节点
                TreeLinkNode* upNode = pNode->next; //保存上一级节点
                if(upNode->left == pNode)  //判断是否为上一级根节点的左子树节点
                    return upNode; //注意:返回上一级根节点
                else
                    pNode = pNode->next;
            }
        return nullptr; //子树根节点已经到底
    }
};

58 对称的二叉树(复杂递归)

描述
请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

//递归解法
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot) {
        if(pRoot == nullptr) return true; //边界条件
       //构造一个函数,输入两个指针来判断是否相同
        return isSame(pRoot->left, pRoot->right); //不能传根指针,否则指针会同时指向左右两个
    }
    // 递归函数
    bool isSame(TreeNode* pRoot1, TreeNode* pRoot2) {
        if((pRoot1 == nullptr && pRoot2 != nullptr) || (pRoot1 != nullptr && pRoot2 == nullptr)) 
            return false; //递归结束条件
        if(pRoot1 == nullptr && pRoot2 == nullptr)
            return true;
        if(pRoot1 != nullptr && pRoot2 != nullptr)
            if(pRoot1->val != pRoot2->val) return false; //不相等返回false,相等不一定返回true
       //返回给上一级
        return isSame(pRoot1->right, pRoot2->left) && isSame(pRoot1->left, pRoot2->right);
    }
};

59 按之字形顺序打印二叉树(变种层序遍历)

描述
给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)
例如:
给定的二叉树是{3,9,20,15,7},
在这里插入图片描述
该二叉树之字形层序遍历的结果是
[
[3],
[20,9],
[15,7]
]

// 层序遍历的变种,bfs
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> res;
        if(pRoot == nullptr) return res;
        
        queue<TreeNode*> q; // 借助辅助队列
        q.push(pRoot);
        int count = 0;
        while(!q.empty()) {
            vector<int> vec;
            int len = q.size(); //size是动态变化的,需要存储
            for(int i = 0; i < len; ++i) {
                pRoot = q.front(); //移动根指针
                vec.push_back(pRoot->val);
                q.pop();
                if(pRoot->left != nullptr) q.push(pRoot->left);
                if(pRoot->right != nullptr) q.push(pRoot->right);
            }
            count++; //记录层次数
            if((count&1) == 0) reverse(vec.begin(), vec.end()); //偶数反转,位运算一定要加括号
            res.push_back(vec);
        }
        return res;
    }
};

60 把二叉树打印成多行(打印多行的层序遍历)

描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

// 层序遍历 bfs
class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            vector<vector<int>> res;
            if(pRoot == nullptr) return res;
            // 借助辅助队列
            queue<TreeNode*> q;
            q.push(pRoot);
            while(!q.empty()) {
                int len = q.size();
                vector<int> vec;
                for(int i = 0; i < len; ++i){
                    pRoot = q.front();
                    vec.push_back(pRoot->val);
                    q.pop();
                    if(pRoot->left != nullptr) q.push(pRoot->left);
                    if(pRoot->right != nullptr) q.push(pRoot->right);
                }
                res.push_back(vec);
            }
            return res;
        }
};

61 序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。 假如一棵树共有 2 个结点, 其根结点为 1 ,根结点右子结点为 2 ,没有其他结点。按照上面第一种说法可以序列化为“1,#,2,#,#”,
你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

// 这也能通过
class Solution {
public:
    TreeNode* node;
    char* Serialize(TreeNode *root) {    
        node = root;
        return "(*^_^*)";
    }
    TreeNode* Deserialize(char *str) {
        return node;
    }
};
//根左右,前序遍历
class Solution {
private:
    string SerializeCore(TreeNode* root) {
        if (root == nullptr) {
            return "#!";
        }
        string str;
        str = to_string(root->val) + "!";
        str += SerializeCore(root->left);
        str += SerializeCore(root->right);
        return str;
    }

    TreeNode* DeserializeCore(char*& str) {
        if (*str == '#') {
            str++;
            return nullptr;
        }
        int num = 0;
        while (*str != '!') {
            num = num * 10 + *str - '0';
            str++;
        }
        TreeNode* node = new TreeNode(num);
        node->left = DeserializeCore(++str);
        node->right = DeserializeCore(++str);
        return node;
    }
public:
    char* Serialize(TreeNode* root) {
        string str = SerializeCore(root);
        char* res = new char[str.size()];
        for (int i = 0; i < str.size(); i++) {
            res[i] = str[i];
        }
        return res;
    }

    TreeNode* Deserialize(char* str) {
        return DeserializeCore(str);
    }
};

62 二叉搜索树的第k个结点

描述
给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点。

// 中序遍历,递归解法
class Solution {
public:
    int count = 0; 
    TreeNode* KthNode(TreeNode* pRoot, int k) {
        if(pRoot == nullptr) return nullptr;
        TreeNode* left_KthNode = KthNode(pRoot->left, k);
        if(left_KthNode != nullptr) return left_KthNode; //找到第k个节点就返回
        
        count++; //计数第几小
        if(count == k) return pRoot;
        
        TreeNode* right_KthNode = KthNode(pRoot->right, k);
        if(right_KthNode != nullptr) return right_KthNode;
        
        return nullptr; //若都没找到返回空
    }
};
//迭代,辅助栈,dfs
    TreeNode* KthNode(TreeNode* pRoot, int k) {
        if(pRoot == nullptr) return nullptr;//边界条件
        int count = 0;
        stack<TreeNode*> s;
        while(pRoot != nullptr || !s.empty()) {
            while(pRoot != nullptr) {
                s.push(pRoot);
                pRoot = pRoot->left;
            }
            pRoot = s.top();
            count++; //开始计数
            if(count == k) return pRoot;
            s.pop();
            pRoot = pRoot->right;
        }
        return nullptr; //没找到,返回空
    }

63 数据流中的中位数

描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数

class Solution {
public:
    vector<int> vec;
    
    void Insert(int num) {
        vec.push_back(num);
    }

    double GetMedian() { 
        sort(vec.begin(), vec.end());
        int len = vec.size();
        if((len & 1) == 1) //是否为奇数
            return vec[len / 2];
        else 
            //注意这里是2.0 这样才能返回值为double
            return (vec[(len / 2) - 1] + vec[len / 2]) / 2.0;
    }
};
// 大小堆
//1.只需要知道中位数左右那两堆数的最大和最小即可,然后根据是奇数项还是偶数项返回结果
//2.大根堆这种的数据结构他根结点是最大的,小根堆的根结点是最小的,所以利用这个特点,
//我们把比中位数小的那堆数放在大根堆,把中位数大的那堆数放在小根堆
//3.有个注意的地方,就是每次插入数据时,我们都要保持小根堆的所有数都大于大根堆的所有
class Solution {
private:
    priority_queue<int> maxHeap; // 大顶堆
    priority_queue<int, vector<int>, greater<int> > minHeap; //小顶堆
    
public:
    void Insert(int num) {
          /*
         * 当两堆的数据个数相等时候,左边堆添加元素。
         * 采用的方法不是直接将数据插入左边堆,而是将数据先插入右边堆,算法调整后
         * 将堆顶的数据插入到左边堆,这样保证左边堆插入的元素始终是右边堆的最小值。
         * 同理左边数据多,往右边堆添加数据的时候,先将数据放入左边堆,选出最大值放到右边堆中。
         */
        if (maxHeap.size() == minHeap.size()){ //左边堆添加元素
            minHeap.push(num);
            int top = minHeap.top();
            minHeap.pop();
            maxHeap.push(top);
        }
        else { //此时左边数据多,往右边堆添加元素
            maxHeap.push(num);
            int top = maxHeap.top();
            maxHeap.pop();
            minHeap.push(top);
        }
    }

    double GetMedian() { 
        if(maxHeap.size() == minHeap.size())
            return (maxHeap.top() + minHeap.top()) / 2.0;
        else
            return maxHeap.top() * 1.0;
    }
};

64 滑动窗口的最大值(堆)

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

// 大根堆 + 滑动窗口法
class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
        vector<int> vec;
        if(size > num.size()) return vec; //窗口大于数组长度的时候
        
        int left = 0, right = size - 1; // 滑动窗口的左右指针
        while(right <= num.size()-1) {
            priority_queue<int> pq; // 局部变量,系统自动清除
            for(int i = left; i <= right; ++i){
                pq.push(num[i]); // 优先队列中插入元素
            }
            vec.push_back(pq.top()); // 数组中插入大根堆的最大值
            right++;
            left++;
        }
        return vec;
    }
};

65 矩阵中的路径

描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
在这里插入图片描述

// 回溯解法

//题目没理解:用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径
class Solution {
public:
    bool hasPath(vector<vector<char> >& matrix, string word) {
        // 路径中的元素访问到的做标记
        vector<vector<bool>> visit(matrix.size(), vector<bool>(matrix[0].size(), false));
        // 从矩阵的不同起点开始
        for(int i = 0; i < matrix.size(); ++i){
            for(int j = 0; j < matrix[0].size(); ++j){
                if(backtrack(matrix, visit, word, i, j, 0)) return true; //有一条路径可以就行
            }
        }
        return false;
    }

    bool backtrack(vector<vector<char> >& matrix, vector<vector<bool> >& visit, string& word, int row, int col, int index){
        // 结束条件
        if(index >= word.size()) return true; // 字符串结束,说明匹配完成
        
        // 剪枝
        if(row < 0 || row >= matrix.size() || col < 0 || col >= matrix[0].size() ||
           visit[row][col] || matrix[row][col] != word[index]){
            return false;
        }
        //做选择(枚举所有选择)
        visit[row][col] = true;
            
        // 本来递归后就完成选择了,但最后没有完成,故需要退回重新选择
        if(backtrack(matrix, visit, word, row-1, col, index+1) ||
          backtrack(matrix, visit, word, row+1, col, index+1) ||
          backtrack(matrix, visit, word, row, col-1, index+1) ||
          backtrack(matrix, visit, word, row, col+1, index+1)){
            return true;
        }
        // 撤销选择
        visit[row][col] = false;
        return false;
    }
};

66 机器人的运动范围(回溯)

描述
地上有一个rows行和cols列的方格。坐标从 [0,0] 到 [rows-1,cols-1]。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于threshold的格子。 例如,当threshold为18时,机器人能够进入方格[35,37],因为3+5+3+7 = 18。但是,它不能进入方格[35,38],因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
范围:
1 <= rows, cols<= 100
0 <= threshold <= 20

// 无返回值、无撤销回溯
class Solution {
public:
    int count = 0;
    
    int movingCount(int threshold, int rows, int cols) {
        if(threshold < 0) return 0;
        //pair<int, int> path(0, 0); // 记录机器人的横纵坐标
        vector<vector<bool> > select(rows, vector<bool>(cols, false));
        backtrack(threshold, rows, cols, 0, 0, select);// 机器人初始横纵坐标(0,0)
        return count;
    }
    
    void backtrack(int threshold, int rows, int cols, int x, int y, vector<vector<bool>>& select){
        if(x > rows - 1 || x < 0|| y > cols - 1 || y < 0){
            return; //表明遍历完成
        }
        
        if(cut(x, y, threshold) == true) return;   // 满足剪枝条件
        if(select[x][y]) return; // 去重
        count++;
        select[x][y] = true;
        backtrack(threshold, rows, cols, x-1, y, select);
        backtrack(threshold, rows, cols, x+1, y, select);
        backtrack(threshold, rows, cols, x, y-1, select);
        backtrack(threshold, rows, cols, x, y+1, select);
       
        //count--;
        //select[x][y] = false; //满足一定条件才能撤销选择(不是全部撤销)
    }
    
    bool cut(int x, int y, int& threshold) {
        int row = x, col = y; 
        int sum = 0;
        while(row != 0){
            sum = sum + row % 10; // 取个位数求和
            row = row / 10;
        }
        while(col != 0){
            sum = sum + col % 10; 
            col = col / 10;
        }
        if(sum > threshold) return true; // 不符合,剪掉
        return false;
    }
};
// 有返回值、无撤销回溯
class Solution {
public:
    int movingCount(int threshold, int rows, int cols) {
        if(threshold < 0) return 0;
        //pair<int, int> path(0, 0); // 记录机器人的横纵坐标
        vector<vector<bool> > select(rows, vector<bool>(cols, false));
        return  backtrack(threshold, rows, cols, 0, 0, select);// 机器人初始横纵坐标(0,0)
    }
    
    int backtrack(int threshold, int rows, int cols, int x, int y, vector<vector<bool>>& select){
        if(x > rows - 1 || x < 0|| y > cols - 1 || y < 0){
            return 0; //表明遍历完成
        }
        
        if(cut(x, y, threshold) == true) return 0;   // 满足剪枝条件
        if(select[x][y]) return 0; // 去重
        select[x][y] = true;
        
        return backtrack(threshold, rows, cols, x-1, y, select)+
               backtrack(threshold, rows, cols, x+1, y, select)+
               backtrack(threshold, rows, cols, x, y-1, select)+
               backtrack(threshold, rows, cols, x, y+1, select)+1;
        //select[x][y] = false; //满足一定条件才能撤销选择(不是全部撤销)
    }
    
    bool cut(int x, int y, int& threshold) {
        int row = x, col = y; 
        int sum = 0;
        while(row != 0){
            sum = sum + row % 10; // 取个位数求和
            row = row / 10;
        }
        while(col != 0){
            sum = sum + col % 10; 
            col = col / 10;
        }
        if(sum > threshold) return true; // 不符合,剪掉
        return false;
    }
};
// bfs(辅助队列)
bool canReach(int threshold, int x, int y) {
    int sum = 0;
    while (x > 0) {
        sum += x % 10;
        x /= 10;
    }
    while (y > 0) {
        sum += y % 10;
        y /= 10;
    }
    return sum <= threshold;
}

int movingCount(int threshold, int rows, int cols)
{
    vector<vector<bool>> grid(rows,vector<bool>(cols,false));
    queue<pair<int, int>> que;
    if (canReach(threshold, 0, 0)) {
        que.push(make_pair(0, 0));
        grid[0][0] = true;
    }
    int cnt = 0;
    while (!que.empty()) {
        ++cnt;
        int x, y;
        tie(x, y) = que.front();
        que.pop();
        if (x + 1 < rows && !grid[x + 1][y] && canReach(threshold, x + 1, y)) {
            grid[x + 1][y] = true;
            que.push(make_pair(x + 1, y));
        }
        if (y + 1 < cols && !grid[x][y + 1] && canReach(threshold, x, y + 1)) {
            grid[x][y + 1] = true;
            que.push(make_pair(x, y + 1));
        }
    }
    return cnt;

}

67 剪绳子(动态规划)

描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入描述
输入一个数n,意义见题面。(2 <= n <= 60)

// 动态规划
class Solution {
public:
    int cutRope(int number) {
        if(number < 2) return -1; //无意义
        if(number == 2) return 1;
        if(number == 3) return 2;
        
        // 定义dp数组:代表长度为n的绳子的最大乘积
        vector<int> dp(number + 1, 0); //初始化多一个元素,dp[0]没有实际意义
        
        //因为长度>=4,他们不需要拆,拆了反而会变小,对于小于4的情况我们直接开头处理
        dp[0] = 1; // dp数组初值和函数初值不一定一样
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        int maxNum = 0; //记录某一划分的最大值
        for(int i = 4; i <= number; ++i){
            for(int j = 1; j <= i/2; ++j){
                dp[i] = max(maxNum, dp[j] * dp[i - j]);
                maxNum = dp[i];
            }
        }
        return maxNum;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

宇光_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值