当前位置: 首页 > news >正文

动态规划应该如何学习?

动态规划如何学习

参考灵神的视频和题解做的笔记(灵神YYDS,以后也都会用这套逻辑去思考)

枚举选哪个:

动态规划入门:从记忆化搜索到递推_哔哩哔哩_bilibili

746. 使用最小花费爬楼梯 - 力扣(LeetCode)
选或不选:

0-1背包 完全背包_哔哩哔哩_bilibili
494. 目标和 - 力扣(LeetCode)

个人觉得枚举选哪个和选或不选的区别是
枚举选哪个是在回溯函数中有for循环
而选或不选只有两个选择,一般会是二叉树

文章目录

  • 动态规划如何学习
    • 学习步骤:
    • 枚举选哪个
    • 746.使用最小花费爬楼梯
      • 第一步:回溯法(深度优先遍历)
      • 第二步:改成记忆化搜索
      • 第三步:一比一翻译成动态规划(递推)
      • 优化
    • 选或不选
    • 01背包(模板,可以配合该视频和代码随想录博客一起看)
      • 第一步:回溯法(深度优先遍历)
      • 第二步:改成记忆化搜索
      • 第三步:一比一翻译成动态规划(递推)
      • 滚动数组代码

学习步骤:

1.思考递归回溯的暴力穷举法(即深度优先遍历DFS)

注意要画树形结构,画完很容易看出哪里是重复的计算,对改成记忆化搜索有好处

可以使用代码随想录的递归三部曲

2.改成记忆化搜索,就是准备一个备忘录数组,保存已经计算过的结果

3.把记忆化搜索1:1翻译成为DP

翻译的时候可以想想代码随想录的动规五部曲,其实到这里五部曲的内容已经全部解决了

刷了几道题以后的发现:
1.思考回溯的时候一般就会把dp数组和下标的含义给想清楚,这一过程就是分割子问题的过程,不然回溯想不出来的

2.回溯算法终止条件一般是dp数组的初始化

3.记忆化搜索一般是自底向上,和动态规划的从前往后其实是一样的,只是记忆化是递归里的栈,而动态规划是for循环

4.dfs函数就是dp数组

5.dfs一般传入的参数n在动态规划里面一般是一层for循环,dfs中如果有for循环,那动态规划的递推一般是两层循环,而且n可以和循环中的一个进行替换

image-20241028203740524

**动态规划有「选或不选」和「枚举选哪个」两种基本思考方式。**在做题时,可根据题目要求,选择适合题目的一种来思考。

枚举选哪个

746.使用最小花费爬楼梯

746. 使用最小花费爬楼梯 - 力扣(LeetCode)

第一步:回溯法(深度优先遍历)

这个过不了,时间复杂度太高,但是这是学习动态规划的必由之路

思路:

首先思考子问题并画出树形结构图

我们要求到第5个台阶的最小花费,我们可以从第4个加上4的花销上一个台阶,也可以从3加上3的花销上两个台阶

我们要求到第n个台阶的最小花费,我们可以从第n-1个加上n-1的花销上一个台阶,也可以从n-2加上n-2的花销上两个台阶,n-1就是下一个要求的子问题,由n-2和n-3得到,一直到1或2,它的花费就是0,因为可以选择从1或2开始爬楼梯

这样就得到了一个二叉树(以5为例子)

我们要到5的台阶,就要依靠到达4和到达3的结果,来比较得到最小值,所以要得到下层结点向上层返回的结果,那就得用后序遍历来得到4,3的结果

image-20241029105344787

1.返回值和参数

我们要下层结点的最小值,所以要int

cost是题目中的花费数组,index是记录我们遍历到哪里了,相当于for循环里面的i

int dfs(vector<int>& cost,int index)

2.终止条件

if(index==0||index==1)return 0;

0其实是第一个台阶,1其实是第二个台阶,因为cost数组从0开始。

爬第一个或第二个台阶不需要花费,所以直接返回0

3.本层逻辑

左子树返回爬上第n-1台阶的最小值

右子树返回爬上第n-2台阶的最小值

最后本层返回的就是两者的最小值作为结果

int l=dfs(cost,index-1)+cost[index-1];
int r=dfs(cost,index-2)+cost[index-2];
return min(l,r);

完整代码:

class Solution {
public:int dfs(vector<int>& cost,int index){if(index==0||index==1)return 0;int l=dfs(cost,index-1)+cost[index-1];int r=dfs(cost,index-2)+cost[index-2];return min(l,r);}int minCostClimbingStairs(vector<int>& cost) {return dfs(cost,cost.size());}
};

C++还可用lambda来写

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {function<int(int)> dfs=[&](int index)->int{if(index==0||index==1)return 0;return min(dfs(index-1)+cost[index-1],dfs(index-2)+cost[index-2]);};return dfs(cost.size());}
};

第二步:改成记忆化搜索

000

从图中可以看到,这些地方就是重复计算的地方,可以直接砍掉。

怎么砍呢?

我们在遍历过程中,如果是第一次碰到这个数字呢,我们就把它的计算结果给保存到一个数组当中去,下次要用的话直接从数组里面拿而不用再次进行递归计算了。

砍完之后还剩下啥:只需要计算3,4,5,你会发现时间复杂度竟然变成了O(n),成了线性的,那这不就和递推一样了么?用1,2计算3,用2,3计算4,用3,4计算5得出结果。

在递归进行记忆化搜索里面,这是自底向上的计算,算5,要去递归3,4,然后一层一层递归

到最后1和2的时候直接返回0,然后得出3的最小,然后根据2,3得出4的最小,最后一层一层返回知道最后。

下面就来看递推。

完整代码:

class Solution {
public:int dfs(vector<int>& cost,vector<int>& dp,int index){if(index==0||index==1)return 0;//碰到了算过的,那就直接返回if(dp[index]!=-1)return dp[index];int l=dfs(cost,dp,index-1)+cost[index-1];int r=dfs(cost,dp,index-2)+cost[index-2];return dp[index]=min(l,r);//1.return dp[index]=min(dfs(cost,dp,index-1)+cost[index-1],dfs(cost,dp,index-2)+cost[index-2]);这样写也行//2.dp[index]=min(l,r);//return dp[index];也行}int minCostClimbingStairs(vector<int>& cost) {vector<int> dp(cost.size()+1,-1);return dfs(cost,dp,cost.size());}
};
class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {vector<int> dp(cost.size()+1,-1);function<int(int)> dfs=[&](int index)->int{if(index==0||index==1)return 0;if(dp[index]!=-1)return dp[index];int& res=dp[index];return res=min(dfs(index-1)+cost[index-1],dfs(index-2)+cost[index-2]);};return dfs(cost.size());}
};

注意点
1.传入的dp备忘录必须是引用传入,不然的话下层的结果保存不到数组里面,每一层的dp数组还全都是-1,就和DFS逻辑一样了其实。dp数组不会起到任何作用

2.对dp[index]一定要赋值,不能直接返回min(l,r),不然dp数组不会更新

3.l和r不可以替换成dp[index-1]和dp[index-2]。(如果有小伙伴不幸和一样犯了这个错误,那就继续看吧)

int dp[index-1]=dfs(cost,dp,index-1)+cost[index-1];
int dp[index-2]=dfs(cost,dp,index-2)+cost[index-2];
return dp[index]=min(l,r);

我先说答案:这种乍一看挺对的,实际上没有想清楚dfs返回值到底是什么,dfs(index-1)的返回值是dp[index-1],如果再加上cost[index-1],那得到的应该是dp[index],而不是dp[index]。

下面是我没彻底弄懂之前写的,只是记录一下自己的思考过程,不想看的可以跳过了

这种方式会刷新我们已经保存过的dp[index],就比如起始的0和1,dp[0]和dp[1]花费最小值本来都是0,但是用这个在计算dp[3]的时候会把dp[0]和dp[1]赋值为cost[0]和cost[1]。

然后dp[2]=dp[0]+cost[0]=2,可是我们一看这肯定是1怎么能是2呢?

后面的dp[3]也是最开始被更新为3,后面直接变成4了,这就是再次刷新了

输入
cost =
[1,100,1,1,1,100,1,1,100,1]
标准输出
index=2
1 100 -1 -1 -1 -1 -1 -1 -1 -1 -1 
index=3
1 100 2 -1 -1 -1 -1 -1 -1 -1 -1 
index=4
1 100 3 3 -1 -1 -1 -1 -1 -1 -1 
index=5
1 100 3 4 4 -1 -1 -1 -1 -1 -1 
index=6
1 100 3 4 5 104 -1 -1 -1 -1 -1 
index=7
1 100 3 4 5 204 6 -1 -1 -1 -1 
index=8
1 100 3 4 5 204 7 7 -1 -1 -1 
index=9
1 100 3 4 5 204 7 8 107 -1 -1 
index=10
1 100 3 4 5 204 7 8 207 9 -1 

