剑指offer刷题总结(Leetcode未完结,持续更新)

剑指offer刷题总结

一、分类:

单调队列:

59 - II. 队列的最大值
59 - I. 滑动窗口的最大值

栈、队列

09. 用两个栈实现队列
22. 链表中倒数第k个节点
40. 最小的k个数
30. 包含min函数的栈
31. 栈的压入、弹出序列
41. 数据流中的中位数(优先级队列)

斐波那契数列:(简单)

10- I. 斐波那契数列
10- II. 青蛙跳台阶问题

重复数字问题

03. 数组中重复的数字,很多对重复的数,找到一对即可

二叉树

DFS的几种经典写法:

1、判断当前是否满足并且继续搜索后续子树是否满足

return (DFS(A,B) || isSubStructure(A->left,B) || isSubStructure(A->right,B));
04. 二维数组中的查找
07. 重建二叉树
26. 树的子结构
27. 二叉树的镜像(简单)
28. 对称的二叉树(简单,和上面那题类似)

二分查找法

代码格式:

while(left<right){
 int mid = left + (right - left) / 2;
 if(...)	right = mid-1;
 else	left = mid + 1;
}
11. 旋转数组的最小数字

字符串处理

05. 替换空格(简单)

链表处理

06. 从尾到头打印链表(简单)
25. 合并两个排序的链表
24. 反转链表(简单)
18. 删除链表的节点(简单)
35. 复杂链表的复制(中等)
36. 二叉搜索树与双向链表(困难)

剪枝

12. 矩阵中的路径(困难)

矩阵数组路径搜索

12. 矩阵中的路径(困难)
13. 机器人的运动范围 (困难)

双指针

快慢指针

头尾指针

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

位运算

15. 二进制中1的个数

动态规划

42. 连续子数组的最大和

需要背诵的写法(找规律)

29. 顺时针打印矩阵
16. 数值的整数次方
17. 打印从1到最大的n位数
35. 复杂链表的复制(中等)
36. 二叉搜索树与双向链表(困难)

二、序号索引:

09. 用两个栈实现队列

class CQueue {
private:
    stack<int> instack,outstack;	//定义两个队列,一个输入栈 instack,一个输出栈 outstack
    void in2out(){	//将输入栈中的元素全部压入输出栈中
        while(!instack.empty()){
            outstack.push(instack.top());
            instack.pop();
        }      
    }
public:
    CQueue() {}	//类构造函数
    
    void appendTail(int value) {	//push
        instack.push(value);
    }
    
    int deleteHead() {	//pop
        if(outstack.empty() && !instack.empty()){	//当输出栈中没有元素并且输入栈中存在元素时
            in2out();
        }
        else if(outstack.empty() && instack.empty()){	//当输出栈和输入栈中都为空时
            return -1;
        }
        int x=outstack.top();	//返回队列 pop() 的元素
        outstack.pop();
        return x;
    }
};

10- I. 斐波那契数列

一、数组法
class Solution {
public:
//数组方法
    int fib(int n) {
        vector<long long> ans(n+2,0);
        ans[0]=0;//初始化解
        ans[1]=1;
        if(n<2) return ans[n];

        for(int i=2;i<=n;i++){
            ans[i]=ans[i-1]+ans[i-2];
            ans[i]%=1000000007;
        }
        return ans[n];
    }
};
二、滑动窗口
class Solution {
public:
//节省空间的数组法(滑动窗口)
    int fib(int n) {
        
        vector<long> ans(3,0);
        ans[0]=0;
        ans[1]=1;
        if(n<2) return ans[n];
        for(int i=2;i<=n;i++){
            ans[2]=(ans[1]+ans[0])%1000000007;
            ans[0]=ans[1];
            ans[1]=ans[2];
        }
        return ans[2];

    }
};
三、带记忆的递归
class Solution {
    int nums[101]={0};
public:
    int fib(int n) {
        int ans;
        if(n==0)    return 0;
        else if(n==1)   return 1;
        else if(0!=nums[n])  return nums[n];//确保在 fib(n-1) 递归到 n==2 后,得出 f(2)=f(1)+f(0) 后,在返回到 f(4)=f(3)+f(2)时,f(n-2) 不会重新去递归计算 f(2),而是直接返回数组中记录的值
        nums[n]=(fib(n-1)+fib(n-2))%1000000007;
        return nums[n];
    }
};

03. 数组中重复的数字

找出数组中重复的数字。

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

示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 
哈希法:

思路:

1、存入 unordered_map 哈希表

2、第二次遍历哈希 map 寻找 val 值大于1的数

class Solution {
    //哈希表法
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_multiset<int> T;
        
        int a[nums.size()];
        //存入哈希表
        for(auto num:nums){
            T.insert(num);
        }
        //遍历哈希表,寻找相同的数字
        int i=0;
        for(auto num:T){
            if(T.count(num)>1){
                return num;

            }
        }
        return {-1};

    }
};
原地交换
class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        int i = 0;
        while(i < nums.size()) {
            if(nums[i] == i) {//遇到索引等于值的就跳过
                i++;
                continue;
            }
            if(nums[nums[i]] == nums[i])	return nums[i];//当跳过上面的判断就说明 i!=nums[i]
                
            swap(nums[i],nums[nums[i]]);//当前值等于i索引之前的一个索引
        }
        return -1;
    }
};

04. 二维数组中的查找

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

示例:

现有矩阵 matrix 如下:

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

给定 target = 5,返回 true

给定 target = 20,返回 false

类似于二叉树的做法
class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(!matrix.size())  return false;//特殊情况判断
        int n=matrix[0].size();//列数
        int m=matrix.size();//行数
        for(int i=0,j=n-1;i<m && j>=0;){
            if(matrix[i][j] < target)   i++;
            else if(matrix[i][j] > target)    j--;
            else return true;
        }
        return false;
    }
};

10- II. 青蛙跳台阶问题

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

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

示例 1:

输入:n = 2
输出:2

示例 2:

输入:n = 7
输出:21

示例 3:

输入:n = 0
输出:1
动态规划,滑动窗口
class Solution {
public:
    int numWays(int n) {
        int ans[3]={0};
        ans[0]=1;
        ans[1]=1;
        if(n<2) return ans[n];
        for(int i=2;i<=n;i++){
            ans[2]=(ans[0]+ans[1])%1000000007;
            ans[0]=ans[1];
            ans[1]=ans[2];
        }
        return ans[2];
    }
};

11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2][1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

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

示例 2:

输入:[2,2,2,0,1]
输出:0
暴力法

(找到第一个比前面元素小的数)

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int max=INT_MIN;
        for(int num:numbers){
            if(num>=max){
                max=num;
            }
            else    return num;
        }
        return numbers[0];
    }
};
return numbers[left]【二分】

思路;

1、当 numbers[mid]<numbers[right]说明最小数在mid左边,并且有可能就是mid所以 right 只能等于 mid,因为不能完全排除mid

2、当 numbers[mid]>numbers[right]说明最小数在mid右边,且不包含mid,所以left=mid+1

3、当numbers[mid]==numbers[right] right--

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int left=0;
        int right=numbers.size()-1;
        int mid;
        while(left < right){//记住这种二分查找的格式
            mid=left+(right-left)/2;
            if(numbers[mid]<numbers[right]){//在左半边
                right=mid;
            }
            else if(numbers[mid]>numbers[right]){
                left=mid+1;
            }
            else    //numbers[mid]==numbers[right]
            right--;
        }
        return numbers[left];
    }
};

return numbers[right]【二分】
class Solution {
public:
    int minArray(vector<int>& numbers) {
        int left=0;
        int right=numbers.size()-1;
        int mid;
        while(left < right){
            mid=left+(right-left)/2;
            if(numbers[mid]<numbers[right]){//在左半边
                right=mid;
            }
            else if(numbers[mid]>numbers[right]){
                left=mid+1;
            }
            else    //numbers[mid]==numbers[right]
            right--;
        }
        return numbers[right];
    }
};

12. 矩阵中的路径

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

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

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

img

