【LeetCode Cookbook(C++ 描述)】一刷二叉树综合(下)

news2024/11/16 12:44:03

目录

  • LeetCode #257:Binary Tree Paths 二叉树的所有路径
    • 深度优先搜索
    • 广度优先搜索
  • LeetCode #404:Sum of Left Leaves 左叶子之和
    • 深度优先搜索
    • 广度优先搜索
  • LeetCode #199:Binary Tree Right Side View 二叉树的右视图
    • 广度优先搜索
    • 深度优先搜索
  • LeetCode #513:Find Bottom Left Tree Value 找树左下角值
    • 深度优先搜索
    • 广度优先搜索
  • LeetCode #112:Path Sum 路径总和
    • 深度优先搜索
    • 广度优先搜索
  • LeetCode #617:Merge Two Binary Trees 合并二叉树
    • 深度优先搜索
    • 广度优先搜索
  • LeetCode #236:Lowest Common Ancestor of a Binary Tree 二叉树的最近公共祖先

本系列文章仅是 GitHub 大神 @halfrost 的刷题笔记 《LeetCode Cookbook》的提纲以及示例、题集的 C++转化。原书请自行下载学习。
本篇文章涉及新手应该优先刷的几道经典二叉树综合算法题。

LeetCode #257:Binary Tree Paths 二叉树的所有路径

#257
给定一个二叉树的根节点 root ,按任意顺序,返回所有从根节点到叶子节点的路径。

深度优先搜索

类似于二叉树的最大深度问题,从根节点递归到叶子节点,记录路径上的节点,并更新维护路径。每次都是先从根节点开始,先递归左子树, 再递归右子树,自顶向下,考虑采用前序遍历的方式遍历二叉树。

对于这一问题,我们采用二维字符串数组来存储每一条路径,如果当前节点不是叶子节点,则在当前的路径末尾添加该节点,并继续递归遍历该节点的每一个子节点;如果当前节点是叶子节点,则在当前路径末尾添加该节点,就得到了一条从根节点到叶子节点的路径,将该路径加入到答案即可。

我们需要一个辅助函数 construct_paths() 来反复操作字符串数组 paths 以正确地遍历左右子树:

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> paths;
        construct_paths(root, "", paths);
        return paths;
    }

private:
    void construct_paths(TreeNode* root, string path, vector<string>& paths) {
       if (root != nullptr) {
           path += to_string(root->val);
           if (root->left == nullptr && root->right == nullptr) paths.push_back(path);   //当前节点是叶子节点
           else {
               path += "->";    //当前节点不是叶子节点,继续递归遍历
               construct_paths(root->left, path, paths);
               construct_paths(root->right, path, paths);
           }
       }
    }
};

在这一算法中,每个节点会被访问一次且只会被访问一次,每一次会对 path 变量进行拷贝构造,时间代价为   O ( n ) \ O(n)  O(n),总体的时间复杂度为   O ( n 2 ) \ O(n^2)  O(n2)

在最坏情况下,当二叉树中每个节点只有一个孩子节点时,即整棵二叉树呈一个链状,此时递归的层数为 n n n ,此时每一层的 path 变量的空间代价的总和为 O ( ∑ i = 1 n i ) = O ( n 2 ) O\left(\sum^{n}_{i=1}i\right) = O(n^2) O(i=1ni)=O(n2),即该算法的空间复杂度。

广度优先搜索

