剑指offer力扣刷题总结(目前更新到52题)

剑指offer刷题时参考答案解析进行的总结,感谢力扣题解区的大神们,如果有侵权,随时联系我,我会立马删除,感谢

剑指offer第三题

找出数组中重复的数字。

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

示例 1:

输入:

[2, 3, 1, 0, 2, 5, 3]

输出:2 或 3

限制:2 <= n <= 100000

题解:

由于数字范围都在0~n-1之内,考虑利用一个大小和nums相同的布尔数组记录某个数字是否出现过,如果当前数字没有出现过(对应flag为false),将对应flag标记为true;如果当前数字对应flag为true,说明出现过,将其输出。目前为止100%

class Solution {
public:
​    int findRepeatNumber(vector<int>& nums) {
​        bool flag[nums.size()];
​        memset(flag, false, sizeof(flag));
​        for(int i = 0; i < nums.size(); i++)
​            if(flag[nums[i]])
​                return nums[i];
​            else
​                flag[nums[i]] = true;
​        return -1;   //返回值是必须的,以防某种情况下没有返回值。
​    }
};

剑指offer第四题

题目描述:

在一个 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。

题解:

清华电子本科转行刷题学基础,30天leetcode从0到100题,春招面试做出hard,拿到一线厂核心部门和二线厂算法offer,我的自学笔记和题解,我的知乎,欢迎找我交流、内推(邮箱/知乎咨询/私信)。

关键在于起点的选取,从左下角或者右上角开始

bool searchMatrix(vector<vector<int>>& matrix, int target) {
​    int i=matrix.size()-1,j=0;
​    while(i>=0&&j<matrix[0].size())
​        if(matrix[i][j]==target) return true;
​        else if(matrix[i][j]>target) i--;
​        else j++;
​    return false;
}

剑指offer第五题

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

示例 1:

输入:s = “We are happy.”

输出:“We%20are%20happy.”

限制:0 <= s 的长度 <= 10000

题解:

解题思路

没有开辟额外空间,先根据空格数量在字符串末尾扩容两个字符的空间(因为一个空格变为%20需要多出两个空间),

然后倒叙遍历将原来位置的字符放到后面, 最后返回s就可以了.

代码

class Solution {
public:
​    string replaceSpace(string s) {
​        int l1 = s.length() - 1;
​        for (int i = 0; i <= l1; i++) {
​            if (s[i] == ' ') {
​                s += "00";
​            }
​        }
​        int l2 = s.length() - 1;
​        if (l2 <= l1) {
​            return s;
​        }
​        for (int i = l1; i >= 0; i--) {
​            char c = s[i];
​            if (c == ' ') {
​                s[l2--] = '0';
​                s[l2--] = '2';
​                s[l2--] = '%';
​            } else {
​                s[l2--] = c;
​            }
​        }
​        return s;
​    }
};

剑指offer第六题

题目描述:

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

示例 1:

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

输出:[2,3,1]

限制:

0 <= 链表长度 <= 10000

解答:

解题思路

4种解法:reverse反转法、堆栈法、递归法、改变链表结构法

在编写代码前,要跟面试官沟通清楚,根据面试官提出的不同性能需求(时间效率优先 or 空间效率优先 or 不允许改变链表结构 or 云云),给出不同的算法。

下面的代码思路清晰,注释都很好理解。

代码

/**
 \* Definition for singly-linked list.
 \* struct ListNode {
 \*     int val;
 \*     ListNode *next;
 \*     ListNode(int x) : val(x), next(NULL) {}
 \* };
 */
class Solution {
public:
​    vector<int> res;
​    vector<int> reversePrint(ListNode* head) {
​        //方法1:reverse反转法
​        /*
​        while(head){
​            res.push_back(head->val);
​            head = head->next;
​        }
​        //使用algorithm算法中的reverse反转res
​        reverse(res.begin(),res.end());
​        return res;
​        */
​        //方法2:入栈法
​        /*
​        stack<int> s;
​        //入栈
​        while(head){
​            s.push(head->val);
​            head = head->next;
​        }
​      //出栈
​        while(!s.empty()){
​            res.push_back(s.top());
​            s.pop();
​        }
​        return res;
​        */
​        //方法3:递归
​        /*
​        if(head == nullptr)
​            return res;
​        reversePrint(head->next);
​        res.push_back(head->val);
​        return res;
​        */
​        //方法4:改变链表结构
​        ListNode *pre = nullptr;
​        ListNode *next = head;
​        ListNode *cur = head;
​        while(cur){
​            next = cur->next;//保存当前结点的下一个节点
​            cur->next = pre;//当前结点指向前一个节点,反向改变指针
​            pre = cur;//更新前一个节点
​            cur = next;//更新当前结点
​        }
​        while(pre){//上一个while循环结束后,pre指向新的链表头
​            res.push_back(pre->val);
​            pre = pre->next;
​        }
​        return res;
​    }
};

剑指offer第七题

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]

中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

​ 3

/ \

9 20

​ / \

15 7

限制:

0 <= 节点个数 <= 5000

解答:

代码

/**

 \* 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* buildTree(vector<int>& preorder, vector<int>& inorder) {

​        //递归分治

​        return recursionBuild(preorder.begin(),preorder.end(),inorder.begin(),inorder.end());
//wzhibao:这里要注意preorder.end()指向的是容器的最后一个元素的后一个位置。
​    }

 

​    //递归分治

​    TreeNode* recursionBuild(vector<int>::iterator preBegin, vector<int>::iterator preEnd,vector<int>::iterator inBegin, vector<int>::iterator inEnd )

​    {

​        if(inEnd==inBegin) return NULL;

​        TreeNode* cur = new TreeNode(*preBegin);

​        auto root = find(inBegin,inEnd,*preBegin);

​        cur->left = recursionBuild(preBegin+1,preBegin+1+(root-inBegin),inBegin,root);

​        cur->right = recursionBuild(preBegin+1+(root-inBegin),preEnd,root+1,inEnd);

​        return cur;

​    }

};

鉴于有些同学跟我讨论的

cur->left = recursionBuild(preBegin+1,preBegin+1+(root-inBegin),inBegin,root);

中第二个参数加不加一的问题,更新了如下解释

其前提是,输入的参数区间是前闭后开区间

当然,不加1确实能运行成功,但这是程序的一个bug:

用上面的例子来说,如果重建左子树时,不加1,那么输入的区间是[1,1)与[0,1):

那么首先,输入函数的前序区间与中序区间长度是不等的,这逻辑上就不合理

至于为什么能运行成功,是因为以下原因:

if(inEnd==inBegin) return NULL;程序只会判断输入的中序区间前后是否相等而不会检查前序区间

因此,上述输入前序区间[1,1)时,虽然区间什么元素都没有但仍然不会停止运行,将继续执行下一TreeNode* cur = new TreeNode(*preBegin);

这个时候,由于我们传入的是原向量的引用,所以*preBegin仍然能正确取到值,即preoder[1]

所以程序仍然能够正确运行

所以呢,重建左子树时,第二个参数还是要加一的!

感兴趣的同学不妨在if(inEndinBegin) return NULL;后加一句if(preBeginpreEnd) return NULL;试试,就明白了。

此时,如果不加1,那么将会得到错误答案。

剑指offer第九题

题目描述:

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

示例 1:

输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:

输入:
[“CQueue”,“deleteHead”,“appendTail”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

解答:

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

```
}
void appendTail(int value) {
    s1.push(value);
}
int deleteHead() {
    if(s2.empty()){
        if(s1.empty()){
            return -1;
        }
        else{
            while(!s1.empty()){
                s2.push(s1.top());
                s1.pop();
            }               
        }
    }
    int head=s2.top();
    s2.pop();
    return head;
}
```
};
/**
  • Your CQueue object will be instantiated and called as such:
  • CQueue* obj = new CQueue();
  • obj->appendTail(value);
  • int param_2 = obj->deleteHead();
    */

剑指offer第十题(斐波那契数列)

题目描述:

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:

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

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

示例 1:

输入:n = 2
输出:1
示例 2:

输入:n = 5
输出:5

题目解答:

class Solution {
public:
    long long fib(unsigned int n) {
     
     int result[2]={0,1};
     if(n<2)
     return result[n];

     unsigned long long Num1=1;
     unsigned long long Num2=0;
     unsigned long long Final=0;
     for(int i=2;i<=n;i++)
     {
           Final=(Num1+Num2)%1000000007;//这里要求是取余,对于每个结果都要去取余,过程中就是,而不只是在最终结果。
           Num2=Num1;
           Num1=Final;
 
     }
   //  while(Final>1e9+7)
   //  Final=Final%1000000007;

     return Final;

    }
};

剑指offer10-1 青蛙跳台问题

题目描述:

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

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

示例 1:

输入:n = 2
输出:2
示例 2:

输入:n = 7
输出:21

题解:

