deepl
+ ++ + + Last updated on February 2, 2024 am + + +
+ + +https://blog.csdn.net/weixin_42693876/article/details/120345924
+ + + + ++
diff --git "a/2023/11/27/go-\345\237\272\347\241\200/index.html" "b/2023/11/27/go-\345\237\272\347\241\200/index.html" index ed1e790..d6ba350 100644 --- "a/2023/11/27/go-\345\237\272\347\241\200/index.html" +++ "b/2023/11/27/go-\345\237\272\347\241\200/index.html" @@ -42,7 +42,7 @@ - + @@ -227,7 +227,7 @@
@@ -280,7 +280,7 @@- Last updated on January 27, 2024 pm + Last updated on February 3, 2024 am
@@ -426,6 +426,8 @@执行以下命令开启go mod管理
1 |
|
Go mod操作
+1 |
|
对于已经写好的go文件,这里以hello.go作为例子,直接使用以下语句进行编译并运行
@@ -851,7 +853,7 @@1 |
|
思路:
可以选择使用单调栈的方法来求解,具体的思路是设置一个栈,遍历数组的时候和栈顶元素进行比较,小于栈顶元素的时候就需要将当前元素放入栈中
+首先这道题必须有一个向量数组来存储对应位置的元素的值,vector<int> res(temperatures.size(),0)
方便修改对应的元素
如果大于当前的栈顶元素的值,那么就要进行比较while
循环,只要还是大于当前栈顶的元素都需要对栈顶的元素进行pop()
1 |
|
+ + + Last updated on February 2, 2024 am + + +
+ + +https://blog.csdn.net/weixin_42693876/article/details/120345924
+ + + + ++ + + Last updated on February 3, 2024 pm + + +
+ + +Consul是hashicorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。 +内置了服务注册与发现框架、分布一致性协议实现、健康检查、key/value存储、多数据中心方案,不再需要依赖其它工具。
+Consul是一个服务网络解决方案,它使团队能够管理服务之间以及跨多云环境和运行时的安全网络连接。Consul提供服务发现、基于身份的授权、L7流量管理和服务到服务加密。
+ +服务发现和注册
+client
,统称为agent
consul client
是相对无状态的,只负责转发rpc
到server
,资源开销很少server
是一个有一组扩展功能的代理,这些功能包括参与raft
选举,维护集群状态,响应rpc
查询,与其它数据中心交互wan gossip
和转发查询给leader
或远程数据中心。client
和server
是混合的,一般建有3-5台server
安装地址:https://www.consul.io/,这里建议安装终端版本
+1 |
|
1 |
|
登陆这个默认的端口就能看见可视化的界面
+producer
:服务提供者
consumer
:服务消费者
ip/host
等信息通过发送请求告知consulpost
+服务注册 /health
健康定期检查consul
中拿存储的producer
服务的ip和port的临时表(temp table
),从表中任选一个producer
的ip和port
ip
和port
,发送访问请求producer
信息,并且每隔10秒更新Temp table
拉取服务列表
+从临时表中拿producer
的ip和端口发送请求下面给出一个例子对于在GO项目中是如何结合Consul
进行使用的
首先给出consul
中间件的结构体图,这里主要对consul
进行了sdk
的封装,包含了以下的内容:
consulclient
:服务注册与发现的板块consulconfig
:主要是中心化配置的内容consulsdk
:主要是对以上的两个板块进行了封装main_test
:是给出了一个例子对上述的内容进行测试这部分着重来写如何实现对服务注册和服务发现的功能
+ConsulClient
结构体:1 |
|
ConsulClient
结构体包含了一个 Consul
+客户端指针和一个服务端口。
1 |
|
NewConsulClient
函数用于创建一个新的 Consul
+客户端实例。它接收 Consul 服务器的地址和服务端口作为参数,并返回一个
+ConsulClient
实例以及可能的错误。
1 |
|
RegisterService
方法用于向 Consul
+注册服务。它接收服务的ID、名称、主机和端口作为参数,并向 Consul
+注册该服务。
1 |
|
DiscoverService
方法用于从 Consul
+中发现服务实例。它接收服务名称作为参数,并返回一个服务实例的地址。在内部,它通过健康检查来获取可用的服务实例,并随机选择一个健康的实例返回其地址。
这段代码实现了一个基本的 Consul 配置中心客户端,用于从 Consul +中获取、设置和删除键值对的配置信息。
+ConsulConfigCenter
结构体1 |
|
Consul
配置中心客户端1 |
|
NewConsulConfigCenter
函数用于创建一个新的 Consul
+配置中心客户端实例。它接收 Consul 服务器的地址作为参数,并返回一个
+ConsulConfigCenter
实例以及可能的错误
1 |
|
GetValue
方法用于从 Consul
+中获取特定键对应的值。它接收键名作为参数,并返回键对应的值以及可能的错误。
1 |
|
SetValue
方法用于在 Consul
+的键值存储中设置一个键值对。它接收键名和值作为参数,并将其设置到 Consul
+中。
1 |
|
DeleteValue
方法用于从 Consul
+的键值存储中删除指定的键值对。它接收键名作为参数,并将对应的键值对从
+Consul 中删除。
实现了一个基于单例模式的 Consul SDK,用于管理 Consul +客户端和配置中心。让我们逐段解释代码的功能
+ConsulSDK
结构体1 |
|
ConsulSDK
结构体包含了 Consul 客户端和配置中心的实例
sync.Once
实例1 |
|
定义了 instance
变量用于保存 ConsulSDK
+实例,once
变量用于确保 GetInstance()
+函数只被执行一次
NewConsulSDK
函数1 |
|
NewConsulSDK
函数用于创建一个新的 ConsulSDK 实例,它接收
+Consul 服务器地址和端口作为参数,并返回一个 ConsulSDK
+实例以及可能的错误
GetInstance()
函数:1 |
|
GetInstance()
函数用于获取 ConsulSDK 的单例实例。它通过
+once.Do()
确保只执行一次,创建 Consul
+客户端和配置中心,并将其保存到全局变量 instance
+中,然后返回该实例
这个部分主要对上述的sdk接口给出了一个具体的测试用例
+注意在终端运行的时候的运行为:go test
+go默认对_test
会进行测试
1 |
|
15 posts in total
+17 posts in total
15 posts in total
+17 posts in total
15 posts in total
+17 posts in total
15 posts in total
+17 posts in total
15 posts in total
+17 posts in total
2024
+ + +15 posts in total
+17 posts in total
2024
+ + +15 posts in total
+17 posts in total
2024
+ + +15 posts in total
+17 posts in total
2023
+ + +3 posts in total
+4 posts in total
2024
+ + +Consul是hashicorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。内置了服务注册与发现框架、分布一致性协议实现、健康检查、key/value存储、多数据中心方案,不再需要依赖其它工具。
Consul是一个服务网络解决方案,它使团队能够管理服务之间以及跨多云环境和运行时的安全网络连接。Consul提供服务发现、基于身份的授权、L7流量管理和服务到服务加密。
服务发现和注册
client
,统称为agent
consul client
是相对无状态的,只负责转发rpc
到server
,资源开销很少server
是一个有一组扩展功能的代理,这些功能包括参与raft
选举,维护集群状态,响应rpc
查询,与其它数据中心交互wan gossip
和转发查询给leader
或远程数据中心。client
和server
是混合的,一般建有3-5台server
安装地址:
1 |
|
1 |
|
登陆这个默认的端口就能看见可视化的界面
producer
:服务提供者
consumer
:服务消费者
ip/host
等信息通过发送请求告知consulpost
服务注册 /health
健康定期检查consul
中拿存储的producer
服务的ip和port的临时表(temp table
),从表中任选一个producer
的ip和port
ip
和port
,发送访问请求producer
信息,并且每隔10秒更新Temp table
拉取服务列表从临时表中拿producer
的ip和端口发送请求下面给出一个例子对于在GO项目中是如何结合Consul
进行使用的
首先给出consul
中间件的结构体图,这里主要对consul
进行了sdk
的封装,包含了以下的内容:
consulclient
:服务注册与发现的板块consulconfig
:主要是中心化配置的内容consulsdk
:主要是对以上的两个板块进行了封装main_test
:是给出了一个例子对上述的内容进行测试这部分着重来写如何实现对服务注册和服务发现的功能
ConsulClient
结构体:1 |
|
ConsulClient
结构体包含了一个 Consul客户端指针和一个服务端口。
1 |
|
NewConsulClient
函数用于创建一个新的 Consul客户端实例。它接收 Consul 服务器的地址和服务端口作为参数,并返回一个ConsulClient
实例以及可能的错误。
1 |
|
RegisterService
方法用于向 Consul注册服务。它接收服务的ID、名称、主机和端口作为参数,并向 Consul注册该服务。
1 |
|
DiscoverService
方法用于从 Consul中发现服务实例。它接收服务名称作为参数,并返回一个服务实例的地址。在内部,它通过健康检查来获取可用的服务实例,并随机选择一个健康的实例返回其地址。
这段代码实现了一个基本的 Consul 配置中心客户端,用于从 Consul中获取、设置和删除键值对的配置信息。
ConsulConfigCenter
结构体1 |
|
Consul
配置中心客户端1 |
|
NewConsulConfigCenter
函数用于创建一个新的 Consul配置中心客户端实例。它接收 Consul 服务器的地址作为参数,并返回一个ConsulConfigCenter
实例以及可能的错误
1 |
|
GetValue
方法用于从 Consul中获取特定键对应的值。它接收键名作为参数,并返回键对应的值以及可能的错误。
1 |
|
SetValue
方法用于在 Consul的键值存储中设置一个键值对。它接收键名和值作为参数,并将其设置到 Consul中。
1 |
|
DeleteValue
方法用于从 Consul的键值存储中删除指定的键值对。它接收键名作为参数,并将对应的键值对从Consul 中删除。
实现了一个基于单例模式的 Consul SDK,用于管理 Consul客户端和配置中心。让我们逐段解释代码的功能
ConsulSDK
结构体1 |
|
ConsulSDK
结构体包含了 Consul 客户端和配置中心的实例
sync.Once
实例1 |
|
定义了 instance
变量用于保存 ConsulSDK实例,once
变量用于确保 GetInstance()
函数只被执行一次
NewConsulSDK
函数1 |
|
NewConsulSDK
函数用于创建一个新的 ConsulSDK 实例,它接收Consul 服务器地址和端口作为参数,并返回一个 ConsulSDK实例以及可能的错误
GetInstance()
函数:1 |
|
GetInstance()
函数用于获取 ConsulSDK 的单例实例。它通过once.Do()
确保只执行一次,创建 Consul客户端和配置中心,并将其保存到全局变量 instance
中,然后返回该实例
这个部分主要对上述的sdk接口给出了一个具体的测试用例
注意在终端运行的时候的运行为:go test
go默认对_test
会进行测试
1 |
|
vector的长度:
初始化数组:
构造vector:
for循环:
题目描述
链接:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回-1。
示例 1:
1 |
|
示例 2:
1 |
|
思路
题目表示的是有序数组,而且题目没有重复元素。在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则
1 |
|
注意这里给出的题解法:当left <= right
的时候,以下的条件中全部都不取到等号nums[middle] > target nums[middle] < target
需要注意的是:right=nums.size()-1
C++版本
1 |
|
Go版本
1 |
|
题目描述
示例 1:
1 |
|
示例 2:
1 |
|
思路
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
C++版本
1 |
|
python版本
1 |
|
GO版本:
1 |
|
题目描述
示例 1:
1 |
|
示例 2:
1 |
|
思路
双指针法,首尾遍历比较并存储
1 |
|
Python:
1 |
|
GO:
1 |
|
题目描述
给定一个含有 n
个正整数的数组和一个正整数target
。
找出该数组中满足其总和大于等于 target
的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回0
。
示例 1:
1 |
|
示例 2:
1 |
|
示例 3:
1 |
|
思路
滑动窗口法
滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动本题中实现滑动窗口,主要确定如下三点:
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
1 |
|
题目描述
给你一个正整数 n
,生成一个包含 1
到n2
所有元素,且元素按顺时针顺序螺旋排列的n x n
正方形矩阵 matrix
1 |
|
示例 2:
1 |
|
思路:大模拟循环遍历
1 |
|
一般哈希表都是用来快速判断一个元素是否出现集合里
只需要初始化把所有元素都存在哈希表里,在查询的时候通过索引直接就可以知道元素在不在这哈希表里了
建立索引:哈希函数
题目描述
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s的字母异位词。
示例 1: 输入: s = "anagram", t = "nagaram" 输出: true
示例 2: 输入: s = "rat", t = "car" 输出: false
思路
暴力的方法可能时间复杂度会很高
判断有没有异位词的本质就是查看当前的字母是不是有出现过,那么思路就是选择哈希表
定义一个数组叫做record用来上记录字符串s里字符出现的次数。
需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。这样就将字符串s中字符出现的次数,统计出来了
1 |
|
题目描述
示例 1:
1 |
|
示例 2:
1 |
|
思路
使用哈希表存储,但是用set(unordered_set)
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表,使用unordered_set读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set
1 |
|
题目描述
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
1 |
|
思路:
注意,题目中提到一个点是无限循环,说明计算的结果sum是有限的只需要在哈希表中将这部分的结果存储进去,并每次比较是不是出现1如果是那么就是快乐数,否则就不是快乐数
1 |
|
题目描述
给定一个整数数组 nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
1 |
|
示例 2:
1 |
|
示例 3:
1 |
|
思路:
构建一个哈希表,然后遍历一遍就行了在哈希表中找n-a的值是否存在,但是最大的问题是数组中同一个元素在答案里不能重复出现,所以不能简单考虑unordered_set
这里提供一种新的思路,就是用unordered_map来存储数组中的数据内容和下标的数值
1 |
|
给你四个整数数组nums1
、nums2
、nums3
和nums4
,数组长度都是 n
,请你计算有多少个元组(i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
1 |
|
示例 2:
1 |
|
思路
1 |
|
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
用哈希表unordered_map来存储次数,对于ransomNote来减去次数
1 |
|
给你一个整数数组 nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
1 |
|
思路
其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码,而且使用哈希法在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2)
这道题可以用双指针法求解
拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a =nums[i],b = nums[left],c = nums[right]。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right]> 0 就说明此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时三数之和小了,left就向右移动,才能让三数之和大一些,直到left与right相遇为止。
还有一个难度就是不能有重复的结果,需要做一次去重的操作
1 |
|
示例 1:
1 |
|
示例 2:
1 |
|
思路:
使用快慢指针来实现两个指针之间的移动,对于找到了和val数值一样的就进行替换
示例 1:
1 |
|
示例 2:
1 |
|
思路:
采用两个指针之间互相交换,首尾交换
1 |
|
示例 1:
1 |
|
示例 2:
1 |
|
示例 3:
1 |
|
思路:
首先对字符串中额外的空格进行删除
字符串进行全局的逆序
再根据空格作为一个单独字母的节点进行分格分别进行逆序
1 |
|
1 |
|
思路:本质上就是利用了两个链表指针实现对元素的转向
1 |
|
1 |
|
示例 2:
1 |
|
思路:
遍历,用两个指针分别来记录
如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了
1 |
|
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须保持其原始结构 。
示例 1:
1 |
|
思路:
简单来说,就是求两个链表交点节点的指针,注意返回的是结点的指针,不是对应的数值,同时注意这里比较的是相同的指针不是数值相同,因此直接比较指针是不是相同就可以了
由于题目说的相交的结构如图所示,如果存在相交的指针位置,只可能出现在后面只需要考虑利用双指针从相差的数值位开始遍历
1 |
|
判断是否是有还存在,如果有那么返回开始入环的第一个节点的下标
1 |
|
思路:
这道题用快慢指针的思路,就是慢指针每次只走一步,快指针每次走两步,如果在到达null之前出现快慢指针指向了同一个地方,说明这个链表有环存在,那么怎么判断下标的位置呢?
具体的证明过程:
相遇时slow指针走过的节点数为: x + y
,fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针,(y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以fast指针走过的节点数 = slow指针走过的节点数 * 2:
1 |
|
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为fast指针至少要多走一圈才能相遇slow指针
所以可以得到的规律是:从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是 环形入口的节点
1 |
|
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
找到最大的左边和最大的右边并相减
1 |
|
1 |
|
1 |
|
深度优先搜索的三部曲:
别忘了最开始的初始化步骤
给定两个整数 n
和 k
,返回范围[1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
1 |
|
示例 2:
1 |
|
思路,使用深度优先搜索算法进行处理
startindex
用来存储下一次进行选择的位置点这样能够避免重复1 |
|
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
返回 所有可能的有效组合的列表。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例:
1 |
|
思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加sum变量进行记录
1 |
|
为了优化可以做一个剪枝操作
1 |
|
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1不对应任何字母。
示例 :
1 |
|
这道题需要注意的地方是,首先第一步做好map字符的映射
第二步最关键是要写清楚回溯函数的参数可能包含index,就是第几位置的字符,同时需要区分backtracking函数的for循环的内容是相当于横向的遍历,而函数体内部的实现是纵向的遍历
1 |
|
给你一个 无重复元素 的整数数组candidates
和一个目标整数 target
,找出candidates
中可以使数字和为目标数 target
的所有 不同组合 ,并以列表形式返回。你可以按任意顺序 返回这些组合。
candidates
中的 同一个 数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于150
个。
示例 :
1 |
|
思路:
题目最关键的点在于能重复使用元素但是不能重复元素的组合不能被重复输出
因此需要调整startindex的开始的位置是在backtracking(candidates,target,sum,i);
注意,这个时候从i开始保证还能用到自己的元素重复使用,还有最重要的sort(candidates.begin(), candidates.end()); // 需要排序
排序之后能够很好的进行剪枝,将一些加了之后元素大于目标的删掉直接跳过
1 |
|
给定一个候选人编号的集合 candidates
和一个目标数target
,找出 candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次。
注意:解集不能包含重复的组合。
示例 :
1 |
|
思路:
这个地方最大的困难在于每个数字在每个组合中只能使用一次,同时集合中的元素存在重复的元素,那么这个时候有一个问题是如何才能对元素进行去重处理呢,就是让每个元素只能被使用一次
去重的操作就在于vector<bool> used(candidates.size(),false); sort(candidates.begin(), candidates.end());
首先需要在backtracking
中定一个continue,这个地方是为了筛选不是重复的部分,那么如何区分开是否是同一个数组中重复的元素而不是重复利用的元素呢?
i>0&&candidates[i]==candidates[i-1]
这个地方表明了对元素相邻之间进行比较used[i-1]==false
如果这个地方是false,那么说明这个元素是同一层的元素(同一个数组中的元素)i<candidates.size()&&sum + candidates[i] <= target
为了避免出现超出时间限制的情况1 |
|
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例:
1 |
|
思路:
startindex
的使用,利用这个来移动s.substr(startindex, i-startindex+1)
来截取并筛选出相应的字符串的值进行回文比较startindex>=s.size()
如果超出了范围那么久说明到终点了1 |
|
有效 IP 地址 正好由四个整数(每个整数位于0
到 255
之间组成,且不能含有前导0
),整数之间用 '.'
分隔。
"0.1.2.201"
和"192.168.1.1"
是有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。给定一个只包含数字的字符串 s
,用以表示一个 IP地址,返回所有可能的有效 IP 地址,这些地址可以通过在s
中插入 '.'
来形成。你 不能重新排序或删除 s
中的任何数字。你可以按任何 顺序返回答案。
示例 :
1 |
|
思路:
s.inset(n,'.')
和删除s.erase(n)
1 |
|
给你一个整数数组 nums
,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按任意顺序 返回解集。
示例:
1 |
|
思路:
这道题比较简单,就是简单的遍历就可以了
1 |
|
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按任意顺序 排列。
示例:
1 |
|
注意:
凡是涉及到去重的操作,都需要优先进行排序操作
1 |
|
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按 任意顺序返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
首先这道题不需要去重同时也不需要提前进行排序
但是需要对同一层的元素进行去重操作
1 |
|
给定一个不含重复数字的数组 nums
,返回其所有可能的全排列 。你可以 按任意顺序返回答案。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
要求解全排列,因此回溯退出的条件是当path的长度和nums的长度一样的时候就达到了退出的条件
因为这道题没有重复的元素,求解全排列需要每次都从0开始选择,因此难点在于如何标记出已经选择过的元素
1 |
|
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
首先这个全排列有重复的元素,因此需要有去重的操作,既然涉及到去重那需要重新排序,同时需要跳过重复的元素
第二步,既然是全排列,那么需要标记重复选择的元素并选择跳过
1 |
|
给你一份航线列表 tickets
,其中tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。思路:
【困难】
首先第一步:确定终止条件,遇到的机场个数,如果达到了(航班数量+1)
记录航班的数量,使用unordered_map<string, map<string, int>> targets;
来记录航班的映射关系,我定义为全局变量。
当然把参数放进函数里传进去也是可以的,我是尽量控制函数里参数的长度。参数里还需要ticketNum,表示有多少个航班
回溯的过程中,如何遍历一个机场所对应的所有机场呢?
这里刚刚说过,在选择映射函数的时候,不能选择unordered_map<string, multiset<string>> targets
,因为一旦有元素增删multiset的迭代器就会失效,当然可能有牛逼的容器删除元素迭代器不会失效,这里就不在讨论了。
可以说本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。
所以我选择了unordered_map<string, map<string, int>> targets
来做机场之间的映射
1 |
|
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
1 |
|
思路:
这道题关键在于用好数据结构和写好合法性的判断
关键在于定义好chessboard
第二步是把合法性位置判断写好
1 |
|
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
1-9
在每一行只能出现一次。1-9
在每一列只能出现一次。1-9
在每一个以粗实线分隔的 3x3
宫内只能出现一次。(请参考示例图)数独部分空格内已填入了数字,空白格用 '.'
表示。
1 |
|
思路:
深度优先搜索,加上合法性判断
1 |
|
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸 s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 :
1 |
|
示例 :
1 |
|
思路:
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
可以尝试使用贪心策略,先将饼干数组和小孩数组排序。
然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
1 |
|
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
[1, 7, 4, 9, 2, 5]
是一个摆动序列 ,因为差值 (6, -3, 5, -7, 3)
是正负交替出现的。给你一个整数数组 nums
,返回 nums
中作为摆动序列 的 最长子序列的长度
示例 :
1 |
|
1 |
|
思路:
本题异常情况的本质,就是要考虑平坡,平坡分两种,一个是 上下中间有平坡,一个是单调有平坡,如图
同时需要注意的是在判断条件语句的时候,不能简单的用判断相乘法小于0作为判断,因为存在平坡的情况
1 |
|
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例:
1 |
|
思路:
这道题使用的是局部的最优贪心的思路,如果遇到让总的值小于0,那么久立刻让总的值变成0,那么下一轮就从头开始记了,同时max会每一轮进行判断是否有比当前的最大值大,如果有那么就进行替换
1 |
|
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多 只能持有 一股股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
1 |
|
思路:
把利润分解为每天为单位的维度,而不是从 0 天到第 3天整体去考虑!
那么根据 prices可以得到每天的利润序列:(prices[i] - prices[i - 1]).....(prices[1] - prices[0])
相当于是每天的利润之差和0的比较,只选择为正的值,负数的情况直接忽略
1 |
|
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
注意:动态规划的问题一般只会输出最后的一个结果,不会输出比如中间的路径等相关的值
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
1 |
|
给定 n
,请计算 F(n)
。
示例:
1 |
|
思路:
因为这道题给出了递推公式:F(n) = F(n - 1) + F(n - 2)
动规五部曲:
这里我们要用一个一维dp数组来保存递归的结果
确定dp数组以及下标的含义:dp[i]的定义为:第i个数的斐波那契数值是dp[i]
确定递推公式F(n) = F(n - 1) + F(n - 2)
dp数组如何初始化
1 |
|
确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i- 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
举例推导dp数组
1 |
|
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。每次你可以爬1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
1 |
|
思路:
动态规划简单题,递推公式:dp[i] = dp[i-2]+dp[i-1];
1 |
|
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为0
或下标为 1
的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。
示例 1:
1 |
|
思路:
动态规划可以有两个途径得到dp[i],一个是dp[i-1]一个是dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费dp[i - 1] + cost[i - 1]
。
dp[i - 2] 跳到 dp[i] 需要花费dp[i - 2] + cost[i - 2]
。
那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢?
一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
;
1 |
|
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish” )。问总共有多少条不同的路径?
思路:
简单的动态规划问题,只需要保证每次迭代都从上面和左边进行叠加
1 |
|
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用1
和 0
来表示。
思路:
和上一题的思路一样,都是需要遍历路径就行,但是这里加入了一个新的数组用来存储有障碍物的位置,因此需要额外进行标记&&obstacleGrid[i][0]==0
的信息,同时遇到障碍物就不改变对应的值,直接continue
就好
1 |
|
给定一个正整数 n
,将其拆分为 k
个正整数 的和( k >= 2
),并使这些整数的乘积最大化。返回 你可以获得的最大乘积 。
示例 :
1 |
|
思路:
给出递推公式一个是j * (i - j)
直接相乘。一个是j * dp[i - j]
,相当于是拆分(i - j)
,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j
取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
;
1 |
|
dp[j]
为容量为j
的背包所背的最大价值,那么如何推导dp[j]
呢?dp[j]
可以通过dp[j - weight[i]]
推导出来,dp[j - weight[i]]
表示容量为j - weight[i]
的背包所背的最大价值。
dp[j - weight[i]] + value[i]
表示 容量为 j
- 物品i重量 的背包 加上物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])
此时dp[j]有两个选择,一个是取自己dp[j]
相当于二维dp数组中的dp[i-1][j]
,即不放物品i,一个是取dp[j - weight[i]] + value[i]
,即放物品i,指定是取最大的,毕竟是求最大价值,
递推公式:
1 |
|
初始化:
全部初始化为0
遍历顺序:
1 |
|
整体的代码结构是:
1 |
|
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
在代码层面的区别在于背包遍历的时候是从头开始到尾遍历,int j = weight[i]; j <= bagWeight; j++
,因为所有的背包内部都是无限的
1、先遍历物品再遍历背包
1 |
|
2、先遍历背包再遍历物品
1 |
|
给你一个只包含正整数的非空 数组nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等示例:
1 |
|
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用0
来代替。
示例 1:
1 |
|
思路:
可以选择使用单调栈的方法来求解,具体的思路是设置一个栈,遍历数组的时候和栈顶元素进行比较,小于栈顶元素的时候就需要将当前元素放入栈中
如果大于当前的栈顶元素的值,那么就要进行比较while
循环,只要还是大于当前栈顶的元素都需要对栈顶的元素进行pop()
1 |
|
给你一个有 n
个节点的有向无环图(DAG),请你找出所有从节点 0
到节点 n-1
的路径并输出(不要求按特定顺序) graph[i]
是一个从节点 i
可以访问的所有节点的列表(即从节点i
到节点 graph[i][j]
存在一条有向边)。
1 |
|
思路:
深度优先搜索
注意在用dfs做题的时候需要初始化path.push_back(0)
每一次都需要初始化输入这个数值起点
1 |
|
给你一个由 '1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
示例 :
1 |
|
深度优先搜索版本:
思路在于利用dfs来对岛屿中的数量进行标记是否能visited,必须是联通的才能继续标记为res++
1 |
|
vector的长度:
初始化数组:
构造vector:
for循环:
题目描述
链接:
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回-1。
示例 1:
1 |
|
示例 2:
1 |
|
思路
题目表示的是有序数组,而且题目没有重复元素。在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则
1 |
|
注意这里给出的题解法:当left <= right
的时候,以下的条件中全部都不取到等号nums[middle] > target nums[middle] < target
需要注意的是:right=nums.size()-1
C++版本
1 |
|
Go版本
1 |
|
题目描述
示例 1:
1 |
|
示例 2:
1 |
|
思路
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
C++版本
1 |
|
python版本
1 |
|
GO版本:
1 |
|
题目描述
示例 1:
1 |
|
示例 2:
1 |
|
思路
双指针法,首尾遍历比较并存储
1 |
|
Python:
1 |
|
GO:
1 |
|
题目描述
给定一个含有 n
个正整数的数组和一个正整数target
。
找出该数组中满足其总和大于等于 target
的长度最小的连续子数组[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回0
。
示例 1:
1 |
|
示例 2:
1 |
|
示例 3:
1 |
|
思路
滑动窗口法
滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动本题中实现滑动窗口,主要确定如下三点:
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
1 |
|
题目描述
给你一个正整数 n
,生成一个包含 1
到n2
所有元素,且元素按顺时针顺序螺旋排列的n x n
正方形矩阵 matrix
1 |
|
示例 2:
1 |
|
思路:大模拟循环遍历
1 |
|
一般哈希表都是用来快速判断一个元素是否出现集合里
只需要初始化把所有元素都存在哈希表里,在查询的时候通过索引直接就可以知道元素在不在这哈希表里了
建立索引:哈希函数
题目描述
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s的字母异位词。
示例 1: 输入: s = "anagram", t = "nagaram" 输出: true
示例 2: 输入: s = "rat", t = "car" 输出: false
思路
暴力的方法可能时间复杂度会很高
判断有没有异位词的本质就是查看当前的字母是不是有出现过,那么思路就是选择哈希表
定义一个数组叫做record用来上记录字符串s里字符出现的次数。
需要把字符映射到数组也就是哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了。这样就将字符串s中字符出现的次数,统计出来了
1 |
|
题目描述
示例 1:
1 |
|
示例 2:
1 |
|
思路
使用哈希表存储,但是用set(unordered_set)
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表,使用unordered_set读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set
1 |
|
题目描述
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
1 |
|
思路:
注意,题目中提到一个点是无限循环,说明计算的结果sum是有限的只需要在哈希表中将这部分的结果存储进去,并每次比较是不是出现1如果是那么就是快乐数,否则就不是快乐数
1 |
|
题目描述
给定一个整数数组 nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
1 |
|
示例 2:
1 |
|
示例 3:
1 |
|
思路:
构建一个哈希表,然后遍历一遍就行了在哈希表中找n-a的值是否存在,但是最大的问题是数组中同一个元素在答案里不能重复出现,所以不能简单考虑unordered_set
这里提供一种新的思路,就是用unordered_map来存储数组中的数据内容和下标的数值
1 |
|
给你四个整数数组nums1
、nums2
、nums3
和nums4
,数组长度都是 n
,请你计算有多少个元组(i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
1 |
|
示例 2:
1 |
|
思路
1 |
|
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
用哈希表unordered_map来存储次数,对于ransomNote来减去次数
1 |
|
给你一个整数数组 nums
,判断是否存在三元组[nums[i], nums[j], nums[k]]
满足i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
1 |
|
思路
其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码,而且使用哈希法在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2)
这道题可以用双指针法求解
拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a =nums[i],b = nums[left],c = nums[right]。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right]> 0 就说明此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时三数之和小了,left就向右移动,才能让三数之和大一些,直到left与right相遇为止。
还有一个难度就是不能有重复的结果,需要做一次去重的操作
1 |
|
示例 1:
1 |
|
示例 2:
1 |
|
思路:
使用快慢指针来实现两个指针之间的移动,对于找到了和val数值一样的就进行替换
示例 1:
1 |
|
示例 2:
1 |
|
思路:
采用两个指针之间互相交换,首尾交换
1 |
|
示例 1:
1 |
|
示例 2:
1 |
|
示例 3:
1 |
|
思路:
首先对字符串中额外的空格进行删除
字符串进行全局的逆序
再根据空格作为一个单独字母的节点进行分格分别进行逆序
1 |
|
1 |
|
思路:本质上就是利用了两个链表指针实现对元素的转向
1 |
|
1 |
|
示例 2:
1 |
|
思路:
遍历,用两个指针分别来记录
如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了
1 |
|
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须保持其原始结构 。
示例 1:
1 |
|
思路:
简单来说,就是求两个链表交点节点的指针,注意返回的是结点的指针,不是对应的数值,同时注意这里比较的是相同的指针不是数值相同,因此直接比较指针是不是相同就可以了
由于题目说的相交的结构如图所示,如果存在相交的指针位置,只可能出现在后面只需要考虑利用双指针从相差的数值位开始遍历
1 |
|
判断是否是有还存在,如果有那么返回开始入环的第一个节点的下标
1 |
|
思路:
这道题用快慢指针的思路,就是慢指针每次只走一步,快指针每次走两步,如果在到达null之前出现快慢指针指向了同一个地方,说明这个链表有环存在,那么怎么判断下标的位置呢?
具体的证明过程:
相遇时slow指针走过的节点数为: x + y
,fast指针走过的节点数:x + y + n (y + z)
,n为fast指针在环内走了n圈才遇到slow指针,(y+z)为 一圈内节点的个数A。
因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以fast指针走过的节点数 = slow指针走过的节点数 * 2:
1 |
|
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)中提出一个(y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为fast指针至少要多走一圈才能相遇slow指针
所以可以得到的规律是:从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是 环形入口的节点
1 |
|
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
找到最大的左边和最大的右边并相减
1 |
|
1 |
|
1 |
|
深度优先搜索的三部曲:
别忘了最开始的初始化步骤
给定两个整数 n
和 k
,返回范围[1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
1 |
|
示例 2:
1 |
|
思路,使用深度优先搜索算法进行处理
startindex
用来存储下一次进行选择的位置点这样能够避免重复1 |
|
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
返回 所有可能的有效组合的列表。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例:
1 |
|
思路:简单的深度优先搜索,但需要注意的是可以适当采用减枝操作和必要的时候添加sum变量进行记录
1 |
|
为了优化可以做一个剪枝操作
1 |
|
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1不对应任何字母。
示例 :
1 |
|
这道题需要注意的地方是,首先第一步做好map字符的映射
第二步最关键是要写清楚回溯函数的参数可能包含index,就是第几位置的字符,同时需要区分backtracking函数的for循环的内容是相当于横向的遍历,而函数体内部的实现是纵向的遍历
1 |
|
给你一个 无重复元素 的整数数组candidates
和一个目标整数 target
,找出candidates
中可以使数字和为目标数 target
的所有 不同组合 ,并以列表形式返回。你可以按任意顺序 返回这些组合。
candidates
中的 同一个 数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于150
个。
示例 :
1 |
|
思路:
题目最关键的点在于能重复使用元素但是不能重复元素的组合不能被重复输出
因此需要调整startindex的开始的位置是在backtracking(candidates,target,sum,i);
注意,这个时候从i开始保证还能用到自己的元素重复使用,还有最重要的sort(candidates.begin(), candidates.end()); // 需要排序
排序之后能够很好的进行剪枝,将一些加了之后元素大于目标的删掉直接跳过
1 |
|
给定一个候选人编号的集合 candidates
和一个目标数target
,找出 candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次。
注意:解集不能包含重复的组合。
示例 :
1 |
|
思路:
这个地方最大的困难在于每个数字在每个组合中只能使用一次,同时集合中的元素存在重复的元素,那么这个时候有一个问题是如何才能对元素进行去重处理呢,就是让每个元素只能被使用一次
去重的操作就在于vector<bool> used(candidates.size(),false); sort(candidates.begin(), candidates.end());
首先需要在backtracking
中定一个continue,这个地方是为了筛选不是重复的部分,那么如何区分开是否是同一个数组中重复的元素而不是重复利用的元素呢?
i>0&&candidates[i]==candidates[i-1]
这个地方表明了对元素相邻之间进行比较used[i-1]==false
如果这个地方是false,那么说明这个元素是同一层的元素(同一个数组中的元素)i<candidates.size()&&sum + candidates[i] <= target
为了避免出现超出时间限制的情况1 |
|
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
示例:
1 |
|
思路:
startindex
的使用,利用这个来移动s.substr(startindex, i-startindex+1)
来截取并筛选出相应的字符串的值进行回文比较startindex>=s.size()
如果超出了范围那么久说明到终点了1 |
|
有效 IP 地址 正好由四个整数(每个整数位于0
到 255
之间组成,且不能含有前导0
),整数之间用 '.'
分隔。
"0.1.2.201"
和"192.168.1.1"
是有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。给定一个只包含数字的字符串 s
,用以表示一个 IP地址,返回所有可能的有效 IP 地址,这些地址可以通过在s
中插入 '.'
来形成。你 不能重新排序或删除 s
中的任何数字。你可以按任何 顺序返回答案。
示例 :
1 |
|
思路:
s.inset(n,'.')
和删除s.erase(n)
1 |
|
给你一个整数数组 nums
,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按任意顺序 返回解集。
示例:
1 |
|
思路:
这道题比较简单,就是简单的遍历就可以了
1 |
|
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按任意顺序 排列。
示例:
1 |
|
注意:
凡是涉及到去重的操作,都需要优先进行排序操作
1 |
|
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素 。你可以按 任意顺序返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
首先这道题不需要去重同时也不需要提前进行排序
但是需要对同一层的元素进行去重操作
1 |
|
给定一个不含重复数字的数组 nums
,返回其所有可能的全排列 。你可以 按任意顺序返回答案。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
要求解全排列,因此回溯退出的条件是当path的长度和nums的长度一样的时候就达到了退出的条件
因为这道题没有重复的元素,求解全排列需要每次都从0开始选择,因此难点在于如何标记出已经选择过的元素
1 |
|
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
1 |
|
示例 2:
1 |
|
思路:
首先这个全排列有重复的元素,因此需要有去重的操作,既然涉及到去重那需要重新排序,同时需要跳过重复的元素
第二步,既然是全排列,那么需要标记重复选择的元素并选择跳过
1 |
|
给你一份航线列表 tickets
,其中tickets[i] = [fromi, toi]
表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。所有这些机票都属于一个从JFK
(肯尼迪国际机场)出发的先生,所以该行程必须从JFK
开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
["JFK", "LGA"]
与["JFK", "LGB"]
相比就更小,排序更靠前。思路:
【困难】
首先第一步:确定终止条件,遇到的机场个数,如果达到了(航班数量+1)
记录航班的数量,使用unordered_map<string, map<string, int>> targets;
来记录航班的映射关系,我定义为全局变量。
当然把参数放进函数里传进去也是可以的,我是尽量控制函数里参数的长度。参数里还需要ticketNum,表示有多少个航班
回溯的过程中,如何遍历一个机场所对应的所有机场呢?
这里刚刚说过,在选择映射函数的时候,不能选择unordered_map<string, multiset<string>> targets
,因为一旦有元素增删multiset的迭代器就会失效,当然可能有牛逼的容器删除元素迭代器不会失效,这里就不在讨论了。
可以说本题既要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。
所以我选择了unordered_map<string, map<string, int>> targets
来做机场之间的映射
1 |
|
n 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题的棋子放置方案,该方案中 'Q'
和 '.'
分别代表了皇后和空位。
1 |
|
思路:
这道题关键在于用好数据结构和写好合法性的判断
关键在于定义好chessboard
第二步是把合法性位置判断写好
1 |
|
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
1-9
在每一行只能出现一次。1-9
在每一列只能出现一次。1-9
在每一个以粗实线分隔的 3x3
宫内只能出现一次。(请参考示例图)数独部分空格内已填入了数字,空白格用 '.'
表示。
1 |
|
思路:
深度优先搜索,加上合法性判断
1 |
|
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子i
,都有一个胃口值g[i]
,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j
,都有一个尺寸 s[j]
。如果s[j] >= g[i]
,我们可以将这个饼干 j
分配给孩子 i
,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 :
1 |
|
示例 :
1 |
|
思路:
这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩。
可以尝试使用贪心策略,先将饼干数组和小孩数组排序。
然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
1 |
|
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
[1, 7, 4, 9, 2, 5]
是一个摆动序列 ,因为差值 (6, -3, 5, -7, 3)
是正负交替出现的。给你一个整数数组 nums
,返回 nums
中作为摆动序列 的 最长子序列的长度
示例 :
1 |
|
1 |
|
思路:
本题异常情况的本质,就是要考虑平坡,平坡分两种,一个是 上下中间有平坡,一个是单调有平坡,如图
同时需要注意的是在判断条件语句的时候,不能简单的用判断相乘法小于0作为判断,因为存在平坡的情况
1 |
|
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例:
1 |
|
思路:
这道题使用的是局部的最优贪心的思路,如果遇到让总的值小于0,那么久立刻让总的值变成0,那么下一轮就从头开始记了,同时max会每一轮进行判断是否有比当前的最大值大,如果有那么就进行替换
1 |
|
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多 只能持有 一股股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例 1:
1 |
|
思路:
把利润分解为每天为单位的维度,而不是从 0 天到第 3天整体去考虑!
那么根据 prices可以得到每天的利润序列:(prices[i] - prices[i - 1]).....(prices[1] - prices[0])
相当于是每天的利润之差和0的比较,只选择为正的值,负数的情况直接忽略
1 |
|
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
注意:动态规划的问题一般只会输出最后的一个结果,不会输出比如中间的路径等相关的值
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
1 |
|
给定 n
,请计算 F(n)
。
示例:
1 |
|
思路:
因为这道题给出了递推公式:F(n) = F(n - 1) + F(n - 2)
动规五部曲:
这里我们要用一个一维dp数组来保存递归的结果
确定dp数组以及下标的含义:dp[i]的定义为:第i个数的斐波那契数值是dp[i]
确定递推公式F(n) = F(n - 1) + F(n - 2)
dp数组如何初始化
1 |
|
确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i- 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
举例推导dp数组
1 |
|
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。每次你可以爬1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
1 |
|
思路:
动态规划简单题,递推公式:dp[i] = dp[i-2]+dp[i-1];
1 |
|
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为0
或下标为 1
的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。
示例 1:
1 |
|
思路:
动态规划可以有两个途径得到dp[i],一个是dp[i-1]一个是dp[i-2]。
dp[i - 1] 跳到 dp[i] 需要花费dp[i - 1] + cost[i - 1]
。
dp[i - 2] 跳到 dp[i] 需要花费dp[i - 2] + cost[i - 2]
。
那么究竟是选从dp[i - 1]跳还是从dp[i - 2]跳呢?
一定是选最小的,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
;
1 |
|
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish” )。问总共有多少条不同的路径?
思路:
简单的动态规划问题,只需要保证每次迭代都从上面和左边进行叠加
1 |
|
一个机器人位于一个 m x n
网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用1
和 0
来表示。
思路:
和上一题的思路一样,都是需要遍历路径就行,但是这里加入了一个新的数组用来存储有障碍物的位置,因此需要额外进行标记&&obstacleGrid[i][0]==0
的信息,同时遇到障碍物就不改变对应的值,直接continue
就好
1 |
|
给定一个正整数 n
,将其拆分为 k
个正整数 的和( k >= 2
),并使这些整数的乘积最大化。返回 你可以获得的最大乘积 。
示例 :
1 |
|
思路:
给出递推公式一个是j * (i - j)
直接相乘。一个是j * dp[i - j]
,相当于是拆分(i - j)
,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j
取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j))
;
1 |
|
dp[j]
为容量为j
的背包所背的最大价值,那么如何推导dp[j]
呢?dp[j]
可以通过dp[j - weight[i]]
推导出来,dp[j - weight[i]]
表示容量为j - weight[i]
的背包所背的最大价值。
dp[j - weight[i]] + value[i]
表示 容量为 j
- 物品i重量 的背包 加上物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])
此时dp[j]有两个选择,一个是取自己dp[j]
相当于二维dp数组中的dp[i-1][j]
,即不放物品i,一个是取dp[j - weight[i]] + value[i]
,即放物品i,指定是取最大的,毕竟是求最大价值,
递推公式:
1 |
|
初始化:
全部初始化为0
遍历顺序:
1 |
|
整体的代码结构是:
1 |
|
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和01背包问题唯一不同的地方就是,每种物品有无限件。
在代码层面的区别在于背包遍历的时候是从头开始到尾遍历,int j = weight[i]; j <= bagWeight; j++
,因为所有的背包内部都是无限的
1、先遍历物品再遍历背包
1 |
|
2、先遍历背包再遍历物品
1 |
|
给你一个只包含正整数的非空 数组nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等示例:
1 |
|
给定一个整数数组 temperatures
,表示每天的温度,返回一个数组 answer
,其中answer[i]
是指对于第 i
天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用0
来代替。
示例 1:
1 |
|
思路:
可以选择使用单调栈的方法来求解,具体的思路是设置一个栈,遍历数组的时候和栈顶元素进行比较,小于栈顶元素的时候就需要将当前元素放入栈中
首先这道题必须有一个向量数组来存储对应位置的元素的值,vector<int> res(temperatures.size(),0)
方便修改对应的元素
如果大于当前的栈顶元素的值,那么就要进行比较while
循环,只要还是大于当前栈顶的元素都需要对栈顶的元素进行pop()
1 |
|
给你一个有 n
个节点的有向无环图(DAG),请你找出所有从节点 0
到节点 n-1
的路径并输出(不要求按特定顺序) graph[i]
是一个从节点 i
可以访问的所有节点的列表(即从节点i
到节点 graph[i][j]
存在一条有向边)。
1 |
|
思路:
深度优先搜索
注意在用dfs做题的时候需要初始化path.push_back(0)
每一次都需要初始化输入这个数值起点
1 |
|
给你一个由 '1'
(陆地)和'0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
示例 :
1 |
|
深度优先搜索版本:
思路在于利用dfs来对岛屿中的数量进行标记是否能visited,必须是联通的才能继续标记为res++
1 |
|
参考学习资料:
go的官网下载网站,选择合适的系统版本进行安装
下载安装包并按照安装包的指引下载相关的内容
对于Mac系统会直接配置好环境变量,根据官网的安装手册进行安装
测试GO的版本
1 |
|
测试GO的环境变量
1 |
|
GOROOT 表示的是安装包所在的位置,一般不需要修改
GOPATH表示的是运行文件所在的位置,表示的是workspace的文件位置,GOPATH是我们的工作空间,保存go项目代码和第三方依赖包GOPATH可以设置多个,其中,第一个将会是默认的包目录,使用go get 下载的包都会在第一个path中的src目录下,使用 goinstall时,在哪个GOPATH中找到了这个包,就会在哪个GOPATH下的bin目录生成可执行文件
修改GOPATH的路径
1 |
|
将文件查找的路径设置为GOROOT和GOPATH的并集合
1 |
|
将两个部分并在一起之后,就能从两个地方开始寻找定义的包
首先会从GOROOT进行搜索,接着从GOPATH进行搜索。
GOPATH是开发时的工作目录。用于:
go get
和go install
命令会下载go代码到GOPATH。使用GOPATH时,GO会在以下目录中搜索包:
GOROOT/src
:该目录保存了Go标准库代码。GOPATH/src
:该目录保存了应用自身的代码和第三方依赖的代码。GOPATH的弊端
在 GOPATH 的 $GOPATH/src
下进行 .go
文件或源代码的存储,我们可以称其为 GOPATH的模式,这个模式拥有一些弊端.
无版本控制概念.在执行go get
的时候,你无法传达任何的版本信息的期望,也就是说你也无法知道自己当前更新的是哪一个版本,也无法通过指定来拉取自己所期望的具体版本。
无法同步一致第三方版本号. 在运行 Go应用程序的时候,你无法保证其它人与你所期望依赖的第三方库是相同的版本,也就是说在项目依赖库的管理上,你无法保证所有人的依赖版本都一致。
无法指定当前项目引用的第三方版本号. 你没办法处理v1、v2、v3 等等不同版本的引用问题,因为 GOPATH模式下的导入路径都是一样的,都是github.com/foo/bar
。
这个环境变量主要是用于设置 Go 模块代理(Go moduleproxy),其作用是用于使 Go在后续拉取模块版本时直接通过镜像站点来快速拉取。
GOPROXY的默认值是:https://proxy.golang.org,direct
,proxy.golang.org
国内访问不了,需要设置国内的代理
并通过以下的命令进行设置
1 |
|
GOPROXY 的值是一个以英文逗号 “,” 分割的 Go模块代理列表,允许设置多个模块代理,假设你不想使用,也可以将其设置为“off” ,这将会禁止 Go 在后续操作中使用任何 Go 模块代理。
1 |
|
GO111MODULE 有三个值:off, on和auto(默认值)。
执行以下命令开启go mod管理
1 |
|
对于已经写好的go文件,这里以hello.go作为例子,直接使用以下语句进行编译并运行
1 |
|
或者将编译和运行两个过程分开,先编译后运行:
1 |
|
首先给出基本框架
1 |
|
程序的第一行声明了名为main的package。一个package会包含一个或多个.go源代码文件。每一个源文件都是以package开头。比如我们的例子里是packagemain。这行声明语句表示该文件是属于哪一个package。
.
和没有.
导入包的区别,如果一开始引入的时候有.
那么就不需要指定哪个包的来调用函数,否则需要再调用函数的时候指定对应的包package
一个程序的
main
入口函数必须不带任何输入参数和返回结果。而且go语言的语法,定义函数的时候,‘{’必须和函数名在同一行,不能另起一行
声明变量的一般形式是使用 var 关键字
指定变量类型,声明后若不赋值,使用默认值0
1 |
|
根据值自行判定变量类型。
1 |
|
省略var, 注意:=左侧的变量不应该是已经声明过的,就是:=只能用于没有被声明的变量赋值上,否则会编译错误
1 |
|
几种声明类型的对比
1 |
|
和一般的定义变量的方式一样
1 |
|
特殊的定义全局变量的方式,而且:=的定义方式不能够用于定义全局变量
1 |
|
:=不能用于已经被初始化之后的变量的赋值,如果对于_的情况是不具备可读性,相当于忽略
1 |
|
常量是一个简单值的标识符,在程序运行时,不会被修改的量。常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
1 |
|
隐式定义类型方法:
1 |
|
多重赋值
1 |
|
枚举类型
1 |
|
常量可以用len(), cap(),unsafe.Sizeof()常量计算表达式的值。常量表达式中,函数必须是内置函数
1 |
|
输出结果为:abc, 3, 16
unsafe.Sizeof(a) = 16
字符串类型在 go 里是个结构,包含指向底层数组的指针和长度,这两部分每部分都是 8个字节,所以字符串类型大小为 16 个字节。
在 golang中,一个方便的习惯就是使用iota
标示符,简化了常量用于增长数字的定义。
下面的代码中,当第一行赋值了iota之后,那么相当于初始化位置是0,后面的依次增加是1,2
1 |
|
如果对iota
进行运算,其实相当于是选择当前的行作为iota的取值进行运算,如果中间不对运算加以改变,那么会一直持续按照当前的运算规则执行下去
1 |
|
同样的在同一个const中去定义不同的iota
的计算方式也可以,iota
的取值就是选择当前的行,从哪个地方开始改变,那么就改成不同的计算方式
1 |
|
以下是输出的内容:
1 |
|
多个返回值初始化设置了函数的形参之后,初始值是0
go每次设置一个变量值之后都有初始值,如果是数据就是0,如果是字符串那么就是空,防止出现一些野指针的情况
1 |
|
输出的结果是
1 |
|
所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,执行main包中的init函数,最后执行main函数。下图详细地解释了整个执行过程:
分别建不同的文件夹对应的就是package的名字,相应的在.go文件内部声明package的名字
main 函数只能在package main中
注意:在包中设置接口的时候,函数名称必须第一个字母是大写,如果是小写的话将无法识别
init函数的调用过程,首先会对包中的init进行初始化再进行调用接口
如果你导入了包比如lib1,但是没有使用这个包里面的接口函数,仍然会报错
以下是一个import包的例子,首先定义两个不同包以及对应的接口函数和初始化函数的实现
1 |
|
1 |
|
1 |
|
"GolangTraining/InitLib1""GolangTraining/InitLib2"
是两个包的地址,go会默认从GOROOT和GOPATH两个默认的位置进行寻找,首先要保证地址的正确性
代码的输出:
1 |
|
如果我不想调用lib1的函数接口,但是想使用lib1的init()函数怎么办呢,如果这个时候直接导入了包但是不调用接口,就会出现上述的错误
在导入的包前面加上下划线来认为这个包是匿名的,这样就能知进行init操作
1 |
|
那么这个时候就只会调用init()
函数同时不会出错
除了能够匿名导包之外,还能给新导入的包起个别的名字,比如叫mylib作为新的别名
或者直接使用·
来进行调用
最好别使用这种,如果两个包的函数名称一样那么可能会导致出现歧义的情况
函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。调用函数,可以通过两种方式来传递参数:值传递和指针传递
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到实际参数。
1 |
|
接下来,让我们使用值传递来调用 swap() 函数:
1 |
|
运行的结果为:
1 |
|
和C++以及C中的是一样的,对go中的指针定义的时候 *int传递变量的地址&
在对一个指针赋值的时候,传递的是某一个变量的地址,就是传递这个变量的引用,引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数,主要作用:
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
1 |
|
输出的内容
1 |
|
Go 语言切片 slice
是对数组的抽象
通过这种方式进行初始化数组以及进行切片操作,通过range关键字进行遍历数组,并给出index和value进行给出不同的下标和数值
固定数组传递的是一个值拷贝
切片不需要说明长度
1 |
|
也可以指定容量,其中capacity
为可选参数。
1 |
|
将arr中从下标startIndex到endIndex-1下的元素创建为一个新的切片
1 |
|
缺省endIndex时将表示一直到arr的最后一个元素,缺省startIndex时将表示从arr的第一个元素开始
1 |
|
通过切片s初始化切片s1
1 |
|
通过内置函数 make()
初始化切片s,[]int
标识为其元素类型为int的切片
同时动态数组传递的过程中的参数形式是一致的,能够适配所有的slice参数类型,但是对于
这里面的下划线表示的是不需要考虑的index的数值,可以忽略,这里是关于切片slice的声明和打印
1 |
|
打印的结果是
声明slice但是不一定声明了空间,因此需要注意的是声明的同时并给出空间大小,同时没办法中途增加空间
1 |
|
判断一个切片是不是空的
1 |
|
注意if-else的格式有要求,{
必须是出现在else和if紧接着的位置,不能换行写
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来
注意,如果append超过了当前的空间,那么slice就会继续增加空间,增加的大小是cap的大小增加
拷贝copy()操作
1 |
|
关于切片的截取操作
1 |
|
map和slice类似,只不过是数据结构不同,下面是map的一些声明方式。
1 |
|
1 |
|
分别定义不同的拷贝和引用的函数
1 |
|
结果是使用值拷贝的输出的name没有改变,只有使用引用的才发生了改变
关于结构体定义的细节,内部的成员变量和结构体本身的大小写就是蕴含了是不是私有和公有的关系,大写标识公有,小写表示私有
1 |
|
对于结构体内部的成员函数,必须是传递了引用的地址才能够修改,否则就是默认的值传递
1 |
|
如果新定义的类继承了某个类,那么只需要在内部写上所继承的类的名称,同时这里没有C++中的公有保护等其他类型的继承,公有私有的设定保持一致
1 |
|
对继承类中的方法重写,同样传递的还是引用和指针
1 |
|
重新定义新的方法
1 |
|
关于主函数中的调用
1 |
|
在继承和多态上,一系列家族定义的接口,每个子类能够重写方法,实现同一个方法有多个接口表现形式
本质上利用interface来实现类的多态性
1 |
|
那怎么认为这个cat继承了这个animal类呢?只需要对animal中的所有函数重写即可认为是继承了
1 |
|
同理对于dog也是一样
1 |
|
主函数中如何实现不同的多态调用呢?注意哦,这个地方传递的是继承类的引用进去来实现多态
1 |
|
golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}
的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})
的方式传进来的时候,也就意味着这个参数被自动的转为interface{}
的类型。
1 |
|
interface{}
相当于是一个万能的数据类型,适用于对任何的函数的参数传递中的使用
1 |
|
如果断言失败一般会导致panic的发生。所以为了防止panic的发生,我们需要在断言前进行一定的判断
1 |
|
如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。
定义一个断言类型的函数
1 |
|
主函数的调用关系如下:
1 |
|
输出的内容是:
1 |
|
在讲反射之前,先来看看Golang关于类型设计的一些原则
static type
和concrete type
.简单来说static type
是你在编码是看见的类型(如int、string),concrete type
是runtime
系统看见的类型concrete type
,而不是static type
.因此,一个reader
变量如果它的concrete type
也实现了write
方法的话,它也可以被类型断言为writer
.反射
,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是statictype),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concretetype),只有interface类型才有反射一说
在Golang的实现中,每个interface
变量都有一个对应pair
,pair中记录了实际变量的值和类型
1 |
|
value是实际变量值,type是实际变量的类型。一个interface{}
类型的变量包含了2个指针,一个指针指向值的类型concrete type
,另外一个指针指向实际的值对应value
reflect的反射类型对象:TypeOf和ValueOf
那么在Golang的reflect反射包中有什么样的方式可以让我们直接获取到变量内部的信息呢?它提供了两种类型(或者说两个方法)让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf()
和 reflect.TypeOf()
1 |
|
reflect.TypeOf()
是获取pair中的type,reflect.ValueOf()
获取pair中的value,示例如下:
1 |
|
说明:
reflect.Type
和reflect.Value这
两种1 |
|
主函数的调用
1 |
|
注意,在使用反射之前需要引入reflect的包
1 |
|
1 |
|
首先定义一个类以及关于这个类的方法
1 |
|
再定义一个利用反射选择类中值和方法的函数
1 |
|
注意点:
通过运行结果可以得知获取未知类型的interface的所属方法(函数)的步骤为:
reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。
1 |
|
本质上还是利用了反射,通过以下形式给结构体中的变量添加标签作用:其他包在调用这个当前包的时候对于某个属性的一个说明,指示某个包在具体使用中的作用。
作用:能够将结构体转化为json格式
1 |
|
输出之后在json格式转换中可以看到如下,注意可以看到的是输出的内容是根据给定的tag来进行标题的命名的
利用反射取出元素查询
利用编码和解码对struct 和json之间的转化
1 |
|
参考学习资料:
go的官网下载网站,选择合适的系统版本进行安装
下载安装包并按照安装包的指引下载相关的内容
对于Mac系统会直接配置好环境变量,根据官网的安装手册进行安装
测试GO的版本
1 |
|
测试GO的环境变量
1 |
|
GOROOT 表示的是安装包所在的位置,一般不需要修改
GOPATH表示的是运行文件所在的位置,表示的是workspace的文件位置,GOPATH是我们的工作空间,保存go项目代码和第三方依赖包GOPATH可以设置多个,其中,第一个将会是默认的包目录,使用go get 下载的包都会在第一个path中的src目录下,使用 goinstall时,在哪个GOPATH中找到了这个包,就会在哪个GOPATH下的bin目录生成可执行文件
修改GOPATH的路径
1 |
|
将文件查找的路径设置为GOROOT和GOPATH的并集合
1 |
|
将两个部分并在一起之后,就能从两个地方开始寻找定义的包
首先会从GOROOT进行搜索,接着从GOPATH进行搜索。
GOPATH是开发时的工作目录。用于:
go get
和go install
命令会下载go代码到GOPATH。使用GOPATH时,GO会在以下目录中搜索包:
GOROOT/src
:该目录保存了Go标准库代码。GOPATH/src
:该目录保存了应用自身的代码和第三方依赖的代码。GOPATH的弊端
在 GOPATH 的 $GOPATH/src
下进行 .go
文件或源代码的存储,我们可以称其为 GOPATH的模式,这个模式拥有一些弊端.
无版本控制概念.在执行go get
的时候,你无法传达任何的版本信息的期望,也就是说你也无法知道自己当前更新的是哪一个版本,也无法通过指定来拉取自己所期望的具体版本。
无法同步一致第三方版本号. 在运行 Go应用程序的时候,你无法保证其它人与你所期望依赖的第三方库是相同的版本,也就是说在项目依赖库的管理上,你无法保证所有人的依赖版本都一致。
无法指定当前项目引用的第三方版本号. 你没办法处理v1、v2、v3 等等不同版本的引用问题,因为 GOPATH模式下的导入路径都是一样的,都是github.com/foo/bar
。
这个环境变量主要是用于设置 Go 模块代理(Go moduleproxy),其作用是用于使 Go在后续拉取模块版本时直接通过镜像站点来快速拉取。
GOPROXY的默认值是:https://proxy.golang.org,direct
,proxy.golang.org
国内访问不了,需要设置国内的代理
并通过以下的命令进行设置
1 |
|
GOPROXY 的值是一个以英文逗号 “,” 分割的 Go模块代理列表,允许设置多个模块代理,假设你不想使用,也可以将其设置为“off” ,这将会禁止 Go 在后续操作中使用任何 Go 模块代理。
1 |
|
GO111MODULE 有三个值:off, on和auto(默认值)。
执行以下命令开启go mod管理
1 |
|
Go mod操作
1 |
|
对于已经写好的go文件,这里以hello.go作为例子,直接使用以下语句进行编译并运行
1 |
|
或者将编译和运行两个过程分开,先编译后运行:
1 |
|
首先给出基本框架
1 |
|
程序的第一行声明了名为main的package。一个package会包含一个或多个.go源代码文件。每一个源文件都是以package开头。比如我们的例子里是packagemain。这行声明语句表示该文件是属于哪一个package。
.
和没有.
导入包的区别,如果一开始引入的时候有.
那么就不需要指定哪个包的来调用函数,否则需要再调用函数的时候指定对应的包package
一个程序的
main
入口函数必须不带任何输入参数和返回结果。而且go语言的语法,定义函数的时候,‘{’必须和函数名在同一行,不能另起一行
声明变量的一般形式是使用 var 关键字
指定变量类型,声明后若不赋值,使用默认值0
1 |
|
根据值自行判定变量类型。
1 |
|
省略var, 注意:=左侧的变量不应该是已经声明过的,就是:=只能用于没有被声明的变量赋值上,否则会编译错误
1 |
|
几种声明类型的对比
1 |
|
和一般的定义变量的方式一样
1 |
|
特殊的定义全局变量的方式,而且:=的定义方式不能够用于定义全局变量
1 |
|
:=不能用于已经被初始化之后的变量的赋值,如果对于_的情况是不具备可读性,相当于忽略
1 |
|
常量是一个简单值的标识符,在程序运行时,不会被修改的量。常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
常量的定义格式:
1 |
|
隐式定义类型方法:
1 |
|
多重赋值
1 |
|
枚举类型
1 |
|
常量可以用len(), cap(),unsafe.Sizeof()常量计算表达式的值。常量表达式中,函数必须是内置函数
1 |
|
输出结果为:abc, 3, 16
unsafe.Sizeof(a) = 16
字符串类型在 go 里是个结构,包含指向底层数组的指针和长度,这两部分每部分都是 8个字节,所以字符串类型大小为 16 个字节。
在 golang中,一个方便的习惯就是使用iota
标示符,简化了常量用于增长数字的定义。
下面的代码中,当第一行赋值了iota之后,那么相当于初始化位置是0,后面的依次增加是1,2
1 |
|
如果对iota
进行运算,其实相当于是选择当前的行作为iota的取值进行运算,如果中间不对运算加以改变,那么会一直持续按照当前的运算规则执行下去
1 |
|
同样的在同一个const中去定义不同的iota
的计算方式也可以,iota
的取值就是选择当前的行,从哪个地方开始改变,那么就改成不同的计算方式
1 |
|
以下是输出的内容:
1 |
|
多个返回值初始化设置了函数的形参之后,初始值是0
go每次设置一个变量值之后都有初始值,如果是数据就是0,如果是字符串那么就是空,防止出现一些野指针的情况
1 |
|
输出的结果是
1 |
|
所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,执行main包中的init函数,最后执行main函数。下图详细地解释了整个执行过程:
分别建不同的文件夹对应的就是package的名字,相应的在.go文件内部声明package的名字
main 函数只能在package main中
注意:在包中设置接口的时候,函数名称必须第一个字母是大写,如果是小写的话将无法识别
init函数的调用过程,首先会对包中的init进行初始化再进行调用接口
如果你导入了包比如lib1,但是没有使用这个包里面的接口函数,仍然会报错
以下是一个import包的例子,首先定义两个不同包以及对应的接口函数和初始化函数的实现
1 |
|
1 |
|
1 |
|
"GolangTraining/InitLib1""GolangTraining/InitLib2"
是两个包的地址,go会默认从GOROOT和GOPATH两个默认的位置进行寻找,首先要保证地址的正确性
代码的输出:
1 |
|
如果我不想调用lib1的函数接口,但是想使用lib1的init()函数怎么办呢,如果这个时候直接导入了包但是不调用接口,就会出现上述的错误
在导入的包前面加上下划线来认为这个包是匿名的,这样就能知进行init操作
1 |
|
那么这个时候就只会调用init()
函数同时不会出错
除了能够匿名导包之外,还能给新导入的包起个别的名字,比如叫mylib作为新的别名
或者直接使用·
来进行调用
最好别使用这种,如果两个包的函数名称一样那么可能会导致出现歧义的情况
函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。调用函数,可以通过两种方式来传递参数:值传递和指针传递
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到实际参数。
1 |
|
接下来,让我们使用值传递来调用 swap() 函数:
1 |
|
运行的结果为:
1 |
|
和C++以及C中的是一样的,对go中的指针定义的时候 *int传递变量的地址&
在对一个指针赋值的时候,传递的是某一个变量的地址,就是传递这个变量的引用,引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
defer语句被用于预定对一个函数的调用。可以把这类被defer语句调用的函数称为延迟函数,主要作用:
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
1 |
|
输出的内容
1 |
|
Go 语言切片 slice
是对数组的抽象
通过这种方式进行初始化数组以及进行切片操作,通过range关键字进行遍历数组,并给出index和value进行给出不同的下标和数值
固定数组传递的是一个值拷贝
切片不需要说明长度
1 |
|
也可以指定容量,其中capacity
为可选参数。
1 |
|
将arr中从下标startIndex到endIndex-1下的元素创建为一个新的切片
1 |
|
缺省endIndex时将表示一直到arr的最后一个元素,缺省startIndex时将表示从arr的第一个元素开始
1 |
|
通过切片s初始化切片s1
1 |
|
通过内置函数 make()
初始化切片s,[]int
标识为其元素类型为int的切片
同时动态数组传递的过程中的参数形式是一致的,能够适配所有的slice参数类型,但是对于
这里面的下划线表示的是不需要考虑的index的数值,可以忽略,这里是关于切片slice的声明和打印
1 |
|
打印的结果是
声明slice但是不一定声明了空间,因此需要注意的是声明的同时并给出空间大小,同时没办法中途增加空间
1 |
|
判断一个切片是不是空的
1 |
|
注意if-else的格式有要求,{
必须是出现在else和if紧接着的位置,不能换行写
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来
注意,如果append超过了当前的空间,那么slice就会继续增加空间,增加的大小是cap的大小增加
拷贝copy()操作
1 |
|
关于切片的截取操作
1 |
|
map和slice类似,只不过是数据结构不同,下面是map的一些声明方式。
1 |
|
1 |
|
分别定义不同的拷贝和引用的函数
1 |
|
结果是使用值拷贝的输出的name没有改变,只有使用引用的才发生了改变
关于结构体定义的细节,内部的成员变量和结构体本身的大小写就是蕴含了是不是私有和公有的关系,大写标识公有,小写表示私有
1 |
|
对于结构体内部的成员函数,必须是传递了引用的地址才能够修改,否则就是默认的值传递
1 |
|
如果新定义的类继承了某个类,那么只需要在内部写上所继承的类的名称,同时这里没有C++中的公有保护等其他类型的继承,公有私有的设定保持一致
1 |
|
对继承类中的方法重写,同样传递的还是引用和指针
1 |
|
重新定义新的方法
1 |
|
关于主函数中的调用
1 |
|
在继承和多态上,一系列家族定义的接口,每个子类能够重写方法,实现同一个方法有多个接口表现形式
本质上利用interface来实现类的多态性
1 |
|
那怎么认为这个cat继承了这个animal类呢?只需要对animal中的所有函数重写即可认为是继承了
1 |
|
同理对于dog也是一样
1 |
|
主函数中如何实现不同的多态调用呢?注意哦,这个地方传递的是继承类的引用进去来实现多态
1 |
|
golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}
的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})
的方式传进来的时候,也就意味着这个参数被自动的转为interface{}
的类型。
1 |
|
interface{}
相当于是一个万能的数据类型,适用于对任何的函数的参数传递中的使用
1 |
|
如果断言失败一般会导致panic的发生。所以为了防止panic的发生,我们需要在断言前进行一定的判断
1 |
|
如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。
定义一个断言类型的函数
1 |
|
主函数的调用关系如下:
1 |
|
输出的内容是:
1 |
|
在讲反射之前,先来看看Golang关于类型设计的一些原则
static type
和concrete type
.简单来说static type
是你在编码是看见的类型(如int、string),concrete type
是runtime
系统看见的类型concrete type
,而不是static type
.因此,一个reader
变量如果它的concrete type
也实现了write
方法的话,它也可以被类型断言为writer
.反射
,就是建立在类型之上的,Golang的指定类型的变量的类型是静态的(也就是指定int、string这些的变量,它的type是statictype),在创建变量的时候就已经确定,反射主要与Golang的interface类型相关(它的type是concretetype),只有interface类型才有反射一说
在Golang的实现中,每个interface
变量都有一个对应pair
,pair中记录了实际变量的值和类型
1 |
|
value是实际变量值,type是实际变量的类型。一个interface{}
类型的变量包含了2个指针,一个指针指向值的类型concrete type
,另外一个指针指向实际的值对应value
reflect的反射类型对象:TypeOf和ValueOf
那么在Golang的reflect反射包中有什么样的方式可以让我们直接获取到变量内部的信息呢?它提供了两种类型(或者说两个方法)让我们可以很容易的访问接口变量内容,分别是reflect.ValueOf()
和 reflect.TypeOf()
1 |
|
reflect.TypeOf()
是获取pair中的type,reflect.ValueOf()
获取pair中的value,示例如下:
1 |
|
说明:
reflect.Type
和reflect.Value这
两种1 |
|
主函数的调用
1 |
|
注意,在使用反射之前需要引入reflect的包
1 |
|
1 |
|
首先定义一个类以及关于这个类的方法
1 |
|
再定义一个利用反射选择类中值和方法的函数
1 |
|
注意点:
通过运行结果可以得知获取未知类型的interface的所属方法(函数)的步骤为:
reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。
1 |
|
本质上还是利用了反射,通过以下形式给结构体中的变量添加标签作用:其他包在调用这个当前包的时候对于某个属性的一个说明,指示某个包在具体使用中的作用。
作用:能够将结构体转化为json格式
1 |
|
输出之后在json格式转换中可以看到如下,注意可以看到的是输出的内容是根据给定的tag来进行标题的命名的
利用反射取出元素查询
利用编码和解码对struct 和json之间的转化
1 |
|
执行以下命令开启go mod管理
|
Go mod操作
+
|
对于已经写好的go文件,这里以hello.go作为例子,直接使用以下语句进行编译并运行
@@ -3234,6 +3236,7 @@ href="https://leetcode.cn/problems/partition-equal-subset-sum/description/">http
|
思路:
可以选择使用单调栈的方法来求解,具体的思路是设置一个栈,遍历数组的时候和栈顶元素进行比较,小于栈顶元素的时候就需要将当前元素放入栈中
+首先这道题必须有一个向量数组来存储对应位置的元素的值,vector<int> res(temperatures.size(),0)
方便修改对应的元素
如果大于当前的栈顶元素的值,那么就要进行比较while
循环,只要还是大于当前栈顶的元素都需要对栈顶的元素进行pop()
|
Consul是hashicorp公司推出的开源工具,用于实现分布式系统的服务发现与配置。 +内置了服务注册与发现框架、分布一致性协议实现、健康检查、key/value存储、多数据中心方案,不再需要依赖其它工具。
+Consul是一个服务网络解决方案,它使团队能够管理服务之间以及跨多云环境和运行时的安全网络连接。Consul提供服务发现、基于身份的授权、L7流量管理和服务到服务加密。
+ +服务发现和注册
+client
,统称为agent
consul client
是相对无状态的,只负责转发rpc
到server
,资源开销很少server
是一个有一组扩展功能的代理,这些功能包括参与raft
选举,维护集群状态,响应rpc
查询,与其它数据中心交互wan gossip
和转发查询给leader
或远程数据中心。client
和server
是混合的,一般建有3-5台server
安装地址:https://www.consul.io/,这里建议安装终端版本
+
|
|
登陆这个默认的端口就能看见可视化的界面
+producer
:服务提供者
consumer
:服务消费者
ip/host
等信息通过发送请求告知consulpost
+服务注册 /health
健康定期检查consul
中拿存储的producer
服务的ip和port的临时表(temp table
),从表中任选一个producer
的ip和port
ip
和port
,发送访问请求producer
信息,并且每隔10秒更新Temp table
拉取服务列表
+从临时表中拿producer
的ip和端口发送请求下面给出一个例子对于在GO项目中是如何结合Consul
进行使用的
首先给出consul
中间件的结构体图,这里主要对consul
进行了sdk
的封装,包含了以下的内容:
consulclient
:服务注册与发现的板块consulconfig
:主要是中心化配置的内容consulsdk
:主要是对以上的两个板块进行了封装main_test
:是给出了一个例子对上述的内容进行测试这部分着重来写如何实现对服务注册和服务发现的功能
+ConsulClient
结构体:
|
ConsulClient
结构体包含了一个 Consul
+客户端指针和一个服务端口。
|
NewConsulClient
函数用于创建一个新的 Consul
+客户端实例。它接收 Consul 服务器的地址和服务端口作为参数,并返回一个
+ConsulClient
实例以及可能的错误。
|
RegisterService
方法用于向 Consul
+注册服务。它接收服务的ID、名称、主机和端口作为参数,并向 Consul
+注册该服务。
|
DiscoverService
方法用于从 Consul
+中发现服务实例。它接收服务名称作为参数,并返回一个服务实例的地址。在内部,它通过健康检查来获取可用的服务实例,并随机选择一个健康的实例返回其地址。
这段代码实现了一个基本的 Consul 配置中心客户端,用于从 Consul +中获取、设置和删除键值对的配置信息。
+ConsulConfigCenter
结构体
|
Consul
配置中心客户端
|
NewConsulConfigCenter
函数用于创建一个新的 Consul
+配置中心客户端实例。它接收 Consul 服务器的地址作为参数,并返回一个
+ConsulConfigCenter
实例以及可能的错误
|
GetValue
方法用于从 Consul
+中获取特定键对应的值。它接收键名作为参数,并返回键对应的值以及可能的错误。
|
SetValue
方法用于在 Consul
+的键值存储中设置一个键值对。它接收键名和值作为参数,并将其设置到 Consul
+中。
|
DeleteValue
方法用于从 Consul
+的键值存储中删除指定的键值对。它接收键名作为参数,并将对应的键值对从
+Consul 中删除。
实现了一个基于单例模式的 Consul SDK,用于管理 Consul +客户端和配置中心。让我们逐段解释代码的功能
+ConsulSDK
结构体
|
ConsulSDK
结构体包含了 Consul 客户端和配置中心的实例
sync.Once
实例
|
定义了 instance
变量用于保存 ConsulSDK
+实例,once
变量用于确保 GetInstance()
+函数只被执行一次
NewConsulSDK
函数
|
NewConsulSDK
函数用于创建一个新的 ConsulSDK 实例,它接收
+Consul 服务器地址和端口作为参数,并返回一个 ConsulSDK
+实例以及可能的错误
GetInstance()
函数:
|
GetInstance()
函数用于获取 ConsulSDK 的单例实例。它通过
+once.Do()
确保只执行一次,创建 Consul
+客户端和配置中心,并将其保存到全局变量 instance
+中,然后返回该实例
这个部分主要对上述的sdk接口给出了一个具体的测试用例
+注意在终端运行的时候的运行为:go test
+go默认对_test
会进行测试
|
6 posts in total
+7 posts in total
2024
+ +