《剑指offer 第2版》算法思路+代码答案(简单&中等)

1.牛客网刷题地址:https://www.nowcoder.com/exam/oj/ta?tpId=13
2.书中的最后 对应 牛客刷题的 66题 结束

1、JZ3 数组中重复的数字

在这里插入图片描述

1.1 思路

  • 遍历数组,从第一个数字开始,一一两个数字对比是否相等

1.2 代码

int duplicate(vector<int>& numbers) {
	for(int i = 0 ;i < numbers.size();i++){
		for (int j = i + 1 ;j < numbers.size();j++) {
			if (numbers[i] == numbers[j]) {
				return  numbers[i];
			}
		}
	}
	return -1;
}

1.3 思路(官方推荐)

  • 既然数组长度为n只包含了0n−1的数字,那么如果数字没有重复,这些数字排序后将会与其下标一一对应。那我们就可以考虑遍历数组,每次检查数字与下标是不是一致的,一致的说明它在属于它的位置上,不一致我们就将其交换到该数字作为下标的位置上,如果交换过程中,那个位置已经出现了等于它下标的数字,那肯定就重复了
  • 做法
    • step 1:遍历数组,遇到数组元素与下标相同的不用管。
    • step 2:遇到数组元素与下标不同,就将其交换到属于它的位置,交换前检查那个位置是否有相同的元素,若有则重复。
    • step 3:遍历结束完全交换也没重复,则返回-1.

1.4 代码

 int duplicate(vector<int>& numbers) {
    for(int i = 0 ;i < numbers.size();i++){
        //位置正确,就继续
        if (numbers[i] == i) {
            continue;
        //位置不对,交换
        }else {
            //位置有相同元素,重复
            if (numbers[i] == numbers[numbers[i]]) {
                return numbers[i];
            //交换位置
            }else {
                swap(numbers[i], numbers[numbers[i]]);
                //为了再次检查这个位置的元素
                i--;
            }
        }
    }
    //没有重复
    return -1;
}

2、JZ5 替换空格

在这里插入图片描述

2.1 思路

  • 我们可以用下标遍历字符串,每次检查下标所在位置的字符是否为空格,如果不是空格,下标继续往后,如果是空格则调用substr函数将字符串从空格前后截断,然后中间添加"%20"后相连即可。

2.2 代码

string replaceSpace(string s) {
    string res = "";
    // size() 和 length() 这两个成员函数都用于返回字符串中字符的数量
    // length() 函数通常是直接调用 size() 函数来实现的
    // size():这个方法更符合 STL(标准模板库)容器的使用习惯。
    // length():它更侧重于表达字符串的长度这一概念。
    for (int i = 0; i < s.length(); i++) {
        if (s[i] == ' ') {
            res += "%20";
        }else {
            res += s[i];
        }
    }
    return res;
}

3、JZ6 从尾到头打印链表

在这里插入图片描述

3.1 思路

  • step 1:我们可以顺序遍历链表,将链表的值push到栈中。
  • step 2:然后再依次弹出栈中的元素,加入到数组中,即可实现链表逆序。

3.2 代码

class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> res;
        stack<int> s;
        //正序输出链表到栈中
        while(head != NULL){ 
            s.push(head->val);
            head = head->next;
        }
        //输出栈中元素到数组中
        while(!s.empty()){ 
            res.push_back(s.top());
            s.pop();
        }
        return res;
    }
};

3.3 思路(官方推荐)

  • step 1:从表头开始往后递归进入每一个节点。
  • step 2:遇到尾节点后开始返回,每次返回依次添加一个值进入输出数组。
  • step 3:直到递归返回表头。

3.4 代码

class Solution {
public:
    //递归函数
    void recursion(ListNode* head, vector<int>& res){ 
        if(head != NULL){
            //先往链表深处遍历
            recursion(head->next, res); 
            //再填充到数组就是逆序
            res.push_back(head->val); 
        }
    }
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> res;
        //递归函数打印
        recursion(head, res); 
        return res;
    }
};

4、JZ7 重建二叉树

在这里插入图片描述
在这里插入图片描述

4.1 思路(官方推荐)

  • 对于二叉树的前序遍历,我们知道序列的第一个元素必定是根节点的值,因为序列没有重复的元素,因此中序遍历中可以找到相同的这个元素,而我们又知道中序遍历中根节点将二叉树分成了左右子树两个部分,如下图所示:
    在这里插入图片描述
    我们可以发现,数字1是根节点,并将二叉树分成了(247)和(3568)两棵子树,而子树的的根也是相应前序序列的首位,比如左子树的根是数字2,右子树的根是数字3,这样我们就可以利用前序遍历序列找子树的根节点,利用中序遍历序列区分每个子树的节点数。

  • 具体做法:

    • step 1:先根据前序遍历第一个点建立根节点。
    • step 2:然后遍历中序遍历找到根节点在数组中的位置。
    • step 3:再按照子树的节点数将两个遍历的序列分割成子数组,将子数组送入函数建立子树。
    • step 4:直到子树的序列长度为0,结束递归。

4.2 代码

class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        int n = pre.size();
        int m = vin.size();
        //每个遍历都不能为0
        if(n == 0 || m == 0) 
            return NULL;
        //构建根节点
        TreeNode *root = new TreeNode(pre[0]); 
        for(int i = 0; i < vin.size(); i++){
            //找到中序遍历中的前序第一个元素
            if(pre[0] == vin[i]){ 
                //左子树的前序遍历
                vector<int> leftpre(pre.begin() + 1, pre.begin() + i + 1);  
                //左子树的中序遍历
                vector<int> leftvin(vin.begin(), vin.begin() + i); 
                //构建左子树
                root->left = reConstructBinaryTree(leftpre, leftvin); 
                //右子树的前序遍历
                vector<int> rightpre(pre.begin() + i + 1, pre.end()); 
                //右子树的中序遍历
                vector<int> rightvin(vin.begin() + i + 1, vin.end()); 
                //构建右子树
                root->right = reConstructBinaryTree(rightpre, rightvin); 
            }
        }
        return root;
    }
};

5、JZ8 二叉树的下一个结点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.1 思路(官方推荐)

  • 首先要根据给定输入中的结点指针向父级进行迭代,直到找到该树的根节点;然后根据根节点进行中序遍历,当遍历到和给定树节点相同的节点时,下一个节点就是我们的目标返回节点
  • 具体做法
    • step 1:首先先根据当前给出的结点找到根节点
    • step 2:然后根节点调用中序遍历
    • step 3:将中序遍历结果存储下来
    • step 4:最终拿当前结点匹配是否有符合要求的下一个结点

5.2 代码