剪枝
class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        rows=board.size();//行
        cols=board[0].size();//列
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(dfs(board,word,i,j,0))   return true;//将该二维数组中的每一个元素都尝试作为首元素去搜索
            }
        }
        return false;
    }

    int rows,cols;//记录行列数
    
    bool dfs(vector<vector<char>>& board,string word,int i,int j,int k){
        if(i<0 || i>=rows || j<0 || j>=cols || word[k]!=board[i][j])    return false;	//如果超过索引范围或者不匹配就直接返回,如果能到下一步就说明匹配 word[k]!=board[i][j],这一步的return false其实是真正的剪枝,排除从该路径往下走的所有情况
        if(k==word.size()-1) return true;	//说明匹配成功,已经匹配到 word 元素中的最后一个元素
        board[i][j]='\0';	//记录,说明这个节点已经遍历过了
        bool ans=dfs(board,word,i-1,j,k+1) || dfs(board,word,i+1,j,k+1)	//查找该元素的上下左右所有元素
        		|| dfs(board,word,i,j-1,k+1) || dfs(board,word,i,j+1,k+1);
        board[i][j]=word[k];	//还原之前的元素,为了主函数下一次从下一个起点开始搜索的时不会受到剪枝后的干扰
        return ans;
    }
};

05. 替换空格

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

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."
class Solution {
public:
    string replaceSpace(string s) {
        if(s.size()==0) return s;
        string ans="";
        string temp="%20";
        for(int i=0;i<s.size();i++){
            if(s[i]==' '){
                
                ans+=temp;
            }
            else{
                
                ans+=s[i];
            }
        }
        return ans;

    }
};

13. 机器人的运动范围

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

示例 1:

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

示例 2:

输入:m = 3, n = 1, k = 0
输出:1
【迭代】官解(未看)
class Solution {
    int get(int x) {//用于获得一个十进制数每一位数字的和
        int res=0;
        for (; x; x /= 10){// x/=10 等价于 x>>1
            res += x % 10;
        }
        return res;
    }
    
public:
    int movingCount(int m, int n, int k) {
        if (!k) return 1;//k<=0
        vector<vector<int> > vis(m, vector<int>(n, 0));//定义一个存储坐标状态的数组
        int ans = 1;
        vis[0][0] = 1;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if ((i == 0 && j == 0) || get(i) + get(j) > k) continue;//vis[0][0]已经初始化为1了,所以可以跳过i=0和j=0的情况
                // 边界判断
                if (i - 1 >= 0) vis[i][j] |= vis[i - 1][j];//能到这一步说明i+j是小于k的,下面两步if是用于判断当前坐标是否与前面的可达坐标接壤
                if (j - 1 >= 0) vis[i][j] |= vis[i][j - 1];
                ans += vis[i][j];
            }
        }
        return ans;
    }
};
【递归】

(注意这段代码中数组前的&java不写&达到的效果是一样的 )

class Solution {
public:
    int movingCount(int m, int n, int k) {
        vector<vector<bool>> visited(m, vector<bool>(n, 0));//创建一个 m 行 n 列的数组
        return dfs(visited, m, n, k, 0, 0);
    }

    int dfs(vector<vector<bool>> &visited, int m, int n, int k, int i, int j) {
        if(i >= m || j >= n || visited[i][j] || bitSum(i) + bitSum(j) > k) return 0;//因为是以不满足为条件,所以可以从i=0和j=0开始
        visited[i][j] = 1;
        return 1 + dfs(visited, m, n, k, i + 1, j) + dfs(visited, m, n, k, i, j + 1);//往下或者往右走
    }
    
    int bitSum(int n) {//计算一个数字每一位相加后的结果
        int sum = 0;
        while(n > 0) {
            sum += n % 10;
            n /= 10; 
        }
        return sum;
    }
};

06. 从尾到头打印链表

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

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]
【栈】

(这种方法还是要用到数组,只不过是用栈来实现翻转)

class Solution {
public:
//思路:先按照顺序存储到数组中,再颠倒数组的顺序即可
    vector<int> reversePrint(ListNode* head) {
        vector<int> ans;
        stack<int> temp;
        int k=0;
        while(head){
            temp.push(head->val);
            head=head->next;
        }
        while(!temp.empty()){
            ans.push_back(temp.top());
            temp.pop();
        }
        return ans;
    }
};
【翻转数组】
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> ans;
        while(head){//将链表中的值一一记录到数组中
            ans.push_back(head->val);
            head=head->next;
        }
        reverse(ans.begin(),ans.end());//翻转数组
        return ans;
    }
};

07. 重建二叉树

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

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

示例 1:

img

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

示例 2:

Input: preorder = [-1], inorder = [-1]
Output: [-1]
【前序遍历是专门用来找根的】
class Solution {
private:
    unordered_map<int,int> in_map;//存储序遍历的值和索引,方便查找前序遍历中根在中序遍历中的位置
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.size()==0)  return NULL;
        for(int i=0;i<preorder.size();i++){
            in_map[inorder[i]]=i;//根据值来查数组索引
        }
        return find_build(preorder,inorder,0,0,inorder.size()-1);
    }

    TreeNode* find_build(vector<int>& preorder,vector<int>& inorder,int pre_root,int in_left_bound,int in_right_bound){//参数列表中的 pre_root 就是根节点的索引
        if(in_left_bound>in_right_bound)    return NULL;	//递归基
        int in_root=in_map[preorder[pre_root]];	//确定前序遍历的根在中序遍历的哪个索引位置
        TreeNode* root=new TreeNode(preorder[pre_root]);//创建该根节点
        root->left=find_build(preorder,inorder,pre_root+1,in_left_bound,in_root-1);	//链接左节点
        root->right=find_build(preorder,inorder,pre_root+(in_root-in_left_bound+1),in_root+1,in_right_bound);	//链接右节点
        return root;
    }
};

【未做】 14- I. 剪绳子

【未做】 14- II. 剪绳子 II

25. 合并两个排序的链表

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

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
【归并排序中的写法】
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1==nullptr || l2==nullptr) return l1 == nullptr ? l2 : l1;//边缘条件判断
        ListNode *ans=new ListNode(0);	//创建答案链表
        ListNode *curr=ans;	//curr为了保存ans这个指针而存在
        
        while(l1!=nullptr && l2!=nullptr){//进行归并两个链表
            while(l1!=nullptr&&l2!=nullptr&&l1->val<=l2->val){
                curr->next = l1;
                l1 = l1->next;
                curr=curr->next;
            }
            while(l1!=nullptr&&l2!=nullptr&&l1->val>=l2->val){
                curr->next=l2;
                l2=l2->next;
                curr=curr->next;
            }
        }
        
        if(l1!=nullptr)	curr->next=l1;
        else if(l2!=nullptr)	curr->next=l2;
        //下面这种写法是冗余的,因为这是链表,不是数组
        //while(l1!=nullptr){
        //    curr->next=l1;
        //    l1=l1->next;
        //    curr=curr->next;
        //} 
        //while(l2!=nullptr){
        //    curr->next=l2;
        //    l2=l2->next;
        //    curr=curr->next;
        //}
        
        return ans->next;

    }
};
【官解】

(其实写法和上面的本质上是一样的 ,这个更简洁)

class Solution {
    public:
    ListNode* mergeTwoLists(ListNode *l1, ListNode *l2) {
        ListNode *ans = new ListNode(0); 
        ListNode *curr = ans;//这是个头结点
        while(l1!=nullptr && l2!=nullptr) {
            if(l1->val < l2->val) {
                curr->next = l1;
                l1 = l1->next;
            }
            else {
                curr->next = l2;
                l2 = l2->next;
            }
            curr = curr->next;
        }
        curr->next = l1 != nullptr ? l1 : l2;
        return ans->next;
    }
};

26. 树的子结构

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

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

例如:
给定的树 A:

3
/
4 5
/
1 2
给定的树 B:

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

DFS

一个负责控制需要遍历的树的起点,一个 DFS 控制以该节点的树是否是一个子结构