正确的打印:

cost =
[1,100,1,1,1,100,1,1,100,1]
标准输出
index=2
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 
index=3
-1 -1 1 -1 -1 -1 -1 -1 -1 -1 -1 
index=4
-1 -1 1 2 -1 -1 -1 -1 -1 -1 -1 
index=5
-1 -1 1 2 2 -1 -1 -1 -1 -1 -1 
index=6
-1 -1 1 2 2 3 -1 -1 -1 -1 -1 
index=7
-1 -1 1 2 2 3 3 -1 -1 -1 -1 
index=8
-1 -1 1 2 2 3 3 4 -1 -1 -1 
index=9
-1 -1 1 2 2 3 3 4 4 -1 -1 
index=10
-1 -1 1 2 2 3 3 4 4 5 -1 

第三步:一比一翻译成动态规划(递推)

1.确定dp数组以及下标的含义

dp数组含义是登上第i个台阶由多少种方法

i下标就是第i+1个台阶

2.确定递推公式

dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);

这个就是在回溯的两个选择里面挑一个小的

就是记忆化搜索里面的返回值

3.dp数组如何初始化

就是回溯里面的终止条件

vector<int> dp(cost.size()+1,-1);
dp[0]=0,dp[1]=0;

4.确定遍历顺序

后面的5要依靠前面3,4的计算结果,所以肯定是正向遍历

完整代码:

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

优化

其实也不需要把所有的结果全都给记住,只需要记住i的前一个i-1(b)和前前一个i-2(a)

class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {//a=dp[0],b=dp[1]int a=0,b=0;for(int i=2;i<=cost.size();i++){int c=min(a+cost[i-2],b+cost[i-1]);a=b;b=c;} //c=dp[i] a=dp[i-1] b=dp[i]然后下一次循环的时候i++,a就变成dp[i-2],b变成dp[i-1]return b;}
};

选或不选

0-1背包 完全背包_哔哩哔哩_bilibili

01背包(模板,可以配合该视频和代码随想录博客一起看)

image-20241031120326938

image-20241031125050721

恰好装这个容量,求方案数量的最大最小价值和,那就是把选最大值的操作换成加号

494. 目标和 - 力扣(LeetCode)

后续如果做到了其他的变形题目会同步更新到这里

第一步:回溯法(深度优先遍历)

思路:

配合视频一起看更棒

0-1背包 完全背包_哔哩哔哩_bilibili

image-20241031122300319

下面的就不画了,大家知道这个意思就行

选编号为i的话就是返回dfs(i-1,c-w[i])+v[i],就是代表已经把编号为i的物品已经放入了背包(表现为容量减了w[i],价值加了v[i]),然后继续递归下一个物品

不选编号为i的话就是返回dfs(i-1,c),这个代表的是一共有i-1个物品,总共是c的容量,那背包能装的最大价值是多少

我们本层函数会产生两个递归,一个是选了i,一个是没选i,返回的都是对应情况的最大值,我们要选最大的,所以要在这两个里面再选一个更大的作为返回值返回

从而得出了递推公式。

这个虽然过不了,时间复杂度太高,但是这是学习动态规划的必由之路

1.返回值和参数

w各个物品所占空间

v各个物品价值

i遍历物品

c是当前剩余的容量

返回值返回选或不选编号为i的物品的最大值

int dfs(vector<int>& w,vector<int>& v,int i,int c)

2.终止条件

if(i<0)return 0;
if(c<w[i])return dfs(w,v,i-1,c);

如果编号小于0说明已经到了树形结构最下面了,要开始从第一个物品选了,即自底(第一个物品)向上(i依次增大)开始遍历

如果当前容量已经小于要选的物品,那就直接返回给上层不选i号物品的结果

3.本层逻辑

在选和不选当前物品两种情况中(只要返回回来的一定是最大值),挑一个更大的返回

return max(dfs(w,v,i-1,c),dfs(w,v,i-1,c-w[i])+v[i]);

完整代码:

class Solution {
public:int dfs(vector<int>& w,vector<int>& v,int i,int c){if(i<0)return 0;if(c<w[i])return dfs(w,v,i-1,c);return max(dfs(w,v,i-1,c),dfs(w,v,i-1,c-w[i])+v[i]);}int 01knapsack(vector<int>& nums,int c) {vector<int> w(nums.begin(),nums.end());vector<int> v(nums.begin(),nums.end());return dfs(w,v,nums.size()-1,c);}
};

C++还可用lambda来写

class Solution {
public:int 01knapsack(vector<int>& nums,int c) {vector<int> w(nums.begin(),nums.end());vector<int> v(nums.begin(),nums.end());function<int(int,int)> dfs=[&](int i,int c)->int{if(i<0)return 0;if(c<w[i])return dfs(i-1,c);return max(dfs(i-1,c),dfs(i-1,c-w[i])+v[i]);};return dfs(nums.size()-1,c);}
};

第二步:改成记忆化搜索

注意,在递归函数中,我们同时有物品编号i和容量c,所以要用一个二维数组作为哈希表来存储计算结果进行复用。

然后在每次返回结果前都赋值一下,把计算结果给存储起来

完整代码:

class Solution {
public:int dfs(vector<int>& w,vector<int>& v,int i,int c,vector<vector<int>>& dp){if(i<0)return 0;if(dp[i][c]!=-1)return dp[i][c];if(c<w[i])return dp[i][c]=dfs(w,v,i-1,c,dp);return dp[i][c]=max(dfs(w,v,i-1,c,dp),dfs(w,v,i-1,c-w[i],dp)+v[i]);}int 01knapsack(vector<int>& nums,int c) {vector<int> w(nums.begin(),nums.end());vector<int> v(nums.begin(),nums.end());vector<vector<int>> dp(nums.size(),vector<int>(c+1,-1));return dfs(w,v,nums.size()-1,c,dp);}
};
class Solution {
public:int 01knapsack(vector<int>& nums,int c) {vector<int> w(nums.begin(),nums.end());vector<int> v(nums.begin(),nums.end());vector<vector<int>> dp(nums.size(),vector<int>(c+1,-1));function<int(int,int)> dfs=[&](int i,int c)->int{if(i<0)return 0;if(dp[i][c]!=-1)return dp[i][c];if(c<w[i])return dp[i][c]=dfs(i-1,c);return dp[i][c]=max(dfs(i-1,c),dfs(i-1,c-w[i])+v[i]);};return dfs(nums.size()-1,c);}
};

第三步:一比一翻译成动态规划(递推)

1.确定dp数组以及下标的含义

二维数组,dp[i][c]就是第i个物品在容量为c时可以取到的最大价值

i是物品编号

c是当前背包的总容量

2.确定递推公式

对应回溯算法本层逻辑部分

选或者不选第i号物品,如果没选,那就和上一个物品第i-1件遍历到j时一样的价值,因为没有选第i号

如果选了那就是 第i-1件物品在j-w[i]时的价值+选择的第i件物品的价值v[i]

dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

3.dp数组如何初始化

全都初始化为0

第一行在容量大于第一件物品所需容量的时候就当做把第一件给放了进行初始化

因为dp[0][i]的物品编号只有0,即第一件物品,所以只能选择第一件物品得到最大价值,不选的话价值就为0

vector<vector<int>> dp(nums.size(),vector<int>(c+1,0));
for(int i=w[0];i<=c;i++)dp[0][i]=v[0];

4.确定遍历顺序

20240730174246

20240730174436

从前往后遍历,先遍历物品或者先遍历容量都是可以的,因为先物品是按照行一行一行来遍历,递推公式中的两个值都可以在遍历得出来,按照容量一列一列遍历也同样可以得出来这两个值

但仅限于二维,如果是一维那就只能先遍历物品后遍历容量,而且只能从后往前遍历容量

因为递推公式中用到的两个值在一维中变成了这样:

vector<int> dp(c+1,0);
for(int i=0;i<nums.size();i++)for(int j=c;j>=w[i];j--)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

image-20241031155145293

第一行是刷新前的数组,第二行是要对第一行进行覆盖的值,通过第一行的dp[j]和dp[j-w[i]]这两个值进行更新

如果从前往后,那dp[j-w[i]]就会被覆盖,从而得到一个错误的答案