class Solution {
public:
    // 定义一个向量,用于存储中序遍历得到的节点指针
    vector<TreeLinkNode*> nodes;

    TreeLinkNode* GetNext(TreeLinkNode* pNode) {
        // 从给定节点出发,通过 next 指针找到二叉树的根节点
        TreeLinkNode* root = pNode;
        while(root->next) root = root->next;    

        // 调用 InOrder 函数进行中序遍历,并将结果存储在 nodes 向量中
        InOrder(root);                          
        int n = nodes.size();

        // 遍历 nodes 向量,找到与给定节点匹配的位置
        for(int i = 0; i < n - 1; i++) {
            TreeLinkNode* cur = nodes[i];
            // 将当前节点与给定节点进行匹配
            if(pNode == cur) {
                // 如果匹配成功,返回下一个节点的指针
                return nodes[i+1];              
            }
        }
        // 如果没有找到匹配节点或者匹配节点是最后一个节点,返回 NULL
        return NULL;                            
    }

    // 中序遍历函数
    void InOrder(TreeLinkNode* root) {          
        if(root == NULL) return;
        // 递归遍历左子树
        InOrder(root->left);
        // 将当前节点的指针添加到 nodes 向量中
        nodes.push_back(root);
        // 递归遍历右子树
        InOrder(root->right);
    }
};

示例过程
假设存在如下二叉树:

    1
   / \
  2   3
 / \
4   5

节点的 next 指针指向其父节点。

  1. 获取根节点:假设给定节点是 4,从 4出发,通过 next指针找到根节点 1
  2. 中序遍历:对以 1 为根节点的二叉树进行中序遍历,得到的节点顺序是 4, 2, 5, 1, 3,将这些节点的指针依次存储在 nodes 向量中。
  3. 查找匹配节点:遍历 nodes 向量,找到与给定节点 4 匹配的位置,该位置是 0
  4. 返回结果:匹配位置的下一个位置是 1,对应节点 2,所以返回节点 2 的指针。

6、JZ9 用两个栈实现队列

在这里插入图片描述

6.1 思路

  • 入队操作(push):把元素直接压入 stack1 的栈顶,因为 stack1 负责存储新入队的元素,这样能保证新元素在队列尾部。
  • 出队操作(pop)
    • 先检查 stack2 是否为空。若为空,说明 stack2 里没有可直接出队的元素,就把 stack1 中的所有元素依次弹出并压入 stack2。这样做之后,stack2 中元素的顺序和队列中元素的顺序一致,即最早入队的元素位于 stack2 的栈顶。
    • 若 stack2 仍为空,这表明队列里没有元素,返回 -1 表示队列为空。
    • 若 stack2 不为空,直接弹出 stack2 的栈顶元素,此元素就是队列中最早入队的元素,也就是要出队的元素。

6.2 代码

class Solution
{
public:
    // 入队操作:将元素压入 stack1 栈顶
    // 因为 stack1 用于存储新入队的元素,所以新元素会在队列尾部
    void push(int node) {
        stack1.push(node);
    }

    int pop() {
        // 检查 stack2 是否为空
        // 如果为空,需要将 stack1 中的元素转移到 stack2 中
        if (stack2.empty()) {
            // 当 stack1 不为空时,将 stack1 的元素依次弹出并压入 stack2
            // 这样 stack2 中的元素顺序就和队列顺序一致
            while (!stack1.empty()) {
                stack2.push(stack1.top());
                stack1.pop();
            }
        }

         // 从 stack2 中弹出栈顶元素,该元素就是队列的头部元素
        int res = stack2.top();
        stack2.pop();

        return res;
    }

private:
    stack<int> stack1;
    stack<int> stack2;
};

7、JZ10 斐波那契数列

在这里插入图片描述

7.1 思路

  • 根据公式进行递归

7.2 代码

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 
     * @return int整型
     */
    int Fibonacci(int n) {
        if (n == 1 || n == 2) {
            return 1;
        }

       int x = Fibonacci(n - 1) + Fibonacci(n - 2);
       return x;
    }
};

8、JZ11 旋转数组的最小数字

在这里插入图片描述

8.1 思路

  • 遍历对比查找

8.2 代码

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型vector 
     * @return int整型
     */
    int minNumberInRotateArray(vector<int>& nums) {
        int min = nums[0];

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

        return min;
    }
};

8.3 思路(官方推荐)

  • 旋转数组将原本有序的数组分成了两部分有序的数组,因为在原始有序数组中,最小的元素一定是在首位,旋转后无序的点就是最小的数字。我们可以将旋转前的前半段命名为A,旋转后的前半段命名为B,旋转数组即将AB变成了BA,我们想知道最小的元素到底在哪里。

  • 因为A部分和B部分都是各自有序的,所以我们还是想用分治来试试,每次比较中间值,确认目标值(最小元素)所在的区间。

  • 具体做法:

    • step 1:双指针指向旋转后数组的首尾,作为区间端点。
    • step 2:若是区间中点值大于区间右界值,则最小的数字一定在中点右边。
    • step 3:若是区间中点值等于区间右界值,则是不容易分辨最小数字在哪半个区间,比如[1,1,1,0,1],应该逐个缩减右界。
    • step 4:若是区间中点值小于区间右界值,则最小的数字一定在中点左边。
    • step 5:通过调整区间最后即可锁定最小值所在。

8.4 代码

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        int left = 0;
        int right = rotateArray.size() - 1;
        while(left < right){
            int mid = (left + right) / 2;
            //最小的数字在mid右边
            if(rotateArray[mid] > rotateArray[right]) 
                left = mid + 1;
            //无法判断,一个一个试
            else if(rotateArray[mid] == rotateArray[right]) 
                right--;
            //最小数字要么是mid要么在mid左边
            else 
                right = mid;
        }
        return rotateArray[left];
    }
};

9、JZ12 矩阵中的路径

在这里插入图片描述

9.1 思路

要在矩阵中找到从某个位置开始,位置不重复的一条路径,路径为某个字符串,那我们肯定是现在矩阵中找到这个位置的起点。没办法直观的找出来,只能遍历矩阵每个位置都当作起点试一试

找到起点后,它周围的节点是否可以走出剩余字符串子串的路径,该子问题又可以作为一个递归。因此,可以用递归来解决。

  • 终止条件: 到了矩阵的边界或者是下一个字符与这个位置的字符不匹配,或者字符串匹配完成,都需要返回,
  • 返回值:将每条查询路径是否有这样的字符串返回,即true or false。
  • 本级任务:检查这个位置的字符与字符串中这个字符是否匹配,并向与它相邻的四个方向(且不是来的方向)延伸子问题。
