给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
if(nums2.empty()) return; // nums2中没有元素
int last = m + n - 1;
int tail1 = m-1, tail2 = n-1;
while(tail1>=0 && tail2>=0){
if(nums1[tail1]<nums2[tail2]){ // nums2中的更大
nums1[last--] = nums2[tail2--];
}else{
nums1[last--] = nums1[tail1--];
}
}
// 如果nums2中处理完毕(tail1>=0,tail2<0),但nums1中还剩余一部分,已经在nums1中,不用处理;
// 如果nums1中处理完毕(tail2>=0,tail1<0),但nums2中还剩余一部分,继续处理,如下:
while(tail2>=0){
nums1[last--] = nums2[tail2--];
}
}
};
思路:跟合并有序链表差不多的思路,但这个是从后往前排序分配,比较方便。需要注意的是注释的部分,第一个while是只要有一方数组处理完毕即停止,但必须要保证将nums2中的部分都移入nums1才行;
一条包含字母 A-Z 的消息通过以下方式进行了编码:
‘A’ -> 1
‘B’ -> 2
…
‘Z’ -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
输入: “12”
输出: 2
解释: 它可以解码为 “AB”(1 2)或者 “L”(12)。
输入: “226”
输出: 3
解释: 它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
class Solution {
public:
int numDecodings(string s) {
if(s.empty()) return 0; // 为空,编码方式为0
if(s[0]=='0') return 0; // 如果第一个数为0,则直接无法翻译
int pre = 1, result = 1; // 斐波那契数列递增的辅助节点, 其代表下标-1,0的数值
for(int i=1; i<s.size(); ++i){
int tmp = result; // 暂时保存result的值,当result更新后将其赋给pre
if(s[i]=='0'){
if( s[i-1]=='1' || s[i-1]=='2' ){ // 是10或20的情况,唯一解码,不增加种数!
result = pre; // f(n)=f(n-2) 这里一定要注意是赋较小值!
}else{ // 比如是30,则无法解码
return 0;
}
}
else{ // s[i]=1-9
if( s[i-1]=='1' || (s[i-1]=='2'&&s[i]>='0'&&s[i]<='6') ){ // 如果遇到可以分别解码的情况
result = result + pre; // f(n)=f(n-1)+f(n-2)
} // 如果是所有其他没提到的情况,全部不操作,因为唯一解码,没有增加种数
}
pre = tmp; // 将pre更改为上一个result的值
}
return result;
}
};
思路:本题思路比较明确,但非常难!!!(边界条件和特殊情况较多较复杂),思路就是有条件的斐波那契数列,f(n)=f(n-1)+f(n-2),用动态规划即可解决;但需要注意的是各种特殊情况,如如果选取状态转移方程的判断条件?0,00,110,101,110312等特殊情况如何考虑?等等,上述代码是非常不错的解法,这里解斐波那契数列只用到了result和pre两个变量,十分巧妙,避免了利用l1,l2,result三个变量来计算110312这种特殊情况的错判。具体思路见图和代码注释;
给定一个二叉树,检查它是否是镜像对称的。例如,二叉树 [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
// 常规递归遍历做法
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(root==nullptr) return true; // 如果是空树,则匹配
return isSymmetric(root->left, root->right);
}
bool isSymmetric(TreeNode* root1, TreeNode* root2){
if(root1==nullptr&&root2==nullptr) return true; // 同为空节点,则匹配
if(root1==nullptr||root2==nullptr) return false; // 有一个节点为空,另一个不空,则不匹配
return (root1->val==root2->val) // 当前节点相等
&& isSymmetric(root1->left, root2->right) // root1前序遍历,root2后序遍历进行判断
&& isSymmetric(root1->right, root2->left);
}
};
// 非递归的循环判断法(用到队列和BFS)
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(root==nullptr) return true;
deque<TreeNode*> list; // 利用队列(或stack,queue都可以)暂存节点,BFS判断节点是否对称
list.push_front(root->left); // 入队第一对左右节点
list.push_front(root->right);
while(!list.empty()){
TreeNode* node1 = list.back(); // 获取队列头部两个对应位置的节点
list.pop_back(); // 出队列
TreeNode* node2 = list.back();
list.pop_back();
if(node1==nullptr&&node2==nullptr) continue; // 如果当前为两个空节点,匹配
if(node1==nullptr||node2==nullptr) return false; // 如果一方为空,则不匹配
// 都不为空时
if(node1->val!=node2->val) return false;
list.push_back(node1->left); // 将这对节点的对应位置的子节点分别入队
list.push_back(node2->right);
list.push_back(node1->right);
list.push_back(node2->left);
}
return true;
}
};
思路:即判断一个树的前序遍历和后序遍历是否相等,递归遍历的方法延伸,没什么好说的。非递归的方法比较有趣,BFS广度优先搜索,可以参考,程序比较容易理解。
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
二叉树:[3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[3],
[9,20],
[15,7]
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int> > result;
if(root==nullptr) return result;
int left = 1, next = 0; // left为该层的剩余节点数,next为下一层的节点数
queue<TreeNode*> list; // 广度优先遍历,利用queue队列来暂存节点
list.push(root); // 根节点入队
vector<int> res_tmp; // 每层遍历结果暂存容器
while(!list.empty()){ // 遍历开始
TreeNode* node = list.front(); // 获取队列前端节点
res_tmp.push_back(node->val); // 输出
if(node->left!=nullptr){
list.push(node->left); // 左子节点入队
++next;
}
if(node->right!=nullptr){
list.push(node->right); // 右子节点入队
++next;
}
list.pop(); --left; // 该节点出队,该层剩余节点数-1
if(left==0){ // 如果该层处理完毕
result.push_back(res_tmp); // 保存结果
res_tmp.clear(); // 清空暂存容器,以保存下一行的节点
left = next; // 开始处理下一行,该行剩余节点数等于之前的下一层节点数
next = 0; // 下一层节点数清零,重新计数
}
}
return result;
}
};
思路:常规题,BFS,利用队列保存每一层的节点,同时设定两个变量保存该层的剩余节点数和下一层的节点数,依次从队列中读取并处理节点;
给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回锯齿形层次遍历如下:
[3],
[20,9],
[15,7]
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int> > result;
if(root==nullptr) return result;
stack<TreeNode*> stk[2]; // 设置两个栈,用来存放该行和下一行的节点
int index = 0;
stk[index].push(root);
vector<int> res_tmp;
while(!stk[index].empty()){
TreeNode* node = stk[index].top();
if(index==0){ // 当前是stk[0]往stk[1]中入栈子节点,先左子节点再右子节点
if(node->left){
stk[1-index].push(node->left);
}
if(node->right){
stk[1-index].push(node->right);
}
}
else{ // 当前是stk[1]往stk[0]中入栈子节点,先右子节点再左子节点
if(node->right){
stk[1-index].push(node->right);
}
if(node->left){
stk[1-index].push(node->left);
}
}
res_tmp.push_back(node->val);
stk[index].pop();
if(stk[index].empty()){ // 当前栈为空,则该行处理完毕,该换行了
index = 1 - index; // 两个栈交换
result.push_back(res_tmp);
res_tmp.clear();
}
}
return result;
}
};
思路:利用两个栈的数组间隔存储从左到右的行及从右到左的行,具体做法与树的广度优先遍历无异;可以画图理解过程;
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
// 常规递归做法
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==nullptr) return 0;
int left = maxDepth(root->left);
int right = maxDepth(root->right);
return left > right ? left+1 : right+1;
}
};
// 广度优先遍历做法
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==nullptr) return 0;
int deep = 0;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
++deep;
int size = q.size();
for(int i=0; i<size; ++i){
TreeNode* node = q.front();
q.pop();
if(node->left) q.push(node->left);
if(node->right) q.push(node->right);
}
}
return deep;
}
};
思路:递归的做法非常经典,应记住;其次也可以利用树的DFS或BFS来做,BFS比较简单,每次遍历一行的时候给行数加1即可,直到队列为空,则遍历完毕;
根据一棵树的前序遍历与中序遍历构造二叉树。你可以假设树中没有重复的元素。
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.empty()||inorder.empty()||preorder.size()!=inorder.size()) return nullptr;
int size = preorder.size();
return buildTree(preorder,0,size-1,inorder,0,size-1); // 进入递归,创建左右子树,参数中四个整数代表当前子树节点在数组中的范围下标
}
TreeNode* buildTree(vector<int>& preorder, int l_p, int r_p, vector<int>& inorder, int l_i, int r_i){
TreeNode * node = nullptr;
if(l_p<=r_p&&l_i<=r_i){ // 如果当前左右子树中还有节点
node = new TreeNode(preorder[l_p]); // 创建新的节点
int index = l_i; // index是当前子树的根节点在中序遍历数组中的下标位置
for(; index<=r_i; ++index){ // 寻找根节点在中序遍历中的位置
if(inorder[index]==preorder[l_p]) break;
}
node->left = buildTree(preorder, l_p+1, l_p+1+(index-l_i), inorder, l_i, index-1); // 创建当前节点的左子树部分
node->right = buildTree(preorder, l_p+1+(index-l_i), r_p, inorder, index+1, r_i); // 创建当前节点的右子树部分
}
return node;
}
};
思路:灵活利用前序和中序遍历的特点,从根节点入手创建节点,然后利用递归分别创建根节点的左子树和右子树,方法好想,递归程序比较抽象不好想,具体思路看代码注释即可,注意各类下标的范围设定和计算问题;必须十分细心才可;
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
int size = nums.size();
if(size<=0) return nullptr;
return buildAVLtree(nums,0,size-1);
}
TreeNode* buildAVLtree(vector<int>& nums, int left, int right){
if(right<left) return nullptr;
int mid = (left + right) >> 1;
TreeNode * node = new TreeNode(nums[mid]);
node->left = buildAVLtree(nums, left, mid-1);
node->right = buildAVLtree(nums, mid+1, right);
return node;
}
};
思路:比较简单的题,虽然是构建AVL平衡搜索二叉树,只要每次遵循二分的思路就可以创建出来;首先是二分找到中间节点作为根节点,然后左边的数组和右边的数组分别用来构建左子树和右子树,形成递归;关于递归终止的条件需要仔细思考判断,可以通过举例的方式发现规律;
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
提示:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
class Solution {
public:
Node* connect(Node* root) { // 注意题目这是一个完美二叉树,即树是满的
if(root==nullptr) return root;
if(root->left&&root->right){ // 该节点还有左右节点
root->left->next = root->right; // 将左子节点指向右子节点
if(root->next){ // 如果该节点还指向了其右侧节点
root->right->next = root->next->left; // 将该节点的右子节点指向该节点右侧节点的左子节点
}
}
connect(root->left); // 前序遍历继续
connect(root->right);
return root;
}
};
思路:题目给出了只能使用常数额外空间的要求,不然的话可以用层序遍历来做,利用队列在每层弹出的时候设置next指针;但空间复杂度是O(n/2); 所以可以利用前序遍历的递归过程中进行next指针的设置,注意这种方法只能用在完美二叉树中,即树是满的;具体的思路可以通过画图轻松发现,也可以参照代码的注释;
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
输入: 5 输出:
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
class Solution {
public:
vector<vector<int>> generate(int numRows) {
if(numRows<=0) return {};
if(numRows==1) return {{1}};
if(numRows==2) return {{1},{1,1}};
vector<vector<int> > result;
result.reserve(numRows); // 给result预留出空间
result.push_back({1});
result.push_back({1,1});
vector<int> res_tmp;
res_tmp.reserve(numRows); // 注意预留出res_tmp足够的空间,提高效率
for(int row=2; row<numRows; ++row){ // 从第三行开始遍历
res_tmp.push_back(1); // 该行第一个元素是1
for(int i=1; i<=row/2; ++i){ // 计算前一半数据
res_tmp.push_back(result[row-1][i-1] + result[row-1][i]);
}
for(int i=row/2+1; i<=row; ++i){ // 复制后一半数据
res_tmp.push_back(res_tmp[row-i]);
}
result.push_back(res_tmp); // 打印改行
res_tmp.clear(); // 置空,准备计算下一行
}
return result;
}
};
思路:按部就班的一层一层计算赋值即可,这里需要注意下标的使用和边界条件的判断问题;细心即可;
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.empty()) return 0;
int maxgrow = 0, minprice = prices[0]; //设置当前最大的价格增幅,及之前最低的价格
for(int i=1; i<prices.size(); ++i){ // 从第二个元素开始遍历
if(maxgrow < prices[i]-minprice)
maxgrow = prices[i]-minprice; // 如果当前元素的增幅更大,则更新maxgrow
if(minprice > prices[i])
minprice = prices[i]; // 如果当前元素的价格为新低,则更新最低价格
}
return maxgrow;
}
};
思路:剑指offer中的题目,比较简单,就是查找数组中增量最大的两个数字,通过设置变量当前最大的价格增幅,及之前最低的价格就可以一次遍历解决问题;具体过程见代码;
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.empty()) return 0;
int size = prices.size();
int result = 0;
for(int i=1; i<size; ++i){ // 从第二个节点开始遍历
if(prices[i]>prices[i-1]){ // 今天的股价比昨天高
result += prices[i]-prices[i-1]; // 就算作买入,统计result
}
}
return result;
}
};
思路:一道有趣的题,一开始想用递归或者贪心算法来做,但递归要遍历所有子问题很复杂,而且有重复子问题的问题;贪心算法仔细想起来实现也非常复杂;后来发现只要把整个股价趋势看作是折线图,最终的最大利润就是所有上升的折线部分而已,故只需要一次遍历,把所有增加的相邻节点之差加起来即可;有时候一个很简单的题可能会想得很复杂,但只要灵活转变思路,就会发现异常简单;