如果不太理解可以转至:代码随想录

0-1背包 完全背包_哔哩哔哩_bilibili视频里面也会讲到滚动数组相关

for(int i=1;i<nums.size();i++)
for(int j=0;j<=c;j++)if(j>w[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);elsedp[i][j]=dp[i-1][j];

完整代码:

class Solution {
public:int 01knapsack(vector<int>& nums,int c) {vector<int> w(nums.begin(),nums.end());vector<int> v(nums.begin(),nums.end());vector<vector<int>> dp(nums.size(),vector<int>(c+1,0));for(int i=w[0];i<=c;i++)dp[0][i]=v[0];for(int i=1;i<nums.size();i++)for(int j=0;j<=c;j++)if(j>w[i])dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);elsedp[i][j]=dp[i-1][j];return dp[w.size()-1][c];}
};

滚动数组代码

class Solution {
public:int 01knapsack(vector<int>& nums,int c) {vector<int> w(nums.begin(),nums.end());vector<int> v(nums.begin(),nums.end());vector<int> dp(c+1,0);for(int i=0;i<nums.size();i++)for(int j=c;j>=w[i];j--)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);return dp[c];}
};

相关文章:

动态规划应该如何学习?

动态规划如何学习 参考灵神的视频和题解做的笔记&#xff08;灵神YYDS&#xff0c;以后也都会用这套逻辑去思考&#xff09; 枚举选哪个&#xff1a; 动态规划入门&#xff1a;从记忆化搜索到递推_哔哩哔哩_bilibili 746. 使用最小花费爬楼梯 - 力扣&#xff08;LeetCode&a…...

【力扣 + 牛客 | SQL题 | 每日4题】牛客SQL热题210,213,212,219

1. 力扣SQL1076&#xff1a;项目员工2 1.1 题目&#xff1a; 表&#xff1a;Project ---------------------- | Column Name | Type | ---------------------- | project_id | int | | employee_id | int | ---------------------- (project_id, employee_id) 是…...

Qt 应用开发之 MVC 架构

在Qt应用开发中&#xff0c;MVC&#xff08;Model-View-Controller&#xff09;架构确实是一种常用的设计模式&#xff0c;它通过将应用程序的业务逻辑、数据展示和用户交互分离开来&#xff0c;显著提高了代码的可维护性和可扩展性。以下是MVC架构在Qt应用开发中的原理阐述&am…...

python之字符串总结

字符串&#xff08;str&#xff09; 对于字符串的学习&#xff0c;我整理了网上的一些资料&#xff0c;希望可以帮助到各位&#xff01;&#xff01;&#xff01; 概述 由多个字母&#xff0c;数字&#xff0c;特殊字符组成的有限序列 字符串的定义&#xff1a;可以使用一对…...

Flutter鸿蒙next 封装 Dio 网络请求详解:登录身份验证与免登录缓存

✅近期推荐&#xff1a;求职神器 https://bbs.csdn.net/topics/619384540 &#x1f525;欢迎大家订阅系列专栏&#xff1a;flutter_鸿蒙next &#x1f4ac;淼学派语录&#xff1a;只有不断的否认自己和肯定自己&#xff0c;才能走出弯曲不平的泥泞路&#xff0c;因为平坦的大路…...

sql server复制一张表(表结构或表数据)SQL语句整理

1. 复制表结构及数据到新表 CREATE TABLE 新表 SELECT * FROM 旧表;这种方法会复制 旧表 中的所有内容到 新表&#xff0c;但新表不会保留原表的主键、自动递增等属性。为了保持这些属性&#xff0c;需要使用 ALTER 语句进行后续处理 2. 只复制表结构到新表 使用条件始终为假…...

c语言-进位计数制

文章目录 一、进位计数制是什么&#xff1f;二、c语言1.二进制转十进制2.十进制转二进制 一、进位计数制是什么&#xff1f; 进位计数制简称进制&#xff0c;是人类用于计算数量的基本规则。 可使用数字符号的数目称为基数或底数&#xff0c;基数个数为n个&#xff0c;即可称n…...

记本地第一次运行seatunnel示例项目

前置 静态源码编译通过&#xff1a;https://blog.csdn.net/u011924665/article/details/143372464 参考 seatunnel官方的开发环境搭建文档&#xff1a;https://seatunnel.incubator.apache.org/zh-CN/docs/2.3.5/contribution/setup 安装scala 下载scala 去官网下载&…...