dfs(matrix, n, m, i - 1, j, word, k + 1, flag)
||dfs(matrix, n, m, i + 1, j, word, k + 1, flag)
||dfs(matrix, n, m, i, j - 1, word, k + 1, flag)
||dfs(matrix, n, m, i , j + 1, word, k + 1, flag)

同时,在走的过程中,要设置一个和矩阵大小相同的bool矩阵表示是否已经访问,如果某个结点访问了,在同一条路线上就不能再访问了,其他路线依旧可以访问,所以若是某条路线不达标,需要回溯,将其改回来。

flag[i][j] = true;
...//递归
//没找到经过此格的,此格未被占用
flag[i][j] = false; 
  • 具体做法:
    • step 1:优先处理矩阵为空的特殊情况。
    • step 2:设置flag数组记录某一次路径中矩阵中的位置是否被经过,因此一条路径不能回头。
    • step 3:遍历矩阵,对每个位置进行递归。
    • step 4:递归查找的时候,到了矩阵的边界或者是下一个字符与这个位置的字符不匹配,或者节点已经访问过了,或者字符串匹配完成都结束递归。
    • step 5:访问节点,修改flag数组,向其他四个方向延伸,回溯的时候修改flag数组。

9.2 代码

class Solution {
public:
	/**
     * matrix:表示输入的字符矩阵,以引用形式传递,避免复制开销,用于在其中寻找路径。
     * n:矩阵的行数。
     * m:矩阵的列数。
     * i,j:当前在矩阵中的坐标位置,从这个位置开始继续搜索路径。
     * word:要寻找路径的目标字符串。
     * k:记录当前在目标字符串word中匹配到第几个字符。
     * flag:二维布尔型数组,用于标记矩阵中哪些格子已经被访问过,避免重复访问,同样以引用形式传递。
     */
    bool dfs(vector<vector<char> >& matrix, int n, int m, int i, int j, string word, int k, vector<vector<bool> >& flag){
        if(i < 0 || i >= n || j < 0 || j >= m || (matrix[i][j] != word[k]) || (flag[i][j] == true))
            //下标越界、字符不匹配、已经遍历过不能重复
            return false;
            
        //k为记录当前第几个字符
        //当k等于目标字符串长度减1时,说明已经匹配完目标字符串的所有字符,找到了满足条件的路径,
        if(k == word.length() - 1) 
            return true;
            
        //当前位置(i, j)标记为已访问(flag[i][j] = true),然后向当前位置的四个方向递归调用dfs函数
        flag[i][j] = true;
        //该结点任意方向可行就可
        //上(i - 1, j )、下(i + 1, j )、左(i, j - 1 )、右(i, j + 1 )
        if(dfs(matrix, n, m, i - 1, j, word, k + 1, flag)
          ||dfs(matrix, n, m, i + 1, j, word, k + 1, flag)
          ||dfs(matrix, n, m, i, j - 1, word, k + 1, flag)
          ||dfs(matrix, n, m, i , j + 1, word, k + 1, flag))
            return true; 
        //如果四个方向都找不到路径,将当前位置标记重新设为未访问(flag[i][j] = false),并返回false
        flag[i][j] = false; 
        return false;
    }
    bool hasPath(vector<vector<char> >& matrix, string word) {
        //优先处理特殊情况
        if(matrix.size() == 0)
            return false;
        int n = matrix.size();
        int m = matrix[0].size();
        //初始化flag矩阵记录是否走过
        vector<vector<bool> > flag(n, vector<bool>(m, false)); 
        //遍历矩阵找起点
        for(int i = 0; i < n; i++){  
            for(int j = 0; j < m; j++){
                //通过dfs找到路径
                if(dfs(matrix, n, m, i, j, word, 0, flag))
                    return true;
            }
        }
        return false;
    }
};

10、JZ14 剪绳子

在这里插入图片描述

10.1 思路

在这里插入图片描述
在这里插入图片描述

10.2 代码

class Solution {
public:
    int cutRope(int number) {
        //不超过3直接计算
        if(number <= 3) 
            return number - 1;
        //dp[i]表示长度为i的绳子可以被剪出来的最大乘积
        vector<int> dp(number + 1, 0);
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        dp[4] = 4;
        //遍历后续每一个长度
        for(int i = 5; i <= number; i++)
            //可以被分成两份
            for(int j = 1; j < i; j++)
                //取最大值
                dp[i] = max(dp[i], j * dp[i - j]);
        return dp[number];
    }
};

11、JZ15 二进制中1的个数

在这里插入图片描述

11.1 思路

  • 知识点:位运算

    计算机的数字由二进制表示,我们平常的运算是对整个数字进行运算,但是还可以按照二进制的每一位分别进行运算。常见运算有位与、位或、移位、位异或等。

  • 思路:

    我们可以检查该数字的二进制每一位是否为1,如果遍历二进制每一位呢?可以考虑移位运算,每次移动一位就可以。至于怎么统计到1呢?我们都只知道数字1与数字相位与运算,其实只是最后一位为1就是1,最后一位为0就是0,这样我们只需要将数字1移位运算,就可以遍历二进制的每一位,再去做位与运算,结果为1的就是二进制中为1的。

  • 具体做法:

    • step 1:遍历二进制的32位,通过移位0-31次实现。
    • step 2:将移位后的1与数字进行位与运算,结果为1就记录一次。

11.2 代码

class Solution {
public:
    int  NumberOf1(int n) {
        int res = 0;
        //遍历32位
        for(int i = 0; i < 32; i++){
            //按位比较
            if((n & (1 << i)) != 0)   
                res++;
        }
        return res;
     }
};

12、JZ16 数值的整数次方

在这里插入图片描述

12.1 思路

  • 直接运算

12.2 代码

class Solution {
public:
    double Power(double base, int exponent) {
        if (base == 0) {
            return 0;
        }

        if (exponent == 0) {
            return 1;
        }

        double res = base;
        for (int i = 1; i < exponent; i++) {
            res *= base;
        }

        return res;
    }
};

13、JZ17 打印从1到最大的n位数

在这里插入图片描述

13.1 思路

  • 直接运算

13.2 代码

#include <vector>
class Solution {
public:
    vector<int> printNumbers(int n) {
        int num = 1;
        for (int i = 1; i <= n; i++) {
            num *= 10;
        }

        vector<int> res;
        for (int i = 1; i < num; i++) {
            res.push_back(i);
        }
        return res;
    }
};

14、JZ18 删除链表的节点

在这里插入图片描述

