Bert Waver
type: channel
link: https://ixfish.cn/
description: 给时光以生命，给岁月以文明
items: Leetcode.3102 最小化曼哈顿距离
items: SignUp —— 适用于大学/中小学/职场的打卡签到系统
items: 打家劫舍I/II/III
items: 欧几里得算法 —— 计算机中的最大公约数与最小公倍数
items: 二叉搜索树
items: Leetcode.654 最大二叉树
items: 三门问题
items: 排序链表
items: blinker_win - blinker点灯物联网设备pc客户端
items: 循环队列

Leetcode.3102 最小化曼哈顿距离
type: item
date: 2024-07-10T05:23:00+08:00
description: 题目简介曼哈顿距离的定义：两个单元格 (xi, yi) 和 (xj, yj) 之间的曼哈顿距离为 |xi - xj| + |yi - yj|。题目分析由曼哈顿距离的定义可知：若要使用其定义求两个点之间的曼哈顿距离，则要求的两个点的坐标必须是已知的。因此，根据题目要求，我们需要依次枚举（去除）每个点，然后再将剩下的点两两一组，使用定义式来求出它们的曼哈顿距离，然后在这些曼哈顿距离中取一个最小值。枚举完毕后，再在这些"对于每次枚举，求出的曼哈顿距离中的最小值"中再取一次它们的最小值。即可解决本问题。但细心的你可能会发现，这么做会使得算法的时间复杂度过高，因此我们需要对算法进行优化。我们可以先对曼哈顿距离公式进行化简，化简过程如图所示：由化简后的式子可知，对于任意两个点的曼哈顿距离，我们可以这样求解：1.先分别求出每个点的横纵坐标之和 以及 横纵坐标之差2.将这两个点的横纵坐标之和相减并求绝对值，记作a。将这两个点的横纵坐标之差相减并求绝对值，记作b。3.求a和b中的最大值，记作c。c即为两点之间的曼哈顿距离。根据这个原理，我们继续对其化简：因此，我们可以维护两个数组Sxy1和Sxy2。Sxy1数组存储了所有点的横纵坐标之和，Sxy2数组存储了所有点的横纵坐标之差。如果我们要求曼哈顿距离的最大值，那么只需要 用每个数组的最大值减去最小值，求绝对值，并将求出的两个值取最大值 即可。但根据题目的要求，我们需要求的是移除某个点后的曼哈顿距离的最大值所形成的数组中的最小值。因此，我们需要按照以下步骤来解决问题：1.枚举每个点，求出其 x+y ，并在Sxy1数组中删除。求出其 x-y，并在Sxy2数组中删除。2.使用曼哈顿距离公式的推论，求出曼哈顿距离的最大值Q。3.将Q与之前推出的数（即之前推出的Q）做比较，假设Q小于之前推出的数，则更新结果sum为Q，记作min(Q)。4.将之前删除的 x+y 和 x-y 重新加入数组中，开始下一个数的枚举。5.完成所有数的枚举遍历后，返回min(Q)。值得一提的是，对于Sxy1和Sxy2，之前为了便于讲解，一直强调其为两个数组。实际上，该算法涉及到频繁地对Sxy1和Sxy2进行增删改查，这会降低算法的执行效率。对于C++实现，推荐使用multiset 来进行操作。这是一种基于红黑树的数据结构，在本问题中可以大幅降低算法的时间复杂度。完整代码实现#include <vector> #include <set> #include <math.h> using namespace std; class Solution { public: int minimumDistance(vector<vector<int>>& points) { multiset<int> Sxy1,Sxy2; for(int cptime=1;cptime<=points.size();cptime++) { Sxy1.emplace(points[cptime-1][0]+points[cptime-1][1]); Sxy2.emplace(points[cptime-1][0]-points[cptime-1][1]); } int sum=INT_MAX; for(int cptime=1;cptime<=points.size();cptime++) { int j=points[cptime-1][0]+points[cptime-1][1]; int k=points[cptime-1][0]-points[cptime-1][1]; Sxy1.erase(Sxy1.find(j)); Sxy2.erase(Sxy2.find(k)); int temp_max=max(abs(*(Sxy1.rbegin())-*(Sxy1.begin())),abs(*(Sxy2.rbegin())-*(Sxy2.begin()))); if(temp_max<=sum) { sum=temp_max; } Sxy1.emplace(j); Sxy2.emplace(k); } return sum; } };

