Skip to content

HOT100

哈希

P1 两数之和

java
public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        if (map.containsKey(target - nums[i])) {
            return new int[] {map.get(target - nums[i]), i};
        }
        map.put(nums[i], i);
    }
    return new int[0];
}

P49 字母异位词分组

java
public List<List<String>> groupAnagrams(String[] strs) {
    Map<String, List<String>> map = new HashMap<>();
    for (String str : strs) {
        char[] cnt = new char[26];
        for (int i = 0; i < str.length(); i++) {
            cnt[str.charAt(i) - 'a']++;
        }
        String temp = new String(cnt);
        if (!map.containsKey(temp)) {
            map.put(temp, new ArrayList<>());
        }
        map.get(temp).add(str);
    }
    return new ArrayList<>(map.values());
}

P128 最长连续序列

解法一

  • 使用 set 去重
  • 遍历集合,如果元素是区间的第一个元素,即 set.contains(num - 1),则循环获取区间长度
  • 由于每个元素只会遍历一次,因此时间复杂度为 O(n)
java
public int longestConsecutive(int[] nums) {
    Set<Integer> set = new HashSet<>();
    for (int num : nums) {
        set.add(num);
    }

    int max = 0;
    for (int num : set) {
        if (!set.contains(num - 1)) {
            int currentNum = num;
            int count = 1;

            while (set.contains(currentNum + 1)) {
                currentNum += 1;
                count += 1;
            }

            max = Math.max(max, count);
        }
    }

    return max;
}

解法二:动态规划

java
public int longestConsecutive(int[] nums) {
    // 表示 key 包含在区间中的最长长度
    HashMap<Integer, Integer> map = new HashMap<>();
    int max = 0;
    for (int num : nums) {
        // 不存在哈希表中,说明num第一次出现,因此num-1必为右端点,num+1必为左端点
        if (!map.containsKey(num)) {
            Integer leftLen = map.getOrDefault(num - 1, 0);
            Integer rightLen = map.getOrDefault(num + 1, 0);

            int length = 1 + leftLen + rightLen;
            max = Math.max(length, max);

            map.put(num, length);
            map.put(num - leftLen, length);
            map.put(num + rightLen, length);
        }
    }

    return max;
}

双指针

P283 移动零

java
public void moveZeroes(int[] nums) {
    int left = 0;
    int right = 0;

    while (right < nums.length) {
        if (nums[right] != 0) {
            nums[left++] = nums[right++];
        } else {
            right++;
        }
    }
    while (left < nums.length) {
        nums[left++] = 0;
    }
}

P11 盛水最多的容器

java
public int maxArea(int[] height) {
    int left = 0, right = height.length - 1;
    int max = 0;
    while (left < right) {
        int area = Math.min(height[left], height[right]) * (right - left);
        max = Math.max(max, area);
        if (height[left] < height[right]) {
            left++;
        } else {
            right--;
        }
    }
    return max;
}

P15 三数之和

  • 排序数组
  • 固定 first,枚举 second,由于数组升序,当 second 不断增加,third 会不断减少,由于单调性,固定 first 时,second ~ n 每个元素只会遍历一次,因此时间复杂度为 O(n^2)
  • 为了保证不重复,枚举的值需要与前一个值不同
java
public List<List<Integer>> threeSum(int[] nums) {
    int n = nums.length;
    Arrays.sort(nums);
    List<List<Integer>> ans = new ArrayList<>();
    // 枚举 a
    for (int first = 0; first < n; ++first) {
        // 需要和上一次枚举的数不相同
        if (first > 0 && nums[first] == nums[first - 1]) {
            continue;
        }
        // c 对应的指针初始指向数组的最右端
        int third = n - 1;
        int target = -nums[first];
        // 枚举 b
        for (int second = first + 1; second < n; ++second) {
            // 需要和上一次枚举的数不相同
            if (second > first + 1 && nums[second] == nums[second - 1]) {
                continue;
            }
            // 需要保证 b 的指针在 c 的指针的左侧
            while (second < third && nums[second] + nums[third] > target) {
                --third;
            }
            if (second == third) {
                break;
            }
            if (nums[second] + nums[third] == target) {
                List<Integer> list = new ArrayList<>();
                list.add(nums[first]);
                list.add(nums[second]);
                list.add(nums[third]);
                ans.add(list);
            }
        }
    }
    return ans;
}