14.1 思路

  • step 1:首先我们加入一个头部节点,方便于如果可能的话删除掉第一个元素
  • step 2:准备两个指针遍历链表,一个指针指向当前要遍历的元素,另一个指针指向该元素的前序节点,便于获取它的指针。
  • step 3:遍历链表,找到目标节点,则断开连接,指向后一个。
  • step 4:返回时去掉我们加入的头节点。

14.2 代码

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        //加入头节点
        ListNode* res = new ListNode(0);
        res->next = head;
        //前序节点
        ListNode* pre = res;
        //当前节点
        ListNode* cur = head;

        while (cur != nullptr) {
            if (cur->val == val) {
                pre->next = cur->next;
                break;
            }
            pre = cur;
            cur = cur->next;
        }

        //返回去掉节点
        return res->next;
    }
};

15、JZ21 调整数组顺序使奇数位于偶数前面(一)

在这里插入图片描述

15.1 思路

15.2 代码

class Solution {
public:
    vector<int> reOrderArray(vector<int>& array) {
        vector<int> odd; //奇数
        vector<int> even; //偶数
        vector<int> res; //合并

        for (int i = 0; i < array.size(); i++) {
            if ((array[i] % 2) == 0) { //偶数
                even.push_back(array[i]);
            }else {
                odd.push_back(array[i]);
            }
        }

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

        return res;
    }
};

16、JZ22 链表中倒数最后k个结点

在这里插入图片描述

16.1 思路

  • 链表不能逆向遍历,也不能直接访问。但是对于倒数第k个位置,我们只需要知道是正数多少位还是可以直接遍历得到的。
  • 具体做法:
    • step 1:可以先遍历一次链表找到链表的长度。
    • step 2:然后比较链表长度是否比 k 小,如果比 k小返回一个空节点。
    • step 3:如果链表足够长,则我们从头节点往后遍历 n−k 次即可找到所求。

16.2 代码

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        int n = 0;
        ListNode* pre = pHead;
        //统计链表长度
        while (pre != nullptr) {
            n++;
            pre = pre->next;
        }

        //长度过小,返回空链表
        if (k > n) {
            return nullptr;
        }

        //遍历n-k次,使指针到达要输出的前个节点
        pre = pHead;
        for (int i = 0; i < n - k; i++) {
            pre = pre->next;
        }

        return pre;
    }
};

17、JZ23 链表中环的入口结点

在这里插入图片描述
在这里插入图片描述

17.1 思路(官方推荐)

知识点:双指针

双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。

思路:

根据题干,不说别的,我们能发现这道题需要完成两个任务:

  1. 判断链表是否有环。
  2. 在有环的链表中找到环的入口。

对于第一个任务,可以参考判断链表中是否有环,主要思想是利用环没有末尾NULL,后半部分一定是环,然后快慢双指针相遇就代表有环

在这里插入图片描述
在这里插入图片描述

17.2 代码

class Solution {
public:
    //判断有没有环,返回相遇的地方
    ListNode* hasCycle(ListNode *head) { 
        //先判断链表为空的情况
        if(head == NULL) 
            return NULL;
        //快慢双指针
        ListNode* fast = head; 
        ListNode* slow = head;
        //如果没环快指针会先到链表尾
        while(fast != NULL && fast->next != NULL){ 
            //快指针移动两步
            fast = fast->next->next; 
            //慢指针移动一步
            slow = slow->next; 
            //相遇则有环
            if(fast == slow) 
                //返回相遇的地方
                return slow; 
        }
        //到末尾则没有环
        return NULL; 
    }
    
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        ListNode* slow = hasCycle(pHead);
        //没有环
        if(slow == NULL) 
            return NULL;
        //快指针回到表头
        ListNode* fast = pHead; 
        //再次相遇即是环入口
        while(fast != slow){ 
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

18、JZ24 反转链表

在这里插入图片描述
在这里插入图片描述

18.1 思路

  • 将链表反转,就是将每个表元的指针从向后变成向前,那我们可以遍历原始链表,将遇到的节点一一指针逆向即可。指针怎么逆向?不过就是断掉当前节点向后的指针,改为向前罢了。
  • 具体做法:
    • step 1:优先处理空链表,空链表不需要反转。
    • step 2:我们可以设置两个指针,一个当前节点的指针,一个上一个节点的指针(初始为空)。
    • step 3:遍历整个链表,每到一个节点,断开当前节点与后面节点的指针,并用临时变量记录后一个节点,然后当前节点指向上一个节点,即可以将指针逆向。
    • step 4:再轮换当前指针与上一个指针,让它们进入下一个节点及下一个节点的前序节点。

18.2 代码

class Solution {
public:
    ListNode* ReverseList(ListNode* head) {
        //处理空链表
        if (head == nullptr){
            return nullptr;
        }

        ListNode* pre = nullptr;
        ListNode* cur = head;

        while (cur != nullptr) {
            //断开链表,要记录后续一个
            ListNode* tmp = cur->next;
            //当前的next指向前一个
            cur->next = pre;
            //前一个更新为当前
            pre = cur;
            //当前更新为刚刚记录的后一个
            cur = tmp;
        }

        return pre;
    }
};

19、JZ25 合并两个排序的链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

19.1 思路

知识点:双指针

双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。

思路:

这道题既然两个链表已经是排好序的,都是从小到大的顺序,那我们要将其组合,可以使用归并排序的思想:每次比较两个头部,从中取出最小的元素,然后依次往后。这样两个链表依次往后,我们肯定需要两个指针同方向访问才能实现。

具体过程:

  • step 1:判断空链表的情况,只要有一个链表为空,那答案必定就是另一个链表了,就算另一个链表也为空。
  • step 2:新建一个空的表头后面连接两个链表排序后的节点,两个指针分别指向两链表头。
  • step 3:遍历两个链表都不为空的情况,取较小值添加在新的链表后面,每次只把被添加的链表的指针后移。
  • step 4:遍历到最后肯定有一个链表还有剩余的节点,它们的值将大于前面所有的,直接连在新的链表后面即可。

19.2 代码

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        //一个已经为空了,直接返回另一个
        if(pHead1 == NULL) 
            return pHead2;
        if(pHead2 == NULL)
            return pHead1;
        //加一个表头
        ListNode* head = new ListNode(0); 
        ListNode* cur = head;
        //两个链表都要不为空
        while(pHead1 && pHead2){ 
            //取较小值的节点
            if(pHead1->val <= pHead2->val){ 
                cur->next = pHead1;
                //只移动取值的指针
                pHead1 = pHead1->next; 
            }else{
                cur->next = pHead2;
                //只移动取值的指针
                pHead2 = pHead2->next; 
            }
            //指针后移
            cur = cur->next; 
        }
        //哪个链表还有剩,直接连在后面
        if(pHead1) 
            cur->next = pHead1;
        else
            cur->next = pHead2;
        //返回值去掉表头
        return head->next; 
    } 
};