class Solution {
public:
//思路:通过两次DFS进行扫描,一个DFS负责控制扫描的起点,一个用来控制对树的扫描,A>B
    bool isSubStructure(TreeNode* A, TreeNode* B) {//遍历A直到最后,或者提前退出
    //两个递归基,1、找到一样的结构,2、直到全部遍历完都没有找到一样的结构
    if(A==nullptr||B==nullptr)  return false;
        return (DFS(A,B) || isSubStructure(A->left,B) || isSubStructure(A->right,B));
    }

    bool DFS(TreeNode* A,TreeNode* B){//遍历B直到最后
        //递归基:1、在第一次遇见不匹配的项目是就return false,2、在全部遍历完之后发现全部匹配,返回true
        if(B==nullptr)   return true;	//当B该支路遍历到底后,说明无需继续向下搜索
        if(A==nullptr || A->val!=B->val)  return false;
        //执行下面一句的隐含条件是,A!= nullptr && B!= nullptr && A->val == B->val
        return DFS(A->left,B->left) && DFS(A->right,B->right);
    }
};

27. 二叉树的镜像(简单)

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

例如输入:

4 / \ 2 7 / \ / \ 1 3 6 9
镜像输出:

  4
/   \
7     2
/ \   / \
9   6 3   1

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
【DFS】
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
         while(root==nullptr)    return root;
        TreeNode* temp;
        temp=root->left;
        root->left=mirrorTree(root->right);
        root->right=mirrorTree(temp);
        return root;
    }
};

28. 对称的二叉树(简单)

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

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

1

/
2 2
/ \ /
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:

1
/ \
2   2
\   \
3    3

示例 1:

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

示例 2:

输入:root = [1,2,2,null,3,null,3]
输出:false
【DFS】
class Solution {
public:
//思路:复制一棵树,一棵树从优先向左子树遍历,一颗树优先向右子树遍历
    bool isSymmetric(TreeNode* root) {
        return DFS(root,root);
    }

    bool DFS(TreeNode* t1,TreeNode* t2){
        if(t1==nullptr && t2==nullptr)   return true;//递归基可以有两个
        if(t1==nullptr || t2==nullptr || t1->val != t2->val)    return false;
        return DFS(t1->left,t2->right) && DFS(t1->right,t2->left);
    }
};

【未做】 20. 表示数值的字符串

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

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

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。
【构造了新数组的头尾指针】
class Solution {
public:
//奇数位从数组首部输入数组,偶数位从数组尾部输入数组
    vector<int> exchange(vector<int>& nums) {
        vector<int> ans(nums.size(),0);//新构建的数组
        int high=nums.size()-1;
        int low=0;
        for(int num:nums){
            if(num%2!=0){//说明i是偶数
                ans[low++]=num;//遇到奇数从前往后存储
            }
            else{
                ans[high--]=num;//遇到偶数从后往前存储
            }
        }
        return ans;
    }
};
【快慢指针】容易理解
class Solution {
public:
//思路就是,slow留在前面的第一个偶数处,然后fast去后面寻找奇数,找到了就把这个奇数和前面的偶数换位置,fast经过的地方都是偶数,然后slow++到达下一个偶数,等待fast找到下一个奇数
    vector<int> exchange(vector<int>& nums) {
        int slow=0,fast=0;
        for(int slow=0,fast=0;fast<nums.size();fast++){//fast负责寻找奇数,slow负责待在第一个偶数处
            if(nums[fast]%2!=0){//是奇数
                swap(nums[slow],nums[fast]);
                slow++;
            }
        }
        return nums;
    }
};

15. 二进制中1的个数

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

提示:

  • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
  • 在 Java 中,编译器使用 二进制补码 记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3

示例 1:

输入:n = 11 (控制台输入 00000000000000000000000000001011)
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。

示例 2:

输入:n = 128 (控制台输入 00000000000000000000000010000000)
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。

示例 3:

输入:n = 4294967293 (控制台输入 11111111111111111111111111111101,部分语言中 n = -3)
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
【检验每一位】
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int ans=0;
        for(int i=0;i<32;i++){
            if(n&(1<<i))    ans++;
        }
        return ans;

    }
};
【位运算法】
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int ans=0;
        while(n){
            n&=(n-1);//去除最右边那一位的 1
            ans++;
        }
        return ans;
    }
};

29. 顺时针打印矩阵

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

示例 1:

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

示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
普通法
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.empty()) return {};
        vector<int> res;
        int l = 0;                       //左边界
        int r = matrix[0].size() - 1;    //右边界
        int t = 0;                       //上边界 top
        int b = matrix.size() - 1;       //下边界 bottom
        while(true){
            //从左往右
            //列在变,列为循环值
            //从左往右的下一步是往下走,上边界内缩,故++t
            for(int i = l; i <= r; i++) res.push_back(matrix[t][i]);//注意push_back的使用
            if(++t > b) break;//说明上一个循环中的t=b,那么就意味着已经遍历完了,这里的if条件也可以写成if(t++ >= b)
            //从上往下,行在变
            //从上往下的下一步是从右往左,右边界收缩,--r
            for(int j = t; j <= b; j++) res.push_back(matrix[j][r]);
            if(--r < l) break;
            //从右向左,列在变
            //从右往左的下一步是从下往上,下边界收缩,--b
            for(int i = r; i >= l; i--) res.push_back(matrix[b][i]);
            if(--b < t) break;
            //从下到上,行在变
            //从下到上的下一步是从左到右,左边界收缩,++l
            for(int i = b; i >= t; i--) res.push_back(matrix[i][l]);
            if(++l > r) break;
        }
        
        return res;
    } 
};

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

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

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

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.
【栈】
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        stack<ListNode*> S;
        while(head){	//将链表指针压入栈中
            S.push(head);
            head=head->next;
        }
        
        for(int i=1;i<k;i++){//把k-1个节点弹出栈,剩下的top就是第k个节点
            S.pop();
        }
        return S.top();
    }
};
【双指针】
class Solution {
public:
//双指针
    ListNode* getKthFromEnd(ListNode* head, int k) {
        if(head==nullptr)    return {};
        ListNode* later=head;
        ListNode* former=head;
        while(k--){	// 这个 while 执行完之后 former-later = k
            former=former->next;
        }
        
        while(former){	// 当former 移动到 nullptr 时,later 就到达了倒数第 k 个元素
            later=later->next;
            former=former->next;
        }
        return later;
    }
};

16. 数值的整数次方

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

示例 1:

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

示例 2:

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

示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
【官解】二分递归法
class Solution {
public:
    double myPow(double x, int n) {
        if(n == 0) return 1.0;
        //Java中因为n的最小值可以取到Integer.MIN_VALUE,如果直接取它的相反数的话还是它自己,会导致堆栈溢出,因此提一个x出来,具体看代码
        else if(n < 0) return 1 / (x*myPow(x, -(n+1)));//这里的+1是为了防止奇数越界,因为奇数可以表达的范围大一点
        else if(n % 2 == 1) return x * myPow(x, n - 1);//如果为奇数,就把其中一个x单独拿出来把n恢复成偶数
        else return myPow(x * x, n / 2);//如果为偶数,其实上面两步最终都会把n转换成偶数来算,x的平方等于 x*x 的n/2次幂
        
        //if(n%2==0)  return myPow(x,n/2)*myPow(x,n/2);
        //else  return x*myPow(x,(n-1)/2)*myPow(x,(n-1)/2);
    }
};
官解递归自己重新写了一遍
class Solution {
public:
    double myPow(double x, int n) {
        if(n==0)    return 1;
        if(n<0) return 1/(x*myPow(x,-(n+1)));
        if(n%2==1)  return x*myPow(x,n-1);
        return myPow(x*x,n/2);
    }
};
【二分迭代法】
class Solution {
public:
    double myPow(double x, int n) {
        if(n==0)    return 1;
        double ans=1;
        long b=n;//n为负数时防止-n溢出
        if(n<0) {
            x=1/x;
            b = - b;
        }
        while(b){//这个关系直接背
            if(b%2==1){
                ans*=x;
            }
            b/=2;//如果上面b是奇数的话,在/2的时候,会被直接舍去,所以b不用减1
            x *= x;
        }
        return ans;
    }
};

7.23

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

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

示例 1:

输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
【暴力法】
class Solution {
public:
//思路:n位最大数=n+1位1000-1
    vector<int> printNumbers(int n) {
        vector<int> res;
        int max=pow(10,n)-1;
        for(int i=1;i<=max;i++){
            res.push_back(i);
        }
        return res;
    }
};
【递归大数打印法】
class Solution {
private:
string s;
vector<int> ans;//用来存储打印结果的数组
public:
//1、DFS    2、去0
    vector<int> printNumbers(int n) {
        s.resize(n,'0');
        DFS(n,0);
        return ans;
    }

    void DFS(int end,int index){	//递归按位填充
        if(index==end){	//说明每一位都填充好了,可以进行保存当前的数字组合,每个数字都要经历一次完整的递归到底
            save();return;
        }
        for(int i=0;i<=9;++i){
            s[index]='0'+i;
            DFS(end,index+1);	//递归填充下一位
        }
    }

    void save(){
        int ptr=0;
        while(ptr<s.size() && s[ptr]=='0')  ptr++;	//去0
        if(ptr!=s.size())   ans.emplace_back(stoi(s.substr(ptr)));	//这里也可以使用push_back
    }
};

【未做】 19. 正则表达式匹配

24. 反转链表(简单)

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

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
【普通法】
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* curr=head;
        ListNode* pre=nullptr; //因为反转链表后的链表的尾节点是空节点,所以此处pre设为nulptr
        while(curr){
            ListNode* temp=curr->next;
            curr->next=pre;
            pre=curr;
            curr=temp;
        }
        return pre;
    }
};

18. 删除链表的节点(简单)

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

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

**注意:**此题对比原题有改动

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
【暴力法】
class Solution {
public:
//遍历直到找到这个数,
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode* pre=head;
        ListNode* curr=head->next;
        if(head->val==val)  return head->next;
        while(curr){
            if(curr->val==val)    pre->next=curr->next;
            pre=pre->next;
            curr=curr->next;
        }
        return head;
    }
};

35. 复杂链表的复制(中等)

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

示例 1:

img

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

示例 2:

img

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

示例 3:

img

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

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
【比较笨的哈希】自己想的
class Solution {
public:
//思路:把地址依次存储到数组中
    Node* copyRandomList(Node* head) {
        Node* head2=head;//保存原来链表的头结点
        unordered_map<Node*,int>cube;//存储原链表中所有的节点地址
        int k=0;
        vector<Node*> new_cube;//存储新链表中所有的节点地址
        Node* head1=new Node(0);//新建链表的头结点
        Node* temp=head1;
        //将链表按顺序复制,并复制next
        while(head){ 
            cube[head]=k++;//存储原链表节点的地址与序号的对应关系
            Node* node=new Node(head->val);
            head=head->next;
            new_cube.push_back(node);//从除了头结点外的第一个结点开始放入,是和哈希表中的一一对应的
            temp->next=node;
            temp=node;
        }
        temp=head1->next;
        //复制链表的random
        while(head2){
            if(head2->random)   temp->random=new_cube[cube[head2->random]];
            else    temp->random=nullptr;
            head2=head2->next;
            temp=temp->next;
        }
        return head1->next;
    }
};
【聪明的哈希】,将结点一一对应
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;	//边缘条件判断
        Node* cur = head;	//curr指向旧链表头结点
        unordered_map<Node*, Node*> map;

        // 1. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射(将新链表节点的地址和原链表节点的地址一一对应)
        while(cur != nullptr) {	//第一个 while 建立每一个对应于旧链表节点的节点
            map[cur] = new Node(cur->val);	//key 为旧链表节点地址,val为新链表节点地址
            cur = cur->next;
        }
        cur = head;	//再次指向旧链表的头部
        
        // 2. 构建新链表的 random 和 next 指向
        while(cur != nullptr) {	
            map[cur]->next = map[cur->next];//与cur对应的新链表结点的next指向与curr->next对应的链表结点
            map[cur]->random = map[cur->random];
            cur = cur->next;
        }
        // 3. 返回新链表的头节点
        return map[head];
    }
};

40. 最小的k个数

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

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]
【普通快速排序】
class Solution {
public:
//思路:1、排序 
//快速排序:
    int partition(vector<int>& arr,int low,int high){
        int pivot=arr[low];
        while(low<high){
            while(low<high && pivot <= arr[high]) high--;
            arr[low]=arr[high];
            while(low<high && pivot > arr[low]) low++;
            arr[high]=arr[low];
        }
        arr[low]=pivot;
        return low;


    }
    void quickSort(vector<int>& arr,int low,int high){
        if(low<high){
            int pivot=partition(arr,low,high);
            quickSort(arr,low,pivot);
            quickSort(arr,pivot+1,high);
        }
    }
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        quickSort(arr,0,arr.size()-1);
        vector<int> ans(k,0);
        for(int i=0;i<k;i++){
            ans[i]=arr[i];
        }
        return ans;
    }
};
【缩减版的快速排序】效率高
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        if (k >= arr.size()) return arr;
        return quickSort(arr, k, 0, arr.size() - 1);
    }
private:
    vector<int> quickSort(vector<int>& arr, int k, int l, int r) {
        int i = l, j = r;
        while (i < j) {
            while (i < j && arr[j] >= arr[l]) j--;// 以 arr[l] 作为 pivot
            while (i < j && arr[i] <= arr[l]) i++;
            swap(arr[i], arr[j]);
        }
        swap(arr[i], arr[l]);

        if (i > k) return quickSort(arr, k, l, i - 1);//枢纽所在的位置在第k个元素之后,题目要求的k个元素都在i个已经筛选好的元素中
        if (i < k) return quickSort(arr, k, i + 1, r);//枢纽所在的位置在第k个元素之前,这样的话,就省去了前i个元素排序的过程

        vector<int> res;	//到这一步说明枢纽 i=k ,也就是 i 之前的数就是最小的 k 个数
        res.assign(arr.begin(), arr.begin() + k);
        return res;
    }
};
【堆的思想,看一下】(队列就是堆实现的)
class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector<int> vec(k, 0);
        if (k == 0) { // 排除 0 的情况
            return vec;
        }
        priority_queue<int> Q;
        for (int i = 0; i < k; ++i) {//将k个元素压入队列
            Q.push(arr[i]);
        }
        for (int i = k; i < (int)arr.size(); ++i) {
            if (Q.top() > arr[i]) {//如果队列的顶部(即为当前队列的最大值)大于后续元素,就弹出队列,然后压入新的元素
                Q.pop();
                Q.push(arr[i]);
            }
        }
        for (int i = 0; i < k; ++i) {//将结果输出到答案数组中
            vec[i] = Q.top();
            Q.pop();
        }
        return vec;
    }
};

30. 包含min函数的栈

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

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.min();   --> 返回 -2.
【辅助栈】
class MinStack {
private:
    stack<int> S,min_stack;
public:
    /** initialize your data structure here. */
    MinStack() {
        min_stack.push(INT_MAX);//先存入一个最大值
    }
    
    void push(int x) {//辅助栈和实际栈一一对应,每插入一个,辅助栈中都对应这一个最小值
        if(x<min_stack.top()){
            min_stack.push(x);
        }
        else    min_stack.push(min_stack.top());
        S.push(x);
    }
    
    void pop() {
        S.pop();
        min_stack.pop();
    }
    
    int top() {
        return S.top();
    }
    
    int min() {
        return min_stack.top();
    }
};

41. 数据流中的中位数

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

例如,

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

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

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

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

示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
【优先级队列】,还有一题也用到了优先级队列,记得总结
class MedianFinder {
public:
//按照从小到大的顺序排列的话,最大堆 maxHeap 在左,小根堆 minHeap 在右,

    // 最大堆,存储左边一半的数据,堆顶为最大值, 存放比较小的一半数,因为最大堆中的数都是从最小堆中插入进来的
    priority_queue<int, vector<int>, less<int>> maxHeap;
    // 最小堆, 存储右边一半的数据,堆顶为最小值
    priority_queue<int, vector<int>, greater<int>> minHeap;
    /** initialize your data structure here. */
    MedianFinder() {
    }

