欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。
本期话题:go实现有序的map
最近在做题时遇到需要用有序map来处理的题,但是golang中没有这个功能。
实现方法有很多,比如可以模仿c++用平衡树来实现,在redis中也可以使用跳表实现。
感觉跳表代码更简洁,之前也没有了解过,就用这个来实现一下。
跳表实现参考 :https://blog.csdn.net/weixin_45827856/article/details/103254965
具体原理已经写得很清楚,我就没必要再写。
有序map的功能他都有
import "math/rand"
type KV interface {
GetKey() interface{}
GetValue() interface{}
Less(b KV) bool
}
const SKIPLIST_MAXLEVEL = 32
/*
以1/4的概率递增,最多32层
*/
func slRandomLevel() int {
level := 1
for (rand.Int() & 0xFFFF) < (0xFFFF >> 2) {
level++
}
if level < SKIPLIST_MAXLEVEL {
return level
}
return SKIPLIST_MAXLEVEL
}
// 利用跳表实现可排序的map
/*跳表节点定义*/
type leveInfo struct {
// 前进指针
forward *skiplistNode
// 跨度
span int64
}
type skiplistNode struct {
// value
elem KV
// 后退指针
backward *skiplistNode
// 层
level []leveInfo
}
type Skiplist struct {
// 跳跃表头指针
header *skiplistNode
tail *skiplistNode
// 表中节点的数量
length int64
// 表中层数最大的节点的层数
level int
}
func slCreateNode(level int, e KV) *skiplistNode {
node := &skiplistNode{
elem: e,
backward: nil,
level: make([]leveInfo, level),
}
return node
}
func NewSkipList() *Skiplist {
list := &Skiplist{
length: 0,
level: 1,
header: slCreateNode(SKIPLIST_MAXLEVEL, nil),
tail: nil,
}
return list
}
/*
添加元素
*/
func (list *Skiplist) Add(e KV) {
var (
// 存储搜索路径
update [SKIPLIST_MAXLEVEL]*skiplistNode
x *skiplistNode
// 存储经过的节点跨度
rank [SKIPLIST_MAXLEVEL]int64
)
var i int
// 1. 逐步降级寻找目标节点,得到 "搜索路径"
for x, i = list.header, list.level-1; i >= 0; i-- {
// 前缀和rank[i]代表 update[i]之前经过的结点个数
if i == list.level-1 {
rank[i] = 0
} else {
rank[i] = rank[i+1]
}
// 比较elem与e 找到当前层比e小的最后一个结点
for ; x.level[i].forward != nil && x.level[i].forward.elem.Less(e); x = x.level[i].forward {
rank[i] += x.level[i].span
}
update[i] = x
}
// 2. 生成新节点高度,超过了当前最大高度需要对高出部分初始化
level := slRandomLevel()
for i = list.level; i < level; i++ {
rank[i] = 0
update[i] = list.header
update[i].level[i].span = list.length
}
if level > list.level {
list.level = level
}
// 3. 创建新节点
x = slCreateNode(level, e)
// 4. 重排向前指针
for i = 0; i < level; i++ {
x.level[i].forward = update[i].level[i].forward
update[i].level[i].forward = x
// 更新跨度
x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
update[i].level[i].span = rank[0] - rank[i] + 1
}
for i = level; i < list.level; i++ {
update[i].level[i].span++
}
// 向后结点更新
if update[0] != list.header {
x.backward = update[0]
}
if x.level[0].forward != nil {
x.level[0].forward.backward = x
} else {
list.tail = x
}
list.length++
}
// 删除一个结点
func (list *Skiplist) slDeleteNode(x *skiplistNode, update [SKIPLIST_MAXLEVEL]*skiplistNode) {
for i := 0; i < list.level; i++ {
// 被删除节点在第i层有节点,则update[i]为被删除节点的前一个节点
if update[i].level[i].forward == x {
// 步长 = 原步长 + 被删除节点步长 - 1(被删除节点)
update[i].level[i].span += x.level[i].span - 1
// 指针越过被删除节点
update[i].level[i].forward = x.level[i].forward
} else {
// 被删除节点在第i层无节点,则 步长 = 原步长 - 1(被删除节点)
update[i].level[i].span -= 1
}
}
if x.level[0].forward != nil {
// 更新被删除节点下一节点的后退指针
x.level[0].forward.backward = x.backward
} else {
list.tail = x.backward
}
for list.level > 1 && list.header.level[list.level-1].forward == nil {
list.level--
}
list.length--
}
// 删除结点
func (list *Skiplist) Deletee(e KV) bool {
var (
// 存储搜索路径
update [SKIPLIST_MAXLEVEL]*skiplistNode
x *skiplistNode
)
x = list.header;
for i := list.level-1; i >= 0; i-- {
for ; x.level[i].forward != nil && x.level[i].forward.elem.Less(e); x = x.level[i].forward {}
update[i] = x;
}
x = x.level[0].forward;
if x!=nil && !x.elem.Less(e) && !e.Less(x.elem) {
list.slDeleteNode(x, update);
return true;
}
return false; /* not found */
}
// 遍历结点
func (list *Skiplist) Range(f func(e KV)) {
for x := list.header.level[0].forward; x != nil; x = x.level[0].forward {
f(x.elem)
}
}
具体用时需要自己实现KV
type A struct {
k, v int
}
func (a A) GetKey() interface{} {
return a.k
}
func (a A) GetValue() interface{} {
return a.v
}
func (a A) Less(b KV) bool {
return a.k<b.GetKey().(int)
}
func TestSkipList(t *testing.T) {
sl := NewSkipList()
sl.Add(A{1,2})
sl.Add(A{5,2})
sl.Add(A{4,2})
sl.Add(A{3,2})
sl.Add(A{1,1})
sl.Add(A{1,2})
sl.Add(A{1,1})
sl.Add(A{1,3})
sum :=0
sl.Range(func(e KV) {
fmt.Printf("%+v ",e)
sum += e.GetKey().(int)
})
fmt.Println("\n",sum)
fmt.Println(sl.Deletee(A{1,2}))
fmt.Println(sl.Deletee(A{1,2}))
fmt.Println(sl.Deletee(A{1,2}))
fmt.Println(sl.Deletee(A{5,2}))
sl.Add(A{5,9})
sl.Range(func(e KV) {
fmt.Printf("%+v ",e)
})
fmt.Println()
}
上面由于有类型转换,比较耗时,可以根据需要直接使用struct 代替interface。
实际做题时由类型转换超时了,使用struct直接访问。
链接:https://leetcode-cn.com/problems/minimize-deviation-in-array/
给你一个由 n 个正整数组成的数组 nums 。
你可以对数组的任意元素执行任意次数的两类操作:
如果元素是 偶数 ,除以 2
例如,如果数组是 [1,2,3,4] ,那么你可以对最后一个元素执行此操作,使其变成 [1,2,3,2]
如果元素是 奇数 ,乘上 2
例如,如果数组是 [1,2,3,4] ,那么你可以对第一个元素执行此操作,使其变成 [2,2,3,4]
数组的 偏移量 是数组中任意两个元素之间的 最大差值 。
返回数组在执行某些操作之后可以拥有的 最小偏移量 。
示例 1:
输入:nums = [1,2,3,4]
输出:1
解释:你可以将数组转换为 [1,2,3,2],然后转换成 [2,2,3,2],偏移量是 3 - 2 = 1
示例 2:
输入:nums = [4,1,5,20,3]
输出:3
解释:两次操作后,你可以将数组转换为 [4,2,5,5,3],偏移量是 5 - 2 = 3
示例 3:
输入:nums = [2,10,8]
输出:3
提示:
n == nums.length
2 <= n <= 10^5
1 <= nums[i] <= 10^9
试想一下如果所有数字都偶数,那我们唯一可操作的就是把最大数字除以2,这样结果才有可能变优。
再来考虑有奇数的情况。我们可以先把所有奇数都乘以2。这样就变成都是偶数的情况。如果有些数字结果需要变成奇数,通过除以2,最终肯定会到达奇数状态。
具体过程,先把所有奇数乘以2变偶数。
每次取最大值,是否为偶数,如果不是就停止算法返回结果。
如果是偶数则除以2,更新最优结果。
这里就需要1个数据结果,他可以让我们快速取得最大值,并且快速更新1个数。
我首先想到的是有序map, 更新值可能通过删除原来的值,再插入新值来实现。
c++中有map, go中的map是无序的。我使用跳表来实现。
C++
typedef long long lld;
class Solution {
public:
int minimumDeviation(vector<int>& nums) {
set<lld> s;
vector<lld> odd; // 记录所有奇数
for(lld n: nums) {
s.insert(n);
if(n&1) odd.push_back(n);
}
sort(odd.begin(), odd.end());
if(s.size()==1)return 0;
lld ans = *s.rbegin() - *s.begin();
// 从小到大奇数变偶数,并更新结果
for(lld n: odd) {
s.erase(n);
s.insert(2*n);
lld t = *s.rbegin() - *s.begin();
ans = min(ans, t);
}
// 最大偶数除以2
for(;(*s.rbegin() &1)==0;) {
lld t = *s.rbegin();
s.erase(*s.rbegin());
s.insert(t>>1);
t = *s.rbegin() - *s.begin();
ans = min(ans, t);
}
return ans;
}
};
GO
import "math/rand"
/*
type KV interface {
GetKey() interface{}
GetValue() interface{}
Less(b KV) bool
}*/
type KV struct {
k int
}
func (a KV) GetValue() int {
return a.k
}
func (a KV) GetKey() int {
return a.k
}
func (a KV) Less(b KV) bool {
return a.k<b.GetKey()
}
const SKIPLIST_MAXLEVEL = 16
/*
以1/4的概率递增,最多32层
*/
func slRandomLevel() int {
level := 1
for (rand.Int() & 0xFFFF) < (0xFFFF >> 2) {
level++
}
if level < SKIPLIST_MAXLEVEL {
return level
}
return SKIPLIST_MAXLEVEL
}
// 利用跳表实现可排序的map
/*跳表节点定义*/
type leveInfo struct {
// 前进指针
forward *skiplistNode
// 跨度
span int64
}
type skiplistNode struct {
// value
elem KV
// 后退指针
backward *skiplistNode
// 层
level []leveInfo
}
type Skiplist struct {
// 跳跃表头指针
header *skiplistNode
tail *skiplistNode
// 表中节点的数量
length int64
// 表中层数最大的节点的层数
level int
}
func slCreateNode(level int, e KV) *skiplistNode {
node := &skiplistNode{
elem: e,
backward: nil,
level: make([]leveInfo, level),
}
return node
}
func NewSkipList() *Skiplist {
list := &Skiplist{
length: 0,
level: 1,
header: slCreateNode(SKIPLIST_MAXLEVEL, KV{0}),
tail: nil,
}
return list
}
/*
添加元素
*/
func (list *Skiplist) Add(e KV) {
var (
// 存储搜索路径
update [SKIPLIST_MAXLEVEL]*skiplistNode
x *skiplistNode
// 存储经过的节点跨度
rank [SKIPLIST_MAXLEVEL]int64
)
var i int
// 1. 逐步降级寻找目标节点,得到 "搜索路径"
for x, i = list.header, list.level-1; i >= 0; i-- {
// 前缀和rank[i]代表 update[i]之前经过的结点个数
if i == list.level-1 {
rank[i] = 0
} else {
rank[i] = rank[i+1]
}
// 比较elem与e 找到当前层比e小的最后一个结点
for ; x.level[i].forward != nil && x.level[i].forward.elem.Less(e); x = x.level[i].forward {
rank[i] += x.level[i].span
}
update[i] = x
}
// 2. 生成新节点高度,超过了当前最大高度需要对高出部分初始化
level := slRandomLevel()
for i = list.level; i < level; i++ {
rank[i] = 0
update[i] = list.header
update[i].level[i].span = list.length
}
if level > list.level {
list.level = level
}
// 3. 创建新节点
x = slCreateNode(level, e)
// 4. 重排向前指针
for i = 0; i < level; i++ {
x.level[i].forward = update[i].level[i].forward
update[i].level[i].forward = x
// 更新跨度
x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
update[i].level[i].span = rank[0] - rank[i] + 1
}
for i = level; i < list.level; i++ {
update[i].level[i].span++
}
// 向后结点更新
if update[0] != list.header {
x.backward = update[0]
}
if x.level[0].forward != nil {
x.level[0].forward.backward = x
} else {
list.tail = x
}
list.length++
}
// 删除一个结点
func (list *Skiplist) slDeleteNode(x *skiplistNode, update [SKIPLIST_MAXLEVEL]*skiplistNode) {
for i := 0; i < list.level; i++ {
// 被删除节点在第i层有节点,则update[i]为被删除节点的前一个节点
if update[i].level[i].forward == x {
// 步长 = 原步长 + 被删除节点步长 - 1(被删除节点)
update[i].level[i].span += x.level[i].span - 1
// 指针越过被删除节点
update[i].level[i].forward = x.level[i].forward
} else {
// 被删除节点在第i层无节点,则 步长 = 原步长 - 1(被删除节点)
update[i].level[i].span -= 1
}
}
if x.level[0].forward != nil {
// 更新被删除节点下一节点的后退指针
x.level[0].forward.backward = x.backward
} else {
list.tail = x.backward
}
for list.level > 1 && list.header.level[list.level-1].forward == nil {
list.level--
}
list.length--
}
// 删除结点
func (list *Skiplist) Deletee(e KV) bool {
var (
// 存储搜索路径
update [SKIPLIST_MAXLEVEL]*skiplistNode
x *skiplistNode
)
x = list.header;
for i := list.level-1; i >= 0; i-- {
for ; x.level[i].forward != nil && x.level[i].forward.elem.Less(e); x = x.level[i].forward {}
update[i] = x;
}
x = x.level[0].forward;
if x!=nil && !x.elem.Less(e) && !e.Less(x.elem) {
list.slDeleteNode(x, update);
return true;
}
return false; /* not found */
}
// 遍历结点
func (list *Skiplist) Range(f func(e KV)) {
for x := list.header.level[0].forward; x != nil; x = x.level[0].forward {
f(x.elem)
}
}
var cnt =0
func minimumDeviation(nums []int) int {
cnt++
// if cnt==76{return 0}
m:=map[int]int{}
odd := []int{};
for _, n:= range nums {
m[n]=1;
}
sl := NewSkipList()
// 筛选出所有奇数
for k:= range m {
sl.Add(KV{k})
if (k&1)>0 {
odd = append(odd, k)
}
}
sort.Ints(odd); // 排序奇数
if len(m)==1 {return 0;}
ans := sl.tail.elem.GetKey()- sl.header.level[0].forward.elem.GetKey()
// 从小到大更新奇数,并更新最优解
for _, n:=range odd{
sl.Deletee(KV{n})
sl.Add(KV{2*n})
t := sl.tail.elem.GetKey() - sl.header.level[0].forward.elem.GetKey()
if t< ans {
ans = t
}
}
// 对最大偶数除以2,并更新结果
for s :=sl.tail.elem.GetKey() ;(s&1)==0; s =sl.tail.elem.GetKey() {
sl.Deletee(KV{s})
sl.Add(KV{s>>1})
t := sl.tail.elem.GetKey() - sl.header.level[0].forward.elem.GetKey()
if t< ans {
ans = t
}
}
return ans;
}
前几次超时了,是因为用了接口转int的语句比较耗时,直接改成结构做就好了。
本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。