20、JZ26 树的子结构

在这里插入图片描述

20.1 思路

知识点:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

思路:

既然是要找到A树中是否有B树这样子树,如果是有子树肯定是要遍历这个子树和B树,将两个的节点一一比较,但是这样的子树不一定就是A树根节点开始的,所以我们还要先找到子树可能出现的位置。

既然是可能的位置,那我们可以对A树的每个节点前序递归遍历,寻找是否有这样的子树,而寻找是否有子树的时候,我们就将A树与B树同步前序遍历,依次比较节点值。

具体做法:

  • step 1:因为空树不是任何树的子树,所以要先判断B树是否为空树。
  • step 2:当A树为空节点,但是B树还有节点的时候,不为子树,但是A树不为空节点,B树为空节点时可以是子树。
  • step 3:每次递归比较A树从当前节点开始,是否与B树完全一致,同步前序遍历。
  • step 4:A树自己再前序遍历进入子节点,当作子树起点再与B树同步遍历。
  • step 5:以上情况任意只要有一种即可。

20.2 代码

class Solution {
public:
    bool recursion(TreeNode* root1, TreeNode* root2) {
        // B 树的节点已经遍历完,匹配成功
        if (root2 == NULL)
            return true;
        // A 树的节点为空但 B 树的节点非空,匹配失败
        if (root1 == NULL)
            return false;
        // 值匹配
        if (root1->val == root2->val)
            return true;
        // 递归比较左右子树
        return recursion(root1->left, root2->left) && recursion(root1->right, root2->right);
    }

    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
        // 空树不是任何树的子结构
        if (pRoot2 == NULL)
            return false;
        // A 树为空,无法匹配非空的 B 树
        if (pRoot1 == NULL)
            return false;
        // 检查当前节点是否匹配
        if (recursion(pRoot1, pRoot2))
            return true;
        // 递归检查左子树和右子树
        return HasSubtree(pRoot1->left, pRoot2) || HasSubtree(pRoot1->right, pRoot2);
    }
};

21、JZ27 二叉树的镜像

在这里插入图片描述
在这里插入图片描述

21.1 思路

因为我们需要将二叉树镜像,意味着每个左右子树都会交换位置,如果我们从上到下对遍历到的节点交换位置,但是它们后面的节点无法跟着他们一起被交换,因此我们可以考虑自底向上对每两个相对位置的节点交换位置,这样往上各个子树也会被交换位置。

自底向上的遍历方式,我们可以采用后序递归的方法。

具体做法:

  • step 1:先深度最左端的节点,遇到空树返回,处理最左端的两个子节点交换位置。
  • step 2:然后进入右子树,继续按照先左后右再回中的方式访问。
  • step 3:再返回到父问题,交换父问题两个子节点的值。

21.2 代码

class Solution {
public:
    TreeNode* Mirror(TreeNode* pRoot) {
        //空树返回
        if(pRoot == NULL) 
            return NULL;
        //先递归子树
        TreeNode* left = Mirror(pRoot->left);  
        TreeNode* right = Mirror(pRoot->right);
        //交换
        pRoot->left = right; 
        pRoot->right = left;
        return pRoot;
    }
};

22、JZ29 顺时针打印矩阵

在这里插入图片描述

22.1 思路

方法:边界模拟法

这道题就是一个简单的模拟,我们想象有一个矩阵,从第一个元素开始,往右到底后再往下到底后再往左到底后再往上,结束这一圈,进入下一圈螺旋

具体做法:

  • step 1:首先排除特殊情况,即矩阵为空的情况。
  • step 2:设置矩阵的四个边界值,开始准备螺旋遍历矩阵,遍历的截止点是左右边界或者上下边界重合。
  • step 3:首先对最上面一排从左到右进行遍历输出,到达最右边后第一排就输出完了,上边界相应就往下一行,要判断上下边界是否相遇相交。
  • step 4:然后输出到了右边,正好就对最右边一列从上到下输出,到底后最右边一列已经输出完了,右边界就相应往左一列,要判断左右边界是否相遇相交。
  • step 5:然后对最下面一排从右到左进行遍历输出,到达最左边后最下一排就输出完了,下边界相应就往上一行,要判断上下边界是否相遇相交。
  • step 6:然后输出到了左边,正好就对最左边一列从下到上输出,到顶后最左边一列已经输出完了,左边界就相应往右一列,要判断左右边界是否相遇相交。
  • step 7:重复上述3-6步骤直到循环结束。

22.2 代码

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        vector<int> res;
        int n = matrix.size();
        //先排除特殊情况
        if(n == 0) 
            return res;
        //左边界
        int left = 0; 
        //右边界
        int right = matrix[0].size() - 1; 
        //上边界
        int up = 0; 
        //下边界
        int down = n - 1; 
        //直到边界重合
        while(left <= right && up <= down){ 
            //上边界的从左到右
            for(int i = left; i <= right; i++) 
                res.push_back(matrix[up][i]); 
            //上边界向下
            up++; 
            if(up > down)
                break;
            //右边界的从上到下
            for(int i = up; i <= down; i++) 
                res.push_back(matrix[i][right]);
            //右边界向左
            right--; 
            if(left > right)
                break;
            //下边界的从右到左
            for(int i = right; i >= left; i--) 
                res.push_back(matrix[down][i]);
            //下边界向上
            down--; 
            if(up > down)
                break; 
            //左边界的从下到上
            for(int i = down; i >= up; i--) 
                res.push_back(matrix[i][left]);
            //左边界向右
            left++; 
            if(left > right)
                break;
        }
        return res;
    }
};

23、JZ30 包含min函数的栈

在这里插入图片描述
在这里插入图片描述

23.1 思路

  • step 1:使用一个栈记录进入栈的元素,正常进行push、pop、top操作。
  • step 2:使用另一个栈记录每次push进入的最小值。
  • step 3:每次push元素的时候与第二个栈的栈顶元素比较,若是较小,则进入第二个栈,若是较大,则第二个栈的栈顶元素再次入栈,因为即便加了一个元素,它依然是最小值。于是,每次访问最小值即访问第二个栈的栈顶。

23.2 代码

class Solution {
public:
    //用于栈的push 与 pop
    stack<int> s1;  
    //用于存储最小min
    stack<int> s2;  
    void push(int value) {
        s1.push(value);  
        //空或者新元素较小,则入栈
        if(s2.empty() || s2.top() > value)  
            s2.push(value);
        else
            //重复加入栈顶,为保证 s2 和 s1 的元素数量一致,方便后续同步出栈操作
            s2.push(s2.top());  
    }
    void pop() {
        s1.pop();
        s2.pop();
    }
    int top() {
        return s1.top();
    }
    int min() {
        return s2.top();
    }
};