    //因为m==n时是将num最终插入到minHeap中的,所以最终minHeap中的元素个数>=maxHeap中的元素个数
    // 因为插入小根堆中的数据要确保比大根堆中所有的元素都小,所以需要先进小根堆,元素插入大根堆时同理
    void addNum(int num) {
        if (maxHeap.size() == minHeap.size()) {	//m==n,最终将num插入minHeap中,但是要先进maxHeap,再把maxHeao的堆顶元素压入minHeap,防止maxHeap中有比num还要大的数
            maxHeap.push(num);
            int top = maxHeap.top();
            maxHeap.pop();
            minHeap.push(top);
        } else {	// m < n,元素插入大根堆
            minHeap.push(num);
            int top = minHeap.top();
            minHeap.pop();
            maxHeap.push(top);
        }
    }
    
    double findMedian() {
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.top()+minHeap.top())*1.0/2;
        } else {
            return minHeap.top()*1.0;
        }
    }
};
【优先级队列】重新练一遍
class MedianFinder {
private:
    priority_queue<double,vector<double>,greater<double>> minHeap;//小根堆,在右边
    priority_queue<double,vector<double>,less<double>> maxHeap;//大根堆,在左边
public:
    /** initialize your data structure here. */
    MedianFinder() {

    }
    
    void addNum(int num) {//优先插右边minHeap,所以右边的minHeap的长度>=maxHeap
        if(minHeap.size()==maxHeap.size()){
            maxHeap.push(num);
            double top=maxHeap.top();
            maxHeap.pop();
            minHeap.push(top);
        }
        else{
            minHeap.push(num);
            double top=minHeap.top();
            minHeap.pop();
            maxHeap.push(top);
        }
    }
    
    double findMedian() {
        if(minHeap.size()==maxHeap.size())    return (minHeap.top()+maxHeap.top())/2;
        else    return minHeap.top();
    }
};

42. 连续子数组的最大和

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

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

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
【动态规划】

以当前元素结尾的 数组和 最大值等于max(他前面的最大值,他前面的最大值+当前元素)

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> temp(nums.size(),0);
        temp[0]=nums[0];
        for(int i=1;i<nums.size();i++){
            temp[i]=max(nums[i],nums[i]+temp[i-1]);
        }
        sort(temp.begin(),temp.end());//sort默认是从小到大排列,等价于less()
        return temp[nums.size()-1];
    }
};

36. 二叉搜索树与双向链表(困难)

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

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

img

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

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

img

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

【中序遍历】
class Solution {
    //pre->left=cur cur->right=pre
private:
    Node* pre,*head;
public:

    Node* treeToDoublyList(Node* root) {
        if(root==nullptr)   return nullptr;	//边界条件
        DFS(root);
        
        pre->right=head;	//连接头尾节点,在DFS结束后,pre指向了最后的尾结点
        head->left=pre;
        
        return head;
    }

    void DFS(Node* cur){
        if(cur==nullptr)   return;
        
        DFS(cur->left);	//左
        
        if(pre!=nullptr)    pre->right=cur;	//根
        else    head=cur;	//这个if else 是针对头结点的处理而存在
        cur->left=pre;	//为什么这里不能用右子树指向前一个节点是因为下面还有往右子树的递归,如果这句改变了右节点的指向,那么后面的递归都将乱套
        pre=cur;

        DFS(cur->right);	//右
    }
};

31. 栈的压入、弹出序列

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

示例 1:

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

示例 2:

输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
【辅助栈】
class Solution {
public:
//创建一个辅助栈来模拟出栈和入栈的过程
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int>  S;
        int k=0;
        for(int num:pushed){
            S.push(num);
            while(!S.empty() && S.top()==popped[k]){
                S.pop();
                k++;
            }
        }
        return S.empty();
    }
};

7.24

37. 序列化二叉树

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

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

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

示例:

img

输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
【DFS】
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:
//记录原本的二叉树的节点
//思路,DFS该二叉树,为空就记录"None,",有值的话就记录“val,”
    void rserialize(TreeNode* root,string& str){
        if(root==nullptr){
            str+="None,";
        }
        else{
            str+=to_string(root->val)+",";
        
        rserialize(root->left,str);//之所以这两行要放在else中是因为放在外面会导致二叉树为空也会进行递归
        rserialize(root->right,str);
        }
    }
    
    string serialize(TreeNode* root) {
        string res;
        rserialize(root,res);
        return res;
    }

    TreeNode* rdeserialize(list<string>& temp){//把list转化成二叉树
            if(temp.front()=="None"){
                temp.erase(temp.begin());
                return nullptr; 
            }
            TreeNode* node=new TreeNode(stoi(temp.front()));
            temp.erase(temp.begin());
            node->left=rdeserialize(temp);
            node->right=rdeserialize(temp);
            return node;
    }


    TreeNode* deserialize(string data) {
    //先把data转化成list
    list<string> temp;
    string str;
    for(auto ch:data){
        if(ch==','){
            temp.push_back(str);
            str.clear();
        }
        else{
            str+=ch;
        }
    }
    
    return rdeserialize(temp);
    }
};

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

38. 字符串的排列

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

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

示例:

输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
【回溯法】
class Solution {
public:
    vector<string> permutation(string s) {
        DFS(s,0);
        return res;
    }

    vector<string>  res;
    void DFS(string &s,int x){
        if(x==s.size()-1){
            res.push_back(s);
            return;
        } 
        set<int> st;
        for(int i=x;i<s.size();i++){
            if(st.find(s[i])!=st.end())    continue;
            st.insert(s[i]);
            swap(s[i],s[x]);
            DFS(s,x+1);
            swap(s[i],s[x]);
        }
    }

};

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

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

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

示例 1:

输入:n = 12
输出:5

示例 2:

输入:n = 13
输出:6
【固定每一位的1】
class Solution {
public:
/*思路:对每一位进行判定是否有1
1、限定要转化的位数
2、判断n中该位cur与1的关系,分类处理
n分为3个部分:a cur b
base=k(当前位数),b=n%base,a=n/base/10,cur=n/base%10
cur>1时,令cur直接等于1,a的范围是 0~a,b的范围是0~10^base-1
cur==1时,令cur=1,当a的范围是0~a-1,b的范围是0~10^base-1
                  当a的范围是a=a时,b的范围是0~b
cur<1时,令cur=1,a的范围是0~a-1,b的范围是0~10^base-1
将这些情况全部加起来就是最终的结果
*/
    int countDigitOne(int n) {
        int temp_n=n;//用于控制循环次数
        int base=0;//记录当前的位数
        int k=0;//记录n的总位数
        int cur=0;//记录当前位上的数字
        int a=0;//记录当前cur的前半部分
        int b=0;//记录当前位cur的后半部分
        int count=0;//记录1存在的总的个数
        //需要单独排除两种特殊情况
        while(temp_n){//获得n的总位数
            k++;
            temp_n/=10;
        }

        for(int i=1;i<=k;i++){//掐头去尾(排除了两种特殊情况),i代表cur所在的位数
            base=pow(10,i-1);
            cur=(n/base)%10;
            a=n/base/10;
            b=n%base;

            if(cur>1){
                count+=(a+1)*base;
            }
            else if(cur==1){
                count+=a*base;
                count+=1*(b+1);
            }
            else if(cur==0){
                count+=a*base;
            }

        }
        return count;

    }
};

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

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

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

示例 1:

输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
【multi哈希表】超时
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_multiset<int> st;
        for(int num:nums){
            st.insert(num);
        }
        int max=0;
        int ans=0;
        for(int num:st){
            if(st.count(num)>max){
                max=st.count(num);
                ans=num;
            }   
        }
        return ans;
    }
};
【库函数排序】
class Solution {
public:
//快速排序
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        return nums[(nums.size()-1)/2];
    }

    void quickSort(vector<int>& nums,int l,int r){
        if(l>=r)    return;
        int i=l;
        int j=r;
        while(i<j){
            while(i<j&&nums[i]<=nums[l]) i++;
            while(i<j&&nums[j]>=nums[l]) j--;
            swap(nums[i],nums[j]);
        }
        swap(nums[l],nums[i]);
        quickSort(nums,l,i-1);
        quickSort(nums,i+1,r);
    }
};
【自己的哈希】(用map的效率比set高)
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int,int> mp;
        int max=0;
        int ans=0;
        for(int num:nums){
            mp[num]++;
            if(mp[num]>max){
                max=mp[num];
                ans=num;
            }   
        }
        return ans;
    }
};
【摩尔投票】
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int candicate=-1;
        int count=0;
        for(int num:nums){
            if(candicate==num){
                count++;
            }
            else if(--count<0){
                candicate=num;
                count=1;
            }
        }
        return candicate;
    }
};

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

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

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

 3