我们维护两个队列,分别存储节点以及根到该节点的路径,在每步迭代中,我们取出队首节点,如果它是叶子节点,则将它对应的路径加入到答案中;如果它不是叶子节点,则将它的所有子节点加入到队列的末尾,直到队列为空。

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> paths;
        if (root == nullptr) return paths;

        queue<TreeNode*> node_queue;   //存储待处理的节点
        queue<string> path_queue;      //存储到达当前节点的路径

        node_queue.push(root);   //将根节点加入节点队列
        path_queue.push(to_string(root->val));   //将根节点的值转换为字符串,‌并加入路径队列

        while (!node_queue.empty()) {
            TreeNode* node = node_queue.front(); 
            string path = path_queue.front();
            node_queue.pop(); 
            path_queue.pop();

            if (node->left == nullptr && node->right == nullptr) paths.push_back(path);
            else {
                //如果左子节点非叶子节点,‌则将左子节点加入节点队列,‌并更新路径
                if (node->left != nullptr) {
                    node_queue.push(node->left);
                    path_queue.push(path + "->" + to_string(node->left->val));
                }
                //如果右子节点非叶子节点,‌则将右子节点加入节点队列,‌并更新路径
                if (node->right != nullptr) {
                    node_queue.push(node->right);
                    path_queue.push(path + "->" + to_string(node->right->val));
                }
            }
        }
        return paths;
    }
};

LeetCode #404:Sum of Left Leaves 左叶子之和

#404
给定二叉树的根节点 root ,返回所有左叶子之和。

非常简单,遍历整棵树:

  • 如果遍历的当前节点的左孩子是一个叶子节点,则左孩子的值累加入结果。
  • 如果遍历的当前节点的左孩子不是叶子节点,则继续遍历。

深度优先搜索

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (root == nullptr) return 0;
        //如果当前节点的左孩子存在,‌并且左孩子为叶子节点‌,‌则将左孩子的值加入res
        if (root->left != nullptr && root->left->left == nullptr && root->left->right == nullptr) res += root->left->val;
        //递归遍历左子树和右子树
        sumOfLeftLeaves(root->left);
        sumOfLeftLeaves(root->right);
        return res;
    }
    
private:
	int res = 0;
};

该算法的时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)

广度优先搜索

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (!root) return 0;

        queue<TreeNode*> q;
        q.push(root);
        int ans = 0;
        
        while (!q.empty()) {
            TreeNode* node = q.front();
            q.pop();
            
            if (node->left)
                if (isLeafNode(node->left)) ans += node->left->val;
                else q.push(node->left);

            if (node->right)
                if (!isLeafNode(node->right)) q.push(node->right);
        }
        return ans;
    }

private:
	 bool isLeafNode(TreeNode* node) {
        return !node->left && !node->right;
     }
};

LeetCode #199:Binary Tree Right Side View 二叉树的右视图

#199
给定一个二叉树的根节点 root ,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

所谓右视图,其实就是每一层最右边的节点,涉及到“层”的概念,直接利用层次遍历也就是广度优先搜索算法来解决。对应到具体操作上,对于每一层的节点从左到右遍历,保存每层最后一个遍历的节点即可。

广度优先搜索

使用队列保存每一层的所有节点,把队列里的所有节点弹出队列,如果当前节点是当前层
的最后一个节点,入 res

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> res;
        if (root == nullptr) return res;
        
        queue<TreeNode*> q;
        q.push(root);
        
        while (!q.empty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = q.front();
                q.pop();
                //如果当前节点是该层的最后一个节点,‌则将其值添加到结果中
                if (i == size - 1) res.push_back(node->val);
                //将当前节点的左右子节点‌添加到队列中
                if (node->left != nullptr) q.push(node->left);
                if (node->right != nullptr) q.push(node->right);
            }
        }
        
        return res;
    }
};

该算法的时间复杂度与空间复杂度均为 O ( n ) O(n) O(n)

深度优先搜索

层次遍历对于每一层,均为从左到右遍历,而我们可以换个方向,从右向左遍历, 这样对于每一层来说,遍历的第一个节点就是所谓右视图的子节点。 因此,我们可以先遍历右子树,再遍历左子树

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> res;
        if (root == nullptr) return res;

        level(root, 1, res);
        return res;
    }

private:
    void level(TreeNode* root, int depth, vector<int>& res) {
        //如果当前节点的深度还未在 res 中出现(‌每层仅有一个节点)‌
        //这意味着当前节点是该层第一个被访问的,‌将当前节点的值添加到 res 中
        if (res.size() < depth) res.push_back(root->val);
        //遍历右子树
        if (root->right != nullptr) level(root->right, depth + 1, res);
        //遍历左子树
        if (root->left != nullptr) level(root->left, depth + 1, res);
    }
};