24、JZ31 栈的压入、弹出序列

在这里插入图片描述

24.1 思路

辅助栈

可以用一个模拟。对于入栈序列,只要栈为空,序列肯定要依次入栈。那什么时候出来呢?自然是遇到一个元素等于当前的出栈序列的元素,那我们就放弃入栈,让它先出来。

//入栈:栈为空或者栈顶不等于出栈数组
while(j < n && (s.isEmpty() || s.peek() != popA[i])){
    s.push(pushA[j]);
    j++;
}

如果能按照这个次序将两个序列都访问完,那说明是可以匹配入栈出栈次序的。

具体做法:

  • step 1:准备一个辅助栈,两个下标分别访问两个序列。
  • step 2:辅助栈为空或者栈顶不等于出栈数组当前元素,就持续将入栈数组加入栈中。
  • step 3:栈顶等于出栈数组当前元素就出栈。
  • step 4:当入栈数组访问完,出栈数组无法依次弹出,就是不匹配的,否则两个序列都访问完就是匹配的。

24.2 代码

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        int n = pushV.size();
        //辅助栈
        stack<int> s;
        //遍历入栈的下标
        int j = 0;
        //遍历出栈的数组
        for(int i = 0; i < n; i++){
            //入栈:栈为空或者栈顶不等于出栈数组
            while(j < n && (s.empty() || s.top() != popV[i])){
                s.push(pushV[j]);
                j++;
            }
            //栈顶等于出栈数组
            if(s.top() == popV[i])
                s.pop();
            //不匹配序列
            else
                return false;
        }
        return true;
    }
};

25、JZ32 从上往下打印二叉树

在这里插入图片描述
在这里插入图片描述

25.1 思路

思路:

二叉树的层次遍历就是按照从上到下每行,然后每行中从左到右依次遍历,得到的二叉树的元素值。对于层次遍历,我们通常会使用队列来辅助:

因为队列是一种先进先出的数据结构,我们依照它的性质,如果从左到右访问完一行节点,并在访问的时候依次把它们的子节点加入队列,那么它们的子节点也是从左到右的次序,且排在本行节点的后面,因此队列中出现的顺序正好也是从左到右,正好符合层次遍历的特点。

具体做法:

  • step 1:首先判断二叉树是否为空,空树没有遍历结果。
  • step2:建立辅助队列,根节点首先进入队列。不管层次怎么访问,根节点一定是第一个,那它肯定排在队伍的最前面。
  • step 3:每次遍历队首节点,如果它们有子节点,依次加入队列排队等待访问

25.2 代码

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
        vector<int> res;
        if(root == NULL)
            //如果是空,则直接返回空vector
            return res; 
        //队列存储,进行层次遍历
        queue<TreeNode*> q; 
        q.push(root);
        TreeNode* cur;
        while(!q.empty()){
            cur = q.front();
            q.pop();
            res.push_back(cur->val);
            //若是左右孩子存在,则存入左右孩子作为下一个层次
            if(cur->left)
                q.push(cur->left);
            if(cur->right)
                q.push(cur->right);
        }
        return res;
    }
};

26、JZ33 二叉搜索树的后序遍历序列

在这里插入图片描述

26.1 思路

在这里插入图片描述

26.2 代码

class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
        if(sequence.size() == 0) return false;
        return order(sequence, 0, sequence.size() - 1);
    }
    bool order(vector<int>& sequence, int l, int r) {
        // 剩一个节点的时候 返回 true
        if(l >= r) return true;
        int j;
        int mid = sequence[r];
        
        // 找到左子树和右子树的分界点,j代表左子树的最后一个索引位置
        for(j = r; j >= l; j--) {
            int cur = sequence[j];
            if(cur < mid) break;
        }
        
        // 判断所谓的左子树中是否又不合法(不符合二叉搜索树)的元素
        for(int i = j; i >= l; i--) {
            int cur = sequence[i];
            if(cur > mid) return false;
        }
        return order(sequence, l, j) && order(sequence, j+1, r-1);
    }
};

27、JZ36 二叉搜索树与双向链表

在这里插入图片描述
在这里插入图片描述

27.1 思路

递归中序遍历

二叉搜索树最左端的元素一定最小,最右端的元素一定最大,符合“左中右”的特性,因此二叉搜索树的中序遍历就是一个递增序列,我们只要对它中序遍历就可以组装称为递增双向链表

具体做法:

  • step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre)。
  • step 2:首先递归到最左,初始化head与pre。
  • step 3:然后处理中间根节点,依次连接pre与当前节点,连接后更新pre为当前节点。
  • step 4:最后递归进入右子树,继续处理。
  • step 5:递归出口即是节点为空则返回。

27.2 代码

class Solution {
public:
    //返回的第一个指针,即为最小值,先定为NULL
    TreeNode* head = NULL;  
    //中序遍历当前值的上一位,初值为最小值,先定为NULL
    TreeNode* pre = NULL;   
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if(pRootOfTree == NULL)
            //中序递归,叶子为空则返回
            return NULL;     
        //首先递归到最左最小值   
        Convert(pRootOfTree->left); 
        //找到最小值,初始化head与pre
        if(pre == NULL){       
            head = pRootOfTree;
            pre = pRootOfTree;
        }
        //当前节点与上一节点建立连接,将pre设置为当前值
        else{       
            pre->right = pRootOfTree;
            pRootOfTree->left = pre;
            pre = pRootOfTree;
        }
        Convert(pRootOfTree->right);
        return head;
    }
};

28、JZ38 字符串的排列

在这里插入图片描述
在这里插入图片描述

28.1 思路

在这里插入图片描述
在这里插入图片描述

28.2 代码

class Solution {
public:
    void recursion(vector<string> &res, string &str, string &temp, vector<int> &vis){
        //临时字符串满了加入输出
        if(temp.length() == str.length()){ 
            res.push_back(temp);
            return;
        }
        //遍历所有元素选取一个加入
        for(int i = 0; i < str.length(); i++){ 
            //如果该元素已经被加入了则不需要再加入了
            if(vis[i]) 
                continue;
            if(i > 0 && str[i - 1] == str[i] && !vis[i - 1])
                //当前的元素str[i]与同一层的前一个元素str[i-1]相同且str[i-1]已经用过了
                continue;
            //标记为使用过  
            vis[i] = 1;  
            //加入临时字符串
            temp.push_back(str[i]); 
            recursion(res, str, temp, vis);
            //回溯
            vis[i] = 0; 
            temp.pop_back();
        }
    }
    
