剑指offer练习合集

题目: 剑指 Offer 05. 替换空格

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

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

限制:

0 <= s 的长度 <= 10000

题解:

class Solution {
private:
string str;

public:
    string replaceSpace(string s) {
        for (auto c : s) {
            if (c == ' ') {
                str.push_back('%');
                str.push_back('2');
                str.push_back('0');
            } else {
                str.push_back(c);
            }
        }
        return str;
    }
};

思路: 创建一个新串,然后在遍历旧串的过程中,如果没有遇见' '(空格字符),就将当前遍历的字符添加到新串中,如果遇见了空格字符,就将'%' '2' '0' ,这三个字符添加到新串当中,最终遍历完旧串之后返回新串即可。

题目: 剑指 Offer 58 - II. 左旋转字符串

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

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

示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

限制:

1 <= k < s.length <= 10000

题解:

class Solution {
private:
string str;

public:
    string reverseLeftWords(string s, int n) {
        int length = s.size();
        for (int i = n; i < length; ++i) {
            str.push_back(s[i]);
        }
        for (int i = 0; i < n; ++i) {
            str.push_back(s[i]);
        }
        return str;
    }
};

思路: 首先创建一个新串,然后我们从旧串的第n个元素开始遍历,遍历的每个元素都添加到新串中,这一次遍历结束后,我们开始第二次遍历,从旧串的第0个下标开始,到第n - 1,个元素结束,仍然是将每次遍历的字符添加到新串当中,等遍历结束后我们返回新串即可。

题目: 剑指 Offer 03. 数组中重复的数字

找出数组中重复的数字。

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

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

限制:

2 <= n <= 100000

题解:

class Solution {
private:
array <int, 100000> arrayFirst = {0};

public:
    int findRepeatNumber(vector<int>& nums) {
        for (int num : nums) {
            arrayFirst[num]++;
        }
        int k = 0;
        for (int cnt : arrayFirst) {
            if (cnt > 1) {
                break;
            }
            ++k;
        }
        return k;
    }
};

**思路:**首先创建一个与n相同大小的数组arrayFirst,然后初始化元素全为零,然后遍历一遍原数组,利用桶排序的思想,每遇见一个元数组的值就将其桶中对应下标值中的值加1,遍历完之后再遍历一遍桶,返回桶中第一个值>1的元素所对应的下标即可。

题目: 剑指 Offer 53 - I. 在排序数组中查找数字 I

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

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

示例 2:

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

提示:

  1. 0 <= nums.length <= 105
  2. -109 <= nums[i] <= 109
  3. nums 是一个非递减数组
  4. -109 <= target <= 109

题解:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int cnt = 0;
        for (int num : nums) {
            if (num == target) {
                ++cnt;
            }
        }
        return cnt;
    }
};

**思路:**该题属于是一道送分题,我们只需要初始化一个值为零的计数器cnt,然后遍历一遍数组,每遇见一次与target值相同的元素让计数器值加1,遍历结束后返回计数器cnt的值即可。

题目: 剑指 Offer 53 - II. 0~n-1中缺失的数字

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

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

示例 2:

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

限制:

1 <= 数组长度 <= 10000

题解:

class Solution {
public:
    int missingNumber(vector<int>& nums) {
    	//开始beforeValue用于记录数组中的第一个元素,并判断其情况,若值不为0,则证明数组中缺少值0,返回0即可。后续用于记录for循环中当前遍历的元素的上一个元素的值
        int beforeValue = nums[0];
        if (beforeValue) {
            return 0;
        }
		
		//用for循环从下标1开始遍历整个数组,若当前所遍历的元素的值比beforeValue所记录的上一个元素的值大2,那么就说明数组中缺少当前值减1所得到的那个值,所以返回当前值减1即可。
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i] - beforeValue == 2) {
                return nums[i] - 1;
            }
            beforeValue = nums[i];
        }
		
		//若出了for循环仍然没有发生return操作,证明数组中的值都是连续的,那现在说明缺少了哪个值呢?当然是缺少数组中最后一个元素值加1的值,所以我们返回数组最后一个元素加1的值即可。
        return nums[nums.size() - 1] + 1;
    }
};

思路: 详见代码中的注释。

题目:剑指 Offer 04. 二维数组中的查找

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例:
现有矩阵 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。

限制:

0 <= n <= 1000
0 <= m <= 1000

题解:

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        int r = 0, l = matrix.size() - 1;
        for (; l >= 0 && r < matrix[0].size();) {
            if (matrix[l][r] == target) {
                return true;
            } 
            if (matrix[l][r] > target) {
                --l;
            } else {
                ++r;
            }

        }
        return false;
    }
};

思路: 我们选择从二维数组的左下角开始查找,如果当前元素大于target的值,那我们就从上一行开始查找,如果小于或大于target的值,那就从改行开始向右查找,如果过程中发现与target相同的值就返回true,如果遍历完都没有发现的话就返回false。

题目: 剑指 Offer 11. 旋转数组的最小数字

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

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

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例1:

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

示例2:

输入:numbers = [2,2,2,0,1]
输出:0

提示:

  1. n == numbers.length
  2. 1 <= n <= 5000
  3. -5000 <= numbers[i] <= 5000
  4. numbers 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

题解:

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int t = numbers[0];
        for (int i = 1; i < numbers.size(); ++i) {
            if (numbers[i] < t) {
                return numbers[i];
            }
            t = numbers[i];
        }
        return numbers[0];
    }
};

思路: 我们首先用t保留数组的第一个元素,然后我们从数组的第一个元素开始遍历该数组,每次更新t的值,如果发现当前遍历的元素的值小于上次保留的t(也就是当前元素的上一个元素),那么就返回当前遍历的元素,如果全部遍历完之后仍然没有返回值,那么就返回数组的第一个元素。

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

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

示例 1:

输入:s = "abaccdeff"
输出:'b'

示例 2:

输入:s = "" 
输出:' '

限制:

0 <= s 的长度 <= 50000

题解:

class Solution {
public:
    char firstUniqChar(string s) {
        int temp[300] = {0};
        for (int num : s) {
            temp[num]++;
        }
        for (int num : s) {
            if (temp[num] == 1) {
                return num;
            }
        }
        return ' ';
    }
};

思路: 我们利用桶排序的思路,向temp数组中进行累计,然后最后利用原字符串中各个字符对应的int值的顺序在temp中进行查找,如果找到值为1的元素,就返回其对应的下标值,如果没有找到,出循环后就返回’ '即可。

题目: 剑指 Offer 32 - 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> res;
        //使用队列先进先出的特性,实现二叉树的广度优先搜索BFS
        queue<TreeNode *> q;
        //判断二叉树是佛为空
        if (!root) {
            return res;
        }
        q.push(root);
        //遍历队列,直到队列为空
        while (!q.empty()) {
            TreeNode *node = q.front();//暂存头元素
            q.pop();//队头元素出队
            res.push_back(node -> val);//把节点值存到res中
            //若当前节点的左右子节点不为空,则先左后右将其子节点入队
            if (node -> left) {
                q.push(node -> left);
            }   
            if (node -> right) {
                q.push(node -> right);
            }
        }
        return res;
    }
};

思路: 我们采用队列先进后出的特性,实现二叉树的广度优先搜索BFS,首先创建一个新队列,然后判断二叉树是否为空,如果是空就返回空数组,如果不是空就先将根节点入队列,然后以队列是否为空为条件,遍历队列,每次先将队头节点中的val值追加到数组中,然后队头元素出队,出队后判断刚才出队的节点的左右子节点是否存在(先判断左,后判断右),如果存在的话就入队,如果不存在就不执行操作,最后出循环之后返回数组即可。

题目: 剑指 Offer 32 - II. 从上到下打印二叉树 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>> res;
        if (!root) {
            return res;
        }
        queue<TreeNode *> q;
        q.push(root);
        while (!q.empty()) {
            vector<int> resTest;
            int size = q.size();
            for (int i = 0; i < size; ++i) {
                TreeNode *node = q.front();
                q.pop();
                resTest.push_back(node -> val);
                if (node -> left) {
                    q.push(node -> left);
                }
                if (node -> right) {
                    q.push(node -> right);
                }

            }
            res.push_back(resTest);
        }
        return res;
    }
};

思路: 我们继续利用队列先进先出的特性实现层级遍历二叉树,与上一道题不同的是,我们本次每次执行while循环时,第一步都是先判断此时队列里还有多少个元素,用size来接受元素的个数,然后初始化一个新数组并创建一个for循环,该for循环需要循环size次,即就是将每一个队列中存在的元素的值都依次存入新数组,并存一个值出一次队列,待for循环结束后我们将新数组作为元素添加到外侧汇总结果的大数组中,一直等到while循环结束后,我们返回大数组即可。

题目: 剑指 Offer 32 - III. 从上到下打印二叉树 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>> res;
        if (!root) {
            return res;
        }

        queue<TreeNode *> q;
        q.push(root);
        int flag = 0;
        while (!q.empty()) {
            vector<int> resTest;
            int size = q.size();
            for (int i = 0; i < size; ++i) {
                TreeNode *node = q.front();
                q.pop();
                resTest.push_back(node -> val);
                if (node -> left) {
                   q.push(node -> left);
                } 
                if (node -> right) {
                   q.push(node -> right); 
                }
            }
            if (flag == 0) {
                res.push_back(resTest);
                flag = 1;
            } else {
                int last = resTest.size() - 1, front = 0, t;
                while (front < last) {
                    t = resTest[last];
                    resTest[last] = resTest[front];
                    resTest[front] = t;
                    ++front;
                    --last;
                }
                res.push_back(resTest);
                flag = 0;
            }
        }
        return res;
    }
};

思路: 此题与上一题非常相似,不同之处就是该题要求返回的二叉树层级是一层顺序,一层逆序的,所以我们利用一个flag,实现一次反转当作大数组元素的小数组中的元素,一次不反转,最终返回大数组即可。

题目: 剑指 Offer 27. 二叉树的镜像

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

例如输入:

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

镜像输出:

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

示例 1:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

限制:

0 <= 节点个数 <= 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:
    TreeNode* mirrorTree(TreeNode* root) {
        if (!root) {
            return root;
        }
        TreeNode *node = root -> left;
        root -> left = root -> right;
        root -> right = node;

        mirrorTree(root -> left);
        mirrorTree(root -> right);
        return root;
    }
};

思路: 此题我们采用了最简单的先序递归遍历二叉树的方法,将每个节点的左子节点和右子节点进行交换,最终返回根节点root即可。

题目: 剑指 Offer 26. 树的子结构

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

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

例如:

	给定的树 A:
	
	     3
	    / \
	   4   5
	  / \
	 1   2

给定的树 B:

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

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false

示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true

限制:

0 <= 节点个数 <= 10000

题解:

/**
 * 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> resFirst;
vector<int> resSecond;
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if (!A) {
            return false;
        }
        if (!B) {
            return false;
        }
        //使用递归现先序遍历,顺便将两颗树的节点中的值都存入两个数组
        readTree(A);
        readTreeSecond(B);
        //下面我们采用的是类似于查找子串的方法,所以先判断如果子串已经比母串长的话直接返回false
        if (resFirst.size() < resSecond.size()) {
            return false;
        }
        int k; 
        //判断子串的第一个元素是否与母串中某一个元素相同,若相同则从母串的那个位置开始判断子串是否存在   
        for (int i = 0; i < resFirst.size(); ++i) {
            if (resFirst[i] == resSecond[0]) {
                //如果母串剩余串的长度小于子串,则返回false
                if (resFirst.size() - 1 - i < resSecond.size() - 1) {
                    return false;
                }
                //用k标记当前元素在母串中的位置
                k = i;
                //循环遍历子串的元素个数次,遇见不符合的情况就返回false,否则返回true
                for (int j = 0; j < resSecond.size(); ++j) {
                    //如果子串只有最后的一个元素匹配不上,先不着急返回false,有可能是子串最后一个节点在树中没有它的右兄弟,但是母串相应的节点在树中有它的右兄弟,所以要先判断一下子串最后一个元素是否可以于母串中下一个元素相匹配,若匹配成功仍然返回true,若没有成功则返回false
                    if (resFirst[k] != resSecond[j]) {
                        if (resFirst[k + 1] == resSecond[j] && j == resSecond.size() - 1 && j != 1) {
                        return true;
                    }
                        break;
                    }
                    if (resFirst[k] == resSecond[j] && j == resSecond.size() - 1) {
                        return true;
                    }
                    ++k;

                }

            }
        }
        return false;
    }

    TreeNode *readTree(TreeNode *root) {
        if (!root) {
            return root;
        }

        resFirst.push_back(root -> val);
        readTree(root -> left);
        readTree(root -> right);
        return root;
    }

    TreeNode *readTreeSecond(TreeNode *root) {
        if (!root) {
            return root;
        }

        resSecond.push_back(root -> val);
        readTreeSecond(root -> left);
        readTreeSecond(root -> right);
        return root;
    }
};

思路: 我们采用的先用数组接收两个树的先序遍历结果,然后判断B树的结果是否为A树结果的子串(不一定是子串,因为最后一个元素可能存在特殊情况),详细思路见上方注释。

题目: 剑指 Offer 28. 对称的二叉树

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

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

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

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

	    1
	   / \
	  2   2
	   \   \
	   3    3

示例 1:

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

示例 2:

输入:root = [1,2,2,null,3,null,3]
输出:false

限制:

0 <= 节点个数 <= 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:
    bool isSymmetric(TreeNode* root) {
        if(!root){
            return true;
        }
        //先传根节点的左右子节点为参数
        return helper(root->left, root->right);
    }
    bool helper(TreeNode* r1, TreeNode* r2){
        //两个子节点都不存在的时候返回true
        if(!r1 && !r2){
            return true;
        }
        //其中一个节点不存在的时候返回false
        if (!r1 || !r2){
            return false;
        }
        //递归判断是否为镜像的二叉树
        //后面的两个函数中的参数分别为左节点的左儿子加右节点的右儿子 或 左节点的右儿子加右节点的左儿子
        return r1->val == r2->val && helper(r1->left, r2->right) && helper(r1->right, r2->left);
    }
};

思路: 本题采用了递归调用的方法,我们通过先将根节点的左右子节点为参数先行判断,接着后续判断所传进两个节点的对称位置的节点是否符合情况,如果全部符合的话就返回true,否则返回false。

题目: 剑指 Offer 10- I. 斐波那契数列

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

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

输入:n = 2
输出:1

示例 2:

输入:n = 5
输出:5

提示:

0 <= n <= 100

题解:

class Solution {
public:
    int fib(int n) {
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 1;
        }
        int tFirst = 1, tSecond = 1, tThird;
        for (int i = 0; i < n - 2; ++i) {
            tThird = tFirst + tSecond;
            tFirst = tSecond % 1000000007;
            tSecond = tThird % 1000000007; 
        }
        return tThird % 1000000007;
    }
};

思路: 我们采用循环,每次更新计算下一个值所需的前两个数值,然后每次循环都计算一个新值,

题目: 剑指 Offer 10- II. 青蛙跳台阶问题

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

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

示例 1:

输入:n = 2
输出:2

示例 2:

输入:n = 7
输出:21

示例 3:

输入:n = 0
输出:1

提示:

0 <= n <= 100

题解:

class Solution {
public:
    int numWays(int n) {
        int stepFirst = 1, stepSecond = 2, stepThird;
        if (n == 0) {
            return 1;
        }
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }

        for (int i = 0; i < n - 2; ++i) {
            stepThird = stepFirst + stepSecond; 
            stepFirst = stepSecond % 1000000007;
            stepSecond = stepThird % 1000000007;
        }

        return stepThird % 1000000007;
    }
};

思路: 我们发现小青蛙🐸跳台阶其实就是一个斐波那契数列,因为最后一次可以选择跳两个,也可以选择跳一个,也就是说跳n个台阶的时候,跳法总数就是n-1次的总方法数加上n-2次的总方法数,其余算法同上题。

题目: 剑指 Offer 63. 股票的最大利润

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

示例 1:

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

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0

限制:

0 <= 数组长度 <= 10^5

题解:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //temp是每次遍历到的低价, res是计算出的利润
        int temp = INT_MAX, res = 0;
        //遍历价目表
        for (int t : prices) {
            //如果发现比已经保存的低价还低的话,就更新低价
            if (t < temp) {
                temp = t;
            }
            //选择已经保存的最大利润和新计算利润中较大的那个
            res = max(res, t - temp);
        }
        //出循环之后返回保存的最大利润结果
        return res;
    }
};

思路: 我们用动态规划的思想,利用一次循环,每次循环尝试更新最低价和计算并更新最大利润,出循环后返回最终结果即可,详细思路见上方代码。

题目: 剑指 Offer 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

题解:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //我们初始化动态规划过程中需要进行累加的变量sum和更新最大值的变量max
        int sum = 0, max = nums[0];
        //循环遍历过程中,不断更新sum的值和最大值max
        for (int i = 0; i < nums.size(); ++i) {
            if (sum + nums[i] >= nums[i]) {
                sum += nums[i];
                if (sum > max) {
                    max = sum;
                }
            } else {
                sum = nums[i];
                if (sum > max) {
                    max = sum;
                }    
            }
        }
        return max;
    }
};

思路: 我们采用了动态规划的思想,从数组的第一个数开始进行累加和判断,判断加上当前所遍历的数后是否大于当前的数(即跟当前的数相加的数是否为正数),若大于当前遍历的数的话就用sum += nums[i];更新sum的值,并判断更新后的sum是否大于已经保存的max,若大于Max就更新max,若小于当前遍历的数的话,就用sum = nums[i]; 更新sum的值,然后也判断sum是否大于max,并根据情况更新max的值。

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

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 13521 可以拿到最多价值的礼物

提示:

0 < grid.length <= 200
0 < grid[0].length <= 200

题解:

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        //使用二维矩阵matV[i][j],表示到(i,j)位置的最大值;由于路径方向是往右或者往下走,可以得到下列方程。
        //matV[i][j] = max(matV[i-1][j], matV[i][j-1]) + grid[i][j];
        if (grid.size() == 0 || grid[0].size() == 0) {
            return 0;
        }
        vector<int> res(grid[0].size(), 0);
        for (int j = 0; j < grid.size(); j++) {
            res[0] += grid[j][0];
            for (int i = 1; i < res.size(); ++i) {
                res[i] = max(res[i - 1], res[i]) + grid[j][i];
            }
        }
        return res.back();
    }
};

思路: 我们首先得到该题的动态规划方程,是:matV[i][j] = max(matV[i-1][j], matV[i][j-1]) + grid[i][j]; 然后我们采用只新建一行一维数组的方法来动态规划该题目,可以只使用一行,或者一列的空间,来递推式地计算。存储空间为O(M)或O(N)

以按计算行为例子,res[j]表示当前第i行第j个元素的最大值(即matV[i][j])。
当运算到第k个元素,而k还没更新时,res[k]存储的是上一行计算完毕的结果,也就对应matV[i-1][k];k位置之前值都已经更新完毕,对应matV[i][]。因此,上面的公式可以运算为:
代码表达:

res[i] = max(res[i-1], res[i]) + grid[l][i];

再结合上方题解代码,不难看懂该题。

思路转载自大佬:
作者:lc3333
链接:https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/solution/11xing-cdong-tai-gui-hua-jie-fa-xiang-ji-6p12/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

题目: 剑指 Offer 18. 删除链表的节点

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

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

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

示例 1:

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

示例 2:

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

说明:

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

题解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if (head == NULL) {
            return head;
        }
        
        if (head -> val == val) {
            head = head -> next;
            return head;
        }
        ListNode *nodeFirst = head, *nodeSecond = head;
        nodeSecond = nodeSecond -> next;
        while (nodeFirst -> next != NULL) {
            if (nodeSecond -> val == val) {
                nodeFirst -> next = nodeSecond -> next;
                break;
            }
            nodeFirst = nodeFirst -> next;
            nodeSecond = nodeSecond -> next;
        }

        return head;
    }
};

思路: 该题是一道很简单的题,首先我们判断链表为空和链表第一个节点元素符合条件的特殊情况,然后进行其他情况的操作,我们利用双指针法,初始时一个前指针指向头节点,后指针指向头节点的下一个节点,然后每次进循环先判断后指针所指的节点是否符合情况,若符合情况则用前指针指向后指针的下一个节点并退出循环,若不符合情况则两个节点都向后走一个节点。

题目: 剑指 Offer 22. 链表中倒数第k个节点

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

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例:

给定一个链表: 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) {
        ListNode *nodeFirst = head, *nodeSecond = head;
        
        for (int i = 0; i < k - 1; ++i) {
            nodeSecond = nodeSecond -> next;
        }

        while (nodeSecond -> next != NULL) {
            nodeFirst = nodeFirst -> next;
            nodeSecond = nodeSecond -> next;
        }
        return nodeFirst;
    }
};

思路: 利用双指针法,初始化时前指针指向头节点,后指针从头节点往后跑k - 1个,然后利用循环两个节点同时往后跑,直到跑到后节点的下一个节点为NULL,然后就将前节点返回即可。

题目: 剑指 Offer 25. 合并两个排序的链表

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

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

限制:

0 <= 链表长度 <= 1000

题解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
         // 如果链表1为空链表就返回链表2; 如果链表2为空链表就返回链表1
        if (l1 == nullptr) return l2;
        else if (l2 == nullptr) return l1;
        // 定义一个新的合并链表链表
         ListNode* pMergeHead = nullptr;
        // 如果链表1的val小于链表2的val, 就把链表1的节点赋给合并链表的当前节点, 然后进入递归过程
        // 链表1的val大于或等于链表2的val, 就把链表2的节点赋给合并链表的当前节点, 然后进入递归
        if (l1->val < l2->val) {
            pMergeHead = l1;
            pMergeHead->next = mergeTwoLists(l1->next, l2);
        } else { 
            pMergeHead = l2;
            pMergeHead->next = mergeTwoLists(l1, l2->next);
            }
        // 返回合并链表
        return pMergeHead;
    }
};

思路: 采用了递归的方法,首先判断为空的两种特殊情况,然后后续每次返回值较小的那个节点,最终就可以得到升序排序的结果。

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

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

如下面的两个链表:
在这里插入图片描述
在节点 c1 开始相交。

示例 1:
在这里插入图片描述

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

示例 2:
在这里插入图片描述

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

示例 3:
在这里插入图片描述

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:

如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
本题与主站 160 题相同:https://leetcode-cn.com/problems/intersection-of-two-linked-lists/

题解:

/**
 * 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 NULL;
        }
        ListNode *nodeFist = headA, *nodeSecond = headB;
        
        //如果两个链表没有公共节点,那两者相同的时候只会是NULL,如果有公共节点,最后返回那个实体节点即可
        while (nodeFist != nodeSecond) {
            //只需要双方变换一次方向就必定可以相遇
            nodeFist = nodeFist == NULL ? headB : nodeFist -> next;
            nodeSecond = nodeSecond == NULL ? headA : nodeSecond -> next;
        }
        return nodeFist;

    }
};

思路: 我们先是从头遍历两个链表,当一个链表遍历到底的时候就将这个指针指向另一个链表的头节点,然后继续遍历,两个指针交换一次后必定会发生一次相遇(相遇时要么是NULL,即没有共同节点,要么是一个实体节点,即符合题意的第一个 共同节点),相遇时返回所相遇的节点即可。

题目: 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

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

示例:

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

提示:

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

题解:

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int flagFirst = 0, flagSecond = nums.size() - 1, t;

        while (flagFirst < flagSecond) {
            if (!(nums[flagFirst] % 2) && nums[flagSecond] % 2) {
                t = nums[flagFirst];
                nums[flagFirst] = nums[flagSecond];
                nums[flagSecond] = t;
                ++flagFirst;
                --flagSecond;
                continue;
            }

            if (nums[flagFirst] % 2) {
                ++flagFirst;
            }    

            if (!(nums[flagSecond] % 2)) {
                --flagSecond;
            }
        }
        return nums;
    }
};

思路: 我们采用了双指针法,一个指针指向数组的第一个元素,一个指针指向数组的第二个元素,然后两个指针相向而跑,第一个指针跑到偶数的时候停下来,等第二个指针跑到奇数的时候两者交换元素值,然后继续相向而跑,直到flagFirst >= flagSecond时退出循环,返回数组即可。

题目: 剑指 Offer 57. 和为s的两个数字

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

示例 1:

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

示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

限制:

1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6

题解:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int flagFirst = 0, flagSecond = nums.size() - 1;
        while (nums[flagSecond] > target) {
            --flagSecond;
        }
        while (flagFirst < flagSecond) {
            if (!(nums[flagFirst] + nums[flagSecond] - target)) {
                break;
            }
            if (nums[flagFirst] + nums[flagSecond] - target > 0) {
                --flagSecond;
            }
            if (nums[flagFirst] + nums[flagSecond] - target < 0) {
                ++flagFirst;
            }
        }
        vector<int> res;
        res.push_back(nums[flagFirst]);
        res.push_back(nums[flagSecond]);
        return res;
    }
};

思路: 利用双指针法,一个指向数组第一个元素,一个指向数组最后一个元素,首先让后面的指针往前跑,跑到其所指元素值<=target后出循环,然后判断两个指针所指的元素值是否相加==target,如果大于target,就让后面的指针向前跑,如果小于target,就让前面的指针往后跑,直至符合情况为止出循环,然后将那两个符合情况的值添加到新数组中并返回该新数组即可。

题目: 剑指 Offer 58 - I. 翻转单词顺序

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

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

示例 2:

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

示例 3:

输入: "a good   example"
输出: "example good a"
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

说明:

无空格字符构成一个单词。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

题解:

class Solution {
public:
    string reverseWords(string s) {
        //判断输入的字符串为空的特殊情况
        if (s.size() == 0) {
            return s;
        }
        
        string str;
        //声明两个指针,一个指向字符串第一个元素,一个指向最后一个元素
        int flagFirst = 0, flagSecond = s.size() - 1;
        
        //利用&&运算符的短路性,将判断是否越界的代码放在前半段
        //下方这两个while循环用于将输入的字符串的两端的空格字符排除掉
        while (flagFirst < s.size() && s[flagFirst] == ' ') {
            ++flagFirst;
        }

        while (flagSecond >= 0 && s[flagSecond] == ' ') {
            --flagSecond;
        }

        //如果排除掉空格字符后出现了这种特殊情况,就去执行相应的特殊情况操作
        if (flagFirst >= flagSecond) {
            //如果排除之后只剩下一个字符且字符不为空格字符,就将那个单个字符组成的字符串返回
            if (flagFirst == flagSecond && s[flagFirst] != ' ') {
                string strTest;
                strTest.push_back(s[flagFirst]);
                return strTest;
            }
            //其他情况直接返回空串
            string strSecond;
            return strSecond;
        }
        
        //再声明两个指针用于下方的反转操作
        //tFirst指针用于从字符串后方向前跑,每次跑完整个单词时停下来进行操作
        int tFirst = flagSecond, tSecond;
        while (tFirst >= flagFirst) {
            while (s[tFirst] != ' ') {
                if (tFirst == flagFirst) {
                    break;
                }
                --tFirst;
            }
            
            //保存调整好的位置
            tSecond = tFirst;
            //如果已经反转到最后一个单词的特殊情况
            if (tFirst == flagFirst) {
                while (tFirst <= flagSecond) {
                    str.push_back(s[tFirst]);
                    ++tFirst;
                }
                break;
            }
            
            //其他非最后一个需要反正的单词的情况
            ++tFirst;
            while (tFirst <= flagSecond) {
                str.push_back(s[tFirst]);
                ++tFirst;
            }
            str.push_back(' ');
            
            //调整指针,准备反转下一个单词
            --tSecond;
            while (s[tSecond] == ' ') {
                --tSecond;
            }
            flagSecond = tSecond;
            tFirst = flagSecond;
        }
        //出循环后返回反转好的结果即可
        return str;
    }
};

思路: 我们先排除掉输入的字符串的左右两端多余的空格字符,然后从后往前以单词为单位往新创建的空串中不断添加单词,考虑到每种特殊情况,并处理完成后返回反转好的结果即可。

题目: 剑指 Offer 12. 矩阵中的路径

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

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

例如,在下面的 3×4 的矩阵中包含单词 “ABCCED”(单词中的字母已标出)。
在这里插入图片描述
示例 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

提示:

1 <= board.length <= 200
1 <= board[i].length <= 200
board 和 word 仅由大小写英文字母组成

题解:

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        rows = board.size();
        cols = board[0].size();
        //先用嵌套循环在二维数组中找到与word的第一个元素相同的元素位置,找到后就开始DFS
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                //如果DFS返回的是true,就直接返回true作为题解
                if (dfs(board, word, i, j, 0)) {
                    return true;
                } 
            }
        }
        return false;
    }

private:
    int rows, cols;
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k) {
        //先判断是否应该继续进行DFS(即是否满足条件)
        if (i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) {
            return false;
        }
        //如果符合条件且已经搜索到了word数组的最后一个元素,就返回true作为题解
        if (k == word.size() - 1) {
            return true;
        }
        //做标记,表明该位置已经走过
        board[i][j] = '\0';
        //由于当次搜索符合条件,所以继续进行DFS,向四个方向中可以走的继续搜索,一直走到没有符合条件的情况或者走到一个符合条件的情况
        bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j , k + 1) || dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i, j - 1, k + 1);
        //搜索完之后把刚才做的标记还原
        board[i][j] = word[k];
        //返回本次搜索的结果
        return res;
    }
};

思路: 先找到二维数组中与word串第一个元素相同的位置,然后开始尝试DFS,若符合条件就返回true,若不符合条件就返回false,详细思路见上方代码。

题目:剑指 Offer 13. 机器人的运动范围

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

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

示例 1:

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

示例 2:

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

提示:

1 <= n,m <= 100
0 <= k <= 20

题解:

class Solution {
public:
    int movingCount(int m, int n, int k) {
        int d = 1;//记录可走到的格子数,默认(0, 0)都可以走到
        vector<vector<int>> matrix(m, vector<int>(n));//设置地图
        queue<pair<int, int>> q;//设置队列,用于记录可以走的坐标位置
        q.emplace(0, 0);//把地图第一个元素的位置添加到队列中
        matrix[0][0] = 1;//用1表示走过的位置,防止回溯时重复计算
        int dx[4] = {1, 0, -1, 0}, dy[4] = {0, -1, 0, 1};//用两个数组记录方向,便于在下方循环查找当前遍历元素的四个方向
        //只要队列不为空就一直循环
        while (!q.empty()) {
            auto[x, y] = q.front();
            q.pop();
            //循环当前位置的四个方向
            for (int i = 0; i < 4; i++) {
                int nx = x + dx[i], ny = y + dy[i];
                //如果位置合法且没有走过
                if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
                    if (matrix[nx][ny] == 0) {
                        //求出当前位置行坐标和列坐标的数位之和
                        int sum = nx % 10 + nx / 10 + ny % 10 + ny / 10;
                        //只要数位之和小于k,格子自动加1,并将坐标添加到队列中
                        if (sum <= k) {
                            d += 1;//对可以达到的位置数进行累加
                            q.emplace(nx, ny);//向队列添加当前符合条件的元素位置
                            matrix[nx][ny] = 1;//标记此处已经走过
                        }
                    }
                }
            }
        }
        return d;//返回最终可以达到的格子数
    }
};

思路: 思路就是从地图的左上角开始向外辐射查找,利用的是BFS广度优先搜索,详细思路见上方代码及注释。

题目: 剑指 Offer 34. 二叉树中和为某一值的路径

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

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

示例 1:
在这里插入图片描述

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:
在这里插入图片描述

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

提示:

树中节点总数在范围 [0, 5000]-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000

题解:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:

vector<vector<int>> res;
vector<int> temp;

    vector<vector<int>> pathSum(TreeNode* root, int target) {
        //若二叉树为空则返回空数组,若不为空则开始进行深搜
        if (!root) {
            return res;
        } else {
            dfs(root, target, 0);
        }
        //经历完深搜后返回保存最终结果的大数组
        return res;
    }
    void dfs(TreeNode *node, int target, int sum) {
        sum += node -> val;
        temp.push_back(node -> val);
        //判断左节点是否为空,若不为空则向下搜索
        if (node -> left != NULL) {
            dfs(node -> left, target, sum);
        }
        //判断右节点是否为空,若不为空则向下搜索
        if (node -> right != NULL) {
            dfs(node -> right, target, sum);
        }
        //判断是否满足题目的条件,满足的话就将temp存入保存最终结果的大数组
        if (sum == target && node -> left == NULL && node -> right == NULL) {
            //将temp数组作为元素添加到存最终结果的大数组中
            res.push_back(temp);
        }
        //移除数组temp的最后一个元素
        temp.pop_back();
    }
};

思路: 本题采用了递归DFS的解法,大体思路为若二叉树不为空则开始先从左子节点开始搜索,直至搜索到叶子节点时判断是否符合题意,若符合,则将temp数组添加到最终结果的大数组中,接着继续搜索,详细思路见上方代码注释。

题目: 剑指 Offer 36. 二叉搜索树与双向链表

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

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

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

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

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

题解:

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

    Node() {}

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

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
public:
//p是在创建单向循环链表时一直不断更新的链表的最后一个节点
Node *p = new Node(0);
//head是链表的头节点
Node *head = new Node(0);
    Node* treeToDoublyList(Node* root) {
        if (!root) {
            return NULL;
        }
        Node *nodeFirst, *nodeSecond;
        p = head;
        //创建单向循环链表
        createCycleList(root);
        //将链表最后一个节点指向head的right(此时最后一个节点还没有指向)
        p -> right = head -> right;
        //将链表中head的right的left指向p(此操作后单向循环链表创建完成)
        head -> right -> left = p;
        //利用双指针法将单向链表改造成双向链表
        nodeFirst = head -> right;
        nodeSecond = nodeFirst -> right;
        createTwowayList(nodeFirst, nodeSecond);
        //最后返回那个创建好的循环链表即可
        return head -> right;
    }

    //首先进行的操作,利用中序遍历先创建一个单向循环链表
    void createCycleList(Node *root) {
        if (root -> left != NULL) {
            createCycleList(root -> left);
        }
        //进行链表添加节点的操作
        p -> right = root;
        p = p -> right;
        if (root -> right != NULL) {
            createCycleList(root -> right);
        }

    }

    //利用双指针法将单向链表改造成双向链表
    void createTwowayList(Node *nodeFirst, Node *nodeSecond) {
        while (nodeSecond != p) {
            nodeSecond -> left = nodeFirst;
            nodeFirst = nodeFirst -> right;
            nodeSecond = nodeSecond -> right;
        }
        nodeSecond -> left = nodeFirst;
    }
};

思路: 利用了二叉树的中序遍历,首先用一次中序遍历创建一个单向循环链表,然后再利用一次循环配合双指针法将单向循环链表改造成双向循环链表即可。

题目: 剑指 Offer 54. 二叉搜索树的第k大节点

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

示例 1:

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

示例 2:

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

限制:

1 ≤ k ≤ 二叉搜索树元素个数

题解:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
//cnt用于记录当前遍历到的节点是二叉树中第几大的节点的值
int cnt = 1;
//ans用于保存最终结果
int ans;

    int kthLargest(TreeNode* root, int k) {
        //开始二叉树的后续遍历(即从二叉树中最大的元素开始查找)
        backErgodic(root, k);
        return ans;
    }

    //二叉树的后序遍历
    void backErgodic(TreeNode *root, int k) {
        if (root -> right != NULL) {
            backErgodic(root -> right, k);
        }
        //中间进行的操作,如果cnt > k则说明答案值已取到,直接进行返回
        if (cnt > k) {
            return;
        }
        //如果发现对应的值,则进行取值并++cnt并返回,否则就++cnt
        if (cnt == k) {
            ++cnt;
            ans = root -> val;
            return;
        } else {
            ++cnt;
        }

        if (root -> left != NULL) {
            backErgodic(root -> left, k);
        }
    }
};

思路: 我们采用了二叉树的后续递归遍历的方法,首先初始化了一个记录器cnt,初始化值为1,然后利用后续遍历从二叉树中值最大的那个元素开始操作,若当前操作的值不是题目所需的,就++cnt,若是题目所需要的,就利用公共变量ans保存该值并开始进行返回操作即可。

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

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

示例 1:

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

示例 2:

输入: n = 10, m = 17
输出: 2

限制:

1 <= n <= 10^5
1 <= m <= 10^6

题解:

class Solution {
public:

    int lastRemaining(int n, int m) {
        //最后剩下一个人的时候安全位置肯定是0,反推安全位置在人数为n时的编号即可
        int result = 0;
        for (int i = 2; i <= n; ++i) {
            result = (result + m) % i;
        }
        return result;
    }
    
};

思路: 最后剩下一个人的时候安全位置肯定是0,循环反推安全位置在人数为n时的编号即可

下面这张图的约瑟夫环你推理逻辑讲解的非常清楚。
请添加图片描述

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

示例 1:

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

示例 2:

输入: [0,0,1,2,5]
输出: True

限制:

数组长度为 5 
数组的数取值为 [0, 13] 

题解:

class Solution {
public:
    //定义一个长度为14的桶,存放的是0到13的各个牌的数量
    vector<int> res = vector<int>(14, 0);
    //用来找到最小的数量不为0的牌号的标兵
    int k = 1;
    //用来找到最大的数量不为0的牌号的标兵
    int j = 13;
    //用来记录两标兵中间牌数为0的牌号的数量
    int cnt = 0;

    bool isStraight(vector<int>& nums) {
        
        //将传进来的五张牌对应添加到桶中
        for (int val : nums) {
            res[val]++;
        }
        
        //若是牌号为0的就有5张,那就不用遍历后面牌号的情况了
        if (res[0] == 5) {
            return true;
        }

        //从前往后找牌数不为0的最小的牌号
        while (1) {
            if (res[k] == 0) {
                k++;
            } else {
                break;
            }
        }

        //从后往前找牌数不为0的最大的牌号
        while (1) {
            if (res[j] == 0) {
                j--;
            } else {
                break;
            }
        }

        //判断最小数量不为0的牌号和最大数量不为0的牌号之间有没有重牌号或者没有牌的牌号
        for (int i = k; i <= j; ++i) {
            if (res[i] == 0) {
                cnt++;
            } else {
                if (res[i] - 1) {
                    return false;
                }
            }
        }

        //统计完最小数量不为0的牌号和最大数量不为0的牌号之间没有牌的牌号之后,与res[0]比较其数量大小,若小于等于res[0],则说明0号牌可以将这五张票补为一个顺子
        if (res[0] >= cnt) {
            return true;
        }

        return false;
    }
};

思路: 我们采用了桶排序的方法,将传进来的五张牌先对应数量到桶当中去,然后再判断五张牌是否连续的情况,主要看有没有相同牌号重牌的情况和最小到最大数量不为0的牌号中间牌数为0的牌号的数量是否小于等于牌号为0的牌号的数量,若小于,就证明可以用0号牌将这五张牌补为顺子,否则这五张牌将不能成为顺子。

再来看一下这个题的巧妙题解:(同样是桶排序加双指针)

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        vector<int> a(14);
        for(int x : nums){
            // 有非0的重复数字
            if(x != 0 && a[x] > 0){
                return false;
            }
            // 桶计数
            a[x]++;
        }
        // 用双指针定位到最小和最大的牌
        int l = 1, r = 13;
        while(a[l] == 0){
            l++;
        }
        while(a[r] == 0){
            r--;
        }
        return r - l < 5;
    }
};

就是说非常的巧妙。

题目: 剑指 Offer 65. 不用加减乘除做加法

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

示例:

输入: a = 1, b = 1
输出: 2

提示:

a, b 均可能是负数或 0
结果不会溢出 32 位整数

题解一:

class Solution {
public:
    int add(int a, int b) {
        while (b) {
            //先算进位并保存
            int carry = a&b;
            //再算本位,并用a保存本位,此时的a尚未将本次循环的进位的结果加进去,但是下次循环的这个步骤就是将本次进位的结果往a里面加,一直循环直到后面算的b为0,就证明不需要进位了,那时的a就是我们所需要的最终结果。
            a = a^b;
            //将进的位进行逻辑的后移处理,十进制需要乘10,二进制就需要乘2(由于考虑到有可能是负数的情况,所以用unsigned规避)
            //进位不存在 负 的情况,所以 unsigned 是修饰 进位
            b = (unsigned)carry << 1;
        }

        return a;

    }
};

题解二:

class Solution {
public:
    int add(int a, int b) {
        //将上面迭代的方法改写为递归的形式
        return b ? add(a ^ b, (unsigned)(a & b) << 1) : a;

    }
};

思路: 用异或^来计算本位的相加结果,用与&来计算每个位置应该进位的值,然后将该进位的值乘2(也就是二进制左移一位),接着用乘完得到的该进位的值再去 ^ 相加(因为每个位置该进位的值已经左移一位了,所以就和原本保留过本位相加的结果值再相加即可),利用相同的操作迭代处理或递归处理,一直执行到该进位的值为0(即不用再进位的时候,也就是下次也不用和原本保留过本位相加的结果值再相加)的时候,将目前计算的本位相加结果作为最终结果返回即可。

具体的详细讲解可参考:大神题解网址

题目: 剑指 Offer 39. 数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

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

限制:

1 <= 数组长度 <= 50000

题解一:
排序后取中间元素:

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        //先排序
        sort(nums.begin(), nums.end());
        //取排序过后数组的中间下标元素
        return nums[nums.size() / 2];
    }
};

题解二:
投票法,只遍历一遍数组,时间复杂度O(n),空间复杂度O(1)

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        //投票法的维护对象一开始可以是任何值
        int temp = -1;
        //记录所维护对象的投票数,第一次维护的时候值会为1,后面遇见和维护对象不同的数值会对cnt-1,如果是我们所需要找到的那个结果值,那么一定是遍历完数组之后cnt>1的那个temp
        int cnt = 0;
        //开始遍历维护投票
        for (int k : nums) {
            //与所维护对象相同时就++cnt
            if (temp == k) {
                ++cnt;
            } else {//否则就--cnt,--cnt之后要判断cnt值是否减为了0,若减为0,就需要更换维护对象
                //次处判断时已经进行了--cnt,所以就不需要else来操作了
                if (--cnt < 1) {
                    temp = k;
                    cnt = 1;
                }
            }
        }
        //将遍历完数组后仍然被维护的那个结果temp返回即可
        return temp;
    }
};

思路: 详见上方代码和注释
详细讲解可参考大佬讲解

题目: 剑指 Offer 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) {
        vector <int> res(k);
        sort(arr.begin(), arr.end());
        for (int i = 0; i < k; ++i) {
            res[i] = arr[i];
        }
        return res;
    }
};

思路: 先用sort排序,然后再取排过序后的数组的前k个元素存进一个数组,最后返回这个数组即可。

题解二:

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        vector <int> res(k);
        if (k == 0) {
            return res;
        }
        priority_queue<int> queue;
        for (int i = 0; i < k; ++i) {
            queue.push(arr[i]);
        }
        for (int i = k; i < arr.size(); ++i) {
            if (queue.top() > arr[i]) {
                queue.pop();
                queue.push(arr[i]);
            }
        }
        for (int i = 0; i < k; ++i) {
            res[i] = queue.top();
            queue.pop();
        }
        return res;
    }
};

思路: 由于C++ 中的优先级队列是一个大堆栈,所以我们先将数组中前k个元素存进一个优先级队列中,接着再判断数组中第k个之后的元素与我们的优先级队列队头的大小,若队头大于当前判断的数组元素,那就将该队头pop掉,再将当前判断的数组元素push进该优先级队列,等遍历完该数组之后,我们将该优先级队列中的全部元素存入一个数组中,最后将这个数组作为结果返回即可。

题目 : 剑指 Offer II 075. 数组相对排序
给定两个数组,arr1 和 arr2,

arr2 中的元素各不相同
arr2 中的每个元素都出现在 arr1 中
对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。

示例:

输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
输出:[2,2,2,1,4,3,3,9,6,7,19]

提示:

1 <= arr1.length, arr2.length <= 1000
0 <= arr1[i], arr2[i] <= 1000
arr2 中的元素 arr2[i] 各不相同
arr2 中的每个元素 arr2[i] 都出现在 arr1 中

题解:

class Solution {
public:
    vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
        vector<int> res(1001);
        vector<int> resSecond(arr1.size());
        int k = 0;
        for (int val : arr1) {
            res[val]++;
        }
        for (int val : arr2) {
            while (res[val] != 0) {
                resSecond[k++] = val;
                --res[val];
            }
        }
        for (int val = 0; val < 1001; ++val) {
            while (res[val] != 0) {
                resSecond[k++] = val;
                --res[val];
            }
        }
        return resSecond;
    }
};

思路: 用空间换取时间,由于该题的数组中元素值不超过1000,所我们创建一个长度1001的桶,然后先将arr1中的各种元素的数量统计到桶中,然后再创建一个新数组,用于存结果,该数组的长度等于arr1的长度,然后我们先按照arr2中的元素的顺序去从桶中取对应元素,取出来之后存放到存结果的新数组中,每次取都先将一种元素的数量取完,等将arr2中的元素的种类都取完之后,我们就去从0到1000遍历一遍桶,遍历过程中如果发现有数量不为0的元素种类,就将那种元素取光,放到存结果的数组中去,等遍历完之后,就可以返回这个存结果的数组了。

题目: 剑指 Offer 17. 打印从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> printNumbers(int n) {
        //定义一个num,用来计算我们本次传进来的n需要返回到的最大的值+1的值为多少
        long long int num = 10;
        //循环计算我们本次传进来的n需要返回到的最大的值+1的值为多少
        for (int i = 0; i < n - 1; ++i) {
            num = num * 10;
        }
        //用来接收结果的数组
        vector<int> res(num - 1);
        //用来给数组从头开始添加元素的标兵
        int k = 0;
        //开始向数组中从头添加结果需要的值
        for (int i = 0; i < num - 1; ++i) {
            res[k++] = i + 1;
        }
        //返回存结果的数组
        return res;
    }
};

思路: 先计算我们传进来的n需要我们返回的结果中的最大的元素+1的值,然后从1开始往保存结果的数组中递增添加元素到最大值即可。

题目: 剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例 1:

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

示例 2:

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

限制:

0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

题解:

class Solution {
private:
    //控制矩阵上下左右走的走向
    static constexpr int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        //如果矩阵中行数或列数为0,就返回一个空数组
        if (matrix.size() == 0 || matrix[0].size() == 0) {
            return {};
        } 
        //计算行数和列数
        int rows = matrix.size(), columns = matrix[0].size();
        vector<vector<bool>> visited(rows, vector<bool>(columns));
        //计算矩阵中的元素总数
        int total = rows * columns;
        vector<int> order(total);
        
        //初始化开头需要找的行和列
        int row = 0, column = 0;
        int directionIndex = 0;
        for (int i = 0; i < total; ++i) {
            //将当前遍历的位置存入存结果的数组
            order[i] = matrix[row][column];
            //将该位置标记为已走过
            visited[row][column] = true;
            //计算下一个需要遍历的位置
            int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
            //判断计算出的下一个需要遍历的位置是否可被遍历
            if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) {
                directionIndex = (directionIndex + 1) % 4;
            }
            //再次更可遍历的下一个需要遍历的位置
            row += directions[directionIndex][0];
            column += directions[directionIndex][1];
        }
        //遍历矩阵完成后返回存放结果的数组即可
        return order;
    }
};

思路: 用一个二维数组directions动态维护遍历的方向,然后从头循环遍历矩阵,边遍历边将结果存放到order数组中,遍历过程中需要不断动态变化遍历方向,遍历完之后返回order数组即可。

题目: 剑指 Offer II 072. 求平方根
给定一个非负整数 x ,计算并返回 x 的平方根,即实现 int sqrt(int x) 函数。

正数的平方根有两个,只输出其中的正数平方根。

如果平方根不是整数,输出只保留整数的部分,小数部分将被舍去。

示例 1:

输入: x = 4
输出: 2

示例 2:

输入: x = 8
输出: 2
解释: 8 的平方根是 2.82842...,由于小数部分将被舍去,所以返回 2

提示:

0 <= x <= 231 - 1

题解一:

class Solution {
public:
    int mySqrt(int x) {
        if (!x) {
            return 0;
        }
        
        //C++中,exp函数的作用是计算e的某次方,log(x)计算的是lnx的值,log10(x)计算的是log以10为底数的x的对数
        int ans = exp(0.5 * log(x));
        return ((long) (ans + 1) * (ans + 1) <= x ? (ans + 1) : ans);
    }
};

思路: 数学方法,详细讲解如下:请添加图片描述
题解二:

class Solution {
public:
    int mySqrt(int x) {
        if (!x) {
            return 0;
        }
        
        //二分查找法
        int l = 0, r = x, mid;
        while (1) {
            //查找中间的数
            mid = (l + r) / 2;
            //如果中间数的平方 <= x,就先判断中间数 +1 后的值的平方是否 > x,如果大于就返回中间数,若不大于就继续循环
            if ((long)mid * mid <= x) {
                if ((long)(mid + 1) * (mid + 1) > x) {
                    return mid;
                }
                
                //本次找的中间数太小,我们从从大的一侧找
                l = mid + 1;
            } else {
                //本次找的中间数太大,我们从从小的一侧找
                r = mid - 1;
            }

        }

        //返回最终结果mid即可
        return mid;

    }
};

思路: 二分查找法,直到找到一个mid * mid <= x,且(mid + 1) * (mid + 1) > x 的情况的mid时,返回这个mid作为结果即可。

题目: 剑指 Offer II 069. 山峰数组的顶部
符合下列属性的数组 arr 称为 山峰数组(山脉数组) :

arr.length >= 3
存在 i(0 < i < arr.length - 1)使得:
arr[0] < arr[1] < … arr[i-1] < arr[i]
arr[i] > arr[i+1] > … > arr[arr.length - 1]
给定由整数组成的山峰数组 arr ,返回任何满足 arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1] 的下标 i ,即山峰顶部。

示例 1:

输入:arr = [0,1,0]
输出:1

示例 2:

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

示例 3:

输入:arr = [0,10,5,2]
输出:1

示例 4:

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

示例 5:

输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2

提示:

3 <= arr.length <= 104
0 <= arr[i] <= 106
题目数据保证 arr 是一个山脉数组

题解一:

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int front = 0, size = arr.size();
        //如果数组只有三个元素,就返回下标1从而避免不必要的判断
        if (size == 3) {
            return 1;
        }
        //否则我们就开始遍历数组,直至找到那个下一个元素小于当前元素的下标位置,就返回那个位置作为最终结果
        while (1) {

            if (arr[front + 1] > arr[front]) {
                ++front;
            } else {
                break;
            }

        }
        return front;

    }
};

思路: 遍历查找,直至找到那个下一个元素小于当前元素的下标位置,就返回那个位置作为最终结果

题解二:(二分查找)

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int front = 0, back = arr.size() - 1, mid;
        //如果数组只有三个元素,就返回下标1从而避免不必要的判断
        if (back == 2) {
            return 1;
        }
        //循环开始进行二分查找
        while (front <= back) {
        	//当前二分找到的下标位置
            mid = (front + back) / 2; 
			//由于在遍历过程中我们会用到当前下标和其前后两个下标的位置的元素,所以当计算出的当前下标为0(头)或者arr.size() - 1(尾)的时候,就分别让其下标值加1或者减1,避免访问到不安全位置导致的程序崩溃
            if (mid == 0) {
                ++mid;
            } else if (mid == arr.size() - 1) {
                --mid;
            }   
			
			//如果当前的下标就是需要的结果,就直接返回
            if (arr[mid] > arr[mid - 1] && arr[mid] > arr[mid + 1]) {
                return mid;
            }
            //如果是 前面元素 < 当前元素 < 后面元素, 就证明当前的这个下标太前了,下回就从后半段去找
            if (arr[mid] > arr[mid - 1] && arr[mid + 1] > arr[mid]) {
                front = mid + 1;
            }
            //如果是 前面元素 > 当前元素 > 后面元素, 就证明当前的这个下标太后了,下回就从前半段去找
            if (arr[mid] < arr[mid - 1] && arr[mid + 1] < arr[mid]) {
                back = mid - 1;
            }
        }
        return mid;
    }
};

思路: 利用了二分查找,每次判断计算出的二分位置和其前后两个下标位置的元素的值的大小情况,根据不同情况进行操作,重点是计算出二分的位置后得判断计算得到的位置是不是头或者尾

题目: 剑指 Offer II 068. 查找插入位置
给定一个排序的整数数组 nums 和一个整数目标值 target ,请在数组中找到 target ,并返回其下标。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

示例 4:

输入: nums = [1,3,5,6], target = 0
输出: 0

示例 5:

输入: nums = [1], target = 0
输出: 0

提示:

1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为无重复元素的升序排列数组
-104 <= target <= 104

题解:

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int front = 0, back = nums.size() - 1, mid;
        //循环进行二分查找
        while (front <= back) {
            //计算当前的中间位
            mid = (front + back) / 2; 

            //如果计算出来的mid位置恰好就是需要的结果位置,就直接返回
            if (nums[mid] == target) {
                return mid;
            }
            //如果计算出来的mid偏小,下次就往大的那一半中找
            if (nums[mid] < target) {
                front = mid + 1;
                //判断当前计算出的mid下标是否到了数组尾部,如果是就直接返回 尾部+1 的值作为最终结果,同时也防止了访问不安全位置导致的程序崩溃
                if (mid == nums.size() - 1) {
                    return nums.size();
                }
                //如果当前计算出来的mid不是数组尾部,就额外判断一下 mid + 1 下标对应的值的情况,判断target值是否在数组中,如果没有在就返回target应该在的位置
                if (mid < nums.size() - 1 && nums[mid + 1] > target) {
                    return mid + 1;
                }
            }

            //如果计算出来的mid偏大,下次就往小的那一半中找
            if (nums[mid] > target) {
                back = mid - 1;
                //判断当前计算出的mid下标是否到了数组头部,如果是就直接返回 头部(0) 的值作为最终结果,同时也防止了访问不安全位置导致的程序崩溃
                if (mid == 0) {
                    return 0;
                }
                //如果当前计算出来的mid不是数组头部,就额外判断一下 mid - 1 下标对应的值的情况,判断target值是否在数组中,如果没有在就返回target应该在的位置
                if (mid > 0 && nums[mid - 1] < target) {
                    return mid;
                }
            }

        }

        return mid;

    }
};

思路: 二分查找,详细逻辑见上方代码注释,重点是判断计算出来的中间下标mid是否是数组的头部或尾部,以及判断相应mid的下一个元素值或上一个元素值,用于判断数组中是否有target值。

题目: 剑指 Offer II 024. 反转链表
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。

示例 1:
在这里插入图片描述

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

示例 2:
在这里插入图片描述

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

示例 3:

输入:head = []
输出:[]

提示:

链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

题解:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
    	//创建一个节点指向nullptr,然后再新建一个一开始指向头节点的指针p
        ListNode *node = nullptr, *p = head;
        //循环迭代处理,遍历一遍链表,每次将当前遍历的链表以头插法创建的形式插入到我们正在存翻转结果的新链表的头部
        while (p) {
            p = p -> next; 
            head -> next = node;
            node = head;
            head = p;
        }
		//循环头插完后返回新链表第一个节点即可
        return node;
    }
};

思路: 遍历一遍旧链表,用头插法创建新链表的形式翻转。

题目: 剑指 Offer II 059. 数据流的第 K 大数值
设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:

输入:
["KthLargest", "add", "add", "add", "add", "add"]
[[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
输出:
[null, 4, 5, 5, 8, 8]

解释:
KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
kthLargest.add(3);   // return 4
kthLargest.add(5);   // return 5
kthLargest.add(10);  // return 5
kthLargest.add(9);   // return 8
kthLargest.add(4);   // return 8

提示:

1 <= k <= 104
0 <= nums.length <= 104
-104 <= nums[i] <= 104
-104 <= val <= 104
最多调用 add 方法 104 次
题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

题解:(利用C++的优先级队列)

class KthLargest {
public:

    priority_queue <int, vector<int>, greater<int>> queue; 
    int K;

    KthLargest(int k, vector<int>& nums) {
        K = k;
        for (int temp : nums) {
            add(temp);
        }
    }
    
    int add(int val) {
        queue.push(val);
        if (queue.size() > K) {
            queue.pop();
        }
        return queue.top();
    }   
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */

思路: C++中的优先级队列的队头是整个队列中最小的元素,所以我们动态维护,让这个优先级队列中只有K个元素,每次调用add函数的时候都在最后返回一下队列的队头即可。

题目: 剑指 Offer II 056. 二叉搜索树中两个节点之和
给定一个二叉搜索树的 根节点 root 和一个整数 k , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 k 。假设二叉搜索树中节点的值均唯一。

示例 1:

输入: root = [8,6,10,5,7,9,11], k = 12
输出: true
解释: 节点 5 和节点 7 之和等于 12

示例 2:

输入: root = [8,6,10,5,7,9,11], k = 22
输出: false
解释: 不存在两个节点值之和为 22 的节点

提示:

二叉树的节点个数的范围是  [1, 104].
-104 <= Node.val <= 104
root 为二叉搜索树
-105 <= k <= 105

题解:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool findTarget(TreeNode* root, int k) {
        unordered_set<int> hashTable;
        queue<TreeNode *> queue; 
        queue.push(root);
        while (!queue.empty()) {
            TreeNode *node = queue.front();
            queue.pop();
            if (hashTable.count(k - node -> val)) {
                return true;
            }
            hashTable.insert(node -> val);   
            if (node -> left != nullptr) {
                queue.push(node -> left);
            }
            if (node -> right != nullptr) {
                queue.push(node -> right);
            }
            
        }
        return false;

    }
};

