常见算法之Hash

155 阅读4分钟

基础知识

哈希表是一种常见的数据结构,在解决算法面试题的时候经常需要用到哈希表。哈希表最大的优点是高效,在哈希表中插入、删除或查找一个元素都只需要O(1)的时间。因此,哈希表经常被用来优化时间效率。

哈希表为何如此高效呢,内部结构又是什么呢? 想要在存入和读取中的时间复杂度为O(1),数组结构是满足的,只要知道数组的下标,就可以用o(1)的时间存入和读取。实际上哈希表内部是一个数组加链表的结构。将哈希的key值进行一个hash函数计算得到一个数组下标值。hash函数的本质是一个求余计算,以数组的长度求余。如果出现哈希值对撞,那么就以链表进行向链表后面追加元素。如果链表过长,那么也会降低哈希操作的效率。所以当哈希表中元素的数目与数组长度的比值超过某一阈值时,就对数组进行扩容(通常让数组的长度翻倍),然后把哈希表中的每个元素根据新的数组大小求余数并存入合适的位置。

常见算法

一、有效的变位词

题目:给定两个字符串s和t,请判断它们是不是一组变位词。在一组变位词中,它们中的字符及每个字符出现的次数都相同,但字符的顺序不能相同。例如,"anagram"和"nagaram"就是一组变位词。

解题思路:

  • 将第一组单词的每个字符作为hash表的key,字符每出现一次就加一
  • 遍历第二组单词的每个字符,在hash表中减一
  • 如果hash表中的value全部为0,证明为变位符

Golang代码

func isAnagram(s string, t string) bool {
   memo := make(map[int32]int)

   if s == t {
      return false
   }

   for _, item := range s {
      memo[item]++
   }

   for _, item := range t {
      memo[item]--
   }

   for _, item := range memo {
      if item != 0 {
         return false
      }
   }

   return true
}

二、外星语言是否排序

题目:有一门外星语言,它的字母表刚好包含所有的英文小写字母,只是字母表的顺序不同。给定一组单词和字母表顺序,请判断这些单词是否按照字母表的顺序排序。例如,输入一组单词["offer","is","coming"],以及字母表顺序"zyxwvutsrqponmlkjihgfedcba",由于字母'o'在字母表中位于'i'的前面,因此单词"offer"排在"is"的前面;同样,由于字母'i'在字母表中位于'c'的前面,因此单词"is"排在"coming"的前面。因此,这一组单词是按照字母表顺序排序的,应该输出true。

解题思路:

  • 将新的字母表遍历生成一个hash表,key为字符,value为字母表顺序的index
  • 逐个对比当前单词的字母是否在下一个单词的字母前面

Golang代码

func isAlienSorted(words []string, order string) bool {
   memo := make(map[uint8]int)

   for i := 0; i < len(order); i++ {
      memo[order[i]] = i
   }

   for i := 0; i < len(words) - 1; i++ {
      if !compareTwo(words[i], words[i+1], memo) {
         //fmt.Println(words[i], words[i+1])
         return false
      }
   }

   return true
}

func compareTwo(w1, w2 string, memo map[uint8]int) bool {

   for i := 0; i < len(w1); i++ {
      if i > len(w2) - 1 {
         return false
      }

      if memo[w1[i]] > memo[w2[i]] {
         return false
      }

      if memo[w1[i]] < memo[w2[i]] {
         return true
      }
   }

   return true
}

三、最小时间差

题目:给定一组范围在00:00至23:59的时间,求任意两个时间之间的最小时间差。例如,输入时间数组["23:50","23:59","00:00"],"23:59"和"00:00"之间只有1分钟的间隔,是最小的时间差。

解题思路:

  • 获取一个hash,key为每分钟的时间字符串,value为整数
  • 遍历时间数组,对应hash[key]++
  • 遍历hash,出现value大于1的,说明有重复时间点,返回0。遍历每个点时间差,取最小值。另外,第一个时间点和最后一个时间,分别离0和1440的距离的和,也参与比较。

Golang代码

func findMinDifference(timePoints []string) int {
   memo, list := getTimeHash()

   for _, item := range timePoints {
      memo[item]++
   }

   diff := 1440
   left := -1
   var (
      first int
      last int
   )

   for i := 0; i < len(list); i++ {

      if memo[list[i]] > 1 {
         return 0
      }

      if memo[list[i]] > 0 {
         if left < 0 {
            first = i
            left = i
         } else {
            if diff > i - left {
               diff  = i - left
            }
            left = i
         }
         last = i
      }
   }

   tmp := first + (1440 - last)

   if tmp < diff {
      diff = tmp
   }


   return diff
}

func getTimeHash() (map[string]int, []string) {

   memo := make(map[string]int)
   list := make([]string, 0)
   var tmp string

   for i := 0; i < 24; i++ {
      for j := 0; j < 60; j++ {
         tmp = fmt.Sprintf("%02d", i) + ":" + fmt.Sprintf("%02d", j)
         memo[tmp] = 0
         list = append(list, tmp)
      }
   }

   return memo, list
}