class Solution {
public:
​    int numWays(int n) {
​     int result[2]={1,2};
​     if(n==0)
​     return 1;//这里要注意 在0阶时 默认是等于1
​     if(n<=2)
​     return result[n-1];
​     long long Num1=1;
​     long long Num2=2;
​     long long Final=0;
​     for(int i=3;i<=n;i++)
​     {
​         Final=Num1+Num2;
​         Final=Final%1000000007;
​         Num1=Num2;
​         Num2=Final;
​     }
​     return Final;
​    }
};

剑指offer 第十一题(旋转数组的最小数字)

题目描述:

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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 length=numbers.size();
​          if(numbers[0]<numbers[length-1])
​               return numbers[0];
​          
​          int begin=0;
​          int end=length-1;
​          int mid=(begin+end)/2;
​          while(numbers[0]>=numbers[length-1])
​          {
​                  if(end-begin==1)
​                  return numbers[end];
​                  if(numbers[begin]==numbers[mid]&&numbers[end]==numbers[mid])
​                  {
​                      return minCom(numbers,begin,end);
​                  }
​                  if(numbers[mid]>=numbers[begin])
​                  {
​                      begin=mid;
​                      mid=(begin+end)/2;
​                  }
​                  else if(numbers[mid]<=numbers[end])
​                  {
​                      end=mid;
​                      mid=(begin+end)/2;
​                  } 
​          }
​          return 1;
​    }
​    int minCom(vector<int>& numbers,int begin,int end)
​    {
​        int result=numbers[begin];
​        for(int i=1;i<=end;i++)
​        {
​              if(result>numbers[i])
​              result=numbers[i];
​        }
​        return result;
​    }
};

剑指offer第十二题(矩阵中的路径)

题目描述:

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

示例 1:

输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true
示例 2:

输入:board = [[“a”,“b”],[“c”,“d”]], word = “abcd”
输出:false

题解:

int x[] = {-1,0,0,1};
int y[] = {0,-1,1,0};
class Solution {
public:
    int row,col;
    bool dfs(vector<vector<char>>& board,string word,int i,int j,int idx) {
        if (idx == word.size()) return true;
        char tmp = board[i][j];
        board[i][j] = '.';
        for (int k = 0; k < 4; k++) {
            int d_x = x[k] + i;
            int d_y = y[k] + j;
            if (d_x >= 0 && d_x < row && d_y >= 0 && d_y < col && word[idx] == board[d_x][d_y]) {
                if (dfs(board,word,d_x,d_y,idx + 1)) return true;
            }
        }
        board[i][j] = tmp;
        return false;
    }
    bool exist(vector<vector<char>>& board, string word) {
        row = board.size();
        col = board[0].size();
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (board[i][j] == word[0]) {
                    if (dfs(board,word,i,j,1)) return true;
                }
            }
        }
        return false;
    }
};

LeetCode第79题(单词搜索(回溯法))

题目描述:

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

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

示例:

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

给定 word = "ABCCED", 返回 true
给定 word = "SEE", 返回 true
给定 word = "ABCB", 返回 false

题解:

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        for(int i = 0; i < board.size(); i++){
            for(int j = 0; j < board[0].size(); j++){
                if(backtrack(board, word, 0, i , j)){ // 从二维表格的每一个格子出发
                    return true;
                }
            }
        }
        return false;
    }
private:
    bool backtrack(vector<vector<char>>& board, string& word, int wordIndex, int x, int y){
        if( board[x][y] != word[wordIndex]){ // 当前位的字母不相等,此路不通
            return false;
        }
        if(word.size() - 1  == wordIndex){ // 最后一个字母也相等, 返回成功
            return true;
        }
        char tmp = board[x][y]; 
        board[x][y] = 0; // 避免该位重复使用
        wordIndex++;
        if((x > 0 && backtrack(board, word, wordIndex, x - 1, y)) // 往上走 (此处多谢笑川兄指正)
        || (y > 0 && backtrack(board, word, wordIndex, x, y - 1)) // 往左走
        || (x < board.size() - 1 && backtrack(board, word, wordIndex, x + 1, y)) // 往下走
        || (y < board[0].size() - 1 && backtrack(board, word, wordIndex, x, y + 1))){ // 往右走
            return  true; // 其中一条能走通,就算成功
        }
        board[x][y] = tmp; // 如果都不通,则回溯上一状态
        return false;
    }
};

面试题十三(机器人的活动范围)

题目描述:

地上有一个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

题解:

思路和算法

我们将行坐标和列坐标数位之和大于 k 的格子看作障碍物,那么这道题就是一道很传统的搜索题目,我们可以使用广度优先搜索或者深度优先搜索来解决它,本文选择使用广度优先搜索的方法来讲解。

那么如何计算一个数的数位之和呢?我们只需要对数 x 每次对 10 取余,就能知道数 x 的个位数是多少,然后再将 x 除 10,这个操作等价于将 x 的十进制数向右移一位,删除个位数(类似于二进制中的 >> 右移运算符),不断重复直到 x 为 0 时结束。

同时这道题还有一个隐藏的优化:我们在搜索的过程中搜索方向可以缩减为向右和向下,而不必再向上和向左进行搜索。如下图,我们展示了 16 * 16 的地图随着限制条件 k 的放大,可行方格的变化趋势,每个格子里的值为行坐标和列坐标的数位之和,蓝色方格代表非障碍方格,即其值小于等于当前的限制条件 k。我们可以发现随着限制条件 k 的增大,(0, 0) 所在的蓝色方格区域内新加入的非障碍方格都可以由上方或左方的格子移动一步得到。而其他不连通的蓝色方格区域会随着 k 的增大而连通,且连通的时候也是由上方或左方的格子移动一步得到,因此我们可以将我们的搜索方向缩减为向右或向下。

class Solution {
    // 计算 x 的数位之和(重要)
    int get(int x) {
        int res=0;
        for (; x; x /= 10) {
            res += x % 10;
        }
        return res;
    }
public:
    int movingCount(int m, int n, int k) {
        if (!k) return 1;
        queue<pair<int,int> > Q; (将两个数据组合成一个数据)
        // 向右和向下的方向数组
        int dx[2] = {0, 1};
        int dy[2] = {1, 0};
        vector<vector<int> > vis(m, vector<int>(n, 0));
        //定义一个二维数组,vector<int>为数据类型,m表示有m行,vector<int>(n,0),表示这个容器中有n个0,属于初始化操作。
        Q.push(make_pair(0, 0));
        vis[0][0] = 1;
        int ans = 1;
        while (!Q.empty()) {
            auto [x, y] = Q.front();
            Q.pop();
            for (int i = 0; i < 2; ++i) {
                int tx = dx[i] + x;
                int ty = dy[i] + y;
                if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) continue;
                Q.push(make_pair(tx, ty));
                vis[tx][ty] = 1;
                ans++;
            }
        }
        return ans;
    }
};

复杂度分析

时间复杂度:O(mn)O(mn),其中 m 为方格的行数,n 为方格的列数。考虑所有格子都能进入,那么搜索的时候一个格子最多会被访问的次数为常数,所以时间复杂度为 O(2mn)=O(mn)O(2mn)=O(mn)。

空间复杂度:O(mn)O(mn),其中 m 为方格的行数,n 为方格的列数。搜索的时候需要一个大小为 O(mn)O(mn) 的标记结构用来标记每个格子是否被走过。

剑指offer第十四题(剪绳子1(动态规划))

题目描述:

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

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

题解:

class Solution {
public:
    int cuttingRope(int n) {
        // n<=3的情况,m>1必须要分段,例如:3必须分成1、2;1、1、1 ,n=3最大分段乘积是2, 同理2的最大乘积为1
        if (n == 2)
            return 1;
        if (n == 3)
            return 2;
        vector<int> dp(n+1);//vector的定义和数组的定义是一致的,n+1即代表有n+1个数
        /*
        下面3行是n>=4的情况,跟n<=3不同,4可以分很多段,比如分成1、3,
        这里的3可以不需要再分了,因为3分段最大才2,不分就是3。记录最大的。
         */
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        for(int i = 4; i <= n; i++) {
            int maxValue = 0;//记录最大的
            //j<=i/2是因为1*3和3*1是一样的,没必要计算在内,只要计算到1*3和2*2就好了
            for(int j = 1; j <= i/2; j++) {
                maxValue = max(maxValue, dp[j] * dp[i-j]);
            }
            dp[i] = maxValue;
        }
        return dp[n];
    }
};

剑指offer第十四题(剪绳子2(贪心算法))

题目描述:

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

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

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

题解:

class Solution {

public:
​    int cuttingRope(int n) {
​         if(n==2) return 1;
​         if(n==3) return 2;
​         int Count1=n/3;
​         if(n-Count1*3==1) 
​             Count1=Count1-1;
​        int Count2=(n-Count1*3)/2;
​         long long end1=1;
​         for(int i=1;i<=Count1;i++)
​         {
​             end1=end1*3%1000000007;
​         }
​         
​         int end=end1*(int)pow(2,Count2)%1000000007;
​         return end;
​    }
};