LeetCode #513:Find Bottom Left Tree Value 找树左下角值

#513
给定一个二叉树的根节点 root ,请找出该二叉树最底层最左边节点的值。

所谓“最底层”,便是二叉树最大深度,最大深度那一层必然是最后一层,其次则是“最左边”。

深度优先搜索

我们可以自顶向下,从根节点递归到叶子节点,对于每一个节点,先判断是否为叶子节点,再按前序遍历左右子树。

我们需要维护两个深度,一个是当前的遍历到的最大深度 maxDepth ,另一个则是当前节点所处的深度 leftDepth 。如果当前节点是叶子节点,且 leftDepth > maxDepth 的时候,证明当前遍历的节点是新的一层最先被遍历的节点1,更新 maxDepth 和当前的结果 res

class Solution {
private:
    int maxDepth = -1;
    int res = -1;

    void leftValue(TreeNode* root, int leftDepth) {
        if (root == nullptr) return;
        //如果当前节点是叶子节点
        if (root->left == nullptr && root->right == nullptr) {
            //当前叶子节点的深度大于之前保存的最大深度
            //此时更新最大深度,‌更新结果值
            if (leftDepth > maxDepth) {
                maxDepth = leftDepth;
                res = root->val;
            }
        }
        //递归左子树
        leftValue(root->left, leftDepth + 1);
        //递归右子树
        leftValue(root->right, leftDepth + 1);
    }

public:
    int findBottomLeftValue(TreeNode* root) {
        leftValue(root, 0);
        return res;
    }
};

该算法的时间复杂度与空间复杂度均为 O ( n ) O(n) O(n)

广度优先搜索

我们也可以自左向右,根节点开始,一层一层地遍历二叉树,那么最后一层的第一个节点便是我们希望得到的结果,即所谓层序遍历。我们依然采用经典的队列进行遍历,使用队列保存每一层的节点,第 1 个出队列的节点值保存(即该层最左边的值),把队列里的所有节点出队列,然后把这些出去节点各自的子节点入队列。用 depth 维护每一层。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> q;
        q.push(root);
        int res = 0;
        
        while (!q.empty()) {
            int n = q.size();
            for (int i = 0; i < n; i++) {
                TreeNode* node = q.front();
                q.pop();
                //存储每一层的第一个元素
                if (i == 0) res = node->val;
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        return res;
    }
};

LeetCode #112:Path Sum 路径总和

#112
给定二叉树的根节点 root 和一个表示目标的整数 targetSum

判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum 。存在返回 true ,否则返回 false

深度优先搜索

依然采用自顶向下的思路,以前序遍历的方式,每次先判断当前的节点,再递归左子树,最后是右子树。具体的解法如下:

  • 判断当前节点是否为叶子节点,如果是,则判断当前叶子节点的值是否为 targetSum 减去之前路径上节点值
  • 递归左子树。
  • 递归右子树。
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr) return false;
        //如果当前节点为叶子节点,‌且叶子节点的值等于减去该路径之前节点的值,‌返回 true
        if (root->left == nullptr && root->right == nullptr && root->val == targetSum) return true;
        //递归左子树
        bool leftPath = hasPathSum(root->left, targetSum - root->val);
        //递归右子树
        bool rightPath = hasPathSum(root->right, targetSum - root->val);
        //返回左子树或右子树的结果
        return leftPath || rightPath;
    }
};

该算法的时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)

广度优先搜索

我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和,以防止重复计算。