思路: 利用二叉树的层级遍历(广度优先搜索)+ 哈希表的形式解决,遍历二叉树的过程中不断地将二叉树节点中的值存放在哈希表中,循环的每一次都判断一下 k值 - 我们当前节点的值 得到的值是否在哈希表中,如果在,就返回true,如果最终不在,就返回false。

题解: 剑指 Offer II 052. 展平二叉搜索树
给你一棵二叉搜索树,请 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。

示例 1:
在这里插入图片描述

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

示例 2:

在这里插入图片描述

输入:root = [5,1,7]
输出:[1,null,5,null,7]

提示:

树中节点数的取值范围是 [1, 100]
0 <= Node.val <= 1000

题解:

class Solution {
public:
    //存中序遍历二叉树的结果的容器
    vector<int> res;

    TreeNode* increasingBST(TreeNode* root) {
        int flag = 1;
        //定义两个指针,一个二叉树头指针,一个用于循环迭代的指针
        TreeNode *headNode, *midNode;
        //调用函数开始递归中序遍历
        sortTree(root);
        //利用中序遍历的结果去创建新树
        for (int valTest : res) {
            //第一次进循环时确定头和用于循环迭代的指针的值
            if (flag == 1) {
                flag = 0;
                midNode = new TreeNode;
                midNode -> val = valTest;
                headNode = midNode;
                
            } else {
                //后续的循环迭代,将新建节点按照顺序添加到新树的背后
                midNode -> right = new TreeNode(valTest);
                midNode = midNode -> right;
            }     
        }
        //返回新二叉树的头节点
        return headNode;
    }
    //递归中序遍历二叉树
    void sortTree(TreeNode *node) {
        if (node -> left != nullptr) {
            sortTree(node -> left);
        }

        res.push_back(node -> val);

        if (node -> right != nullptr ) {
            sortTree(node -> right);
        }
    }
};