注解:

为什么要对1000000007取模(取余)

大数阶乘,大数的排列组合等,一般都要求将输出结果对1000000007取模(取余)

为什么总是1000000007呢= =

1.1000000007是一个质数(素数),对质数取余能最大程度避免冲突~

2.int32位的最大值为2147483647,所以对于int32位来说1000000007足够大

3.int64位的最大值为2^63-1,对于1000000007来说它的平方不会在int64中溢出
所以在大数相乘的时候,因为(a∗b)%c=((a%c)∗(b%c))%c,所以相乘时两边都对1000000007取模,再保存在int64里面不会溢出

其实不止1e9+7,还有1e9+9和998244353。这三个数都是一个质数,同时小于 [公式] 。所以有什么好处呢?

1.所有模过之后的数在加法操作在int范围内不会溢出,即 [公式]

2.在乘法操作后在long long范围内不会溢出,即 [公式]

剑指offer第十五题(二进制位运算)

题目描述:

二进制中1的个数

请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。

示例 1:

输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。
示例 2:

输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 ‘1’。
示例 3:

输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 ‘1’。

题解:

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

剑指offer第十六题(数值的整数次方)

题目描述

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

示例 1:

输入: 2.00000, 10
输出: 1024.00000
示例 2:

输入: 2.10000, 3
输出: 9.26100
示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

题解:

class Solution {
public:
    double myPow(double x, int n) {
        if(x == 1 || n == 0) return 1;
        double ans = 1;
        long num = n;
        if(n < 0){
            num = -num;
            x = 1/x;
        }
        while(num){
            if(num & 1) ans *= x;
            x *= x;
            num >>= 1;
        }
        return ans;
    }
};

剑指offer第十七题(打印从1到最大的n位数)

(大数要转换成字符串来输出,否则会溢出,题目逻辑性很强!!)

题目描述:

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

示例 1:

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

说明:

用返回一个整数列表来代替打印
n 为正整数

题解:

class Solution {
public:
//这个问题的考点是大数问题
​    //打印数字的主函数
​    vector<int> res;
​    vector<int> printNumbers(int n) 
​    {
​        //输入检查
​        if(n <= 0) return res;
​        //分配内存
​        char* number = new char[n + 1]; //这里注意使用字符串表示数字     
​        memset(number, '0', n);
​        number[n] = '\0'; 
​        //开始打印
​        while(!Increment(number))
​        {
​            printNumber(number);
​        }
​        //释放内存
​        delete[] number;
​        //输出结果
​        return res;
​    }
​    //数字遍历的操作
​    bool Increment(char* number)
​    {
​        bool isOverflow = false;
​        int nTakeover = 0;
​        int n =strlen(number);
​        for(int i = n - 1; i >=0; i--)
​        {
​            int nSum = number[i] - '0' + nTakeover;
​            //第一位,一定加一
​            if(i == n - 1) nSum++;            
​            if(nSum >= 10)
​            {//需要进位
​               if(i == 0) isOverflow = true;
​                else{
​                    number[i] = nSum - 10 + '0';
​                    nTakeover = 1;
​                }
​            }
​            else
​            {//不需要进位
​                number[i] = '0' + nSum;
​                break;
​            }
​        }
​        return isOverflow;
​    }
​    //打印数字的操作
​    void printNumber(char* number)
​    {
​        //初始化
​        string s = "";
​        bool isBegin0 = true;
​        //把数组转换成字符串
​        while(*number != '\0')
​        {
​            if(isBegin0 && *number != '0') isBegin0 = false;
​            if(!isBegin0){
​                s += *number;
​            }
​            number++;
​        }
​        int num = stoi(s);
​        res.push_back(num);
​    }
};


//c++中的atoi()和stoi()函数的用法和区别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7g1uWiwe-1595824485506)(/下载.png)]

面试题十八(删除链表的节点)

题目描述:

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

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

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

示例 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.

说明:

题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

面试题十九(正则表达式匹配)

题目描述:

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

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:

输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。

示例 5:

输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。

解题思路:

假设主串为 AA,模式串为 BB 从最后一步出发,需要关注最后进来的字符。假设 AA 的长度为 nn ,BB 的长度为 mm ,关注正则表达式 BB 的最后一个字符是谁,它有三种可能,正常字符、*∗ 和 .(点),那针对这三种情况讨论即可,如下:

如果 BB 的最后一个字符是正常字符,那就是看 A[n-1]是否等于 B[m-1],相等则看 A_{0…n-2}与 B_{0…m-2},不等则是不能匹配,这就是子问题。如果 B的最后一个字符是.,它能匹配任意字符,直接看 A_{0…n-2}与 B_{0…m-2}如果 B的最后一个字符是它代表 B[m-2]=c可以重复0次或多次,它们是一个整体 c

情况一:A[n-1]是 0个 c,B 最后两个字符废了,能否匹配取决于 A_{0…n-1} 和 B_{0…m-3}是否匹配
情况二:A[n-1]是多个 c中的最后一个(这种情况必须 A[n-1]=c 或者 c=’.’),所以 A匹配完往前挪一个,B继续匹配,因为可以匹配多个,继续看 A_{0…n-2}和 B_{0…m-1}。

代码(递归):

class Solution {
    public boolean isMatch(String A, String B) {
        // 如果字符串长度为0,需要检测下正则串
        if (A.length() == 0) {
            // 如果正则串长度为奇数,必定不匹配,比如 "."、"ab*",必须是 a*b*这种形式,*在奇数位上
            if (B.length() % 2 != 0) return false;
            int i = 1;
            while (i < B.length()) {
                if (B.charAt(i) != '*') return false;
                i += 2;
            }
            return true;
        }
        // 如果字符串长度不为0,但是正则串没了,return false
        if (B.length() == 0) return false;
        // c1 和 c2 分别是两个串的当前位,c3是正则串当前位的后一位,如果存在的话,就更新一下
        char c1 = A.charAt(0), c2 = B.charAt(0), c3 = 'a';
        if (B.length() > 1) {
            c3 = B.charAt(1);
        }
        // 和dp一样,后一位分为是 '*' 和不是 '*' 两种情况
        if (c3 != '*') {
            // 如果该位字符一样,或是正则串该位是 '.',也就是能匹配任意字符,就可以往后走
            if (c1 == c2 || c2 == '.') {
                return isMatch(A.substring(1), B.substring(1));
            } else {
                // 否则不匹配
                return false;
            }
        } else {
            // 如果该位字符一样,或是正则串该位是 '.',和dp一样,有看和不看两种情况
            if (c1 == c2 || c2 == '.') {
                return isMatch(A.substring(1), B) || isMatch(A, B.substring(2));
            } else {
                // 不一样,那么正则串这两位就废了,直接往后走
                return isMatch(A, B.substring(2));
            }
        }
    }
}

面试题二十(表示数值的字符串)

题目描述:

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

题解:

class Solution {

public:

​       bool isNumber(string s) 
​    {
​        const char* str=&s[0];   
​        if(str==nullptr) return false;  
​        while(*str==' ') str++;    
​        bool numberic=scanInteger(&str);
​        if(*str=='.')
​        {
​            str++;
​            numberic=scanUnsignedInteger(&str)||numberic;
​        }
​        if(*str=='e'||*str=='E')
​        {
​            str++;
​            numberic=numberic&&scanInteger(&str);
​        }
​       while(*str==' ') str++;       
​        return numberic&&*str=='\0';
​    }
​       bool scanInteger(const char** str)
​       {
​           if(**str=='+'||**str=='-')
​           (*str)++;
​            return scanUnsignedInteger(str);
​       }
​       bool scanUnsignedInteger(const char** str)
​       {
​           const char* before=*str;
​           while(**str!='\0'&&**str>='0'&&**str<='9')
​           (*str)++;
​           return *str>before;
​       }
};

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

题目描述:

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

示例:

输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。

提示:

1 <= nums.length <= 50000
1 <= nums[i] <= 10000

解答:

class Solution {
public:
​    vector<int> exchange(vector<int>& nums) 
​    {
/*       Reorder(nums);
​         return nums;
*/
​      int length=nums.size();
​      if(length==0) return nums;
​      int i=0;
​      int j=length-1;
​      while(i<j)
​      {
​          while(i<j&&(nums[i]&1!=0)) i++;
​          while(i<j&&(nums[j]%2==0)) j--;
​          if(i<j) swap(nums[i],nums[j]);         
​      }
​      return nums;
​    }
/*   void Reorder(vector<int>& nums)
​    {
​        int length=nums.size();
​        vector<int>::iterator begin=nums.begin();
​        vector<int>::iterator end=nums.end()-1;
​        if(length==0||nums.empty()==1) return;
​        while(begin<end)
​        {
​         while(begin<end&&func(begin))
​         begin++;
​         while(begin<end&&func(end))
​         end--;
​         if(begin<end)
​         {
​          swap(*begin,*end);
​         }
​        }
​    }
​    bool func(vector<int>::iterator a)
​    {
​        return *a%2==1;
​    }
*/
};

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