    vector<string> Permutation(string str) {
        //先按字典序排序,使重复字符串相邻
        sort(str.begin(), str.end()); 
        //标记每个位置的字符是否被使用过s
        vector<int> vis(str.size(), 0); 
        vector<string> res;
        string temp;
        //递归获取
        recursion(res, str, temp, vis); 
        return res;
    }
};

29、JZ39 数组中出现次数超过一半的数字

在这里插入图片描述

29.1 思路

  • 遍历一次数组统计各个元素出现的次数,找到出现次数大于数组长度一半的那个数字
  • 使用vector容器将 每个相同位置对应数字 出现次数 保存;
  • 再从中找寻 超过数组长度的一半 数字。

29.2 代码

#include <vector>
class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int>& numbers) {
        int n = numbers.size();
        
        vector<int> tmp;
        int m = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (numbers[i] == numbers[j]) {
                    m++;
                }
            }
            tmp.push_back(m);
            m = 0;
        }
        
        for (int i = 0; i < n; i++) {
            if (tmp[i] >= n/2) {
                return numbers[i];
            }
        }
        return 0;
    }
};

29.3 思路(官方推荐)

  • step 1:构建一个哈希表,统计数组元素各自出现了多少次,即key值为数组元素,value值为其出现次数
  • step 2:遍历数组,每遇到一个元素就把哈希表中相应key值的value值加1,用来统计出现次数。
  • step 3:本来可以统计完了之后统一遍历哈希表找到频次大于数组长度一半的key值,但是根据我们上面加粗的点,只要它出现超过了一半,不管后面还有没有,必定就是这个元素了,因此每次统计后,我们都可以检查value值是否大于数组长度的一半,如果大于则找到了。

29.4 代码

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        //无序哈希表统计每个数字出现的次数
        unordered_map<int, int> mp; 
        //遍历数组
        for(int i = 0; i < numbers.size(); i++){ 
            //哈希表中相应数字个数加1
            mp[numbers[i]]++; 
            //一旦有个数大于长度一半的情况即可返回
            if(mp[numbers[i]] > numbers.size() / 2) 
                return numbers[i];
        }
        return 0;
    }
};

30、JZ40 最小的K个数

在这里插入图片描述

30.1 思路

  • 直接排序,然后返回 前 k个数

30.2 代码

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> ret;
        if (k==0 || k>input.size()) return ret;
        sort(input.begin(), input.end());
        return vector<int>({input.begin(), input.begin()+k});   
    }
};

31、JZ41 数据流中的中位数

在这里插入图片描述

31.1 思路

在这里插入图片描述
在这里插入图片描述

31.2 代码

class Solution {
public:
    //记录输入流
    vector<int> val;
    void Insert(int num) {
        if(val.empty())
            //val中没有数据,直接加入
            val.push_back(num); 
        //val中有数据,需要插入排序
        else{
            int i = 0;
            //遍历找到插入点
            for(; i < val.size(); i++){
                if(num <= val[i]){
                   break;
                }
            }
            val.insert(val.begin() + i, num);
        }
    }
    double GetMedian() {
        int n = val.size();
        //奇数个数字
        if(n % 2 == 1){ 
            //类型转换
            return double(val[n / 2]); 
        }
        //偶数个数字
        else{ 
            double a = val[n / 2];
            double b = val[n / 2 - 1];
            return (a + b) / 2;
        }
    }
};

32、JZ43 整数中1出现的次数(从1到n整数中1出现的次数)

在这里插入图片描述

32.1 思路

在这里插入图片描述

32.2 代码

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n) {
        int res = 0;
        //遍历1-n
        for(int i = 1; i <= n; i++){ 
            //遍历每个数的每一位
            for(int j = i; j > 0; j = j / 10){ 
                //遇到数字1计数
                if(j % 10 == 1) 
                    res++;
            }
        }
        return res;
    }
};

33、JZ44 数字序列中某一位的数字

在这里插入图片描述

33.1 思路

在这里插入图片描述

33.2 代码

class Solution {
public:
    int findNthDigit(int n) {
        //记录n是几位数
        int i = 1; 
        while(i * pow(10, i) < n){
            //前面添0增加的位
            n += pow(10, i);
            i++;
        }
        //根据除法锁定目标数字,根据取模锁定位置
        return to_string(n / i)[n % i] - '0';
    }
};

34、JZ45 把数组排成最小的数

在这里插入图片描述

34.1 思路

在这里插入图片描述

34.2 代码

class Solution {
public:
    //重载排序比较方式
    static bool cmp(string& x, string& y){
        //叠加
        return x + y < y + x;
    }
    string PrintMinNumber(vector<int> numbers) {
        string res = "";
        //空数组的情况
        if(numbers.size() == 0)
            return res;
        vector<string> nums;
        //将数字转成字符
        for(int i = 0; i < numbers.size(); i++)
            nums.push_back(to_string(numbers[i]));
        //排序
        sort(nums.begin(), nums.end(), cmp);
        //字符串叠加
        for(int i = 0; i < nums.size(); i++)
            res += nums[i];
        return res;
    }
};

35、JZ47 礼物的最大价值

在这里插入图片描述

35.1 思路

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

35.2 代码

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 i = 1; i < n; i++)
            grid[0][i] += grid[0][i - 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];
    }
};

36、JZ48 最长不含重复字符的子字符串

在这里插入图片描述

36.1 思路

在这里插入图片描述
在这里插入图片描述

36.2 代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        //哈希表记录窗口内非重复的字符
        unordered_map<char, int> mp; 
        int res = 0;
        //设置窗口左右边界
        for(int left = 0, right = 0; right < s.length(); right++){ 
            //窗口右移进入哈希表统计出现次数
            mp[s[right]]++; 
            //出现次数大于1,则窗口内有重复
            if(mp[s[right]] > 1) 
                //窗口左移,同时减去该字符的出现次数
                //mp[s[left++]]--; 
                mp[s[left]]--;
                left++;
            //维护子串长度最大值
            res = max(res, right - left + 1); 
        }
        return res;
    }
};

37、JZ49 丑数

在这里插入图片描述

37.1 思路

在这里插入图片描述
在这里插入图片描述

37.2 代码

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        //排除0
        if(index == 0)
            return 0; 
        //要乘的因数
        vector<int> factors = {2, 3, 5}; 
        //去重
        unordered_map<long, int> mp; 
        //小顶堆
        priority_queue<long, vector<long>, greater<long>> pq; 
        //1先进去
        mp[1LL] = 1; 
        pq.push(1LL);
        long res = 0;
        for(int i = 0; i < index; i++){ 
            //每次取最小的
            res = pq.top(); 
            pq.pop();
            for(int j = 0; j < 3; j++){
                //乘上因数
                long next = res * factors[j]; 
                //不在哈希表中
                if(mp.find(next) == mp.end()){  
                    mp[next] = 1;
                    pq.push(next);
                }
            }
        }
        return (int)res;
    }
};