Threejs 实现 VR 看房完结

效果&#xff1a; threejs 3d Vr 看房 gitee 地址&#xff1a; threejs-3d-map: 1、threejs 实现3d 地图效果链接&#xff1a;https://blog.csdn.net/qq_57952018/article/details/1430539902、threejs 实现 vr 看房 主要代码&#xff1a; src/views/PanoramicView/index.vu…...

找出目标值在数组中的开始和结束位置(二分查找)

给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示例 1&#xff1a…...

VSCode进阶之路

VSCode进阶之路&#xff1a;从入门到高效率开发 &#x1f680; Hey&#xff0c;朋友们好&#xff01;还在为VSCode的海量功能感到眼花缭乱吗&#xff1f;咱们一起来解锁VSCode的超神技能吧&#xff01; 开篇碎碎念 &#x1f3af; 第一次用VSCode时&#xff0c;就像个闯入魔法世…...

leetcode-21-合并两个有序链表

题解&#xff1a; 1、初始化哑节点dum 2、 3、 代码&#xff1a; 参考&#xff1a;leetcode-88-合并两个有序数组...

SSM项目部署到服务器

将SSM&#xff08;Spring Spring MVC MyBatis&#xff09;项目部署到服务器上&#xff0c;通常需要以下步骤&#xff1a; 打包项目 生成一个WAR文件&#xff0c;通常位于target目录下 配置Tomcat&#xff1a; 将生成的WAR文件复制到Tomcat的webapps目录下。 配置conf/se…...

【Linux】网络编程:初识协议,序列化与反序列化——基于json串实现,网络通信计算器中简单协议的实现、手写序列化与反序列化

目录 一、什么是协议&#xff1f; 二、为什么需要有协议呢&#xff1f; 三、协议的应用 四、序列化与反序列化的引入 什么是序列化和反序列化&#xff1f; 为什么需要序列化和反序列化&#xff1f; 五、序列化推荐格式之一&#xff1a;JSON介绍 六、网络版计算器编程逻…...

Educational Codeforces Round 171 (Rated for Div. 2)(A~D) 题解