SignUp —— 适用于大学/中小学/职场的打卡签到系统
type: item
date: 2024-07-09T11:44:00+08:00
description: SignUp 适用于大学/中小学/职场的打卡签到系统👋 特性支持显示签到日期，签到时间，签到排名。支持同日内签到后不能再次签到，防止重复签到。支持历史记录功能，可以查看签到人员的历史签到记录。支持设置开机自动启动，设备开机后自动启动签到系统，无需手动启动。具有可视化编辑器功能，无需修改源码就可以修改签到人员名单以及在签到界面中的位置。支持通过修改源码限制签到时间，防止签到人员提前签到，可以设置迟到后不可签到。源码采用前后端分离设计，团队协作开发方便快捷。自制仿VS界面皮肤，系统具有实用性的同时兼顾美观。🧩 应用指南中小学/大学将本程序安装在班班通上，每日早晨学生到校后直接点击自己的名字完成签到。公司将本系统整合到门禁系统中，同时可加入人脸识别系统，员工每日可通过该系统打卡上班。若某陌生人员不在系统名单中，则拒绝该人员进入。✔️ 使用方式[quote color="info"]项目Github仓库地址：https://github.com/bertwaver/signup[/quote]1.直接下载Releases页面中的发布。该页面提供了可供32位操作系统以及64位操作系统运行的版本。本程序仅支持Windows操作系统。经测试，该程序可以在Windows 10及更新的操作系统上正常运行，更早的版本暂时没有测试。如果程序无法启动，请确保你已经安装了 .NET Framework 4.7.2 框架。2.克隆仓库的源代码并进行编译。如果你在 Releases 下载的程序始终无法在你的操作系统中运行，那么你可以将本仓库的源代码克隆到要运行本程序的目标机器，并在目标机器上编译源代码来解决该问题。 对于本软件的具体使用问题，请参阅本项目的wiki页面。🌄 软件截图(加载较慢)💻 开发教程请参阅本项目的wiki页面。https://github.com/bertwaver/signup/wiki📓 关于开源本项目基于协议 Apache License 2.0 开源。使用本项目源代码时请遵守协议的相关规定。📧 软件著作权