class Solution {
public:
    bool hasPathSum(TreeNode *root, int targetSum) {
        if (root == nullptr) return false;
        //定义两个队列,‌一个用来存储节点,‌一个用来存储从根节点到当前节点的路径和
        queue<TreeNode *> que_node;
        queue<int> que_val;
        //将根节点和根节点的值入队
        que_node.push(root);
        que_val.push(root->val);
   
        while (!que_node.empty()) {
            //取出当前节点和当前路径和
            TreeNode *node = que_node.front();
            int currSum = que_val.front();
            que_node.pop();
            que_val.pop();
            //如果当前节点是叶子节点
            if (node->left == nullptr && node->right == nullptr) {
                //如果当前路径和等于给定的和,‌则返回ntrue
                if (currSum == targetSum) return true;
                continue;
            }
            //如果左子节点不为空,‌则将左子节点与左子节点路径和入队
            if (node->left != nullptr) {
                que_node.push(node->left);
                que_val.push(node->left->val + currSum);
            }
            //如果右子节点不为空,‌则将右子节点与右子节点路径和入队
            if (node->right != nullptr) {
                que_node.push(node->right);
                que_val.push(node->right->val + currSum);
            }
        }
        //如果遍历完所有路径都没有找到符合条件的路径,‌则返回 false
        return false;
    }
};

LeetCode #617:Merge Two Binary Trees 合并二叉树

#617
给定两棵二叉树 root1root2 ,合并成一棵新的二叉树,合并规则为:

如果两个节点重叠,将两个节点的值相加作为合并后节点的新值,否则,不为 null 的节点将直接作为新二叉树的节点。

大致可以分为两种情况讨论:

  • 如果两棵树对应位置上都有节点,则新节点的值为两个节点的值相加。
  • 如果两棵树对应位置上只有一个节点有值,则新节点的值就为该节点的值。

只需要遍历二叉树,保证遵守如上两条规则即可。

深度优先搜索

对于每一层来说,重新创建一个节点 root 存储 root1->val + root2->val ,那么,之后 root 的左子树就是合root1 左子树和 root2 左子树之后的左子树,root 的右子树就是合并 root1 右子树和 root2 右子树之后的右子树,依然是经典的前序遍历。

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        // base case
        if (root1 == nullptr) return root2;
        if (root2 == nullptr) return root1;

        //如果均存在节点,‌创建一个新的节点存储合并后的值
        TreeNode* root = new TreeNode(root1->val + root2->val);
        //递归合并左子树
        root->left = mergeTrees(root1->left, root2->left);
        //递归合并右子树
        root->right = mergeTrees(root1->right, root2->right);
        
        return root;
    }
};

root1 的节点数为 n n nroot2 的节点数为 m m m,该算法的时间复杂度为 O ( min ⁡ ( n , m ) ) O(\min (n,m)) O(min(n,m)),空间复杂度也为 O ( min ⁡ ( n , m ) ) O(\min (n,m)) O(min(n,m))

广度优先搜索

我们维护两个队列,队列 qMerge 存储合并后的树节点,q 则是 root1root2 的节点。

先创建一个新的根节点 root ,其值为 root1->val + root2->val ,同时初始化两个队列,将 rootqMerge 队列,将 root1root2 入队列 q 。此时队列不为空,qMerge 队首元素出队列,同时q 两个队首元素出队列