P42 接雨水

java
public int trap(int[] height) {
    int n = height.length;
    int left = 0, right = n - 1;
    int leftMax = height[left], rightMax = height[right];
    left++;
    right--;
    int sum = 0;
    // 计算每一列能存储的雨水相加
    while (left <= right) {
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if (leftMax < rightMax) {
            sum += leftMax - height[left++];
        } else {
            sum += rightMax - height[right--];
        }
    }
    return sum;
}

滑动窗口

P3 无重复字符的最长子串

java
public int lengthOfLongestSubstring(String s) {
    int[] cnt = new int[258];
    int ans = 0;
    int left = 0, right = 0;
    while (right < s.length()) {
        char c = s.charAt(right);
        cnt[c]++;
        if (cnt[c] > 1) {
            ans = Math.max(right - left, ans);
            while (cnt[c] > 1) {
                cnt[s.charAt(left++)]--;
            }
        }
        right++;
    }
    ans = Math.max(right - left, ans);
    return ans;
}

P438 找到字符串中所有字母异位词

java
public List<Integer> findAnagrams(String s, String p) {
    if(s.length() < p.length()) return new ArrayList<>();
    int[] cnt = new int[26];
    for (char c : p.toCharArray()) {
        cnt[c - 'a']++;
    }
    int[] counts = new int[26];

    int left = 0, right = p.length();
    for (int i = 0; i < p.length(); i++) {
        counts[s.charAt(i) - 'a']++;
    }
    List<Integer> ans = new ArrayList<>();
    while (right < s.length()) {
        if (Arrays.equals(counts, cnt)) {
            ans.add(left);
        }
        counts[s.charAt(left++) - 'a']--;
        counts[s.charAt(right++) - 'a']++;
    }
    if(Arrays.equals(counts, cnt)) ans.add(left);
    return ans;
}
java
public List<Integer> findAnagrams(String s, String p) {
    List<Integer> res = new ArrayList<>();

    int[] need = new int[26];
    int[] windows = new int[26];
    int needNum = 0;

    for (int i = 0; i < p.length(); i++) {
        int idx = p.charAt(i) - 'a';
        if (need[idx] == 0)
            needNum++;
        need[idx]++;
    }
    // aabaa  ab
    int count = 0;
    int left = 0, right = 0;

    while (right < s.length()) {
        int idx_r = s.charAt(right) - 'a';
        windows[idx_r]++;
        if (need[idx_r] == windows[idx_r]) {
            count++;
        }
        while (right - left + 1 >= p.length()) {
            if (count == needNum) {
                res.add(left);
            }
            int idx_l = s.charAt(left) - 'a';
            if (need[idx_l] == windows[idx_l]) {
                count--;
            }
            windows[idx_l]--;
            left++;
        }
        right++;
    }
    return res;
}

子串

和为 k 的子数组

解法:利用前缀和性质得到公式 pre[j - 1] = pre[i] - k,利用哈希表存储前缀和的个数即可

java
public int subarraySum(int[] nums, int k) {
    // key: 前缀和 value: 下标个数
    Map<Integer, Integer> map = new HashMap<>();
    int count = 0, sum = 0;
    map.put(0, 1);

    // pre[i] - pre[j - 1] = k
    // pre[j - 1] = sum - k;
    for (int num : nums) {
        sum += num;
        if (map.containsKey(sum - k)) {
            count += map.get(sum - k);
        }
        map.put(sum, map.getOrDefault(sum, 0) + 1);
    }
    return count;
}

滑动窗口最大值

Picture1.png
java
public int[] maxSlidingWindow(int[] nums, int k) {
    Deque<Integer> queue = new LinkedList<>();
    int[] res = new int[nums.length - k + 1];
    int index = 0;
    for (int i = 0; i < nums.length; i++) {
        // 保证队列非严格递减
        while (!queue.isEmpty() && nums[i] > nums[queue.peekLast()]) {
            queue.pollLast();
        }
        queue.offerLast(i);
        // 超出窗口大小,弹出队列
        if (queue.peekFirst() < i - k + 1) {
            queue.pollFirst();
        }
        // 形成第一个窗口
        if (i >= k - 1) {
            res[index++] = nums[queue.peekFirst()];
        }
    }
    return res;
}