题目描述:

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例:

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

返回链表 4->5.



题解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        if(head==nullptr||k==0) return nullptr;
        ListNode* Before=head;
        ListNode* Behind=nullptr;
        for(int i=1;i<=k-1;i++)
        {
            if(Before->next!=nullptr)
            Before=Before->next;
            else
            return nullptr;
        }
        Behind=head;
        while(Before->next!=nullptr)
        {
            Before=Before->next;
            Behind=Behind->next;
        }
        return Behind;
    }
};

面试题24. 反转链表

题目描述:

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

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

限制:

0 <= 节点个数 <= 5000

题解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==nullptr||head->next==nullptr) return head;
        ListNode* ReversedHead=nullptr;
        ListNode* PreNode=nullptr;
        ListNode* Node=head;
        ListNode* AfterNode=nullptr;
        while(Node!=nullptr)
        {
            AfterNode=Node->next;

            if(AfterNode==nullptr) ReversedHead=Node;
            
            Node->next=PreNode;
            PreNode=Node;
            Node=AfterNode;
        }
        return ReversedHead;

    }
};

面试题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

题解:

/*
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
//       bool isSym=false;
        TreeNode* root0=copy(root);
        mirrorTree(root0);
//        isSym=ComTree1AndTree2(root,root0);
        return ComTree1AndTree2(root,root0);
    }
//复制一棵树
    TreeNode* copy(TreeNode* root)
    {
        if(root==nullptr) return nullptr;
        TreeNode* Root=nullptr;
        TreeNode* Treeleft=nullptr;
        TreeNode* Treeright=nullptr;
        Treeleft=copy(root->left);
        Treeright=copy(root->right);
        Root=(TreeNode*)malloc(sizeof(TreeNode));// 如果分配成功:则返回指向被分配内存空间的指针
        Root->val=root->val;
        Root->left=Treeleft;
        Root->right=Treeright;
        return Root;
    }
//将一棵树镜像
void mirrorTree(TreeNode* root)
    {
        if(root==nullptr) return;
        if(root->left==nullptr&&root->right==nullptr) return;
        TreeNode* tem=nullptr;
        tem=root->left;
        root->left=root->right;
        root->right=tem;
        if(root->left)
        mirrorTree(root->left);
        if(root->right)
        mirrorTree(root->right);
//        return root;
    }
//比较两棵树是否相同
    bool ComTree1AndTree2(TreeNode* root1,TreeNode* root2)
    {
        if(root1==nullptr&&root2==nullptr) return true;
        if(root1==nullptr||root2==nullptr) return false;
        if(root1->val!=root2->val) return false;
        return ComTree1AndTree2(root1->left,root2->left)&&ComTree1AndTree2(root1->right,root2->right);
        
    }
};

剑指offer29题(顺时针打印矩阵)

题目描述:

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

示例 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]
限制:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

题解:

class Solution {
public:
​    vector<int> spiralOrder(vector<vector<int>>& matrix) 
​    {
​      vector<int> v;
​      if(matrix.empty()) return v;
//     if(matrix.empty()) return ;
​      int lengthX=matrix.size();
​      int lengthY=matrix[0].size();
​      int start=0;
​      while(lengthX>start*2&&lengthY>start*2)
​      {
​               int endX=lengthX-1-start;
​               int endY=lengthY-1-start;
​                for(int i=start;i<=endY;i++)
​               {
​                  v.push_back(matrix[start][i]);
​               }
​                if(start<endX)
​               {
​                  for(int i=start+1;i<=endX;i++)
​                  {
​                   v.push_back(matrix[i][endY]);
​                  }
​               }
​                if(start<endY&&start<endX)
​               {
​                 for(int i=endY-1;i>=start;i--)
​                 {
​                  v.push_back(matrix[endX][i]);
​                 }
​               }
​                if(endX-start>1&&start<endY)
​               {
​                  for(int i=endX-1;i>start;i--)
​                  {
​                   v.push_back(matrix[i][start]);
​                  }
​               }
​          start++;
​      }
​      return v;
​    } 
};

剑指 Offer 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.

提示:

各函数的调用总次数不超过 20000 次

题解:

class MinStack {
public:
        stack<int> A;
        stack<int> Aplus;
    /** initialize your data structure here. */
    MinStack() {
    }   
    void push(int x) {
        A.push(x);
        if(Aplus.empty()||x<Aplus.top()) Aplus.push(x);
        else Aplus.push(Aplus.top());
    }   
    void pop() {
 //       assert(A.size()>0&&Aplus.size()>0);
        A.pop();
//        if(A.top()==Aplus.top())
        Aplus.pop();
    }   
    int top() {
        return A.top();
    }  
    int min() {
        return Aplus.top();
    }
};
/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(x);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->min();
 */

剑指offer 31题I

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