思路: 先利用二叉树的中序遍历将遍历结果按顺序存入一个数组,然后利用数组中的值,按顺序创建新二叉树即可。

题目: 剑指 Offer II 042. 最近请求次数
写一个 RecentCounter 类来计算特定时间范围内最近的请求。

请实现 RecentCounter 类:

  • RecentCounter() 初始化计数器,请求数为 0 。
  • int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。

保证: 每次对 ping 的调用都使用比之前更大的 t 值。

示例:

输入:
inputs = ["RecentCounter", "ping", "ping", "ping", "ping"]
inputs = [[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]

解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1);     // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100);   // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001);  // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002);  // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3

提示:

1 <= t <= 109
保证每次对 ping 调用所使用的 t 值都 严格递增
至多调用 ping 方法 104

题解:

class RecentCounter {
public:

    queue<int> queue;
    RecentCounter() {

    }
    
    int ping(int t) {
        while (!queue.empty()) {
            if (t - queue.front() > 3000) {
                queue.pop();
            } else {
                break;
            }
        }
        queue.push(t);
        return queue.size();
    }
};

/**
 * Your RecentCounter object will be instantiated and called as such:
 * RecentCounter* obj = new RecentCounter();
 * int param_1 = obj->ping(t);
 */

思路: 我们利用队列的特性,队头的元素肯定是最早创建的请求,所以每次往队列中添加新请求时,都先循环淘汰掉旧请求当中和本次要添加的新请求时间差在3000以上的请求,最后添加新请求结束后,该队列的长度就是我们所需要的结果(时段相差在3000毫秒内的所有请求数)。