若两棵树的当前节点都存在左孩子,则直接合并二者之和,为新的节点的值,同时将新节点的左孩子入 qMerge 队列,将两棵树节点的左孩子入 q 队列。以此类推,对于两棵树当前节点的右孩子同理并继续弹出队列,反复遍历,直到队列为空。

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
    	// base case
        if (root1 == nullptr) return root2;
        if (root2 == nullptr) return root1;
		//如果都存在节点,创建一个新的节点存储合并后的值
        TreeNode* root = new TreeNode(root1->val + root2->val);
        //初始化队列
        queue<TreeNode*> qMerge;
        queue<TreeNode*> q;
        qMerge.push(root);
        q.push(root1);
        q.push(root2);
        
        while (!q.empty()) {
        	//从队列中取出当前节点
            TreeNode* node = qMerge.front();
            qMerge.pop();
            TreeNode* node1 = q.front();
            q.pop();
            TreeNode* node2 = q.front();
            q.pop();
            //若两棵树的左孩子都存在
            if (node1->left != nullptr || node2->left != nullptr) {
                if (node1->left != nullptr && node2->left != nullptr) {
                    TreeNode* leftMerge = new TreeNode(node1->left->val + node2->left->val);
                    node->left = leftMerge;
                    qMerge.push(leftMerge);
                    q.push(node1->left);
                    q.push(node2->left);
                } 
                else if (node1->left != nullptr) node->left = node1->left;    //若只有一棵树存在左孩子,直接赋值
                else if (node2->left != nullptr) node->left = node2->left;
            }
            //若两棵树的右孩子都在
            if (node1->right != nullptr || node2->right != nullptr) {
                if (node1->right != nullptr && node2->right != nullptr) {
                    TreeNode* rightMerge = new TreeNode(node1->right->val + node2->right->val);
                    node->right = rightMerge;
                    qMerge.push(rightMerge);
                    q.push(node1->right);
                    q.push(node2->right);
                } 
                else if (node1->right != nullptr) node->right = node1->right;    //若只有一棵树存在右孩子,直接赋值
                else if (node2->right != nullptr) node->right = node2->right;
            }
        }
        return root;
    }
};

LeetCode #236:Lowest Common Ancestor of a Binary Tree 二叉树的最近公共祖先

#236
给定一个二叉树,找到该树中两个指定节点的最近公共祖先。

最近公共祖先:对于有根树 T 的两个节点 pq ,最近公共祖先表示为一个节点 x ,满足 xp q 的最先且 x 的深度尽可能地大(一个节点也可以是它自己的祖先)。

所谓祖先,其实就是从根节点到当前节点所经过的所有节点。我们用 Git 来引出「最近公共祖先」(LCA)这一经典问题。

git pull 默认使用 merge 方式将远端修改拉取到本地,如果加上参数而使用 git pull -r 这一命令,就会使用 rebase 方式拉取。这两者最重要的区别是,merge 方式合并的分支会看到很多「分叉」,而 rebase 方式合并的分支就是一条直线。但无论哪种方式,如果存在冲突,Git 都会检测出来并让用户手动解决冲突。

rebase 命令为例,如图,在 dev 分支上执行 git rebase masterdev 就会接到 master 分支之上:

示例

这个过程中,Git 先找到这两条分支的最近公共祖先 LCA ,然后master 节点开始,重演 LCAdevcommit 的修改。如果这些修改和 LCAmastercommit 有冲突,就会提示用户手动解决冲突,最后的结果就是把 dev的分支完全接到 master 上面。因此,关键在于寻找 LCA 算法的实现

我们先实现一个简单的算法:给出一棵没有重复元素的二叉树根节点 root 和一个目标值 val ,写一个函数以寻找树中值为 val 的节点。

TreeNode* find(TreeNode* root, int val) {
    // base case
    if (root == nullptr) return nullptr;
    //检查 root->val 是否为所求节点
    if (root->val == val) return root;
    //若 root 不是目标节点,则递归左子树
    TreeNode* left = find(root->left, val);
    if (left != nullptr) return left;
    //左子树未找到,递归右子树
    TreeNode* right = find(root->right, val);
    if (right != nullptr) return right;
    
    return nullptr;
}

我们对这个函数进行改动。先修改一下 return 的位置

TreeNode* find(TreeNode* root, int val) {
    if (root == nullptr) return null;
    //前序位置
    if (root->val == val) return root;
    // root 不是目标节点,去左右子树寻找
    TreeNode* left = find(root->left, val);
    TreeNode* right = find(root->right, val);
    //确定左右子树对应目标节点的位置
    return left != nullptr ? left : right;
}

可以实现目的,但是这段代码即使能够在左子树找到目标节点,它还是会去右子树找一圈,实际运行的效率会降低。

更进一步地,对 root->val 的判断从前序位置移动到后序位置