例如:
给定二叉树: [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回:
[3,9,20,15,7]
提示:
节点总数 <= 1000

题解:

/**
 * 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> v;
        if(root==nullptr) return v;
        deque<TreeNode*> dequeTree;
        dequeTree.push_back(root);

        while(dequeTree.size())
        {
            TreeNode* Pnode=dequeTree.front();
            v.push_back(Pnode->val);
            dequeTree.pop_front();
            if(Pnode->left) dequeTree.push_back(Pnode->left);
            if(Pnode->right) dequeTree.push_back(Pnode->right);
        }
        return v;
    }
};

剑指offer第31题II

题目描述:

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

例如:
给定二叉树: [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:
[
  [3],
  [9,20],
  [15,7]
]
提示:
节点总数 <= 1000

题解:

/**
 * 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) {
        vector<vector<int>> v;
        if(root==nullptr) return v;
        deque<TreeNode*> stackTree;
        stackTree.push_back(root);
//      v.push_back(root->val);
        int nextlevel=0;
        int toBeStore=1;
        vector<int> tempv;
        while(stackTree.size())
        {
            TreeNode* temp=stackTree.front();
            tempv.push_back(temp->val);
            if(temp->left) 
            {
                stackTree.push_back(temp->left);
                nextlevel++;
            }
            if(temp->right) 
            {
                stackTree.push_back(temp->right);
                nextlevel++;
            }
            stackTree.pop_front();
            toBeStore--;
            if(toBeStore==0)
            {
                v.push_back(tempv);
                toBeStore=nextlevel;
                nextlevel=0;
                tempv.clear();
            }
        }
        return v;

    }
};

剑指offer第31题III

题目描述:

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

例如:
给定二叉树: [3,9,20,null,null,15,7],
    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:
[
  [3],
  [20,9],
  [15,7]
]
提示:
节点总数 <= 1000

题解:

/**
 * 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) {
          vector<vector<int>> BackValue;
          if(root==nullptr) return BackValue;
          stack<TreeNode*> s1;
          s1.push(root);
          stack<TreeNode*> s2;
          int Level=1;
          int ToStore=1;
          int NextNum=0;
          vector<int> v;
          while(s1.size()||s2.size())
          {       
              if(Level%2==1)
              {
                  TreeNode* temp=s1.top();
                  if(temp!=nullptr)
                {
                  v.push_back(temp->val);
                  if(temp->left!=nullptr) 
                  {
                      s2.push(temp->left);
                      NextNum++;
                  }
                  if(temp->right!=nullptr) 
                  {
                      s2.push(temp->right);
                      NextNum++;
                  }
                  s1.pop();
                  ToStore--;
                }
              }
              else
              {
                  TreeNode* temp=s2.top();
                  if(temp!=nullptr)
                {
                  v.push_back(temp->val);
                  if(temp->right!=nullptr) 
                  {
                      s1.push(temp->right);
                      NextNum++;
                  }
                  if(temp->left!=nullptr) 
                  {
                      s1.push(temp->left);
                      NextNum++;
                  }
                  s2.pop();
                  ToStore--;
                }
              }
              if(ToStore==0)
              {
                  ToStore=NextNum;
                  NextNum=0;
                  BackValue.push_back(v);
                  v.clear();
                  Level++;
              }
          } 
           return BackValue;
    } 
};

剑指offer第33题(二茬搜索树的后序遍历序列)

题目描述:

参考以下这颗二叉搜索树:
     5
    / \
   2   6
  / \
 1   3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
提示:
数组长度 <= 1000

题解:

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        if(postorder.empty()) return true;
        int root=postorder.back();
        int length=postorder.size();
        int i=0;
        vector<int> left;
        vector<int> right;
        for(;i<length-1;i++)
        {
            if(root<postorder[i])
            break;
            left.push_back(postorder[i]);
        }
        int j=i;
        for(;j<length-1;j++)
        {
            if(root>postorder[j])
            return false;
            right.push_back(postorder[j]);
        }
        //左侧子树验证
       bool Isleft=true;
       if(i>0)
       Isleft=verifyPostorder(left);
       bool Isright=true;
       if(i<length-2)
       Isright=verifyPostorder(right);
       return Isleft&&Isright;
    }
};

面试题34( 二叉树中和为某一值的路径)

题目描述:

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

示例:
给定如下二叉树,以及目标和 sum = 22,
          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1
返回:
[
   [5,4,11,2],
   [5,8,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:
    vector<vector<int> > res;
    vector<int> path;
    //回溯算法
    void dfs(TreeNode* root,int sum)
    {
        if(root==nullptr) return;
        //先序遍历
        path.push_back(root->val);
        sum -= root->val;
        if(sum == 0 && root->left ==nullptr && root->right == nullptr)
        {
            res.push_back(path);
        }
        dfs(root->left,sum);
        dfs(root->right,sum);
        path.pop_back();//最后回溯(此时值不进入栈中,也就起到了回溯的效果)
    }
    vector<vector<int>> pathSum(TreeNode* root, int sum) 
    {
        dfs(root,sum);
        return res;
    }
};

面试题第三十五题(复杂链表的复制)

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

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

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

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

示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
 
提示:
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。

题解:

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    //复制
    void copydouble(Node* head)
    {
//      if(head==nullptr) return;
        Node* Pnode=head;
        while(Pnode!=nullptr)
        {
           Node* pclone=new Node(Pnode->val);//创建一个节点,这个节点值为pnode->val
//           pclone->val=head->val;
           pclone->next=Pnode->next;
           pclone->random=nullptr;

           Pnode->next=pclone;
           Pnode=pclone->next; 
        }
    }
//
    void copyrandom(Node* head)
    {
//       if(head==nullptr) return;
        Node* Pnode=head;
//        Node* Pnode2=head->next;
        while(Pnode!=nullptr)
        {
            Node* pclone=Pnode->next;
            if(Pnode->random!=nullptr)
            {
                pclone->random=Pnode->random->next;
            }
            Pnode=pclone->next;
        }
    }
    
    Node* copycomplete(Node* head)
    {
 //       if(head==nullptr) return nullptr;
        Node* pnode3=head;
        Node* copyhead=nullptr;
        Node* copynode=nullptr;
        if(head!=nullptr)
        {
            copyhead=copynode=head->next;
            pnode3->next=copynode->next;
            pnode3=pnode3->next;
        }
        while(pnode3!=nullptr)
        {
            copynode->next=pnode3->next;
            copynode=copynode->next;

            pnode3->next=copynode->next;
            pnode3=pnode3->next;
        }
        return copyhead;
    }

    Node* copyRandomList(Node* head) {
        copydouble(head);
        copyrandom(head);
        return copycomplete(head);  
    }
};

面试题36(二叉搜索树与双向链表)

题目描述:

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iv1T7N2O-1595824485516)(/bstdlloriginalbst.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lounBUtP-1595824485519)(/bstdllreturndll.png)]

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

代码:

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        //以防空指针
        if(!root) return nullptr;
        //定义头尾两个节点指针
        Node* head = nullptr, *pre = nullptr;
        //中序遍历
        helper(root, head, pre);
       //满足要求条件
        head->left = pre;
        pre->right = head;
        return head;
    }
    //中序遍历
    void helper(Node* root, Node*& head, Node*& pre) {
        if(!root)  return;
        helper(root->left, head, pre);
        if(!head) {
            head = root;   // 找到head
            pre = root;    // 对pre进行初始化
        } else {
            pre->right = root;
            root->left = pre;
            pre = root;
        }
        helper(root->right, head, pre);
    }
};

面试题三十七(序列化二叉树)

题目描述:

请实现两个函数,分别用来序列化和反序列化二叉树。
示例: 
你可以将以下二叉树:
    1
   / \
  2   3
     / \
    4   5
序列化为 "[1,2,3,null,null,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 Codec {
public:

    // Encodes a tree to a single string.//序列化
    string serialize(TreeNode* root) 
    {
        string res;
        dfs1(root,res);//先序遍历,保存到字符串中

        return res;    
    }
    void dfs1(TreeNode* root,string &res)
    {
        //如果结点为空
        if(!root)
        {
            res+="&,";
            return;
        }
        //如果结点不为空
        res+=to_string(root->val)+",";//这里将数字转化成了字符
        //先根,再左,最后右
        dfs1(root->left,res);
        dfs1(root->right,res);
    }

    // Decodes your encoded data to tree.//反序列化
    TreeNode* deserialize(string data) 
    {
        int idx=0;
        return dfs2(data,idx);        
    }
    TreeNode* dfs2(string str,int &idx)
    {
        int len=idx;//idx是指向的位置,是不断变化的
        while(str[len]!=',') len++;//len指向','
        //idx还是从第一个位置开始
        //如果第一个位置为NULL
        if(str[idx]=='&')
        {
            idx=len+1;//直接跳到‘,’后的位置
            return NULL;
        }
        //非空结点,计算两个‘,’之间字符串的值
        int num=0;
        //考虑数值+-;
        int sign=1;
        if(idx<len&&str[idx]=='-') sign=-1,idx++;//','后的第一位,看字符的正负
        //将字符串重新转换成数字,因为中间用的是字符串来保存和遍历的,这里num*10相当于进位
        for(int i=idx;i<len;i++) num=num*10+str[i]-'0';
        num*=sign;

        idx=len+1; // idx指向','后的字符

        //也是先序遍历保存,先根,再左,再右
        auto root=new TreeNode(num);//将这个结点保存,
        root->left=dfs2(str,idx);
        root->right=dfs2(str,idx);

        return root;
    }

};

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

//时间:2020年7月1日

面试题38(字符串的排列)

题目描述:

输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
限制:
1 <= s 的长度 <= 8

题解:

class Solution {
public:
    vector<string>res;//对于整个类来说是全局变量,可以在各个函数间自由使用
    vector<string> permutation(string s) {
        //cursor 表示指向变换的位置
        int cursor=0;
        permutation(s,cursor);//递归函数
        return res;
    }
  //这里用的是引用,也可以不用引用,因为输出的是是递归的值,不会有s的返回值,但一般是引用,以节省空间
    void permutation(string &s,int cursor){
        if(cursor==s.size()-1)//递归终止条件,如果长度达到了,则压入容器中,可以理解为最后一位的情况,即递归终止条件
        {
            res.push_back(s);
        }
        else{
            for(int i=cursor;i<s.size();i++){
//从cursor开始,遍历不重复的字符,这里的意思就是如果发现两个值相等,则不进行互换,可以避免重复
//(这里可以考虑用set容器,set容器不能存入重复的元素,可以自动进行甄别)
                if(judge(s,cursor,i))continue;  
                //第一轮交换是原位交换,没有任何改变
                swap(s[cursor],s[i]);
                //这一位确定了,递归剩余部分也是这样
                permutation(s,cursor+1);
                //返回原来的情况,再重新开始交换
                swap(s[cursor],s[i]);
            }
        }
    }
//直接判断当前位是否重复,重复情况下直接跳过,可以避免最终值重复
    bool judge(string& s, int start, int end) {
        for (int i = start; i < end; ++i) {
            if (s[i] == s[end]) return true;
        }
        return false;
    }
};

面试题39(数组中出现次数超过一半的数字)

题目描述:

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
限制:
1 <= 数组长度 <= 50000

题解:

每次将这个数设为1;如果下一个数和这个数相同,则计数减一,反之加一,最后得到的数必是超出一半的数

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int length=nums.size();
        int count=1;
        int temp=nums[0];
        for(int i=1;i<length;i++)
        {
            if(count==0) 
            {
                temp=nums[i];
                count=1;
            }
            else
            {
              if(nums[i]==temp) 
              {
                count++;
              }
            else
              {
                count--;
              }
            }
        }
        return temp;
    }
};

面试题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]

限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000

题解:

class Solution {
public:
     vector<int> getLeastNumbers(vector<int>& arr, int k) {
     multiset<int> set;
     vector<int> v;
     int length=arr.size();
     if(k<=0) return v;
     set.insert(arr[0]);
     for(int i=1;i<length;i++)
     {
         if(i<k)
         {
         set.insert(arr[i]);
         }
         else
         {
            if(arr[i]<*(--set.end()))//set.end()指向的是最后一个元素的后一个位置
            {
                set.erase(--set.end());
                set.insert(arr[i]);
            }
         }
     }  
     for(multiset<int>::iterator it=set.begin();it!=set.end();it++)
     {
        v.push_back(*it);
     }
     return v;
    }
};

//2020/7/3

面试题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]

限制:

最多会对 addNum、findMedia进行 50000 次调用。

题解:

1.也是通过划分成大小堆的方法,不过时间更快,用的heap

class MedianFinder {
public:
    /** initialize your data structure here. */
    MedianFinder() {
    }
    
    void addNum(int num) {
        if(((min.size()+max.size())&1)==0){//偶数个数时插入右边最小堆
            if(max.size()>0&&num<max[0]){
                max.push_back(num);
                //这里没有实现创建堆,因为刚开始堆里并没有数据,所以可以这样,如果有数据,则需要先将现有的数据创建堆之后再加入数据才行
                //这里是用的less,也可以不写,均是表示大根堆
                push_heap(max.begin(),max.end(),less<int>());
                num=max[0];//数组第一个即为最大根值
                //将第一个值与最后一个值替换
                pop_heap(max.begin(),max.end(),less<int>());
                //然后再将最后一个值删掉
                max.pop_back();
            }
            min.push_back(num);
            push_heap(min.begin(),min.end(),greater<int>());
        }
        else{ //奇数个数,插入左边最大堆
            if(min.size()>0&&num>min[0]){
                min.push_back(num);
                push_heap(min.begin(),min.end(),greater<int>());
                num=min[0];
                pop_heap(min.begin(),min.end(),greater<int>());
                min.pop_back();
            }
            max.push_back(num);
            push_heap(max.begin(),max.end(),less<int>());
        }
    }
    
    double findMedian() {
        int size=min.size()+max.size();
        if(size==0)throw invalid_argument("No numbers are available!");
        double median=0;
        if(size&1) median=min[0];
        else median=((double)min[0]+(double)max[0])/2;
        return median;
    }
private:
    vector<int>min;
    vector<int>max;
};

