哈希表的设计

30:插入、删除和随机访问都是O(1)的容器

:::info 题目:设计一个数据结构,使如下3个操作的时间复杂度都是O(1)。
● insert(value):如果数据集中不包含一个数值,则把它添加到数据集中。
● remove(value):如果数据集中包含一个数值,则把它删除。
● getRandom():随机返回数据集中的一个数值,要求数据集中每个数字被返回的概率都相同。 :::

  1. public class RandomSet() {
  2. private Map<Integer, Integer> numToLoc = new HashMap<>();
  3. private ArrayList<Integer> nums = new ArrayList();
  4. public boolean insert(Integer val) {
  5. if (numToLoc.containsKey(val)) {
  6. return false;
  7. }
  8. nums.add(val);
  9. numToLoc.put(val, nums.size());
  10. return true;
  11. }
  12. public boolean remove(Integer val) {
  13. if (!numToLoc.containsKey(val)) {
  14. return false;
  15. }
  16. int location = numToLoc.get(val);
  17. // 将最后一个数字换到当前位置
  18. numToLoc.put(nums.get(nums.size() - 1), location);
  19. nums.set(location - 1, nums.get(nums.size() - 1));
  20. // 删除数组最后一个值
  21. nums.remove(nums.size() - 1);
  22. // 从map删除val
  23. numToLoc.remove(val);
  24. return true;
  25. }
  26. @Override
  27. public String toString() {
  28. return "RandomSet{" +
  29. "nums=" + nums +
  30. '}';
  31. }
  32. public Integer getRandomVal() {
  33. Random random = new Random();
  34. int randomLoc = random.nextInt(nums.size());
  35. return nums.get(randomLoc);
  36. }
  37. }

31:最近最少使用缓存

:::info 题目:请设计实现一个最近最少使用(Least Recently Used,LRU)缓存,要求如下两个操作的时间复杂度都是O(1)。
● get(key):如果缓存中存在键key,则返回它对应的值;否则返回-1。
● put(key,value):如果缓存中之前包含键key,则它的值设为value;否则添加键key及对应的值value。在添加一个键时,如果缓存容量已经满了,则在添加新键之前删除最近最少使用的键(缓存中最长时间没有被使用过的元素)。 :::

  1. public LRUCache {
  2. private Map<Integer, Node> map = new HashMap<>();
  3. int capacity;
  4. Node head;
  5. Node tail;
  6. public LRUCache(int capacity) {
  7. this.capacity = capacity;
  8. // 初始化哨兵头尾节点
  9. // 有了哨兵节点,在增删真实节点时,就可以不用考虑头尾的特殊处理
  10. Node dummyHead = new Node(0, 0);
  11. Node dummyTail = new Node(0, 0);
  12. head = dummyHead;
  13. tail = dummyTail;
  14. head.next = tail;
  15. tail.prev = head;
  16. }
  17. public int get(int key) {
  18. if (map.containsKey(key)) {
  19. moveToTail(map.get(key), map.get(key).val);
  20. return map.get(key).val;
  21. }
  22. return -1;
  23. }
  24. public void put(int key, int value) {
  25. if (map.containsKey(key)) {
  26. map.get(key).val = value;
  27. moveToTail(map.get(key), value);
  28. }
  29. Node node = new Node(key, value);
  30. if (map.size() < capacity) {
  31. insertTail(node);
  32. } else {
  33. map.remove(head.next.key);
  34. deleteNode(head.next);
  35. insertTail(node);
  36. }
  37. map.put(key, node);
  38. }
  39. private void deleteNode(Node node) {
  40. node.prev.next = node.next;
  41. node.next.prev = node.prev;
  42. }
  43. private void insertTail(Node node) {
  44. tail.prev.next = node;
  45. node.prev = tail.prev;
  46. node.next = tail;
  47. tail.prev = node;
  48. }
  49. private void moveToTail(Node node, int newVal) {
  50. // 更新node值
  51. node.val = newVal;
  52. // 先从链表中删除node
  53. this.deleteNode(node);
  54. // insert
  55. this.insertTail(node);
  56. }
  57. private class Node {
  58. public Node prev;
  59. public Node next;
  60. public int key;
  61. public int val;
  62. public Node(int key, int val) {
  63. this.key = key;
  64. this.val = val;
  65. }
  66. }
  67. @Override
  68. public String toString() {
  69. StringBuilder stringBuilder = new StringBuilder();
  70. Node p = head.next;
  71. while (p != tail) {
  72. stringBuilder.append("{").append(p.key).append(",").append(p.val).append("}");
  73. if (p.next != tail) {
  74. stringBuilder.append(" -> ");
  75. }
  76. p = p.next;
  77. }
  78. return stringBuilder.toString();
  79. }
  80. public static void main(String[] args) {
  81. LRUCache lruCache = new LRUCache(5);
  82. System.out.println(lruCache);
  83. System.out.println(lruCache.get(1));
  84. System.out.println(lruCache);
  85. lruCache.put(8, 8);
  86. lruCache.put(9, 9);
  87. lruCache.get(8);
  88. System.out.println(lruCache);
  89. lruCache.put(1, 1);
  90. lruCache.put(2, 2);
  91. lruCache.put(3, 3);
  92. lruCache.put(4, 4);
  93. lruCache.put(5, 5);
  94. System.out.println(lruCache);
  95. lruCache.get(1);
  96. System.out.println(lruCache);
  97. lruCache.put(6, 6);
  98. System.out.println(lruCache);
  99. }
  100. }

