C++ : 力扣_Top(88-122)

C++ : 力扣_Top(88-122)

文章目录

  • C++ : 力扣_Top(88-122)
      • 88、合并两个有序数组(简单)
      • 91、解码方法(中等)
      • 101、对称二叉树(简单)
      • 102、二叉树的层序遍历(中等)
      • 103、二叉树的锯齿形层次遍历(中等)
      • 104、二叉树的最大深度(简单)
      • 105、从前序与中序遍历序列构造二叉树(中等)
      • 108、将有序数组转换为二叉搜索树(简单)
      • 116、填充每个节点的下一个右侧节点指针(中等)
      • 118、杨辉三角(简单)
      • 121、买卖股票的最佳时机(简单)
      • 122、买卖股票的最佳时机II(简单)


88、合并两个有序数组(简单)

给你两个有序整数数组 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才行;


91、解码方法(中等)

一条包含字母 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这种特殊情况的错判。具体思路见图和代码注释;

C++ : 力扣_Top(88-122)_第1张图片


101、对称二叉树(简单)

给定一个二叉树,检查它是否是镜像对称的。例如,二叉树 [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广度优先搜索,可以参考,程序比较容易理解。


102、二叉树的层序遍历(中等)

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

二叉树:[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,利用队列保存每一层的节点,同时设定两个变量保存该层的剩余节点数和下一层的节点数,依次从队列中读取并处理节点;


103、二叉树的锯齿形层次遍历(中等)

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

给定二叉树 [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;
    }
};

思路:利用两个栈的数组间隔存储从左到右的行及从右到左的行,具体做法与树的广度优先遍历无异;可以画图理解过程;


104、二叉树的最大深度(简单)

给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。

给定二叉树 [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即可,直到队列为空,则遍历完毕;


105、从前序与中序遍历序列构造二叉树(中等)

根据一棵树的前序遍历与中序遍历构造二叉树。你可以假设树中没有重复的元素。

前序遍历 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; 
    }
};

思路:灵活利用前序和中序遍历的特点,从根节点入手创建节点,然后利用递归分别创建根节点的左子树和右子树,方法好想,递归程序比较抽象不好想,具体思路看代码注释即可,注意各类下标的范围设定和计算问题;必须十分细心才可;


108、将有序数组转换为二叉搜索树(简单)

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 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平衡搜索二叉树,只要每次遵循二分的思路就可以创建出来;首先是二分找到中间节点作为根节点,然后左边的数组和右边的数组分别用来构建左子树和右子树,形成递归;关于递归终止的条件需要仔细思考判断,可以通过举例的方式发现规律;


116、填充每个节点的下一个右侧节点指针(中等)

给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。

提示:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

C++ : 力扣_Top(88-122)_第2张图片

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指针的设置,注意这种方法只能用在完美二叉树中,即树是满的;具体的思路可以通过画图轻松发现,也可以参照代码的注释;


118、杨辉三角(简单)

给定一个非负整数 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;
    }
};

思路:按部就班的一层一层计算赋值即可,这里需要注意下标的使用和边界条件的判断问题;细心即可;


121、买卖股票的最佳时机(简单)

给定一个数组,它的第 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中的题目,比较简单,就是查找数组中增量最大的两个数字,通过设置变量当前最大的价格增幅,及之前最低的价格就可以一次遍历解决问题;具体过程见代码;


122、买卖股票的最佳时机II(简单)

给定一个数组,它的第 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;    
    }
};

思路:一道有趣的题,一开始想用递归或者贪心算法来做,但递归要遍历所有子问题很复杂,而且有重复子问题的问题;贪心算法仔细想起来实现也非常复杂;后来发现只要把整个股价趋势看作是折线图,最终的最大利润就是所有上升的折线部分而已,故只需要一次遍历,把所有增加的相邻节点之差加起来即可;有时候一个很简单的题可能会想得很复杂,但只要灵活转变思路,就会发现异常简单;


你可能感兴趣的:(C++ : 力扣_Top(88-122))