使用Go自带的container/heap实现最小时间差计算

本文介绍了一种利用优先队列解决寻找最短时间差问题的方法。通过将时间点转换为分钟数,并利用堆结构进行排序,实现了高效查找任意两时间点间的最短分钟差。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

container/heap简介

container/heap提供了具有堆序性质的基本框架,只需要实现响应的接口,便可获得一个优先队列

接口如下:

type Interface interface {
   sort.Interface
   Push(x interface{}) // add x as element Len()
   Pop() interface{}   // remove and return element Len() - 1.
}

其中,sort.Interface接口如下

type Interface interface {
   // Len is the number of elements in the collection.
   Len() int
   // Less reports whether the element with
   // index i should sort before the element with index j.
   Less(i, j int) bool
   // Swap swaps the elements with indexes i and j.
   Swap(i, j int)
}

实践

Given a list of 24-hour clock time points in "Hour:Minutes" format, find the minimum minutes difference between any two time points in the list.

Example 1:

Input: ["23:59","00:00"]
Output: 1

Note:

  1. The number of time points in the given list is at least 2 and won't exceed 20000.
  2. The input time is legal and ranges from 00:00 to 23:59.

分析:求最短时间差,也就是说把00:00到23:59的分钟数从[0,24*60-1]的顺序排列起来并首尾相接,最短时间差只能出现在相邻的两个时间点;

相邻的两个时间点之差有两种情况:

1.两个点都在一天内:从小时间点到大时间点,next-pre<12*60,也就是小于[0,24*60-1]组成的圈的一半,此值可能为最小值

2.两个点跨越00:00,小值在大值的后一天:24*60+pre-next可能为最小值

因此我们可以先把给定的时间排序,然后依次求出相邻两个时间点之间最小差值,返回最小值即可

实现步骤就是,先实现一个优先队列,将数据入队,然后依次出队并计算差值

代码

type StrHeap []string // 使用字符串切片来存储数据,并实现heap的相关接口

func (h StrHeap) Len() int           { return len(h) }
func (h StrHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h StrHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *StrHeap) Push(x interface{}) {
   *h = append(*h, x.(string))
}

func (h *StrHeap) Pop() interface{} {
   old := *h
   n := len(old)
   x := old[n-1]
   *h = old[0: n-1]
   return x
}

func findMinDifference(timePoints []string) int {
   h := StrHeap(timePoints) 
   heap.Init(&h) // 初始化堆
   ret := 24 * 60 
   pre := timeToInt(heap.Pop(&h).(string))
   first:=pre
   next:=24 * 60
   for h.Len() > 0 {
      // 依次取出每个值,求差
     next = timeToInt(heap.Pop(&h).(string))
      tmp := next - pre
      if tmp>12*60{
         tmp=24*60-tmp
      }
      if tmp < ret {
         ret = tmp
      }
      pre = next
   }
   // 最值有可能为首尾的差
  tmp := next - first
   if tmp>12*60{
      tmp=24*60-tmp
   }
   if tmp < ret {
      ret = tmp
   }
   return ret
}

//将字符串时间转换为分钟数,用以计算差值
func timeToInt(t string) int {
   fmt.Println(t)
   arr := strings.Split(t, ":")
   hour, _ := strconv.Atoi(arr[0])
   minute, _ := strconv.Atoi(arr[1])
   return hour*60 + minute
}

测试