TreeNode* find(TreeNode* root, int val) {
    if (root == null) return null;
    //先去左右子树寻找
    TreeNode* left = find(root->left, val);
    TreeNode* right = find(root->right, val);
    //后序位置,检查 root 是否为目标节点
    if (root->val == val) return root;
    // root 非目标节点,再去查看哪边的子树找到了
    return left != nullptr ? left : right;
}

这段代码相当于先去左右子树找,最后才检查 root ,依然可以实现目的,但是这种写法必然会遍历二叉树的每一个节点,效率会进一步降低。

对于之前的算法,在前序位置就检查 root ,如果输入的二叉树根节点的值恰好就是目标值 val ,那么函数直接返回了,其他的节点根本不用搜索;但如果在后序位置判断,那么就算根节点就是目标节点,也要去左右子树遍历完所有节点才能判断出来

此时,如果我们不再寻找值为 val 的单一节点,而是两个值为 val1val2 的节点,仿照这一写法,代码如下:

TreeNode* find(TreeNode* root, int val1, int val2) {
    // base case
    if (root == nullptr) return nullptr;
    //前序位置,检查 root 是否为目标值
    if (root->val == val1 || root->val == val2) return root;
    //寻找左右子树
    TreeNode* left = find(root->left, val1, val2);
    TreeNode* right = find(root->right, val1, val2);
    //后序位置,已经知道左右子树是否存在目标值
    return left != nullptr ? left : right;
}

这一写法有些奇怪,而且也存在其他解法。但是我们可以利用 find() 方法来解决 LCA 问题。

对于本题,如果节点 nodepq 的最近公共祖先,那么会有 3 种情况:

  1. pq 分别在节点 node 的左右子树中。
  2. node 即为节点 pq 在节点 p 的左子树或右子树中。
  3. node 即为节点 qp 在节点 q 的左子树或者右子树中。

两个节点的最近公共祖先其实就是这两个节点向根节点的「延长线」的交汇点,那么对于任意一个节点,如果一个节点能够在它的左右子树中分别找到 pq ,则该节点为 LCA 节点

这就要用到之前实现的 find() 方法了,只需在后序位置添加一个判断逻辑,即可解决本题:

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    return find(root, p->val, q->val);
}
 
//在二叉树中寻找 val1 和 val2 的最近公共祖先节点
TreeNode* find(TreeNode* root, int val1, int val2) {
    if (root == nullptr) return nullptr;
    //前序位置
    if (root->val == val1 || root->val == val2) return root;    //如果遇到目标值,直接返回
        
    TreeNode* left = find(root->left, val1, val2);
    TreeNode* right = find(root->right, val1, val2);
    //后序位置,已经知道左右子树是否存在目标值
    if (left != nullptr && right != nullptr) return root;    //当前节点是 LCA 节点
 
    return left != nullptr ? left : right;
}

find() 方法的后序位置,如果发现 leftright 都非空,就说明当前节点是 LCA 节点,即解决了第一种情况;在 find() 方法的前序位置,如果找到一个值为 val1val2 的节点则直接返回,恰好解决了第二、三种情况。

基于 pq 一定存在于二叉树中这一重要推断,即便遇到 q 就直接返回,根本没遍历到 p ,也依然可以断定 pq 底下,q 就是 LCA 节点。

该算法的时间复杂度为 O ( n ) O(n) O(n),空间复杂度也为 O ( n ) O(n) O(n)

呜啊?


  1. 我们优先进行的是左子树的遍历,所以它肯定是当前层最左边的节点。 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2053080.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单体应用spring Task和分布式调度

Spring Task 1.通过 Spring Task&#xff0c;您可以方便地在 Java 应用程序中实现定时任务&#xff0c;比如每天凌晨进行数据同步、每小时执行一次清理操作等。 2.1 启动类添加EnableScheduling注解(默认情况下&#xff0c;系统会自动启动一个线程) 2.2 在需要定时执行的方…

解决 Swift 6 全局变量不能满足并发安全(concurrency-safe)读写的问题