题目: 剑指 Offer II 041. 滑动窗口的平均值
给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。

实现 MovingAverage 类:

  • MovingAverage(int size) 用窗口大小 size 初始化对象。
  • double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。

示例:

输入:
inputs = ["MovingAverage", "next", "next", "next", "next"]
inputs = [[3], [1], [10], [3], [5]]
输出:
[null, 1.0, 5.5, 4.66667, 6.0]

解释:
MovingAverage movingAverage = new MovingAverage(3);
movingAverage.next(1); // 返回 1.0 = 1 / 1
movingAverage.next(10); // 返回 5.5 = (1 + 10) / 2
movingAverage.next(3); // 返回 4.66667 = (1 + 10 + 3) / 3
movingAverage.next(5); // 返回 6.0 = (10 + 3 + 5) / 3

提示:

1 <= size <= 1000
-105 <= val <= 105
最多调用 next 方法 104

题目:

class MovingAverage {
public:

    int K, cnt = 0;
    double sum = 0;
    queue<int> queue;
    /** Initialize your data structure here. */
    MovingAverage(int size) {
        K = size;
    }
    
    double next(int val) {
        if (cnt < K) {
            queue.push(val);
            sum += val;
            ++cnt;
        } else {
            sum -= queue.front();
            queue.pop();
            queue.push(val);
            sum += val;
        }
        double result = sum / cnt;
        return result;
    }
};

/**
 * Your MovingAverage object will be instantiated and called as such:
 * MovingAverage* obj = new MovingAverage(size);
 * double param_1 = obj->next(val);
 */

思路: 我们利用了队列的特性,用一个计数器和累加器来维护窗口里面的元素的数量和窗口中不超过size个元素的元素值的总和,重点是如果窗口中的元素数量等于size时,我们需要先从累加器减去队列头元素的值再加上本次需要新加入队列的元素的值,最后我们就可以用累加器的结果除以计数器的值去得到我们的结果。

题目: 剑指 Offer II 027. 回文链表
给定一个链表的 头节点 head ,请判断其是否为回文链表。

如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。

示例 1:
在这里插入图片描述

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

示例 2:
在这里插入图片描述

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

提示:

链表 L 的长度范围为 [1, 105]
0 <= node.val <= 9

进阶: 能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

题解:(用O(1)复杂度解决)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        //找到前半截链表的尾节点
        ListNode *halfLastNode = getHalfLastNode(head);
        //锁定后半截链表的头节点
        ListNode *secondHeadNode = halfLastNode -> next;
        halfLastNode -> next = nullptr;

        //翻转后半截链表,翻转后的链表头是nullptr
        ListNode *nodePtr, *newHead = nullptr;
        while (secondHeadNode) {
            nodePtr = secondHeadNode;
            secondHeadNode = secondHeadNode -> next;
            nodePtr -> next = newHead;
            newHead = nodePtr;
        }

        //同时遍历这前半部分链表和翻转后的后半部分链表
        while (head && newHead) {
            //如果有发现值不一样的情况,就返回false
            if (head -> val != newHead -> val) {
                return false;
            }
            head = head -> next;
            newHead = newHead -> next;
        }
        //如果能成功遍历完全部,就证明是回文链表,所以就返回true
        return true;
    }

    //找到前半截链表的尾节点
    ListNode *getHalfLastNode(ListNode *node) {
        ListNode *slow = node, *fast = node;
        while (fast -> next != nullptr && fast -> next -> next != nullptr) {
            slow = slow -> next;
            fast = fast -> next -> next;
        }
        return slow;
    }

};