打家劫舍I/II/III
type: item
date: 2024-06-21T15:06:00+08:00
description: 打家劫舍 I题目简介你是一个专业的小偷，计划偷窃沿街的房屋。每间房内都藏有一定的现金，影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统，如果两间相邻的房屋在同一晚上被小偷闯入，系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组，计算你 不触动警报装置的情况下 ，一夜之内能够偷窃到的最高金额。[tabs][item title="示例一"]输入：[1,2,3,1]输出：4解释：偷窃 1 号房屋 (金额 = 1) ，然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。[/item][item title="示例二"]输入：[2,7,9,3,1]输出：12解释：偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9)，接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。[/item][/tabs]1 <= nums.length <= 1000 <= nums[i] <= 400代码模板class Solution { public: int rob(vector<int>& nums) { } };问题分析递归我们以示例一为例，这条街区一共有四个房屋，我们可以从最后一个房屋（即第四个房屋）开始看。如果我们偷第四个房屋，那么第三个房屋我们就不能偷。如果我们不偷第四个房屋，那么第三个房屋就可以偷或者不偷。依次类推，直到我们推到第一个房屋。示例图如图所示：通过观察我们可以知道，我们从第四个房屋一直推导到第一个房屋的过程中形成了一种二叉树的结构。我们想要求出我们能偷到的最大的钱数，只需要通过深度优先搜索(dfs)的方法遍历整棵树即可。dfs遍历的过程是通过递归实现的。我们通过“递”的过程从根节点（第四个房屋）遍历到了叶子节点（第0个房屋）。然后通过“归”的过程进行回溯，逐渐遍历整棵树，然后对能偷到的钱就行统计，就可以得到我们想要的结果。这种方法虽然可以得到正确的结果，但是它的时间复杂度却很高。原因在于随着数据量的提升，这棵树的规模将会逐渐增大，程序的执行效率也会逐渐下降。另外，当我们对树进行遍历的过程中，产生了一些重复计算，这些重复计算也会随着数据量的提升而越来越多，这也是导致程序的时间复杂度上升的一个重要原因。以下是递归实现的一个示例代码，该算法无法通过力扣的全部检查点。class Solution { public: int rob(vector<int> &nums) { return _rob(nums.size(),nums); } private: int _rob(int k, vector<int> &_nums) { if(k<=0) { return 0; } int selected=_nums[k-1]+_rob(k-2,_nums); //不要忘记加上当前元素的值 int unselected=_rob(k-1,_nums); return max(selected,unselected); } };记忆化搜索正如上方所指出的那样，这个算法的时间复杂度较高的原因之一在于大量的重复计算。那么我们就需要对该算法进行优化，去除这些无效的运算。一个比较好的方法是我们可以引入一个数组"memo"，在每次进行运算时记录当前节点的结果。当我们之后遇到重复的计算时，只需要从"memo"数组中找出相应的结果就行了。从算法的时间复杂度和空间复杂度的角度来看，这是一种用“空间”换“时间”的方法，我们之前通过使用一部分内存来对结果进行存储，这样CPU需要获得相应的结果的时候只需要从内存中取出之前计算的结果就行了，就不需要再浪费一部分时间来进行相应的计算了。从上方构建本问题的二叉树的角度来看，这种方法实现了对二叉树的“剪枝”，从而减少了遍历所需要的时间，大大降低了算法的时间复杂度。这种方法叫做记忆化搜索，掌握记忆化搜索的基本思路有利于我们以后使用动态规划来解决问题，因为动态规划也是通过存储重叠子问题的解的方式来提升时间效率的。本问题的记忆化搜索实现代码如下，该算法能通过力扣的所有检查点：class Solution { public: int rob(vector<int> &nums) { vector<int> memo(nums.size(),-1); return _rob(nums.size(),nums,memo); } private: int _rob(int k, vector<int> &_nums, vector<int> &_memo) { if(k<=0) { return 0; } if(_memo[k-1]!=-1) { return _memo[k-1]; } int selected=_nums[…

欧几里得算法 —— 计算机中的最大公约数与最小公倍数
type: item
date: 2024-04-16T22:33:00+08:00
description: 算数基本定理算数基本定理又叫唯一分解定理，它说明任意大于1的自然数都可以唯一地分解成质数的幂的乘积。对于任意两个满足条件的数a,b，我们可以通过以下公式进行分解：其中p1,p2,....，pk表示质数，而a1,a2,....ak以及b1,b2,...bk表示非负整数。如果一个数拆分成的质数中没有这个质数，可以将幂视作0，比如12=(2^2) (3^1) (5^0) (7^0) .....（后面的7,9,11的幂皆为0）。至于为什么不存在非质数，因为假如将其中的一个非质数视作a的一个因子，那它仍然可以继续拆分成几个质数的乘积（即表示a的式子可以继续化简），经过不断化简，最终得到的式子仍然是所有质数的幂相乘所形成的式子。最大公约数的数学表示我们以12和6为例，将这两个数用算数基本定理表示，可以得到以下公式(忽略幂为0的质数因子):要找到最大公约数，我们就要找到一个共同的数，这个共同的数必须既能将6整除，又能将12整除，而且还必须是最大的。而6和12都可以表示为质数因子2和3的幂的乘积的形式，要找到这个最大的数，我们只需要找到其中 每个因子的最小幂的乘积 即可。寻找最小值是为了保证找到的公约数都能将6和12整除。而寻找每个因子是为了保证这个公约数是最大的（即最大公约数），因为一旦在寻找的过程中遗漏了某个因子，那么就相当于找到的是 用最大公约数除以了这个因子 所得到的数，所以导致找到的这个公约数不是最大的。根据我们的方法，对于因子2，我们应该寻找的是2的1次方，而对于因子3，我们应该寻找的是3的一次方。将它们相乘，我们得到6和12的最大公约数为2x3=6。 综上所述，对于a,b两个数的最大公约数我们可以这样获得： 将a,b两个数用算数基本定理拆解后，对于相同底数的质数因子，取幂相对较小的那个数，然后将它们相乘，得到的即为a,b的最大公约数最小公倍数的数学表示我们仍然以6和12为例，要找到其最小公倍数，即要找到一个既可以被6整除，又可以被12整除的数，并且这个数还要是最小的。根据我们寻找最大公约数的方法，这次我们就应该寻找对于6和12的相同底数的两个因子中较大的那个。这样我们就可以保证了这个数都可以被两个数整除（因为这个数是由两个数的最大因子相乘而得来的，无论用哪个数整除，最终都会将共有的因子除掉，最终得到一个整数）。而且它一定是两个数的最小的公倍数，因为这个数不可以再拆分，如果继续拆分的话，相当于除掉了一个或多个质数，这会导致其无法成为6或者12的一个倍数。（想象一下，我们如果将12和6的最小公倍数12再除以一个2，得到了6，它并不是12的倍数。因为将12拆解后为2的二次方乘以3的一次方，而6拆解后为2的一次方乘以3的一次方，6比12少了一个2的一次方，因此无法成为12的倍数。）综上所述，对于a,b两个数的最小公倍数我们可以这样获得：综上所述，对于a,b两个数的最大公约数我们可以这样获得： 将a,b两个数用算数基本定理拆解后，对于相同底数的质数因子，取幂相对较大的那个数，然后将它们相乘，得到的即为a,b的最小公倍数最大公约数和最小公倍数的乘积我们将a,b的最大公约数视作GCD(a,b)，将a,b的最小公倍数视作LCM（a,b）。则可以得到GCD(a,b) x LCM(a,b) = a x b。推论如下：假设存在 6 和 12，我们将其用算数基本定理进行拆解6= 2^1(q) x 3^1(t)12= 2^2(s) x 3^1(d)6 x 12= 72 = (q) x (t) x (s) x (d)则GCD(6,12)=2^1(q) x 3^1(d)LCM(6,12)=2^2(s) x 3^1(t)则GCD(6,12) x LCM(6,12)= (q) x (t) x (s) x (d) =72=6 x 12因此，对于a,b而言，GCD(a,b) x LCM(a,b) = a x b。欧几里得算法该算法基于以下事实：两个正整数的最大公约数（GCD）不变，即使其中一个数被它们的差替换。假设存在两个非负正整数a,b且存在a>b而言。GCD(a,b)=GCD(b,a-b)。证明过程如下：如果d是a和b的最大公约数，则a=m x d，而b=n x d。（如果你不理解为什么可以这样表示，你需要重新理解前方所提到的知识）a-b=（m-n）x d。因此我们可以通过设计算法不断使其相减，最终使得a,b变量的其中一个值变为0，而另外一个值即为我们们要找到的最大公约数。根据这个原理，我们可以设计两个或者两个以上的数的求最大公约数的算法：// 最大公约数算法 int gcd(int a, int b) { int tmin = min(a, b); int tmax = max(a, b); if (tmin != 0) { tmax = tmax - tmi…

二叉搜索树
type: item
date: 2024-03-16T02:28:00+08:00
description: 什么是二叉搜索树二叉搜索树(binary search tree)是一种具有以下性质的二叉树(假设树不为空)：1.若其左子树不为空，则其左子树的所有节点的值都小于其根节点的值2.若其右子树不为空，则其右子树的所有节点的值都大于其根节点的值3.若将其中的任意一个节点视为根节点的左右子树也都为二叉搜索树因为二叉搜索树具有上述的特点，因此如果我们需要查找其中的一个值时，若该值小于当前节点的值则向左遍历，若该值大于当前节点的值则向右遍历，从而可以快速地查找到所需要的值。这种分治的方法类似于数组中的二分查找，而不是从头开始依次遍历，暴力查找所需的值，因此查找效率较高。另外，由于二叉搜索树是一种非线性的链式结构，因此也像链表一样，增加和删除效率也很高。[card title="二叉搜索树的时间复杂度" color="success"]对于普通的二叉搜索树而言，无论是进行增删改查中的哪一种方式，时间复杂度最低都是 O(log n) ，最高都是 O(n)。理由如下：假设该二叉树是一种平衡二叉树（即长度最长的叶子节点与长度最短的叶子节点的高度差小于或等于1），比如下方这种情况，那么增删改查的效率最高，为O(log n)。(见下方图1)但假设该二叉树极度不平衡，那么左子树和右子树的高度相差就会很大，严重情况下甚至能退化成链表。这样的话增删改查的时间复杂度最高会降低至O(n)(类似于数组的暴力搜索)。(见下方图2) [/card]二叉搜索树的建树二叉搜索树是二叉树的一种，因此我们首先先对树的节点进行定义。每个节点应该具有左节点的指针，右节点的指针，当前节点的值以及当前节点的构造函数。struct TreeNode { TreeNode* left; TreeNode* right; int val; TreeNode(int x):left(nullptr),right(nullptr),val(x){} };我们定义一个bst类并且新增一个init函数用于树的初始构建。这个函数接受一个vector数组，然后依次遍历完成树的构建。如果数组为空，那么返回空指针，否则依次调用add函数将数组中的每个元素添加到树中(add函数的实现会在下方写出)。构建完成后，返回指向根节点的指针。 TreeNode* init(vector<int> &nums) { if(nums.empty()==true) { return nullptr; } //创建根节点 TreeNode*root=new TreeNode(nums[0]); //循环遍历将数组中的数加入树中 for(int cptime=2;cptime<=nums.size();cptime++) { add(root,nums[cptime-1]); } //存储根节点 bst::root=root; //返回根节点 return root; }假设我们使用用例 63 39 97 29 86 99 5 61 建树，形成的结构如下图所示：[acc status="close" title="可视化执行(加载较慢需等待片刻)"] [/acc][card title="关于nullptr" color="danger"]nullptr是c++11中引入的新关键字，用于表示空指针。是一种相较于直接宏定义NULL更安全的一种表示空指针的方法。如果你的编译器报错，请检查你的编译器是否支持c++11标准。相关资料：https://zh.cppreference.com/w/cpp/language/nullptr[/card]根节点的封装我们在建树的过程中返回并存储了根节点的指针。并且我们将根节点的指针存储到了类中的root变量中，而root是一个私有成员变量，我们并不能在类外手动地去修改它，这样可以防止手动修改导致的一些问题。private: //存储根节点 TreeNode* root;如果我们在后方进行增删改查时需要获取根节点，我们可以设计一个公共函数来获取它。public: //获取根节点 TreeNode* Getroot() { return bst::root; }二叉搜索树的新增我们声明一个add函数用于执行二叉树的新增操作，这个函数需要接受二叉树的根节点和需要插入二叉树的值。 bool add(TreeNode* node,int value) {}由于二叉搜索树中的各个节点的值各不相同，因此在设计函数时，如果插入的值在二叉树中已经存在，则直接返回，不进行新数据的插入。（返回值可以设计成一个bool函数，当进行这种操作时，返回false，使函数的调用者知晓新的值已存在于树中，因此不进行插入操作。其它正常插入的情况则返回true）。我们在执行插入操作时，要保证插入完成后继续满足二叉搜索树的三种性质…

Leetcode.654 最大二叉树
type: item
date: 2024-03-10T23:35:00+08:00
description: 问题简介给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:1.创建一个根节点，其值为 nums 中的最大值。2.递归地在最大值 左边 的 子数组前缀上 构建左子树。3.递归地在最大值 右边 的 子数组后缀上 构建右子树。返回 nums 构建的 最大二叉树 。[tabs][item title="示例一"]输入：nums = [3,2,1,6,0,5]输出：[6,3,5,null,2,0,null,null,1]解释：递归调用如下所示：[3,2,1,6,0,5] 中的最大值是 6 ，左边部分是 [3,2,1] ，右边部分是 [0,5] 。[3,2,1] 中的最大值是 3 ，左边部分是 [] ，右边部分是 [2,1] 。空数组，无子节点。[2,1] 中的最大值是 2 ，左边部分是 [] ，右边部分是 [1] 。空数组，无子节点。只有一个元素，所以子节点是一个值为 1 的节点。[0,5] 中的最大值是 5 ，左边部分是 [0] ，右边部分是 [] 。只有一个元素，所以子节点是一个值为 0 的节点。空数组，无子节点。[/item][item title="示例二"]输入：nums = [3,2,1]输出：[3,null,2,null,1][/item][/tabs]提示：1 <= nums.length <= 10000 <= nums[i] <= 1000nums 中的所有整数 互不相同代码模版 struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; class Solution { public: TreeNode* constructMaximumBinaryTree(vector<int>& nums) { } };[card title="tips" color="info"]后文建树过程中提到的遍历数组指的是遍历此处传入的nums数组。[/card]初步分析——建树最大二叉树中的子节点的数值总是小于父节点的数值，并且要构建最大二叉树，我们需要找出其中的最大的那一个数作为根节点。这个根节点的左边的最大的数作为根的左子节点，这个根节点右边的最大的数作为根的右子节点作为根的右子节点，以此类推。因此，我们可以维护一个栈。从数组的第一个数开始遍历。如果栈为空，则直接将当前遍历的数直接入栈。如果栈不为空，则考虑下面的情况：1.如果当前的数小于栈顶的数，则意味着这个数应该作为栈顶的数的右子节点（这个数作为栈顶的右子节点而不作为左子节点的原因在于这个数在数组中的位置一定是在栈顶的数的后方的，因此如果这个数小于栈顶的数，则只能作为栈顶的数的右子节点而不是左子节点），则我们将栈顶的数的右指针指向这个数并且将这个节点压栈。2.如果当前的数大于栈顶的数，则意味着栈顶的数应该作为这个数的左子节点(也是从这两个数在数组中的位置进行考虑，这个数大于栈顶的数且在栈顶的数的右方，则栈顶的数肯定是这个数的左子节点或者这个数的左子节点的子节点)。因此我们需要将栈顶的数进行出栈，并且让这个数的左子节点指向它。通过不断的出栈并且设置这个数的左子节点，这个数将会指向原来栈中小于这个数但是相对于整个栈而言最大的那个数，也就是栈底的那个节点。完成操作后，原来栈中的元素则会作为这个数左节点，而这个数则作为整个树的根节点，我们将这个树压入栈中。不断重复这个操作，如果再往后的数满足情况1，则后面的数会作为这棵具有左子节点但不具有右子节点的树的右子节点。如果满足情况2，则这颗树会作为新的树的左子节点。随着遍历的不断进行，最大二叉树就会被不断构建出来。下面是示例一的构建详情：为什么栈底的元素总是栈中最大的元素在上方建树过程中，一旦我们遍历到的数组中的元素（相对于栈中的元素是较新的数）小于栈顶的元素，则这个数将作为原来的栈顶的数的右节点并且被压入栈中。相反，如果新的数大于栈顶的元素，则这个栈顶的元素会被弹出栈并且作为这个新数的左子节点，然后这个带有原来栈顶元素的新数会被压入栈中。这样使得原来栈顶中小于这个数的元素被弹出了栈，取而代之的是一颗新数作为根节点的树，原来的栈顶元素虽然没有消失，变成了这个新树中的一个左子节点，但当我们后面进行比较时，比较的是在这个栈中的树的根节点的那个数的大小。而作为最大二叉树而言，根节点无疑是最大的那个数，这使得如果在后面继续遍历和比较操作时，比这个数小的数会不断被压入栈中并作为原来比较大的数的右节点，而如果后面一旦有比这个数大的数，原来栈底相对较大的那个数会被弹出栈并作为新的大的树的左子节点，新的大的树会再次入栈，成为栈底元…

三门问题
type: item
date: 2023-11-04T14:44:00+08:00
description: 三门问题简介奖品随机分布在3扇门后，客户随机选择其中一扇，主持人打开另外两扇中任意没有奖品的一扇，问客户选择以下哪种策略赢面更大：1.坚持原来的选择2.改选剩下的那扇未打开的门测试代码及结果[tabs][item title="测试代码"]#include <iostream> #include <random> #include <vector> using namespace std; int get_random_number(int a, int b) { random_device rd; mt19937 gen(rd()); uniform_int_distribution<> distrib(a, b); int random_number = distrib(gen); return random_number; } int main() { int test_time = 0, right_door = 0, right_time = 0, choose_door = 0, open_door = 0; double sum = 0; vector<int> wait_open_door; cout << "请输入测试次数" << endl; cin >> test_time; cout << "第一次测试：不更改门"; for (int cptime = 1; cptime <= test_time; cptime++) { right_door = get_random_number(1, 3); choose_door = get_random_number(1, 3); if (right_door == choose_door) { wait_open_door.clear(); for (int ncptime = 1; ncptime <= 3; ncptime++) { if (ncptime != choose_door) { wait_open_door.push_back(ncptime); } } open_door = wait_open_door[get_random_number(0, wait_open_door.size() - 1)]; right_time++; } else if (right_door != choose_door) { wait_open_door.clear(); for (int ncptime = 1; ncptime <= 3; ncptime++) { if (ncptime != choose_door && ncptime != right_door) { open_door = ncptime; } } } } sum = static_cast<double>(right_time) / static_cast<double>(test_time); cout << "测试完毕。" << endl << "测试次数：" << test_time << endl << "概率为：" << sum << endl; cout << "第二次测试：更改门"; sum = 0, right_door = 0, right_time = 0, choose_door = 0, open_door = 0; wait_open_door.clear(); for (int cptime = 1; cptime <= test_time; cptime++) { right_door = get_random_number(1, 3); choose_door = get_random_number(1, 3); if (right_door == choose_door) { wait_open_door.clear(); for (int ncptime = 1; ncptime <= 3; ncptime++) { if (ncptime != choose_door) { wait_open_door.push_back(ncptime); } } open_door = wait_open_door[get_random_number(0, wait_open_door.size() - 1)]; int temp = 0; for (int ncptime = 1; ncptime <= 3; ncptime++) { if (ncptime != choose_door && ncptime != open_door) { temp = ncptime; } } choose_door = temp; } else if (righ…

排序链表
type: item
date: 2023-09-30T12:54:00+08:00
description: 题目简介给你链表的头结点 head ，请将其按 升序 排列并返回 排序后的链表 。示例 1：输入：head = [4,2,1,3]输出：[1,2,3,4]示例 2：输入：head = [-1,5,3,4,0]输出：[-1,0,3,4,5]示例 3：输入：head = []输出：[]提示：链表中节点的数目在范围 [0, 5 * 104] 内-105 <= Node.val <= 105进阶：你可以在 O(n log n) 时间复杂度和常数级空间复杂度下，对链表进行排序吗？模板/** * 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* sortList(ListNode* head) { } };思路转换为vector动态数组，使用排序算法进行排序，然后用排序后的结果替换掉原来的链表。（无需考虑链表空间的拓展，因为排序后的元素数量必和原链表中的元素数量相同）[quote color="danger"]在转换的过程中要注意验证空指针。[/quote]代码class Solution { public: ListNode* sortList(ListNode* head) { vector<int> a; ListNode* b; b = head; while (b!=nullptr && b->next != nullptr) { a.push_back(b->val); b = b->next; } if (b != nullptr) { a.push_back(b->val); } sort(a.begin(), a.end()); b = head; for (int cptime = 0; b!=nullptr && b->next != nullptr;cptime++ ) { b->val = a[cptime]; b = b->next; } if (b != nullptr) { b->val = a[a.size() - 1]; } return head; } };

blinker_win - blinker点灯物联网设备pc客户端
type: item
date: 2023-08-12T16:36:00+08:00
description: blinker介绍blinker 是一个物联网接入方案，旨在让大家可以畅快 DIY 自己物联网设备。ios、android 都支持，本地、远程都支持，蓝牙、WiFi 都支持，可自己拖拽布局设备控制界面，轻松打造你的物联网设备。但是目前blinker并没有windows版的客户端，在外使用计算机控制blinker设备不太方便，于是使用python做了一个简易版的pc客户端。 [quote color="info"]不过blinker也就是玩玩而已。专业单片机开发还是用keil吧......[/quote]特性支持自动填充登录。也就是说你不需要每次都登录你的blinker账号，在编辑实现自动登录相关js代码后就可以在不同的设备上无需登录随开随用了。具体实现如下（更改前四行的username和password字段）：var usernameInput = document.querySelector("input[name=\'username\']"); var passwordInput = document.querySelector("input[name=\'password\']"); var usernameValue = "usename"; var passwordValue = "password"; var usernameEvent = new Event("input", { bubbles: true }); var passwordEvent = new Event("input", { bubbles: true }); usernameInput.value = usernameValue; usernameInput.dispatchEvent(usernameEvent); passwordInput.value = passwordValue; passwordInput.dispatchEvent(passwordEvent);使用方法1.将代码源文件保存到一个文件夹中 2.安装python3 3.安装pyside6pip install pyside6 4.安装pyinstallerpip install pyinstaller 5.编译并生成程序pyinstaller main.py --noconsole --hidden-import pyside6.qtxml代码开源链接https://github.com/bertwaver/blinker_Win

循环队列
type: item
date: 2023-08-09T07:05:00+08:00
description: Leetcode.622 设计循环队列设计你的循环队列实现。 循环队列是一种线性数据结构，其操作表现基于 FIFO（先进先出）原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里，一旦一个队列满了，我们就不能插入下一个元素，即使在队列前面仍有空间。但是使用循环队列，我们能使用这些空间去存储新的值。你的实现应该支持如下操作：MyCircularQueue(k): 构造器，设置队列长度为 k 。Front: 从队首获取元素。如果队列为空，返回 -1 。Rear: 获取队尾元素。如果队列为空，返回 -1 。enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。isEmpty(): 检查循环队列是否为空。isFull(): 检查循环队列是否已满。[acc status="close" title="示例一"]MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3circularQueue.enQueue(1); // 返回 truecircularQueue.enQueue(2); // 返回 truecircularQueue.enQueue(3); // 返回 truecircularQueue.enQueue(4); // 返回 false，队列已满circularQueue.Rear(); // 返回 3circularQueue.isFull(); // 返回 truecircularQueue.deQueue(); // 返回 truecircularQueue.enQueue(4); // 返回 truecircularQueue.Rear(); // 返回 4[/acc]提示：所有的值都在 0 至 1000 的范围内；操作数将在 1 至 1000 的范围内；请不要使用内置的队列库。动图演示 (动图来自 https://blog.csdn.net/qq_42996461/article/details/125674149)实现容量为k的循环队列，可以定义一个大小为k的动态数组。同时，也可以通过链表实现。本文通过动态数组vector实现，并且在此过程中不使用指针。变量的定义与解释如下：private: vector<int> queue; //循环队列 int front; //队首 int rear; //队尾 int capacity; //队列的容量[quote color="info"]对于使用指针实现容量为k的循环队列，需要定义(k+1)的空间，用于判空判满。但如果不使用指针实现，只需定义k的空间即可。[/quote]验空与验满由于存入队列的值的大小在0-1000之间，因此我们可以在队列初始化以及删除元素时用-1来代替数组中元素为空的情况。所以在验空和验满时，只需要判断数组中是否 全部元素为-1 或者 全部元素不为-1 来实现。 bool isEmpty() { for (int cptime = 0; cptime < capacity; cptime++) { if (queue[cptime] != -1) { return false; } } return true; } bool isFull() { for (int cptime = 0; cptime < capacity; cptime++) { if (queue[cptime] == -1) { return false; } } return true; }入队在进行入队操作时，首先应该考虑队列是否已满，如果队列已满，那就无法进行入队操作，直接返回false即可。再考虑队尾是否处在数组的末尾，如果是这样的话，那就应该在数组的头部插入数据，并且将队尾指向数组的头部。如果不在数组的末尾，那么按顺序插入即可。 bool enQueue(int value) { if (isFull() == true) { return false; } if (rear == capacity - 1) { rear = 0; queue[rear] = value; return true; } queue[rear+1] = value; rear++; return true; }[quote color="info"]由于队列遵循先入先出原则。因此在“再考虑队尾是否处在数组的末尾，如果是这样的话，那就应该在数组的头部插入数据，并且将队尾指向数组的头部。”的时候时，不需要考虑数组的头部是否有数据…