概述 WWDC 24 终于在 Swift 十岁生日发布了全新的 Swift 6。这不仅意味着 Swift 进入了全新的“大”版本时代&#xff0c;而且 Swift 编译器终于做到了并发代码执行的“绝对安全”。 不过&#xff0c;从 Swift 5 一步迈入“新时代”的小伙伴们可能对新的并发检查有些许“水土不…

连锁美业门店收银系统拓客系统预约系统Java源码-博弈美业APP如何进行课程核销?

* 课程开课后&#xff0c;到课程结束前&#xff0c;这段时间均可以进行课程核销 * 课程核销的权限&#xff0c;仅限内部员工 * 核销课程时&#xff0c;需要切换到总部&#xff0c;才有核销课程的权限 方法一&#xff1a;通过“课程核销”直接核销 点击“课程核销”&#xff…

强大的接口测试可视化工具:Postman Flows

Postman Flows是一种接口测试可视化工具&#xff0c;可以使用流的形式在Postman工作台将请求接口、数据处理和创建实际流程整合到一起。如下图所示 Postman Flows是以API为中心的可视化应用程序开发界面。它提供了一个无限的画布用于编排和串连API&#xff0c;数据可视化来显示…

[C#]winform基于深度学习算法MVANet部署高精度二分类图像分割onnx模型高精度图像二值化

【训练源码】 https://github.com/qianyu-dlut/MVANet 【参考源码】 https://github.com/hpc203/MVANet-onnxrun 【算法介绍】 二分图像分割&#xff08;DIS&#xff09;最近出现在从高分辨率自然图像中进行高精度对象分割方面。在设计有效的DIS模型时&#xff0c;主要的挑战是…

XMGoat:一款针对Azure的环境安全检测工具

关于XMGoat XMGoat是一款针对Azure的环境安全检测工具&#xff0c;XM Goat 由 XM Cyber Terraform 模板组成&#xff0c;可帮助您了解常见的 Azure 安全问题。每个模板都是一个用于安全技术学习的靶机环境&#xff0c;包含了一些严重的配置错误。 在该工具的帮助下&#xff0c…

景区门票预订系统开发方案概述

随着旅游业的蓬勃发展&#xff0c;提升游客体验、优化景区管理成为了各大景区亟待解决的问题。景区门票预订系统的开发&#xff0c;正是顺应这一趋势的重要举措。以下是一个简要的景区门票预订系统开发方案概述&#xff0c;旨在通过科技手段实现票务管理的智能化、便捷化。 一、…

faiss向量数据库测试《三体》全集,这家国产AI加速卡,把性能提了7倍!

在人工智能和机器学习技术的飞速发展中&#xff0c;向量数据库在处理高维数据方面扮演着日益重要的角色。近年来&#xff0c;随着大型模型的流行&#xff0c;向量数据库技术也得到了进一步的发展和完善。 向量数据库为大型模型提供了一个高效的数据管理和检索平台&#xff0c;…

如何将音乐保存为文件格式为铃声,怎么把音乐保存为MP3格式

许多小伙伴在听到好听的音乐时&#xff0c;会将其下载保存。如果需要将音乐文件格式转换成来电铃声时&#xff0c;就需要借助专业的音频处理软件了&#xff0c;音频处理软件可以帮助我们转化音乐文件格式&#xff0c;那么下面就来给大家介绍如何将音乐保存为文件格式为铃声&…

Kali Linux-设置系统24小时时间制

文章目录 设置系统24小时时间制 设置系统24小时时间制 在Kali Linux中&#xff0c;如果系统时间不是以24小时制显示&#xff0c;你可以通过修改系统时间格式配置文件来调整为24小时制。以下是具体的操作步骤&#xff1a; 1.编辑/etc/locale.conf配置文件。 vim /etc/locale.c…

【gitlab】gitlab-ce:17.3.0-ce.0 之2:配置

参考阿里云的教程docker的重启 sudo systemctl daemon-reload sudo systemctl restart docker配置 –publish 8443:443 --publish 8084:80 --publish 22:22 sudo docker ps -a 當容器狀態為healthy時,說明GitLab容器已經正常啟動。 root@k8s-master-pfsrv:~

Python WebSocket自动化测试:构建高效接口测试框架

为了更高效地进行WebSocket接口的自动化测试&#xff0c;我们可以搭建一个专门的测试框架。本文将介绍如何使用Python构建一个高效的WebSocket接口测试框架&#xff0c;并重点关注以下四个方面的内容&#xff1a;运行测试文件封装、报告和日志的封装、数据驱动测试以及测试用例…

调查显示:超过30 %企业遭受过网络攻击,如何防范?

对于企业来讲&#xff0c;屡禁不止的网络安全攻击始终是阻碍业务发展的重大隐患。调查结果显示&#xff0c;有近8成的企业将网络安全视为企业很重要的工作之一&#xff0c;另外&#xff0c;有超过三成的企业遭受过网络安全攻击。 企业作为网络安全事故的责任承担方&#xff0c;…

synchronized和Lock(ReentrantLock)及二者区别

synchronized 是用于实现线程同步的关键字。它提供了两种主要的方式来保证多个线程访问共享资源时的互斥性和可见性&#xff1a;同步块和同步方法。 同步块 同步块允许你指定一个对象作为锁&#xff0c;并保护一段代码区域。这样&#xff0c;同一时刻只有一个线程可以执行这…

ChatGPT 为什么不建议关闭 MySQL 严格模式?

社区王牌专栏《一问一实验&#xff1a;AI 版》全新改版归来&#xff0c;得到了新老读者们的关注。其中不乏对 ChatDBA 感兴趣的读者前来咨询&#xff0c;表达了想试用体验 ChatDBA 的意愿&#xff0c;对此我们表示感谢 &#x1f91f;。 目前&#xff0c;ChatDBA 还在最后的准备…

记录一次 npm ERR! cb() never called! 解决过程

gitlab cicd过程&#xff0c;使用docker部署Vue3前端项目&#xff0c;报错如下&#xff1a; 针对 npm ERR! cb() never called! 这个报错&#xff0c;网上有很多解决方案&#xff0c;大都是清空缓存&#xff0c;重新运行npm 之类的。笔者全都试过&#xff0c;无法解决问题。笔者…

linux,docker查看资源消耗总结

在linux和docker中我们将一个程序运行到后台&#xff0c;之后我们想查看它的运行状态&#xff0c;对于服务器的资源消耗等等 1.linux查看进程 ps aux | grep python ps aux&#xff1a;列出所有正在运行的进程。grep python&#xff1a;过滤出包含 python 的进程 2.linux查…

springCloud集成activiti5.22.0流程引擎(分支)

springCloud集成activiti5.22.0流程引擎 点关注不迷路&#xff0c;欢迎再访&#xff01; 精简博客内容&#xff0c;尽量已行业术语来分享。 努力做到对每一位认可自己的读者负责。 帮助别人的同时更是丰富自己的良机。 文章目录 springCloud集成activiti5.22.0流程引擎一.Sprin…

你知道家电的保质期吗?

家人们&#xff0c;你们有关注过家里的电器用了多少年了吗&#xff1f; “家电不坏&#xff0c;就能一直用。” “坏了修一修&#xff0c;一样能用。” 很多家长都有这样的想法&#xff0c;家里的电器即使出了故障&#xff0c;修一修也就继续用了。 其实&#xff0c;家电也…

打造智能化直播商城平台:AI与大数据在平台开发中的应用

在当今竞争激烈的电商市场中&#xff0c;直播商城平台已经成为品牌和商家实现差异化竞争的重要工具。随着人工智能&#xff08;AI&#xff09;和大数据技术的不断进步&#xff0c;智能化直播商城平台的开发成为了行业的新趋势。这些技术不仅可以优化用户体验&#xff0c;还能提…