矩阵

P48 旋转图像

java
public void rotate(int[][] matrix) {
    int n = matrix.length;

    // 上下翻转
    for (int i = 0; i < n / 2; i++) {
        for (int j = 0; j < n; j++) {
            int tmp = matrix[i][j];
            matrix[i][j] = matrix[n - 1 -i][j];
            matrix[n - 1 -i][j] = tmp;
        }
    }

    // 左斜对角线(\)翻转
    for(int i = 0; i < n; i++) {
        // 第二层遍历终止条件为 j < i
        for(int j = 0; j < i; j++) {
            int tmp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = tmp;
        }
    }
}

P240 搜索二维矩阵 II

java
public boolean searchMatrix(int[][] matrix, int target) {
    int n = matrix.length, m = matrix[0].length;
    int i = 0, j = matrix[0].length - 1;
    while (i < n && j >= 0) {
        if (matrix[i][j] == target) {
            return true;
        } else if (matrix[i][j] > target) {
            j--;
        } else if (matrix[i][j] < target) {
            i++;
        }
    }
    return false;
}

链表

P160 相交链表

pA 和 pB 遍历到末尾时重新遍历另一条链表以消除长度差

java
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    if (headA == null || headB == null) return null;
    ListNode pA = headA;
    ListNode pB = headB;
    while (pA != pB) {
        pA = pA == null ? headB : pA.next;
        pB = pB == null ? headA : pB.next;
    }
    return pA;
}

P234 回文链表

  • 慢指针反转前半段链表
  • 遍历前半段和后半段链表是否一致
java
public boolean isPalindrome(ListNode head) {
    ListNode slow = head;
    ListNode fast = head;
    ListNode pre = null;

    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        ListNode next = slow.next;
        slow.next = pre;
        pre = slow;
        slow = next;
    }
    // 奇数节点
    if (fast != null) {
        slow = slow.next;
    }
    while (slow != null) {
        if (slow.val != pre.val) return false;
        slow = slow.next;
        pre = pre.next;
    }
    return true;
}

P141 环形链表

java
public boolean hasCycle(ListNode head) {
    ListNode slow = head;
    ListNode fast = head;

    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) {
            return true;
        }
    }
    return false;
}

P142 环形链表 II

第一次相遇到环点距离等于头节点到环点距离 证明:

  • 环点前有 a 个节点,环有 b 个节点
  • 第一次相遇:f = 2s,f = s + nb,得到:f = 2nb,s = nb,到达环点的步数为 a + nb
  • 第二次相遇:让 slow 到达头节点,s = a,f = a + nb 时相遇,此时为环点
java
public ListNode detectCycle(ListNode head) {
    ListNode slow = head;
    ListNode fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) {
            slow = head;
            while (slow != fast) {
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }
    }
    return null;
}

P25 k 个一组翻转链表

java
public ListNode reverseKGroup(ListNode head, int k) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;

    ListNode pre = dummy;
    ListNode end = dummy;

    while (end.next != null) {
        for (int i = 0; i < k && end != null; i++) end = end.next;
        if (end == null) break;
        ListNode start = pre.next;
        ListNode next = end.next;
        end.next = null;
        // 前一个节点的 next 为翻转后的头节点
        pre.next = reverse(start);
        // 拼接下一个翻转链表
        start.next = next;
        // 更新 pre, end, 开始下一次
        pre = start;
        end = start;
    }
    return dummy.next;
}

// head ~ null 翻转, 返回最后一个节点
private ListNode reverse(ListNode head) {
    ListNode pre = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
}
java
public ListNode reverseKGroup(ListNode head, int k) {
    ListNode currentNode = head;
    int nodeCount = 0;
    // 计算链表长度
    while (currentNode != null) {
        nodeCount++;
        currentNode = currentNode.next;
    }
    // 剩余节点不足k个 直接返回头节点
    if (nodeCount < k) {
        return head;
    }

    ListNode pre = head;
    ListNode cur = head.next;
    for (int i = 0; i < k - 1; i++) {
        ListNode next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }

    head.next = reverseKGroup(cur, k);
    return pre;
}

P148 排序链表

为了找到左部分的最后节点并让 slow.next = null,需要 fast = head.next