哈希表的应用

32:有效的变位词

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

  1. public class Anagram {
  2. public boolean isAnagram(String s1, String s2) {
  3. if(s1.lenth() != s2 || s1.equals(s2)) {
  4. false;
  5. }
  6. Map<Character, Integer> map = HashMap<>();
  7. for(char c : s1.toCharArray()) {
  8. map.put(c, map.getOrDefault(c, 0) + 1);
  9. }
  10. for(char c : s2.toCharArray()) {
  11. if(!map.containsKey(c) || map.get(c) == 0) {
  12. return false;
  13. }
  14. map.put(c, map.get(c) - 1);
  15. }
  16. return true;
  17. }
  18. }

33:变位词组

:::info 题目:给定一组单词,请将它们按照变位词分组。例如,输入一组单词[“eat”,”tea”,”tan”,”ate”,”nat”,”bat”],这组单词可以分成3组,分别是[“eat”,”tea”,”ate”]、[“tan”,”nat”]和[“bat”]。假设单词中只包含英文小写字母。 :::

  1. public GroupAnagram {
  2. public static List<List<String>> groupAnagram(List<String> strs) {
  3. Map<String, List<String>> groups = new HashMap<>();
  4. for (String str : strs) {
  5. char[] arr = str.toCharArray();
  6. Arrays.sort(arr);
  7. String sortedStr = new String(arr);
  8. if (groups.containsKey(sortedStr)) {
  9. groups.get(sortedStr).add(str);
  10. } else {
  11. List<String> list = new ArrayList<>();
  12. list.add(str);
  13. groups.put(sortedStr, list);
  14. }
  15. }
  16. return new ArrayList<>(groups.values());
  17. }
  18. public static void main(String[] args) {
  19. List<String> strs = Arrays.asList("eat","tea","tan","ate","nat","bat");
  20. System.out.println(groupAnagram(strs));
  21. }
  22. }

34:外星语言是否排序

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

  1. public class isAlienSorted {
  2. public static boolean isAlienSorted(List<String> strs, String order) {
  3. int[] orderArr = new int[26];
  4. for (int i = 0; i < order.length(); i++) {
  5. // i 就是当前字母的顺序
  6. orderArr[order.charAt(i) - 'a'] = i;
  7. }
  8. for (int i = 1; i < strs.size(); i++) {
  9. boolean isSorted = isWordSorted(strs.get(i - 1), strs.get(i), orderArr);
  10. if (!isSorted) {
  11. return false;
  12. }
  13. }
  14. return true;
  15. }
  16. private static boolean isWordSorted(String word1, String word2, int[] orderArr) {
  17. for (int i = 0; i < word1.length() && i < word2.length(); i++) {
  18. char ch1 = word1.charAt(i);
  19. char ch2 = word2.charAt(i);
  20. if (orderArr[ch1 - 'a'] < orderArr[ch2 - 'a']) {
  21. return true;
  22. }
  23. if (orderArr[ch1 - 'a'] > orderArr[ch2 - 'a']) {
  24. return false;
  25. }
  26. }
  27. // 如果前序字母都相同,则短的单词排前面
  28. return word1.length() <= word2.length();
  29. }
  30. public static void main(String[] args) {
  31. List<String> strs = Arrays.asList("offer", "is", "coming");
  32. System.out.println(isAlienSorted(strs, "zyxwvutsrqponmlkjihgfedcba"));
  33. }
  34. }

35:最小时间差

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

  1. public class MinDifference {
  2. public static int findMinDifference(String[] strs) {
  3. // 将时间转换成从秒数,然后计入hash表
  4. boolean[] timeTable = new boolean[1440];
  5. for(String str : strs) {
  6. String[] arr = str.spilit(":")
  7. int count = Integer.parse(arr[0]) * 60 + Integer.parse(arr[1]);
  8. if(timeTable[count]) {
  9. // 出现相同的两个时间,那最小时间差就是0;
  10. return 0;
  11. }
  12. timeTable[count] = true;
  13. }
  14. // 遍历 timeTable 计算最小时间差
  15. int prev = -1;
  16. int first = Integer.MAX_VALUE;
  17. int last = -1;
  18. int minDiff = Integer.MAX_VALUE;
  19. for(int i = 0; i < timeTable.size(); i++) {
  20. if(timeTable[i]) {
  21. if(prev >= 0 ) {
  22. minDiff = Math.min(minDiff, i - prev);
  23. }
  24. prev = i;
  25. first = Math.min(first, i);
  26. last = Math.max(last, i;)
  27. }
  28. }
  29. return Math.min(1440-last+first, minDiff);
  30. }
  31. }