Problem - A - Codeforces--PerpendicularSegments 思路:正方形对角线最长,并且相互垂直.直接输出即可. int x,y,k; void solve(){ //Acin>>x>>y>>k;int resmin(x,y);cout<<"0 0"<<" "<<res<<" &q…...

【教程】Git 标准工作流

目录 前言建仓&#xff0c;拉仓&#xff0c;关联仓库修改代码更新本地仓库&#xff0c;并解决冲突提交代码&#xff0c;合入代码其他常用 Git 工作流删除本地仓库和远程仓库中的文件日志打印commit 相关 前言 Git 是日常开发中常用的版本控制工具&#xff0c;配合代码托管仓库…...

Nico,从零开始干掉Appium,移动端自动化测试框架实现

开头先让我碎碎念一波~去年差不多时间发布了一篇《 UiAutomator Nico&#xff0c;一个基于纯 adb 命令实现的安卓自动化测试框》&#xff08;https://testerhome.com/topics/37042&#xff09;&#xff0c; 由于种种原因 (详见此篇帖子) 当时选择了用纯 adb 命令来实现安卓自动…...

PHP合成图片,生成海报图,poster-editor使用说明

之前写过一篇使用Grafika插件生成海报图的文章&#xff0c;但是当我再次使用时&#xff0c;却发生了错误&#xff0c;回看Grafika文档&#xff0c;发现很久没更新了&#xff0c;不兼容新版的GD&#xff0c;所以改用了intervention/image插件来生成海报图。 但是后来需要对海报…...

微信小程序 - 数组 push / unshift 追加后数组返回内容为数字(数组添加后打印结果为 Number 数值类型)

前言 假设一个空数组,通过 push 方法追加了一个项,控制台打印的结果竟然是 Number 数值。 例如,以下微信小程序代码: // 源数组 var arr = [] // 追加数据 var tem = arr.push(数据)...

1、DevEco Studio 鸿蒙仓颉应用创建

1. 仓颉鸿蒙应用简介 因为仓颉是静态编译型语言&#xff0c;使用仓颉开发的应用执行效率更高。而且主打全场景&#xff0c;后续可并入仓颉生态&#xff0c;其和ArkTS都是基于ArkUI进行开发&#xff0c;最大的区别是typescript和仓颉语法间的差异。 2. 应用创建 前置条件&…...

从头开始学PHP之面向对象

首先介绍下最近情况&#xff0c;因为最近入职了且通勤距离较远&#xff0c;导致精力不够了&#xff0c;而且我发现&#xff0c;人一旦上了班&#xff0c;下班之后就不想再进行任何脑力劳动了&#xff08;对大部分牛马来说&#xff0c;精英除外&#xff09;。 话不多说进入今天的…...

C++ | Leetcode C++题解之第519题随机翻转矩阵

题目&#xff1a; 题解&#xff1a; class Solution { public:Solution(int m, int n) {this->m m;this->n n;this->total m * n;srand(time(nullptr));}vector<int> flip() {int x rand() % total;vector<int> ans;total--; // 查找位置 x 对应的…...

vrrp和mstp区别

思路 vrrp是用来虚拟网关&#xff0c;噢&#xff0c;是虚拟一条虚拟网关 优先级&#xff0c;priority越大越优先&#xff0c;优先级相同&#xff0c;哪个的路由器的vrrp先起来&#xff0c;谁就是主 mstp是快速生成树协议&#xff0c;防止环路用的 优先级越小越优先 华为命令…...

前端页面整屏滚动fullpage.js简单使用

官网CSS,JS地址 fullPage.js/dist/fullpage.min.js at master alvarotrigo/fullPage.js GitHub fullPage.js/dist/fullpage.min.css at master alvarotrigo/fullPage.js GitHub <!DOCTYPE html> <html lang"en"><head><meta charset"…...

JQuery基本介绍和使用方法

JQuery基本介绍和使用方法 W3C 标准给我们提供了⼀系列的函数, 让我们可以操作: ⽹⻚内容⽹⻚结构⽹⻚样式 但是原⽣的JavaScript提供的API操作DOM元素时, 代码⽐较繁琐, 冗⻓. 我们可以使⽤JQuery来操作⻚⾯对象. jQuery是⼀个快速、简洁且功能丰富的JavaScript框架, 于20…...

【案例】旗帜飘动

开发平台&#xff1a;Unity 6.0 开发工具&#xff1a;Shader Graph 参考视频&#xff1a;Unity Shader Graph 旗帜飘动特效   一、效果图 二、Shader Graph 路线图 三、案例分析 核心思路&#xff1a;顶点偏移计算 与 顶点偏移忽略 3.1 纹理偏移 视觉上让旗帜保持动态飘动&a…...

大模型思维链推理的综述:进展、前沿和未来

转自公众号AIRoobt A Survey of Chain of Thought Reasoning: Advances, Frontiers and Future 思维链推理的综述&#xff1a;进展、前沿和未来 摘要&#xff1a;思维链推理&#xff0c;作为人类智能的基本认知过程&#xff0c;在人工智能和自然语言处理领域引起了极大的关注…...

项目一:使用 Spring + SpringMVC + Mybatis + lombok 实现网络五子棋

一&#xff1a;系统展示: 二&#xff1a;约定前后端接口 2.1 登陆 登陆请求&#xff1a; GET /login HTTP/1.1 Content-Type: application/x-www-form-urlencodedusernamezhangsan&password123登陆响应&#xff1a; 正常对象&#xff1a;正常对象会在数据库中存储&…...

openEuler 系统中 Samba 文件共享服务器管理(windows、linux文件共享操作方法)

一、Samba 简介 Samba 是在 Linux 和 Unix 系统上实现 SMB/CIFS 协议的一个免费软件&#xff0c;使得这些系统可以与 Windows 系统进行文件和打印机共享。通过 Samba&#xff0c;可以将 openEuler 系统配置为文件服务器&#xff0c;让 Windows、Linux 和其他支持 SMB/CIFS 协议…...

使用 Elasticsearch 进行语义搜索

Elasticsearch 是一款功能强大的开源搜索引擎&#xff0c;可用于全文搜索、分析和数据可视化。传统上&#xff0c;Elasticsearch 以其执行基于关键字/词汇的搜索的能力而闻名&#xff0c;其中文档基于精确或部分关键字匹配进行匹配。然而&#xff0c;Elasticsearch 已经发展到支…...