/ \
9  20
 /  \
15   7

返回:

[3,9,20,15,7]
【队列实现层序遍历】
/**
 * 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) {
        vector<int> res;
        if(root==nullptr)   return res;
        queue<TreeNode*> Q;
        Q.push(root);
        while(Q.size()){
            TreeNode* node=Q.front();
            Q.pop();
            res.push_back(node->val);
            if(node->left)  Q.push(node->left);
            if(node->right) Q.push(node->right);
        }
        return res;
    }
};

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

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

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

 3
/ \
9  20
 /  \
15   7

返回其层次遍历结果:

[
[3],
[9,20],
[15,7]
]
【BFS】注意其中for的使用
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode*> q;
        vector<vector<int> > ans;
        if(root==NULL){
            return ans;
        }
        q.push(root);

        while(!q.empty()){
            vector<int> temp;
            //每进一次for都会把一层的node压入队列
            for(int i=q.size();i>0;i--){
                TreeNode* node = q.front();
                q.pop();
                temp.push_back(node->val);
                if(node->left!=NULL) q.push(node->left);
                if(node->right!=NULL) q.push(node->right);
            }

            ans.push_back(temp);
        }

        return ans;
    }
};

44. 数字序列中某一位的数字

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

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

示例 1:

输入:n = 3
输出:3

示例 2:

输入:n = 11
输出:0
【找规律】
class Solution {
public:
    int findNthDigit(int n) {
        int digit=1;
        int number=1;
        long start=1;
        long count=9*digit*start;
        while(n>count){
            n-=count;
            digit+=1;
            start*=10;
            count=9*digit*start;
        }
        number=start+(n-1)/digit;
        string str=to_string(number);
        return str[(n-1)%digit]-'0';
    }
};

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

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

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

 3
/ \
9  20
 /  \
15   7

返回其层次遍历结果:

[
[3],
[20,9],
[15,7]
]
【BFS】
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if(root==nullptr)   return {};
        vector<vector<int>> res;
        queue<TreeNode*> q;
        q.push(root);
        int k=1;//记录打印的行数
        while(q.size()){
            vector<int> temp;
            for(int i=q.size();i>0;i--){
                TreeNode* node=q.front();
                q.pop();
                temp.push_back(node->val);
                if(node->left)  q.push(node->left);
                if(node->right) q.push(node->right);
            }
            if(k%2==1){//为奇数
                res.push_back(temp);
            }
            else{
                reverse(temp.begin(),temp.end());
                res.push_back(temp);
            }
            k++;
        }
        return res;
    }
};

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

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

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

  5
 / \
2   6
/ \
1   3

示例 1:

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

示例 2:

输入: [1,3,2,6,5]
输出: true
递归
class Solution {
public:
     bool verifyPostorder(vector<int>& postorder) {
        return recur(postorder, 0, postorder.size() - 1);
    }
    bool recur(vector<int>& postorder, int i, int j) {//i为当前索引,j为根节点元素索引
        if(i >= j) return true;//如果i和j之间只有一个元素,返回true
        int p = i;//用p从i开始往后搜索
        while(postorder[p] < postorder[j]) p++;//左子树为从 0 到 m-1
        int m = p;//记录第一个大于根节点的元素
        while(postorder[p] > postorder[j]) p++;//右子树为 m 到 j-1
        return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);//p==j保证当前根节点下的左右子树满足条件
    }
};

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

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

示例:

s = "abaccdeff"
返回 "b"

s = "" 
返回 " "
【哈希表】
class Solution {
public:
    char firstUniqChar(string s) {
        unordered_map<char,int> mp;
        for(char str:s){
            mp[str]++;
        }
        for(char str:s){
            if(mp[str]==1){
                return str;
            }
        }
        return ' ';
    }
};

7.25

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

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

示例:
给定如下二叉树,以及目标和 target = 22

           5
          / \
         4   8
        /   / \
       11  13  4
      /  \    / \
     7    2  5   1

返回:

[
[5,4,11,2],
[5,8,4,5]
]
【利用target-val的思想】
/**
 * 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 {
public:
    vector<vector<int>> ret;
    vector<int> temp;
    void DFS(TreeNode* root,int target){
        if(root==nullptr)   return;

        temp.push_back(root->val);
        target-=root->val;
        if(root->left==nullptr&&root->right==nullptr&&target==0){
            ret.push_back(temp);
        }
        if(root->left)  DFS(root->left,target);
        if(root->right) DFS(root->right,target);
        temp.pop_back();
    }

    vector<vector<int>> pathSum(TreeNode* root, int target) {
        DFS(root,target);
        return ret;
    }
};

51. 数组中的逆序对

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

示例 1:

输入: [7,5,6,4]
输出: 5
【归并排序】
class Solution {
public:
    int mergeSort(vector<int>& nums,vector<int>& tmp,int l,int r){
        if(l>=r)    return 0;
        int mid=(l+r)/2;
        int count=mergeSort(nums,tmp,l,mid)+mergeSort(nums,tmp,mid+1,r);
        int i=l,j=mid+1,pos=l;
        while(i<=mid&&j<=r){
            if(nums[i]<=nums[j]){
                tmp[pos]=nums[i];
                count+=j-(mid+1);
                i++;
            }
            else{
                tmp[pos]=nums[j];
                j++;
            }
            pos++;
        }

        while(i<=mid){
            tmp[pos++]=nums[i++];
            count+=(r-(mid+1)+1);//或者 =j-(m+1);
        }   
        while(j<=r) tmp[pos++]=nums[j++];
        for(int i=l;i<=r;i++)   nums[i]=tmp[i];
        return count;

    }

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

55 - I. 二叉树的深度

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

例如:

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

 3
/ \
9  20
 /  \
15   7

返回它的最大深度 3 。

【DFS】
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
int left_deep=0;
int right_deep=0;
    int maxDepth(TreeNode* root) {
        if(root==nullptr)   return 0;
        int left_deep=maxDepth(root->left)+1;
        int right_deep=maxDepth(root->right)+1;
        return max(left_deep,right_deep);
    }
};

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

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

示例 1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]

示例 2:

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
【排序法】
class Solution {
public:

    vector<int> singleNumbers(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        vector<int> res;
        for(int i=0;i<nums.size();){
            if(i+1<nums.size() && nums[i]==nums[i+1])  i+=2;
            else{
                res.push_back(nums[i]);
                i++;
            }
        }
        return res;
    }
};
【哈希表】
class Solution {
public:
//哈希表法
    vector<int> singleNumbers(vector<int>& nums) {
        unordered_map<int,int>  tmp;
        vector<int> res;
        for(int num:nums){
            tmp[num]++;
        }
        for(auto num:tmp){
            if(num.second==1)  res.push_back(num.first);
        } 
        return res;
    }
};
【异或位运算】
class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int stat=0;//因为0和所有数异或都是本身
        for(int num:nums){
            stat^=num;
        }
        int tmp=stat&(-stat);
        int ans1=0,ans2=0;
        for(int num:nums){
            if(num & tmp)   ans1^=num;
            else    ans2^=num;
        }
        return {ans1,ans2};
    }
};

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

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

示例 1:

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

示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1
【有限状态机】
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(int i=0;i<32;i++){
            int count=0;
            for(int num:nums){
                count+=((num>>i)&1);
            }
            if(count%3==1)  res|=1<<i;
        }
        return res;
    }
};

57. 和为s的两个数字

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

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
【双指针】
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        while(left<right){
            if(nums[left]+nums[right]>target)   right--;
            else if(nums[left]+nums[right]<target)  left++;
            else    return {nums[left],nums[right]};
        }
        return {};
    }
};

45. 把数组排成最小的数

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

示例 1:

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

示例 2:

输入: [3,30,34,5,9]
输出: "3033459"
【快速排序】
class Solution {
public:
//快速排序
    string minNumber(vector<int>& nums) {
        quickSort(nums,0,nums.size()-1);
        string ans;
        for(int num:nums){
            ans+=to_string(num);
        }
        return ans;
    }

    void quickSort(vector<int>& nums,int l,int r){
        if(l>=r)    return;
        int i=l,j=r;
        while(i<j){
            while(i<j&&smaller(nums[l],nums[j])) j--;//nums[j]>=nums[l]
            while(i<j&&smaller(nums[i],nums[l])) i++;//nums[i]<=nums[l]
            swap(nums[i],nums[j]);
        }
        swap(nums[l],nums[i]);
        quickSort(nums,l,i);
        quickSort(nums,i+1,r);
    }

    bool smaller(int x,int y){
        string str_x=to_string(x);
        string str_y=to_string(y);
        string res1=str_x+str_y;
        string res2=str_y+str_x;
        if(res1<=res2){
            return true;
        }
        return false;
    }
};
【内置排序】
class Solution {
public:
//快速排序
    string minNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end(),[](int x,int y){return to_string(x)+to_string(y)<to_string(y)+to_string(x);});
        string ans;
        for(int num:nums){
            ans+=to_string(num);
        }
        return ans;
    }
};

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

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

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

示例 1:

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

示例 2:

输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
【暴力枚举】
class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        vector<vector<int>> res;
        for(int i=1;i<target;i++){
            vector<int> tmp;
            tmp.push_back(i);
            int tar=target-i;

            for(int j=i+1;j<=tar;j++){
                
                if(tar>0){
                    tar-=j;
                    tmp.push_back(j);
                }
                if(tar==0){
                    res.push_back(tmp);
                }  
            }

        }
        return res;
    }
};
【双指针法】
class Solution {
public:
//双指针
    vector<vector<int>> findContinuousSequence(int target) {
        int low=1;
        int high=low+1;
        vector<vector<int>> res;
        while(low<target){
            vector<int> tmp;
            int sum=(low+high)*(high-low+1)/2;
            if(sum==target){
                for(int i=low;i<=high;i++){
                    tmp.push_back(i);
                }
                res.push_back(tmp);
                low++;
            }
            else if(sum<target) high++;
            else    low++;
        }
        return res;
        
    }
};

46. 把数字翻译成字符串

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

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
【动态规划】斐波那契数列
class Solution {
public:
    int translateNum(int num) {
        string str=to_string(num);
        if(str.size()==1||str.size()==0){
            return 1;
        }
        int prepre=1;
        int pre;
        if(stoi(str.substr(0,2))<=25){
            pre=2;
        }
        else    pre=1;
        int n=str.size();
        int cur=0;
        for(int i=2;i<n;i++){
            if(stoi(str.substr(i-1,2))<=25&&stoi(str.substr(i-1,2))>=10){
                cur=pre+prepre;
            }
            else    cur=pre;
            
            prepre=pre;
            pre=cur;
        }
        return pre;
        
    }
};

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

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

如下面的两个链表**:**

img

在节点 c1 开始相交。

示例 1:

img

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

img

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

img

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
【普通方法】
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* A=headA;
        ListNode* B=headB;
        while(A!=B){
            A=A==nullptr?headB:A->next;
            B=B==nullptr?headA:B->next;
        }
        return A;
    }
};

7.26

47. 礼物的最大价值

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

示例 1:

输入: 
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
【动态规划】
class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int m=grid.size();//行
        int n=grid[0].size();//列
        for(int i=1;i<m;i++){
            grid[i][0]+=grid[i-1][0];
        }
        for(int j=1;j<n;j++){
            grid[0][j]+=grid[0][j-1];
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                grid[i][j]+=max(grid[i-1][j],grid[i][j-1]);
            }
        }
        return grid[m-1][n-1];
    }
};

58 - I. 翻转单词顺序

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

示例 1:

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

示例 2:

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

示例 3:

输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
暴力法
class Solution {
public:
    string reverseWords(string s) {
        int n=s.size();
        if(n == 0) return s;
        int r=n-1;
        string ans="";
        while(r>=0){
            while(r>=0&&s[r]==' ')   r--;//排除空格
            if(r<0) break;
            int l=r;
            while(l>=0&&s[l]!=' ')   l--;
            ans+=s.substr(l+1,(r-l));
            ans+=' ';
            r=l;
        }
        ans.pop_back();
        return ans;
    }
};

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

统计一个数字在排序数组中出现的次数。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: 2

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
【双指针法】
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n=nums.size();
        if(n==0)    return 0;
        int low=0,high=n-1;
        int count=0;
        while(nums[low]<target && low<high){
            low++;
            count++;
        }
        while(nums[high]>target && low<high){
            high--;
            count++;
        }
        if(low==high&&nums[low]!=target)   return 0;
        else if(low==high&&nums[low]==target)   return 1;
        return n-(count);
    }
};
【二分法】
class Solution {
public:
//二分法
    int search(vector<int>& nums, int target) {
        int n=nums.size();
        int low=0,high=n-1;
        while(low<=high){
            int mid=low+(high-low)/2;
            if(nums[mid]<target){
                low=mid+1;
            }
            else if(nums[mid]>target){
                high=mid-1;
            }
            else{
                if(nums[low]==nums[high])   return high-low+1;
                while(nums[low]<target&&low<high)   low++;
                while(nums[high]>target&&low<high)    high--;
            }
        }
        return 0;
    }
};

58 - II. 左旋转字符串

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

示例 1:

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

示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
【substr库函数(切片)】
class Solution {
public:
    string reverseLeftWords(string s, int n) {
        string tmp=s.substr(0,n);
        int sz=s.size();
        string tmp1=s.substr(n,sz-n);
        return tmp1+tmp;
    }
};

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

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

示例 1:

输入: [0,1,3]
输出: 2

示例 2:

输入: [0,1,2,3,4,5,6,7,9]
输出: 8
【暴力法】
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int ck=0;//用于校验的参数
        for(int num:nums){
            if(ck==num){
                ck++;
                continue;
            }
            else return ck;
        }
        return ck;
    }
};
【二分查找法】
class Solution {
public:
//二分查找法
    int missingNumber(vector<int>& nums) {
        int n=nums.size();
        if(n!=nums[n-1])    return n;//说明缺的是最后一个数

        int low=0,high=n-1;
        while(low<high){
            int mid=low+(high-low)/2;
            if(nums[mid]>mid){//说明缺失的数在mid左边
                high=mid;//注意这里不能写成high=mid-1,因为并不确定mid-1处是不是就是缺失了的那个数
            }
            else if(nums[mid]==mid){//说明缺失的数在右边,(最开始出错的地方==写成了=)
                low=mid+1;
            }
        }
        return low;
    }
};

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

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

示例 1:

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

示例 2:

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

示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
  请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
【双指针+Hash表】其实是未改进前的动态规划
class Solution {
public:
//双指针法:遇到相同的左指针就往右边走,遇到不同的右指针继续往右走
    int lengthOfLongestSubstring(string s) {
        if(s.size()==0) return 0;
        unordered_map<char,int> mp;
        int mx=1;//注意这里需要初始化为1,因为只有一个元素时mx至少为1
        mp[s[0]]++;
        int left=0,right=1;
        for(int i=1;i<s.size();i++){
            while(mp[s[i]]){
                mp[s[left]]--;
                left++;
            }  
            if(!mp[s[i]]){
                right=i;
            }   
            mp[s[i]]++;
            mx=max(mx,right-left+1);
        }
        return mx;
    }
};

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

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

示例 1:

输入: root = [3,1,4,null,2], k = 1
3
/ \
1   4
\
2
输出: 4

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
    5
   / \
  3   6
 / \
2   4
/
1
输出: 4
【暴力排序法】
/**
 * 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> tmp;
    int kthLargest(TreeNode* root, int k) {
        DFS(root);
        sort(tmp.begin(),tmp.end(),greater());
        return tmp[k-1];
    }
    void DFS(TreeNode* root){
        if(root==nullptr)   return;
        tmp.push_back(root->val);
        DFS(root->left);
        DFS(root->right);
    }
};
【二叉搜索树的中序遍历是递减序列】
/**
 * 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> tmp;
    int kthLargest(TreeNode* root, int k) {
        DFS(root);
        //sort(tmp.begin(),tmp.end(),greater());
        return tmp[k-1];
    }
    void DFS(TreeNode* root){
        if(root==nullptr)   return;
        DFS(root->right);
        tmp.push_back(root->val);
        DFS(root->left);
        
    }
};

49. 丑数

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

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
【动态规划】
class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> dp(n+1,0);
        int i=1,j=1,k=1;
        dp[1]=1;
        for(int z=2;z<=n;z++){
            int a=dp[i]*2,b=dp[j]*3,c=dp[k]*5;
            dp[z]=min(min(a,b),c);
            if(dp[z]==a) i++;//可能会出现重复的问题,
            if(dp[z]==b) j++;//当z=7的时候,由于a==b,所以i和j都需要加1,如果用if else的话就会使得其中的一个数无法加1
            if(dp[z]==c) k++;
        }
        return dp[n];
    }
};

65. 不用加减乘除做加法

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

示例:

输入: a = 1, b = 1
输出: 2
【位运算】注意负数的处理
class Solution {
public:
    int add(int a, int b) {
        while(b!=0){
            int c=(unsigned int)(a&b)<<1;//两个都为1进位就会出现
            a^=b;//加上进位的时候可能还会产生进位,所以需要使用while
            b=c;
        }
    return a;
    }
};

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

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

示例:

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

滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
1 [3  -1  -3] 5  3  6  7       3
1  3 [-1  -3  5] 3  6  7       5
1  3  -1 [-3  5  3] 6  7       5
1  3  -1  -3 [5  3  6] 7       6
1  3  -1  -3  5 [3  6  7]      7
优先级队列
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size()==0)  return {};
        priority_queue<pair<int,int>> q;
        vector<int> ans;
        for(int i=0;i<k;i++){//前k个元素入队
            q.emplace(pair(nums[i],i));
        }
        ans.push_back(q.top().first);
        for(int i=k;i<nums.size();i++){
            q.emplace(pair(nums[i],i));
            while(q.top().second<=i-k){
                q.pop();
            }
            ans.push_back(q.top().first);

        }
        return ans;
    }
};
单调队列
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size()==0)  return {};
        deque<int> q;
        
        for (int i = 0; i < k; ++i) {//先传入k个元素
            while (!q.empty() && nums[i] >= nums[q.back()]) {
                q.pop_back();
            }
            q.push_back(i);
        }

        vector<int> res;
        res.push_back(nums[q.front()]);
        
        for(int i=k;i<nums.size();i++){
            while(!q.empty() && nums[i]>=nums[q.back()]){//滑动窗口移动之后,将新元素插入
                q.pop_back();
            }
            q.push_back(i);
            while (q.front() <= i - k) {//因为不能保证之前滑动窗口中的元素全部已经弹出
                q.pop_front();
            }
            res.push_back(nums[q.front()]);
        }

        return res;
    }
};

7.27

59 - II. 队列的最大值

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

若队列为空,pop_frontmax_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
暴力法
class MaxQueue {
public:
int q[20000];
int begin=0,end=0;
    MaxQueue() {

    }
    
    int max_value() {
        if(begin==end)  return -1;
        int max_num=INT_MIN;
        for(int i=begin;i!=end;i++){
            max_num=max(q[i],max_num);
        }
        return max_num;
    }
    
    void push_back(int value) {
        q[end++]=value;
    }
    
    int pop_front() {
        if(begin==end)  return -1;
        return q[begin++];
    }
};
单调队列
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;
        if(q.front()==d.front()){
            d.pop_front();
        }
        int tmp=q.front();
        q.pop();
        return tmp;
    }
};

【未做】66. 构建乘积数组

【未做】60. n个骰子的点数

【未做】67. 把字符串转换成整数

【未做】61. 扑克牌中的顺子

55 - II. 平衡二叉树

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

示例 1:

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

 3
/ \
9  20
 /  \
15   7

返回 true

示例 2:

给定二叉树 [1,2,2,3,3,null,null,4,4]

    1
   / \
  2   2
 / \
3   3
/ \
4   4

返回 false

【DFS】自底向上
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isBalanced(TreeNode* root) {
        return depth(root) >= 0;
    }

    int depth(TreeNode* root){
        if(root==nullptr)   return 0;
        int left_depth=depth(root->left);
        int right_depth=depth(root->right);
        if(left_depth==-1||right_depth==-1||abs(left_depth-right_depth)>1){
            return -1;
        }
        else  
        return max(left_depth+1,right_depth+1);
    }

};
【DFS】自顶向下
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isBalanced(TreeNode* root) {
        if(root==nullptr)   return true;
        if(abs(height(root->left)-height(root->right))<=1 && isBalanced(root->left) && isBalanced(root->right))  return true;
        return false;
    }
    int height(TreeNode* root){
        if(root==nullptr)   return 0;
        int left_height=height(root->left);
        int right_height=height(root->right);
        return max(left_height,right_height)+1;
    }
};

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

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

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

示例 1:

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

示例 2:

输入: n = 10, m = 17
输出: 2
反推法
class Solution {
public:
    int lastRemaining(int n, int m) {//f(n,m)=(f(n-1,m)+k+1)%n= (f(n-1,m)+m)%n
        int ans = 0;
        // 最后一轮剩下2个人,所以从2开始反推
        for (int i = 2; i <= n; i++) {
            ans = (ans + m) % i;
        }
        return ans;
    }
};

63. 股票的最大利润

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

示例 1:

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

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
动态规划第一种写法
class Solution {
public:
    int maxProfit(vector<int>& prices) {//寻找每个数后的最大值
    int n=prices.size();
    if(n==0)    return 0;
    vector<int> mx(n,1);
    // int cur_max=0;
    
        mx[n-1]=prices[n-1];
        for(int i=n-2;i>=0;i--){
            if(prices[i]>mx[i+1]){
                mx[i]=prices[i];
            }
            else    mx[i]=mx[i+1]; 
        }
        for(int i=0;i<n-1;i++){   
            prices[i]=mx[i+1]-prices[i];
        }
        prices[n-1]=0;//这个0可以用来防止[7,6,4,3,1]这种情况的发生
        sort(prices.begin(),prices.end(),greater());
        return prices[0];
    }
};
动态规划的第二种写法
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size() == 0) return 0;
        int res = 0, mn = prices[0];
        for(int i = 1; i < prices.size(); i ++){
            mn = min(mn, prices[i-1]);
            res = max(res, prices[i] - mn);
        }
        return res;
    }
};

64. 求1+2+…+n

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

示例 1:

输入: n = 3
输出: 6

示例 2:

输入: n = 9
输出: 45
逻辑运算符的短路性质
class Solution {
public:
    int sumNums(int n) {
        n && (n += sumNums(n-1));//使用了逻辑运算符的短路性质,因为n满足n==0时,就不会进入后面的递归
        return n;
    }
};

使用?:运算符
class Solution {
public:
    int sumNums(int n) {
        return n==0?0:n+sumNums(n-1);
    }
};

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

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

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

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

img

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6 
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
利用二叉搜索树的性质
/**
 * 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->val<p->val && root->val<q->val)   
            return lowestCommonAncestor(root->right,p,q);
        if(root->val>p->val && root->val>q->val)
            return lowestCommonAncestor(root->left,p,q);
        return root;
    }
};

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

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

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

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

img

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
递归法:
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr||root==p||root==q)  return root;
        TreeNode* left=lowestCommonAncestor(root->left,p,q);
        TreeNode* right=lowestCommonAncestor(root->right,p,q);
        if(left==nullptr)   return right;
        if(right==nullptr)  return left;
        return root;
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值