思路: 利用快慢指针定位前半截链表的尾部和后半截链表的首部,然后翻转后半截链表,接着同时遍历两个链表看里面各个节点中值的情况进行判断,若可以成功遍历所有链表就返回true,若不能就返回false, 另外该题还可以采用高一点的空间复杂度来解决,比如遍历一遍链表将链表中的值存入一个数组中,然后利用双指针法判断这个数组是不是回文的即可。

题目: 剑指 Offer II 023. 两个链表的第一个重合节点
给定两个单链表的头节点 headA 和 headB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交:

在这里插入图片描述
题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

示例 1:
在这里插入图片描述

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

示例 2:
在这里插入图片描述

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

示例 3:
在这里插入图片描述

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

提示:

listA 中节点数目为 m
listB 中节点数目为 n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

进阶: 能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?
题解一:(O(1)空间复杂度解法)

/**
 * 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 == nullptr || headB == nullptr) {
            return nullptr;
        }

        ListNode *ptrA = headA, *ptrB = headB;
        while (ptrA != ptrB) {
            ptrA = (ptrA == nullptr) ? headB : ptrA -> next;
            ptrB = (ptrB == nullptr) ? headA : ptrB -> next;
        }
        return ptrA;
    }
};

思路: 因为两个链表在相交之前的节点总数可能是不一样的,所以需要利用交替循环遍历的方法,来促使它们可以相遇到同一个点上,就是A链表遍历完后就接着从B链表的表头开始遍历,B遍历完之后就从A的表头开始遍历。
题解二:(哈希表查找方法)

/**
 * 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) {
        unordered_set<ListNode *> visited;
        ListNode *ptr = headA;
        while (ptr) {
            visited.insert(ptr);
            ptr = ptr -> next;
        }

        ptr = headB;
        while (ptr) {
            if (visited.count(ptr)) {
                return ptr;
            }
            ptr = ptr -> next;
        }

        return nullptr;
    }
};

思路: 我们先遍历链表A,然后将A中的节点都存放进一个哈希表当中,接着开始遍历链表B,当B中某节点存在于哈希表中时就说明A和B链表于该节点开始相交,返回该节点即可,但是如果我们遍历完链表B没有发现某个节点在哈希表中存在时就返回nullptr表示这两个链表始终没有相交。

题目: 剑指 Offer II 019. 最多删除一个字符得到回文
给定一个非空字符串 s,请判断如果 最多 从字符串中删除一个字符能否得到一个回文字符串。

示例 1:

输入: s = "aba"
输出: true

示例 2:

输入: s = "abca"
输出: true
解释: 可以删除 "c" 字符 或者 "b" 字符

示例 3:

输入: s = "abc"
输出: false

提示:

1 <= s.length <= 105
s 由小写英文字母组成

题解:

class Solution {
public:
    bool validPalindrome(string s) {
        //首先利用双指针法判断是否本身是回文串
        int front = 0, back = s.size() - 1;
        int temp, tempSecond;
        while (front < back) {
            if (s[front] == s[back]) {
                ++front;
                --back;
            } else {
                //如果有不相同的元素就先停止遍历,先退出循环
                break;
            }
        }
        //如果指针相遇或前指针大于后指针,说明是回文串,返回true
        if (front >= back) {
            return true;
        }
        
        //如果本身不是回文串,就退出循环保留不同的元素位置,先采用跳过前半截的那个不同的元素
        temp = front + 1;
        tempSecond = back;
        //接着判断跳过前半截的那个不同的元素后续是否符合回文串条件
        while (temp < tempSecond) {
            if (s[temp] == s[tempSecond]) {
                ++temp;
                --tempSecond;
            } else {
                //如果有不相同的元素就先停止遍历,先退出循环
                break;
            }
        }
        //如果指针相遇或前指针大于后指针,说明是回文串,返回true
        if (temp >= tempSecond) {
            return true;
        }

        //如果刚才跳过前半截的那个不同的元素的方法不行,就采用跳过后半截的那个不同的元素,再试
        temp = front;
        tempSecond = back - 1;
        //判断跳过后半截的那个不同的元素后续是否符合回文串条件
        while (temp < tempSecond) {
            if (s[temp] == s[tempSecond]) {
                ++temp;
                --tempSecond;
            } else {
                //如果有不相同的元素就先停止遍历,先退出循环
                break;
            }
        }
        //如果指针相遇或前指针大于后指针,说明是回文串,返回true
        if (temp >= tempSecond) {
            return true;
        }
        //两种情况都不能构成回文串的话,那么这个字符串就不能符合题目条件,所以返回false
        return false;

    }
};

思路: 详情可见上方代码注释,该题其实很巧妙,我们可以先用双指针判断该串是否为回文串,如果不是,就记录下第一次两个前后不同元素的位置,然后先试着跳过前面这个不同的元素,后面接着判断是否为回文串,如果不是,就尝试跳过后面那个不同的元素,接着继续判断是否为回文串,如果还是不是就证明删除一个元素不能。使其成为回文串,则返回false。

题目: 剑指 Offer II 018. 有效的回文
给定一个字符串 s ,验证 s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写。

本题中,将空字符串定义为有效的 回文串 。

示例 1:

输入: s = "A man, a plan, a canal: Panama"
输出: true
解释:"amanaplanacanalpanama" 是回文串

示例 2:

输入: s = "race a car"
输出: false
解释:"raceacar" 不是回文串

提示:

1 <= s.length <= 2 * 105
字符串 s 由 ASCII 字符组成

题解:

class Solution {
public:
    string str;
    bool isPalindrome(string s) {

        for (int i = 0; i < s.size(); ++i) {
            //如果是数字字符,就直接添加到新数组中,并continue进入下一次循环
            if (s[i] == '0' || s[i] == '1' || s[i] == '2' || s[i] == '3' || s[i] == '4' || s[i] == '5' || s[i] == '6' || s[i] == '7' || s[i] == '8' || s[i] == '9') {
                str.push_back(s[i]);
                continue;
            }

            //如果是空格或者其他非字母的字符,就直接continue进入下一次循环
            if (s[i] == ' ' || (s[i] > 90 && s[i] < 97) || s[i] < 65 || s[i] > 122) {
                continue;
            }
            //如果是大写字母就转成大写后再添加进入新数组
            if (s[i] >= 97) {
                s[i] -= 32;
            }
            str.push_back(s[i]);
        }
        //利用双指针判断是否是回文串,如果找到不同的前后两个元素,就返回false,如果能够成功出循环,就返回true表示是回文串
        int front = 0, back = str.size() - 1;
        while (front < back) {
            if (str[front] != str[back]) {
                return false;
            }
            ++front;
            --back;
        }
        return true;

    }
};

思路: 详见上方代码注释,我们首先遍历一遍原字符串,然后创建一个新字符串,将原字符串中数字字符都添加入新字符串,接着将其他的非字母字符都去除掉,将所有的字母字符都转为大写后存入新字符串,待遍历完原数组后我们利用双指针法判断新字符串是否为回文串即可。

题目: 剑指 Offer II 012. 左右两边子数组的和相等
给你一个整数数组 nums ,请计算数组的 中心下标 。

数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。

如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。

如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。

示例 1:

输入:nums = [1,7,3,6,5,6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。

示例 2:

输入:nums = [1, 2, 3]
输出:-1
解释:
数组中不存在满足此条件的中心下标。

示例 3:

输入:nums = [2, 1, -1]
输出:0
解释:
中心下标是 0 。
左侧数之和 sum = 0 ,(下标 0 左侧不存在元素),
右侧数之和 sum = nums[1] + nums[2] = 1 + -1 = 0

提示:

1 <= nums.length <= 104
-1000 <= nums[i] <= 1000

题解:

class Solution {
public:
    int pivotIndex(vector<int> &nums) {
        //求数组中元素总和
        int total = 0;
        for (int val : nums) {
            total += val;
        }
        
        //初始化前缀和
        int sum = 0;
        //遍历前缀和判断是否有中心下标
        for (int i = 0; i < nums.size(); ++i) {
            if (sum * 2 + nums[i] == total) {
                return i;
            }
            sum += nums[i];
        }
        return -1;

    }
};

思路: 先算数组元素总和,然后从头开始计算每个前缀的和情况是否能占到总和的一半,如果可以就返回对应下标,如果不能就返回-1。

题目: 剑指 Offer II 088. 爬楼梯的最少成本
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。

每当爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,就可以选择向上爬一个阶梯或者爬两个阶梯。

请找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。

示例 1:

输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15

示例 2:

输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6

提示:

2 <= cost.length <= 1000
0 <= cost[i] <= 999

题解一:(动态规划数组)

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n + 1);
        dp[0] = dp[1] = 0;
        for (int i = 2; i <= n; ++i) {
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[n];
    }
};

思路: 我们所需要的走完全程最低花费方式其实就是从下标0走到下标n的最小花销,所以我们创建一个大小为n + 1的数组,然后利用动态规划从下标2开始循环计算最低花费,循环执行结束后返回我们存结果的数组中下标为n的元素即可。

题解二:(动态规划,利用滚动数组优化内存)

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        int front = 0, back = 0;
        for (int i = 2; i <= n; ++i) {
            int next = min(back + cost[i - 1], front + cost[i - 2]);
            front = back;
            back = next; 
        }
        return back;
    }
};

思路: 用两个int类型的元素来代替数组进行滚动迭代,从而起到代替数组的作用,可以节省很多内存。

题目: 剑指 Offer II 006. 排序数组中两个数字之和
给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。

假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

示例 1:

输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]
解释:26 之和等于目标数 8 。因此 index1 = 1, index2 = 3

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[0,2]

示例 3:

输入:numbers = [-1,0], target = -1
输出:[0,1]

提示:

2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 递增顺序 排列
-1000 <= target <= 1000
仅存在一个有效答案

题解:

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int front = 0, back = numbers.size() - 1;
        vector<int> res;
        while (front < back) {
            if (numbers[front] + numbers[back] > target) {
                --back;
                continue;
            }
            if (numbers[front] + numbers[back] < target) {
                ++front;
                continue;
            }
            if (numbers[front] + numbers[back] == target) {
                res.push_back(front);
                res.push_back(back);
                break;
            }

        }
        return res;
    }
};

思路: 利用双指针法,一个指向头,一个指向尾,每次判断两个指针对应元素相加值的情况,由于是排好序的,所以后面的元素值大,如果两个指针的元素相加值大于target,就将后指针前移,如果小于target,就将前指针后移,如果等于target,就直接记录结果并返回。

题目: 剑指 Offer 57 - II. 和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。

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

示例 1:

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

示例 2:

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

限制:

1 <= target <= 10^5

题解:

class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
        //滑动窗口的左边界
        int i = 1;
        //滑动窗口的右边界
        int j = 1;
        //滑动窗口中的数字之和
        int sum = 0;
        //记录最终结果的数组
        vector<vector<int>> res;

        //循环开始滑动窗口查找各个结果
        while (i <= target / 2) {
            if (sum < target) {
                //右边界向右移动
                sum += j;
                ++j;
            } else if (sum > target) {
                //左边界向右移动
                sum -= i;
                ++i;
            } else {
                //记录本次结果
                vector<int> arr;
                for (int k = i; k < j; ++k) {
                    arr.push_back(k);
                }
                res.push_back(arr);
                //左边界向右移动,便于后续结果的计算
                sum -= i;
                ++i;
            }
        }
        //返回最终结果
        return res;
    }
};

思路: 滑动窗口法,我们从1开始滑动窗口,如果窗口内的总值小于target,就右窗口右移,如果窗口内总值大于target,就左窗口右移,当总值等于target,就记录本次结果,循环滑动窗口的条件是左窗口值 <= target / 2,循环结束后返回结果即可。

题目: 剑指 Offer II 002. 二进制加法
给定两个 01 字符串 a 和 b ,请计算它们的和,并以二进制字符串的形式输出。

输入为 非空 字符串且只包含数字 1 和 0。

示例 1:

输入: a = "11", b = "10"
输出: "101"

示例 2:

输入: a = "1010", b = "1011"
输出: "10101"

提示:

每个字符串仅由字符 '0''1' 组成。
1 <= a.length, b.length <= 10^4
字符串如果不是 "0" ,就都不含前导零。

题解:

class Solution {
public:
    string addBinary(string a, string b) {
        //记录最终结果的容器
        string res;

        //记录进位
        int carry = 0;
        //a字符串的长度
        int i = a.size() - 1;
        //b字符串的长度
        int j = b.size() - 1;

        //循环模拟运算
        while (i >= 0 || j >= 0 || carry > 0) {
            //取出当前位的两个串的值
            int valueA = i >= 0 ? a[i] - '0' : 0;
            int valueB = j >= 0 ? b[j] - '0' : 0;
            //包含进位值在内,进行求和
            int sum = valueA + valueB + carry;
            //判断当前位的求和情况是否需要进位
            carry = sum >= 2 ? 1 : 0;
            //计算去除进位后需要保留的值
            sum = sum >= 2 ? sum - 2 : sum;
            //将需要保留的当前位的值转为字符
            sum += '0';
            //将结果添加到记录结果的字符串中
            res.push_back(sum);
            
            //进行二进制串中下一位的模拟运算
            --i;
            --j;
        }
        //由于记录结果的字符串中的元素是尾部追加的,所以需要翻转整个字符串才能得到正确结果
        reverse(res.begin(), res.end());
        //返回结果即可
        return res;
    }
};

思路: 利用模拟运算的方法,计算每一位上应该保留的值和下一位需要进位的值,直至循环计算结束为止,结束后翻转一下我们保存结果的字符串并返回即可,具体详细逻辑详见上方代码注释。

题目: 剑指 Offer II 003. 前 n 个数字二进制中 1 的个数
给定一个非负整数 n ,请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。

示例 1:

输入: n = 2
输出: [0,1,1]
解释: 
0 --> 0
1 --> 1
2 --> 10

示例 2:

输入: n = 5
输出: [0,1,1,2,1,2]
解释:
0 --> 0
1 --> 1
2 --> 10
3 --> 11
4 --> 100
5 --> 101

说明 :

0 <= n <= 105

题解:

class Solution {
public:
    vector<int> countBits(int n) {
        //记录最终结果的数组
        vector<int> res;
        //循环计算从0到n的所有数的二进制数中1的个数,并逐个记录下来
        for (int i = 0; i <= n; ++i) {
            res.push_back(countOne(i));
        }
        //返回最终结果
        return res;

    }

    int countOne(int x) {
        //初始化一个计数器,用来计算该数字的二进制数中有几个1
        int cnt = 0;

        //循环使用Brian Kernighan算法来计算一个数的二进制数中的1的个数
        while (x) {
            //Brian Kernighan算法核心,每计算一次,就消掉了该数的二进制数中的最后面的那个1,所以能够进几次循环,就证明该数的二进制数中有几个1
            x &= (x - 1);
            ++cnt;
        }
        //返回所记录的该数的二进制数中1的个数
        return cnt;
    }
};

思路: 详见上方代码和注释即可。

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

例如:

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

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

提示:

节点总数 <= 10000

题解一:(DFS)

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

思路: 非常简洁且清晰,看上方代码即可,利用深度优先搜索来找最深的层数。

题解二:(BFS)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == NULL) {
            return 0;
        }

        //记录二叉树层数的计数器
        int cnt = 0;
        queue<TreeNode *> queue;
        queue.push(root);
        //记录队列的元素个数的计数器
        int queueCnt = queue.size();

        //只要队列不为空
        while (!empty(queue)) {
            //二叉树的层数加一
            ++cnt;
            //每次更新一下记录队列的元素个数的计数器的值
            queueCnt = queue.size();
            //队列中有多少个元素我们就循环几次,目的是让每次在对队列中保存的元素只能是二叉树同一层的节点
            while (queueCnt) {
                //记录队头元素
                TreeNode *node = queue.front();
                //队头元素出队列
                queue.pop();
                //记录队列的元素个数的计数器的值减1
                --queueCnt;
                //如果当前队头元素的左子树存在,就将其左子树入队列
                if (node -> left) {
                    queue.push(node -> left); 
                }
                //如果当前队头元素的右子树存在,就将其右子树入队列
                if (node -> right) {
                    queue.push(node -> right);
                }
            }

        }
        //返回最终记录的结果即可
        return cnt;
    }
};

思路: 利用广度优先搜索,也就是二叉树的层级遍历,但是需要对原来的层级遍历进行一些修改,修改的重点是每次队列中保存的元素必须是二叉树中同一层级的节点,详细逻辑见上方代码及注释。

题目: 剑指 Offer 68 - II. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

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

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

在这里插入图片描述
示例 1:

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

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

题解:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        //当前节点为空或者当前节点是我们要找公共祖先的两个节点的其中一个,就返回当前节点root
        if (root == NULL || p == root || q == root) {
            return root;
        }

        //递归(深度优先搜索遍历二叉树)先把左节点这边遍历到底,再遍历右节点那边的分支
        TreeNode* left = lowestCommonAncestor(root -> left, p, q);
        TreeNode* right = lowestCommonAncestor(root -> right, p, q);

        //本题的重点就在下面这里
        //如果左右子节点及以其为根的后面的所有节点中有一方找到一个符合我们要找公共祖先的两个节点的其中之一(也就是有一个节点值不为NULL),就向上返回那个不为空的值
        //如果左右字节点及以其为根的后面的所有节点中没有找到任何符合我们要找公共祖先的两个节点的其中之一(也就是两个节点的值都是NULL),就向上返回NULL
        //如果左右子节点及以其为根的后面的所有节点都找到了符合我们要找公共祖先的两个节点的其中之一(也就是两个节点的值都不为NULL),就向上返回当前本节点root
        //由于是DFS递归,最终都会不断递归回到整个二叉树的根节点开始递归的那个return,在这里,如果left和right中有一个是NULL,就说明那个节点就是题目要求的两个节点的最近公共祖先,如果left和right两个都不是NULL,就说明题目说的那两个节点分别在当前节点的左边和右边,当前节点就是我们题目要求的最近公共祖先,所以返回当前节点root,由于已经递归到了整个二叉树的根节点,所以不存在左右都为NULL的情况了,一定会有上面讲的两种情况之一
        return left == NULL ? right : (right == NULL ? left : root);
    }
};

思路: 利用递归(深度优先搜索二叉树),重点在于每次最后的return,我们每找到一个题目中的两个要找最近祖先的节点中的一个的时候,就直接返回那个对应节点root的值作为上一级递归调用时的left或者right,如果上一级递归调用的left和right结果都为NULL,就证明题目中的那两个节点没有在上一级root节点为根的二叉树的下面,所以上一级就得向上上一级返回NULL作为上上一级的left或者right,但是如果上一级的left或者right有一个不为NULL,就向上上一级返回那个不为NULL的找到对应节点的值作为上上一级的left或者right,如果要是上一级的left和right都不为NULL,就证明题目中的那两个节点分别在上一级root节点为根的二叉树的左右两侧,意思就是上一级的root节点就是题目要找的最近公共祖先,所以将上一级的root返回给上上一级作为其left或者right即可,最终递归回回到整个二叉树的根节点那里,在那里有两种情况,一种是整个二叉树的根节点的left和right中有一个为NULL,这种情况那个不为NULL的结果就是题目要找的最近公共祖先,另一种情况就是整个二叉树的根节点的left和right都不是NULL,这种情况意思就是整个二叉树的根节点就是本体要找的最近公共祖先。

题目: 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

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

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

在这里插入图片描述
示例 1:

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

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。

题解:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == NULL || p == root || q == root) {
            return root;
        }

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

        return left == NULL ? right : (right == NULL ? left : root); 
    }
};

思路: 该题题解与上一题一致。

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

示例 1:

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

    3
   / \
  9  20
    /  \
   15   7

返回 true

示例 2:

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

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

返回 false

限制:

0 <= 树的结点个数 <= 10000

题解:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isBalanced(TreeNode* root) {
        //如果传进来的二叉树为空,那肯定就是一个平衡二叉树
        if (root == NULL) {
            return true;
        }

        //abs()函数是用来求绝对值的
        //我们计算当前节点的左右子树的最大高度,并计算查看是否高度差大于1
        if (abs(getHeight(root -> left) - getHeight(root -> right)) > 1) {
            return false;
        }
        //递归先序遍历二叉树的每一个节点,对每个节点都进行左右子树的最大高度的高度差的判断
        return isBalanced(root -> left) && isBalanced(root -> right);
    }

    //用来计算某节点的左右子树的最大高度值的函数
    int getHeight(TreeNode *root) {
        //当本次要继续向下遍历的节点值为NULL的时候就返回高度0,后续就是上级的递归调用在0上不断加一,最终返回一个最大高度值
        if (root == NULL) {
            return 0;
        }
        //递归向下遍历(先序遍历)
        int leftHeight = getHeight(root -> left);
        int rightHeight = getHeight(root -> right);
        //找到左右两个高度中较大的那个进行加1并返回
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

};

思路: 充分利用递归,先序递归遍历整个二叉树的所有节点,并且在遍历每个节点的时候再利用递归判断当前节点的左右子树最大高度差,若有某个节点的左右子树最大高度差>1,则返回来的值就是false,说明该二叉树不是平衡二叉树,如果遍历完所有的二叉树的节点之后都没有返回false,那就说明该二叉树就是一个平衡二叉树。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值