java
// 递归归并
public ListNode sortList(ListNode head) {
    // 结束条件
    if (head == null || head.next == null) return head;
    ListNode slow = head, fast = head.next;
    // 获取 mid
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    ListNode temp = slow.next;
    // 截断左右两部分
    slow.next = null;
    ListNode left = sortList(head);
    ListNode right = sortList(temp);
    ListNode dummy = new ListNode(-1);
    ListNode p = dummy;
    // 合并
    while (left != null && right != null) {
        if (left.val < right.val) {
            p.next = left;
            left = left.next;
        } else {
            p.next = right;
            right = right.next;
        }
        p = p.next;
    }
    p.next = left == null ? right : left;
    return dummy.next;
}

P146 LRU 缓存

直接使用 LinkedHashMap

java
class LRUCache {
    LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<>();
    int capacity;

    public LRUCache(int capacity) {
        this.capacity = capacity;
    }

    public int get(int key) {
        if (!cache.containsKey(key)) {
            return -1;
        }
        Integer val = cache.get(key);
        cache.remove(key);
        cache.put(key, val);
        return val;
    }

    public void put(int key, int value) {
        if (cache.containsKey(key)) {
            cache.remove(key);
            cache.put(key, value);
            return;
        }
        if (cache.size() + 1 > capacity) {
            cache.remove(cache.keySet().iterator().next());
        }
        cache.put(key, value);
    }
}

手写双向链表

java
class LRUCache {
    class Node {
        int key, value;
        Node prev, next;

        Node(int k, int v) {
            key = k;
            value = v;
        }
    }

    Node dummy = new Node(0, 0); // 哨兵
    HashMap<Integer, Node> cache = new HashMap<>();
    int capacity;


    public LRUCache(int capacity) {
        this.capacity = capacity;
        dummy.prev = dummy;
        dummy.next = dummy;
    }

    // get 流程:
    // getNode(key) 会将存在的 Node 调到最前面
    // 不存在,返回-1,存在,返回value
    public int get(int key) {
        Node node = getNode(key);
        return node == null ? -1 : node.value;
    }

    // put 流程
    // getNode存在,更新value
    // 不存在,创建node,加入map,pushFront
    // 插入后超过容量,需要删除尾节点及map中的key
    public void put(int key, int value) {
        Node node = getNode(key);
        if (node != null) {
            node.value = value;
            return;
        }
        node = new Node(key, value);
        cache.put(key, node);
        pushFront(node);
        if (capacity < cache.size()) {
            Node backNode = dummy.prev;
            remove(backNode);
            cache.remove(backNode.key);
        }
    }

    // 将key对应的Node调到最前面,先remove再pushFront
    private Node getNode(int key) {
        Node node = cache.get(key);
        if (node != null) {
            remove(node);
            pushFront(node);
        }
        return node;
    }

    private void pushFront(Node node) {
        node.prev = dummy;
        node.next = dummy.next;
        dummy.next.prev = node;
        dummy.next = node;
    }

    private void remove(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
}

P114 二叉树展开为链表

java
public void flatten(TreeNode root) {
    TreeNode cur = root;
    while(cur != null){
        if(cur.left != null){
            TreeNode next = cur.left;
            TreeNode pre = next;
            while(pre.right != null){
                pre = pre.right;
            }
            pre.right = cur.right;
            cur.left = null;
            cur.right = next;
        }
        cur = cur.right;
    }
}

P105 前序和后序遍历构造二叉树

java
public TreeNode buildTree(int[] preorder, int[] inorder) {
    for (int i = 0; i < inorder.length; i++) {
        map.put(inorder[i], i);
    }
    return buildTree(preorder, 0, preorder.length - 1, 0);
}

public TreeNode buildTree(int[] preorder, int preIndexLeft, int preIndexRight, int inLeft) {
    if (preIndexLeft > preIndexRight) return null;

    int cur = preorder[preIndexLeft];
    TreeNode root = new TreeNode(cur);

    int inIndex = map.get(cur);
    int sizeLeft = inIndex - inLeft;

    root.left = buildTree(preorder, preIndexLeft + 1, preIndexLeft + sizeLeft, inLeft);
    root.right = buildTree(preorder, preIndexLeft + sizeLeft + 1, preIndexRight, inIndex + 1);
    return root;
}