方法一:排序法

此方法是最简单直接的一个方法,我们将添加的数保存在数组中,返回中位数时,只需将数组排序,返回中间位置数即可。

本题难度为 困难,显然一定存在更加优化的方法。

代码(C++ 超时)

class MedianFinder {
    vector<double> store;

public:
    // Adds a number into the data structure.
    void addNum(int num)
    {
        store.push_back(num);
    }

    // Returns the median of current data stream
    double findMedian()
    {
        sort(store.begin(), store.end());//sort(迭代器1,迭代器2)

        int n = store.size();
        return (n & 1 ? store[n / 2] : (store[n / 2 - 1] + store[n / 2]) * 0.5);
        //n&1相当于n%1==1
    }
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PrT2CiXy-1595824485522)(F:\desktop\刷题\assets\1593794484843.png)]

class MedianFinder {
    vector<int> store; // resize-able container

public:
    // Adds a number into the data structure.
    void addNum(int num)
    {
        if (store.empty())
            store.push_back(num);
        else
            store.insert(lower_bound(store.begin(), store.end(), num), num);    
        //lower_bound意思是在这个区间内找到一个位置,不少于这个值num,返回的是一个迭代器
        //.insert(迭代器,数值)
        // binary search and insertion combined
    }

    // Returns the median of current data stream
    double findMedian()
    {
        int n = store.size();
        return n & 1 ? store[n / 2] : (store[n / 2 - 1] + store[n / 2]) * 0.5;
    }
};

class MedianFinder {
    priority_queue<int> lo;                              // 大顶堆
    priority_queue<int, vector<int>, greater<int>> hi;   // 小顶堆//great<int> 谓词取反

public:
    // Adds a number into the data structure.
    void addNum(int num)
    {
        lo.push(num);                                    // 加到大顶堆

        hi.push(lo.top());                               // 平衡
        lo.pop();

        if (lo.size() < hi.size()) {                     // 维护两个堆元素个数
            lo.push(hi.top());
            hi.pop();
        }
    }

    // Returns the median of current data stream
    double findMedian()
    {
        return lo.size() > hi.size() ? (double) lo.top() : (lo.top() + hi.top()) * 0.5;
    }
};

补充知识:

1.greater()和less() 基于头文件(#include)

用sort举例 sort排序
升序 sort(arr,arr+len,less<>(type));
降序 sort(arr,arr+len,greater<>(type));

2.make_heap(), pop_heap(), push_heap()用法

make_heap()是生成一个堆,大顶堆或小顶堆

make_heap(_RAIter,_RAIter) 默认生成大顶堆
make_heap(_RAIter,_RAIter,_Compare) Compare有两种参数,一种是greater(生成小顶堆),一种是less(生成大顶堆)

push_heap()是向堆中插入一个元素,并且使堆的规则依然成立

push_heap(_RAIter,_RAIter) 默认为大顶堆
push_heap(_RAIter,_RAIter,_Compare) Compare有两种参数,一种是greater(小顶堆),一种是less(大顶堆)

调用push_heap之前必须调用make_heap创建一个堆

首先数组push_back插入元素,然后再调用push_heap,它会使最后一个元素插到合适位置
注意,push_heap中的_Compare和make_heap中的_Compare参数必须是一致的,不然会插入堆失败,最后一个元素还是在最后位置,导致插入失败

pop_heap()是在堆的基础上,弹出堆顶元素。

pop_heap(_RAIter,_RAIter) 默认为大顶堆
pop_heap(_RAIter,_RAIter,_Compare) _Compare有两种参数,一种是greater(小顶堆),一种是less(大顶堆)
比如pop_heap(nums.begin(), nums.end(),greater()),它会将堆顶元素(即为数组第一个位置)和数组最后一个位置对调,然后你可以调用数组pop_back,删除这个元素
注意,pop_heap中的_Compare和make_heap中的_Compare参数必须是一致的,不然会失败

# include <iostream>
# include <functional>
# include <vector>
# include <algorithm>

using namespace std;

void printVec(vector<int> nums)
{
    for (int i = 0; i < nums.size(); ++i)
        cout << nums[i] << " ";
    cout << endl;
}
int main(void)
{
    int nums_temp[] = {8, 3, 4, 8, 9, 2, 3, 4, 10};
    vector<int> nums(nums_temp, nums_temp + 9);
    cout << "make_heap之前: ";
    printVec(nums);

    cout << "(默认(less))make_heap: ";
    make_heap(nums.begin(), nums.end());
    printVec(nums);

    cout << "(less)make_heap: ";
    make_heap(nums.begin(), nums.end(), less<int> ());
    printVec(nums);

    cout << "(greater)make_heap: ";
    make_heap(nums.begin(), nums.end(), greater<int> ());
    printVec(nums);

    cout << "此时,nums为小顶堆 greater" << endl;
    cout << "push_back(3)" << endl;
    nums.push_back(3);
    cout << "默认(less)push_heap 此时push_heap失败: ";
    push_heap(nums.begin(), nums.end());
    printVec(nums);
    cout << "push_heap为greater 和make_heap一致,此时push_heap成功: ";
    push_heap(nums.begin(), nums.end(), greater<int>());
    printVec(nums);
    cout << "(greater,不然会失败)pop_heap: ";
    pop_heap(nums.begin(), nums.end(),greater<int>());
    printVec(nums);
    cout << "pop_back(): ";
    nums.pop_back();
    printVec(nums);
}

面试题42( 连续子数组的最大和)

题目描述:

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

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

示例1:

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

提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100


题解:

解题思路一 贪心法
/*
 * 方法一 贪心法 O(n)
 *
 * 当叠加的和小于0时,就从下一个数重新开始,
 * 同时更新最大和的值(最大值可能为其中某个值),
 * 当叠加和大于0时,将下一个数值加入和中,
 * 同时更新最大和的值,依此继续。
 *
 * 举例: nums = [-2,1,-3,4,-1,2,1,-5,4]
 * sum = INT_MIN <= 0-> sum = -2 <= 0 -> sum = 1 > 0 ->
 * -> sum = -2 <= 0 -> sum = 4 > 0 -> sum = 3 > 0 ->
 * -> sum = 5 > 0 -> sum = 6 > 0 -> sum = 1 > 0 ->
 * -> sum = 5 > 0
 * res = [-2, 1, 1, 4, 4, 5, 6, 6, 6]
 * 最终返回 res = 6
 * */

代码
int Solution42::maxSubArray(std::vector<int> &nums) {
    assert(!nums.empty());
    int resSum = INT_MIN;
    int curSum = 0;
    // 遍历数组
    for (int i = 0; i < nums.size(); i++) {
        // 当sum小于0时,就从下一个数重新开始
        // 同时更新每次叠加的最大值
        if (curSum <= 0) {
            curSum = nums[i];
        } else {
            // 和大于0时
            curSum += nums[i];
        }
        // 不断更新子串的最大值
        if (curSum > resSum) {
            resSum = curSum;
        }
    }
    return resSum;
}

解题思路二 分治法
/*
 * 方法二 分治法 O(nlogn)
 *
 * 分治法模板:
 * 1. 定义基本情况
 * 2. 将问题分解为子问题并递归解决子问题
 * 3. 合并子问题的解以获得原始问题的解
 *
 * 将nums由中点mid分为三种情况:
 * 1. 最大子串在左边
 * 2. 最大子串在右边
 * 3. 最大子串跨中点,左右都有
 *
 * 当子串在左边或右边时,继续分中点递归分解到一个数为止,
 * 对于递归后横跨的子串,再分治为左侧和右侧求最大子串,
 * 可使用贪心算法求最大子串值,再合并为原始的最大子串值
 * */


代码
int maxSubArray2(std::vector<int> &nums) {
    assert(!nums.empty());

    return helper(nums, 0, nums.size() - 1);
}

int helper(std::vector<int> &nums, int left, int right) {
    // 分解到一个值时返回该值
    if (left == right) {
        return nums[left];
    }

    // 求中点值
    int mid = left + (right - left) / 2;

    // 中点左边的最大值
    int leftSum = helper(nums, left, mid);
    // 中点右边的最大值
    int rightSum = helper(nums, mid + 1, right);
    // 横跨中点的最大值
    int croSum = crossSum(nums, left, right, mid);

    // 返回以上三种情况中的最大值
    return std::max(std::max(leftSum, rightSum), croSum);
}

int crossSum(std::vector<int> &nums, int left, int right, int mid) {
    // 分解到一个值时返回该值
    if (left == right) {
        return nums[left];
    }
       // 贪心法求左边的最大值
    int leftSubsum = INT_MIN;
    int curSum = 0;
    for (int i = mid; i > left - 1; i--) {
        curSum += nums[i];
        leftSubsum = std::max(leftSubsum, curSum);
    }

    // 贪心法求右边的最大值
    int rightSubsum = INT_MIN;
    curSum = 0;
    for (int i = mid + 1; i < right + 1; i++) {
        curSum += nums[i];
        rightSubsum = std::max(rightSubsum, curSum);
    }

    return leftSubsum + rightSubsum;
}

解题思路三 动态规划法
/*
 * 方法三 动态规划—— Kadane算法 O(n)
 *
 * 在整个数组或在固定大小的滑动窗口中找到总和或最大值或最小值的问题,
 * 可通过动态规划(DP)在线性时间内解决
 *
 * 两种标志DP适用于数组:
 * 1. 常数空间,沿数组移动并子啊原数组修改;
 * 2. 线性空间,首先沿left->right方向移动,然后沿right->left方向移动,最后合并结果。
 *
 * 本题可通过修改数组跟踪当前位置的最大和,
 * 在知道当前位置的最大和后更新全局最大和。
 * */

代码

int maxSubArray3(std::vector<int> &nums) {
    assert(!nums.empty());

    int n = nums.size();
    int maxSum = nums[0];

    // 如果当前值小于0,
    // 重新开始(全局最大值更新)
    for (int i = 1; i < n; i++) {
        // 更新当前的最大值
        if (nums[i - 1] > 0) {
            nums[i] += nums[i - 1];
        }
        // 更新全局的最大值
        maxSum = std::max(nums[i], maxSum);
    }

    return maxSum;
}

动态规划和贪心算法的区别

1、贪心最经典的例子是找钱问题,某国钱币包含1、3、4元,如果要找6元,如何找钱会找的最少?

   贪心的思想是每次都拿最大的,先拿4元,再拿一个1元,再拿一个1元,一共是三张。每次都拿最大的就是贪心,但实际上,只需要两张三元是最优解

   所以说贪心算法不一定能得到最优解,贪心必须在一定条件下得到的才是最优解,比如我国钱币,1、5、10元,大家平时找钱使用贪心法就是最优解。

2、贪心的做法是自顶向下的,而动态规划的解法是自底向上的。

   比如还是找钱问题,动态规划的求解就是找一元如何找的最少,找两元如何找的最少,找三元如何找的最少,并且每次算当前找的最少的钱都使用了之前计算的各自的最优解,从之前所有子集的最优解中找到最优解。

   同时由于每次都记录了每个子问题的最优解,不用每次都重新求子问题的最优解,而是直接使用,就会比暴力解法快很多。

3、 1)、贪心算法是动态规划的一种特例,贪心算法能解决的问题动态规划一定能解决。动态规划能解决的问题,贪心不一定能够解决
    2)、贪心的复杂度低,动态规划的复杂度高