38、JZ50 第一个只出现一次的字符

在这里插入图片描述

38.1 思路

具体做法:

  • step 1:遍历一次字符串,对于每个字符,放入哈希表中统计出现次数。
  • step2:再次遍历字符串,对于每个字符,检查哈希表中出现次数是否为1,找到第一个即可。
  • step 3:遍历结束都没找到,那就是没有,返回-1.

38.2 代码

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        unordered_map<char, int> mp;
        //统计每个字符出现的次数
        for(int i = 0; i < str.size(); i++) 
            mp[str[i]]++;
        //找到第一个只出现一次的字母
        for(int i = 0; i < str.size(); i++) 
            if(mp[str[i]] == 1)
                return i;
        //没有找到
        return -1; 
    } 
};

39、JZ52 两个链表的第一个公共结点

在这里插入图片描述

39.1 思路

在这里插入图片描述

39.2 代码

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode *ta = pHead1, *tb = pHead2;
        while (ta != tb) {
            ta = ta ? ta->next : pHead2;
            tb = tb ? tb->next : pHead1;
        }
        return ta;
    }
};

40、JZ53 数字在升序数组中出现的次数

在这里插入图片描述

40.1 思路

遍历数组,一一对比,计算次数

40.2 代码

class Solution {
public:
    int GetNumberOfK(vector<int>& nums, int k) {
        int res = 0;

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

        return res;
    }
};

41、JZ54 二叉搜索树的第k个节点

在这里插入图片描述
在这里插入图片描述

41.1 思路

在这里插入图片描述

41.2 代码

class Solution {
public:
    //记录返回的节点
    TreeNode* res = NULL;
    //记录中序遍历了多少个
    int count = 0;
    //当遍历到节点为空或者超过k时,返回
    void midOrder(TreeNode* root, int k){
        if(root == NULL || count > k) 
            return;
        midOrder(root->left, k);
        count++;
        //只记录第k个
        if(count == k)  
            res = root;
        midOrder(root->right, k);
    }
    
    int KthNode(TreeNode* proot, int k) {
        midOrder(proot, k);
        if(res)
            return res->val;
        //二叉树为空,或是找不到
        else 
            return -1;
    }
};

42、JZ55 二叉树的深度

在这里插入图片描述
在这里插入图片描述

42.1 思路

在这里插入图片描述

42.2 代码

class Solution {
public:
    int maxDepth(TreeNode* root) {
        //空节点没有深度
        if(root == NULL) 
            return 0;
        //返回子树深度+1
        return max(maxDepth(root->left), maxDepth(root->right)) + 1; 
    }
};

43、JZ56 数组中只出现一次的两个数字

在这里插入图片描述

43.1 思路

在这里插入图片描述

43.2 代码

#include <unordered_map>
#include <vector>
class Solution {
public:
    vector<int> FindNumsAppearOnce(vector<int>& nums) {
        unordered_map<int, int> mp;
        //使用 哈希 统计每个数字出现的次数
        for (int i = 0; i < nums.size(); i++) {
            mp[nums[i]]++;
        }

        vector<int> res;
        //找到只出现一次的数字
        for (int i = 0; i < nums.size(); i++) {
            if (mp[nums[i]] == 1) {
                res.push_back(nums[i]);
            }
        }

        //非降序排列
        sort(res.begin(),res.end());
        return res;
    }
};

44、JZ57 和为S的两个数字

在这里插入图片描述

44.1 思路

在这里插入图片描述

44.2 代码

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
        vector<int> res;
        //左右双指针
        int left = 0, right = array.size() - 1;
        //对撞双指针
        while(left < right){
            //相加等于sum,找到目标
            if(array[left] + array[right] == sum){
                res.push_back(array[left]);
                res.push_back(array[right]);
                break;
            //和太大,缩小右边
            }else if(array[left] + array[right] > sum)
                right--;
            //和太小,扩大左边
            else
                left++;
        }
        return res;
    }
};

45、JZ58 左旋转字符串

在这里插入图片描述

45.1 思路

左移位数 mod 字符序列长度 = 循环左移开始位置

45.2 代码

class Solution {
public:
    string LeftRotateString(string str, int n) {
        string  res = "";

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

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

        return res;
    }
};

46、JZ61 扑克牌顺子

在这里插入图片描述

46.1 思路

在这里插入图片描述

46.2 代码

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        //先排序
        sort(numbers.begin(), numbers.end()); 
        int zero = 0;
        int gap = 0;
        for(int i = 0; i < numbers.size() - 1; i++){ 
            //统计零牌数
            if(numbers[i] == 0) 
                zero++;
            else{
                //不可重复
                if(numbers[i + 1] - numbers[i] == 0) 
                    return false;
                else
                    //统计间隔数
                    gap += numbers[i + 1] - numbers[i] - 1; 
            }
        }
        //比较间隔与零牌数
        if(gap > zero)
            return false;
        else
            return true;
    }
};

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

在这里插入图片描述
在这里插入图片描述

47.1 思路

在这里插入图片描述

47.2 代码

public class Solution {
    private int function(int n, int m) {
        if (n == 1)  
            return 0;
        //递归
        int x = function(n - 1, m);
        //返回最后删除的那个元素
        return (m + x) % n;  
    }
    public int LastRemaining_Solution(int n, int m) {
        //没有小朋友的情况
        if(n == 0 || m == 0) 
            return -1;
        return function(n, m);
    }
}

48、JZ64 求1+2+3+…+n

在这里插入图片描述

48.1 思路

直接递归

48.2 代码

class Solution {
public:
    int Sum_Solution(int n) {
        if (n == 1) {
            return 1;
        }    

        return Sum_Solution(n - 1) + n;
    }
};

49、JZ66 构建乘积数组

在这里插入图片描述

49.1 思路

输入:[1,2,3,4,5]
返回值:[120,60,40,30,24]
答案第一位为2 * 3 * 4 * 5 = 120 第二位为 1 * 3 * 4 * 5= 60 以此类推

直接 暴力模拟题意

49.2 代码

class Solution {
public:
    vector<int> multiply(vector<int>& A) {
        vector<int> B;
        
        //遍历每一位
        for (int i = 0; i < A.size(); i++) {
            int res = 1;
            for (int j = 0; j < A.size(); j++) {
               if (i != j) {
                    res *= A[j];
               }
            }
            B.push_back(res);
        }

        return B;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值