func TestFindMinDifference(t *testing.T) {
   arr := []string{"08:01", "00:17", "14:12", "12:44", "06:06"}
   ret := findMinDifference(arr)
   fmt.Println(ret)
}
<think>我们使用Go标准库中的container/heap包来实现一个堆(优先队列)。堆是一种完全二叉树,通常用于实现优先队列。在Go中,heap包提供了堆操作的接口,我们需要实现这个接口来使用堆。 heap.Interface接口定义如下: ```go type Interface interface { sort.Interface Push(x interface{}) // 将x添加为元素Len() Pop() interface{} // 移除并返回元素Len()-1 } ``` 而sort.Interface包含: ```go type Interface interface { Len() int Less(i, j int) bool // 决定是最大堆还是最小堆 Swap(i, j int) } ``` 因此,我们需要定义一个类型(比如一个切片),并实现这五个方法:Len, Less, Swap, Push, Pop。 ### 示例:整数最小堆 下面是一个整数最小堆的示例。最小堆即根节点是最小值。 ```go package main import ( "container/heap" "fmt" ) // 定义一个类型,这里使用整数切片 type IntHeap []int // 实现sort.Interface接口 func (h IntHeap) Len() int { return len(h) } func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } // 最小堆,所以使用小于号 func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } // 实现heap.Interface的Push方法 func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) // 将x添加到堆中 } // 实现heap.Interface的Pop方法 func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] // 取出最后一个元素 *h = old[0 : n-1] // 修改切片长度 return x } func main() { h := &IntHeap{2, 1, 5} // 初始化一个堆 heap.Init(h) // 初始化堆,调整堆结构 heap.Push(h, 3) // 插入新元素 fmt.Printf("最小值: %d\n", (*h)[0]) // 最小堆的根是最小值,但不一定是唯一的最小值,但堆保证根是最小 for h.Len() > 0 { fmt.Printf("%d ", heap.Pop(h)) // 依次弹出最小值 } } ``` 输出: ``` 最小值: 1 1 2 3 5 ``` ### 示例:最大堆 如果要实现最大堆,只需修改Less方法: ```go func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] } // 最大堆,使用大于号 ``` ### 示例:使用堆排序 实际上,我们可以利用堆来进行排序(堆排序)。上面的示例中,Pop操作每次弹出最小值(对于最小堆)或最大值(对于最大堆),因此依次弹出就得到了有序序列。 ### 示例:自定义类型堆 假设我们有一个任务结构体,每个任务有优先级,我们按照优先级高低(优先级值越小越优先)来处理任务。 ```go package main import ( "container/heap" "fmt" ) type Task struct { priority int name string } // 定义一个任务堆 type TaskHeap []Task func (h TaskHeap) Len() int { return len(h) } func (h TaskHeap) Less(i, j int) bool { // 优先级的数值越小,优先级越高 return h[i].priority < h[j].priority } func (h TaskHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *TaskHeap) Push(x interface{}) { *h = append(*h, x.(Task)) } func (h *TaskHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } func main() { tasks := &TaskHeap{ {priority: 3, name: "Task3"}, {priority: 1, name: "Task1"}, {priority: 2, name: "Task2"}, } heap.Init(tasks) heap.Push(tasks, Task{priority: 4, name: "Task4"}) heap.Push(tasks, Task{priority: 0, name: "Task0"}) // 最高优先级 // 按优先级顺序处理任务 for tasks.Len() > 0 { task := heap.Pop(tasks).(Task) fmt.Printf("处理任务: %s (优先级: %d)\n", task.name, task.priority) } } ``` 输出: ``` 处理任务: Task0 (优先级: 0) 处理任务: Task1 (优先级: 1) 处理任务: Task2 (优先级: 2) 处理任务: Task3 (优先级: 3) 处理任务: Task4 (优先级: 4) ``` ### 注意事项 1. 在初始化堆时,必须调用`heap.Init(h)`来调整堆结构,否则可能不是合法的堆。 2. 使用`heap.Push`和`heap.Pop`(而不是自定义类型的Push和Pop方法)来操作堆,因为这两个函数会维护堆的性质。 3. 在自定义类型的堆中,Pop方法通常是从末尾弹出,但堆内部会通过调整来保证堆顶元素是最值。 通过以上示例,你应该可以掌握在Go使用container/heap包的方法。根据实际需求,你可以定义不同的类型并实现堆接口。 ### 相关问题 1. 如何实现一个固定大小的堆(比如只保留最大的k个元素)? 2. 在堆中如何更新一个元素的值并重新调整堆? 3. 如何避免在堆操作中使用反射(interface{})来提升性能?</think># Go语言 container/heap使用详解 在Go语言中,`container/heap`包提供了堆数据结构的实现,可用于实现优先队列。堆是一种特殊的树形数据结构,通常用于高效获取集合中的最大或最小元素。 ## 堆的基本概念 堆是一种满足以下性质的完全二叉树: - **最小堆**:每个节点的值都小于或等于其子节点的值(根节点最小) - **最大堆**:每个节点的值都大于或等于其子节点的值(根节点最大) 在Go中,堆的插入和删除操作的时间复杂度为$O(\log n)$,获取最小/最大元素的时间复杂度为$O(1)$[^2]。 ## 实现堆接口 要使用`container/heap`包,需要实现以下接口: ```go type Interface interface { sort.Interface Push(x interface{}) // 添加元素 Pop() interface{} // 移除并返回元素 } ``` 其中`sort.Interface`包含: ```go Len() int Less(i, j int) bool // 决定最小堆或最大堆 Swap(i, j int) ``` ## 最小堆示例 ```go package main import ( "container/heap" "fmt" ) // 定义整数堆类型 type IntHeap []int // 实现sort.Interface func (h IntHeap) Len() int { return len(h) } func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } // 最小堆 func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } // 实现heap.Interface的Push方法 func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) } // 实现heap.Interface的Pop方法 func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } func main() { h := &IntHeap{2, 1, 5} heap.Init(h) // 初始化堆 heap.Push(h, 3) // 插入新元素 fmt.Printf("最小值: %d\n", (*h)[0]) // 查看最小值 // 依次弹出最小值 for h.Len() > 0 { fmt.Printf("%d ", heap.Pop(h)) } // 输出: 1 2 3 5 } ``` ## 最大堆示例 只需修改Less方法即可实现最大堆: ```go func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] // 最大堆 } // 使用相同代码,输出将是: 5 3 2 1 ``` ## 自定义类型堆示例 ```go type Item struct { value string priority int } type PriorityQueue []*Item func (pq PriorityQueue) Len() int { return len(pq) } // 优先级高的先出(最大堆) func (pq PriorityQueue) Less(i, j int) bool { return pq[i].priority > pq[j].priority } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { item := x.(*Item) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } func main() { pq := make(PriorityQueue, 0) heap.Init(&pq) heap.Push(&pq, &Item{value: "紧急任务", priority: 3}) heap.Push(&pq, &Item{value: "普通任务", priority: 1}) heap.Push(&pq, &Item{value: "高优先级", priority: 5}) for pq.Len() > 0 { item := heap.Pop(&pq).(*Item) fmt.Printf("%s (优先级: %d)\n", item.value, item.priority) } // 输出: // 高优先级 (优先级: 5) // 紧急任务 (优先级: 3) // 普通任务 (优先级: 1) } ``` ## 堆操作注意事项 1. **初始化堆**:使用堆前必须调用`heap.Init(h)`初始化堆结构 2. **操作函数**:使用`heap.Push()`和`heap.Pop()`而非直接调用类型的Push/Pop方法 3. **性能特点**: - 插入操作$O(\log n)$ - 删除操作$O(\log n)$ - 获取最小/最大值$O(1)$ 4. **适用场景**:优先队列、Dijkstra算法、堆排序等 ## 固定大小堆实现 ```go type FixedSizeHeap struct { heap []int size int maxSize int } // 实现heap.Interface方法... func (h *FixedSizeHeap) Push(x interface{}) { if h.size < h.maxSize { h.heap = append(h.heap, x.(int)) h.size++ } else if x.(int) > h.heap[0] { // 对于最大堆 h.heap[0] = x.(int) heap.Fix(h, 0) } } // 使用示例:保留最大的10个元素 h := &FixedSizeHeap{maxSize: 10} heap.Init(h) ``` ## 常见问题解答 **Q: 堆和普通队列有什么区别?** A: 普通队列是先进先出(FIFO),而堆是按照优先级出队,最高优先级的元素总是先出队。 **Q: 什么时候应该使用堆而不是平衡二叉搜索树?** A: 当只需要访问最小或最大元素而不需要完全有序遍历时,堆是更好的选择,因为它的实现更简单且常数因子更小。 **Q: 堆排序如何实现?** A: 堆排序分为两步: 1. 将数组构建为堆:$O(n)$ 2. 重复提取根节点并调整堆:$O(n \log n)$ 总时间复杂度$O(n \log n)$ ## 相关问题 1. 如何实现堆排序算法? 2. 在Dijkstra算法中如何使用堆优化性能? 3. 堆与平衡二叉搜索树(BST)在性能和应用场景上有何区别? 4. 如何实现一个支持元素更新的优先队列? 5. Gocontainer/heap包在并发场景下是否安全?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值