面试题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 <= n < 2^31

题解:

class Solution {
public:
    int countDigitOne(int n) {
        int count=0;
        if(n<1) return count=0;
        CountNum1(n,count);

        return count;

    }
    int CountNum1(int n,int& count)
    {
        int temp0=n;
        int length=1;
        while(temp0>=10)
        {
            temp0/=10;
            length++;
        }
        int temp1=n;
        int temp2=0;
        if(temp1<10)
        {
            if(temp1>=1) count+=1;
            return count;
        }
        int temp3=1;
        for(int i=1;i<length;i++)
        {
            temp3*=10;
        }
        int temp5=n/temp3;
        temp2=temp1-temp3*temp5;
        if(temp1<(2*temp3)) 
        {
            count=count+temp2+1;
            int temp4=1;
            for(int j=1;j<=length-2;j++)
            {
                temp4*=10;
            }
            count=count+(length-1)*temp4;
        }
        else
        {
            count=count+temp3;
            int temp4=1;
            for(int j=1;j<=length-2;j++)
            {
                temp4*=10;
            }
            
            count=count+(length-1)*temp4*temp5;
        }
        CountNum1(temp2,count);
        return count;
    }
};

面试题44(数字序列中某一位的数字)

题目描述:

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

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

示例 1:

输入:n = 3
输出:3
示例 2:

输入:n = 11
输出:0

限制:

0 <= n < 2^31

题解1(自己写的):

class Solution {
public:
    int findNthDigit(int n) {
    if(n<=9) return n;
    
    int pos=0;
    long temp=0;
    int Double10=0;
    while(n>temp)
    {
        n=n-temp;
        pos++;
        temp =pos*pow(10,Double10)*9;
        Double10++;
    }
    int i=n/pos;
    int j=0;
    int temp1=0;
    if(n%pos==0) 
    {
        j=pow(10,Double10-1)+i-1;
        temp1=j%10;
    }
    else 
    {
        j=pow(10,Double10-1)+i;
        int p=n%pos;
        for(int k=0;k<pos-p;k++)
       {
          j/=10;
       }
       temp1=j%10;
    }
    return temp1;
    }
};

题解2:

解题思路

图片1.png

观察得除0外,个位数9个占0.9* 10(1) * 1个位置,十位数90个占0.9* 10(2) * 2位置,百位数900个占0.9* 10(3)*3位置,依次类推。

可以根据n所在多少位,先求出n所在的数字,
再将该数字转化为字符串,
最后求出n在数字的第几位并将该位输出即可。

注意点:
0: 本题解中主要处理个数的问题,每前进一段,就将上一段的长度给砍掉。

1: 题目中的n是从0开始到n的,也就是一开始有n+1个数。当取n = 0时,i也应该为0, 不满足循环公式(会出错),所在在while循环之前,单独处理了0段,即将n+1个数减1,表示去除了0段之后,还剩n个数。

2: (n-1)/i 中的n-1是为了在求n所在数字时,低位从0开始。pow(10,i-1)是为了求n所在数字段的起始基础。

  如从基础100开始,通过n-1可以保证从0开始计数,即100+n-1可以取到100,否则只能从101开始,取不到100,导致错误。

代码:

class Solution {
public:
    int findNthDigit(int n) {
        int i = 1;
        while(n > 0.9*pow(10,i)*i) n -= 0.9*pow(10,i)*i,i++;            //大于则前进
        string res = to_string(pow(10, i-1) + (n-1)/i);                 //求出n所在的数字,并转化位字符串,n-1可以理解为取不到0,所以这里使其能取到0,理解为从第一个值to_string(pow(10, i-1)开始。要不然就不对,
       // 例如n=11,计算到这里为2,这里表示10的第2位置,如果不减1则将直接指向11,明显错误。
        return res[(n-1)%i] - '0';                                      //求出n在第几位,输出。
    }
};

面试题45(把数组排成最小的数)

题目描述:

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

示例 1:

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

输入: [3,30,34,5,9]
输出: "3033459"
 
提示:

0 < nums.length <= 100
说明:

输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0

题解:

思路描述:首先我们要明白就是

无论这些数字怎么取排列,形成的数字的位数是不变的
那么就是高位的数字肯定是越小越好。

我们先考虑一下怎么排列两个数字,比如 1 和 20,高位越小越好,放 1,组合成 120

我们再看一下三个数的情况,比如 36、38 和 5,首先肯定先放 36,剩下 38 和 5,然后对这两个数进行排列 385,所以最后的结果为 36385。

由上面的两个例子我们其实就可以知道,放数字的顺序肯定是先放第一位(最左边一位)最小的元素,如果第一位相等,比较第二位....,以此类推。

我们再思考一下,36 < 38 > 5 ,但是 "36" < "38" < "5",


也就是我们如果把所有数字转换成字符串再排列,刚好就是我们希望的情况。

注意:我们这里说的排列大小比较和字符串大小有点区别,比如 3 和 30,明显 30 排在前面比较好,所以我们要重构比较,我们组合 s1 和 s2 ,如果 s1 + s2 > s2 + s1,那么 s1 > s2

至此,我们已经分析出来了。



代码:

class Solution {
public:
    string minNumber(vector<int>& nums) {
     vector<string> temp;
     for(auto n:nums)
     temp.push_back(to_string(n));
     sort(temp.begin(),temp.end(),[](string& s1,string& s2){return s1+s2<s2+s1;});

     string res;
     for(auto a:temp)
     res+=a;

     return res;
    }
};

知识点:

1.使用string&传值的速度会快于string呢?

引用传递不需要调用构造函数去构造函数的局部变量。

2.释下代码中sort(strs.begin(), strs.end(), [](string& s1, string& s2){return s1 + s2 < s2 + s1;});中的[](string& s1, string& s2){return s1 + s2 < s2 + s1;}

lambda表达式的写法.(https://www.jianshu.com/p/d686ad9de817)

// 定义简单的lambda表达式
auto basicLambda = [] { cout << "Hello, world!" << endl; };
// 调用
basicLambda();   // 输出:Hello, world!

// 指明返回类型
auto add = [](int a, int b) -> int { return a + b; };
// 自动推断返回类型
auto multiply = [](int a, int b) { return a * b; };

int sum = add(2, 5);   // 输出:7
int product = multiply(2, 5);  // 输出:10

3.“36” < “38” < "5"是怎么得到的啊

字符串比较大小就是先比较第一个字符大小,再比较后面的字符;

4.for(auto a:temp)

C++11新增特性

vector<int> vec;
vec.push_back(10);
vec.push_back(20);

for (int i : vec)
{
    cout << i;
}

这段代码就是实现了对于vector型变量vec的内容打印,变量i遍历vector中的每一个元素,直到vector的结束。
当然了,在这里,为了迭代更加复杂的数据结构,你可以使用auto关键字。例如,迭代一个map类型数据变量,你可以这样写:

map<string, string> address_book;
for (auto address_entry : address_book)
{
    cout << address_entry.first << " < " address_entry.second << " > " << endl;
}

面试题46(把数字翻译成字符串)

题目描述:

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

示例 1:

输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
 
提示:

0 <= num < 231

题解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cz0dvHZJ-1595824485531)(F:\desktop\刷题\assets\1594094242150.png)]

class Solution {
public:
    int translateNum(int num) {
        string n=to_string(num);
        int length=n.length();
        if(length<2) return length;
        int dp[length+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=1;i<length;i++)
        {
            dp[i+1]=dp[i];
            auto s=n.substr(i-1,2);
            if(s>="10"&&s<="25")
            dp[i+1]+=dp[i-1];
        }
        return dp[length];
    }
};


知识点:

1.c++字符串长度求法(string,char*)

(1)length():最直接字符串长度
(2)size():字符串长度(类似string内char元素个数)
(3)strlen(char*):函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个’\0’,如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到’\0’停止。

(1)length()
#include <string>
string user;
int len = user.length();

(2)size()
#include <string>
string user;
int len = user.size();

(3)strlen()
#include <string>
char* user = new char;
int len = strlen(user);
delete user;

2.substr (C++语言函数)

substr是C++语言函数,主要功能是复制子字符串,要求从指定位置开始,并具有指定的长度。如果没有指定长度_Count或_Count+_Off超出了源字符串的长度,则子字符串将延续到源字符串的结尾。

2020/7/14

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

题目描述:

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

示例 1:
输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
 
提示:
0 < grid.length <= 200
0 < grid[0].length <= 200

题目解析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIEA7A7S-1595824485534)(F:\desktop\刷题\assets\1594701509274.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pjaNqr1C-1595824485538)(F:\desktop\刷题\assets\1594701537782.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6aYnsVes-1595824485541)(F:\desktop\刷题\assets\1594701570917.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-55eAcbVc-1595824485544)(F:\desktop\刷题\assets\1594701649231.png)]

代码:
class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        if(grid.empty()) return 0;
        int row=grid.size();
        int col=grid[0].size();
        if(row==0&&col==0) return grid[0][0];
        //第一行第一列可以直接给出
        //这里有检测过,for循环里的i作用域只是for循环内,可以在下一个for循环重复使用。
        for(int k=1;k<col;k++) grid[0][k]=grid[0][k-1]+grid[0][k];
        for(int l=1;l<row;l++) grid[l][0]=grid[l-1][0]+grid[l][0];
        for(int i=1;i<row;i++)
        {
            for(int j=1;j<col;j++)
            {
                grid[i][j]=max(grid[i][j-1],grid[i-1][j])+grid[i][j];
            }
        }    
        return grid[row-1][col-1];
    }
};

20200715

剑指offer第48题(最长不含重复字符的子字符串)

题目描述:

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

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

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

示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
 
提示:
s.length <= 40000


题目解析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BrMH8bIh-1595824485547)(F:\desktop\刷题\assets\1594777718963.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-shEjZ1Td-1595824485551)(F:\desktop\刷题\assets\1594777748710.png)]

代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
         int length=s.size(); 
         if(length<=0) return 0;
        //注意 这里是所有字符 共有128个字符 在将字符用int型存储时 则可以直接转化为数字
         vector<int> temps(128,-1);
         int dp[length];
         temps[s[0]]=0; 
         dp[0]=1;
         int res=1;
         for(int i=1;i<length;i++)
         {
             if(i-temps[s[i]]>dp[i-1]) 
             {
                 dp[i]=dp[i-1]+1;
             }
             else 
             {
                 dp[i]=i-temps[s[i]];
             }
             temps[s[i]]=i;
             res=max(res,dp[i]);
         }
         return res;
    }
};

2020/7/15

剑指 Offer 49. 丑数

题目描述:

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


示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:  

1 是丑数。
n 不超过1690。

题目解析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3NPhSQP6-1595824485554)(F:\desktop\刷题\assets\1594816944337.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0JDPeZHx-1595824485557)(F:\desktop\刷题\assets\1594816988284.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eZiOSWIO-1595824485560)(F:\desktop\刷题\assets\1594817026340.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJ7OHUph-1595824485564)(F:\desktop\刷题\assets\1594817055589.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A6FIi2EQ-1595824485567)(F:\desktop\刷题\assets\1594817083080.png)]

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

题目描述:

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

示例:

s = "abaccdeff"
返回 "b"

s = "" 
返回 " "
 
限制:
0 <= s 的长度 <= 50000

题目解析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zxacjw3x-1595824485570)(F:\desktop\刷题\assets\1594823471206.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9vTnEtwu-1595824485573)(F:\desktop\刷题\assets\1594823491440.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6jdGNErl-1595824485577)(F:\desktop\刷题\assets\1594823522197.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YF3uwpCt-1595824485580)(F:\desktop\刷题\assets\1594823606043.png)]

代码1:

class Solution {
public:
    char firstUniqChar(string s) {
        if(s=="") return ' ';
        unordered_map<char,int> cmap;

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

    }
};

代码2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3UKzIQC5-1595824485583)(F:\desktop\刷题\assets\1594824105509.png)]

//2020/7/16

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

题目描述:

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

 

示例 1:

输入: [7,5,6,4]
输出: 5
 

限制:

0 <= 数组长度 <= 50000

题目解析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0TnDaQjR-1595824485586)(F:\desktop\刷题\assets\1594895762876.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kl0XRUUu-1595824485589)(F:\desktop\刷题\assets\1594895812488.png)]

代码:

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 inv_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];
                i++;
            }
            else {
                tmp[pos] = nums[j];
                j++;
               inv_count += mid + 1-i;
            }
            pos++;
        }
        for (int k = i; k <= mid; ++k) {
            tmp[pos++] = nums[k];
        }
        for (int k = j; k <= r; ++k) {
            tmp[pos++] = nums[k];
        }
        //这里使用的是copy函数,copy(s.begin(),s.end().dest.beign());
        copy(tmp.begin() + l, tmp.begin() + r +1, nums.begin() + l);
        return inv_count;
    }

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

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

题目描述:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWRJVczn-1595824485593)(F:\desktop\刷题\assets\1594898306767.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5iS5pqm-1595824485596)(F:\desktop\刷题\assets\1594898361499.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YtxUcwM7-1595824485600)(F:\desktop\刷题\assets\1594898392024.png)]

题目解析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TbpYNcgo-1595824485603)(F:\desktop\刷题\assets\1594898450810.png)]

代码:

/**
 * 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) {
        if(!headA||!headB) return nullptr;
        int lengthA=0;
        int lengthB=0;
        ListNode* tempA=headA;
        ListNode* tempB=headB;
        while(tempA)
        {
            lengthA++;
            tempA=tempA->next;
        }
        while(tempB)
        {
            lengthB++;
            tempB=tempB->next;
        }
        if(lengthA>=lengthB)
        {
            int i=lengthA-lengthB;
            while(i)
            {
                headA=headA->next;
                i--;
            }
        }
        else
        {
            int i=lengthB-lengthA;
            while(i)
            {
                headB=headB->next;
                i--;
            }
        }
        while(headA!=headB)
        {
            headA=headA->next;
            headB=headB->next;
        }        
        return headA;      
    }
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值