摘要

简单记录力扣100题解题思路

正文

LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台

一、哈希

1. ✅两数之和

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> map = new LinkedHashMap<>();
    for (int i = 0; i < nums.length; i++) {
        int value = target - nums[i];
        if (map.containsKey(value)) {
            return new int[]{
                    map.get(value),
                    i
            };
        } else {
            map.put(nums[i], i);
        }
    }
    return new int[0];
}

2. ✅字母异位词分组

核心做法:将字符串转为char数组然后排序,再将排序后char数组转为字符串。通过哈希判断即可

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public List<List<String>> groupAnagrams(String[] strs) {
    Map<String, List<String>> map = new HashMap<>();
    for (String str : strs) {
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        String key = Arrays.toString(chars);
        if (map.containsKey(key)) {
            map.get(key).add(str);
        } else {
            List<String> temp = new ArrayList<>();
            temp.add(str);
            map.put(key, temp);
        }
    }
    return new ArrayList<>(map.values());
}

3. ✅最长连续新序列

这个其实没有用到哈希相关的知识,直接暴力for就是最好的解法!

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public int longestConsecutive(int[] nums) {
    if (nums.length <= 1) {
        return nums.length;
    }
    Arrays.sort(nums);
    int max = 1, sum = 1;
    for (int i = 0; i < nums.length - 1; i++) {
        if (nums[i] == nums[i + 1]) {
            continue;
        }
        if (nums[i + 1] - nums[i] == 1) {
            if (++sum > max) {
                max = sum;
            }
        } else {
            sum = 1;
        }
    }
    return max;
}

二、指针

4. ✅移动零

核心思路:设置index指针,将非0值与index值进行交换,之后index++。这里面有个妙处,就是值全不为0时

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public void moveZeroes(int[] nums) {
    int index = 0;
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != 0) {
            int num = nums[index];
            nums[index++] = nums[i];
            nums[i] = num;
        }
    }
}

5. ✅盛最多水的容器

核心思路:

若向内 移动短板 ,水槽的短板可能变大,因此下个水槽的面积 可能增大 。

若向内 移动长板 ,水槽的短板不变或变小,因此下个水槽的面积 一定变小 。

故应向内移动短板

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public int maxArea(int[] height) {
    int leftIndex = 0, rightIndex = height.length - 1, max = 0;
    for (int i = 0; i < height.length; i++) {
        int area = Math.min(height[leftIndex], height[rightIndex]) * (rightIndex - leftIndex);
        if (area > max) {
            max = area;
        }
        if (height[leftIndex] > height[rightIndex]) {
            rightIndex--;
        } else {
            leftIndex++;
        }
    }
    return max;
}

6. ✅三数之和

要点:

  • nums[i]+nums[j]+nums[k]==0
  • i!=j!=k
  • 三元组的顺序不重要,但三元组结果集中数据不可重复
java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    for (int i = 0; i < nums.length; i++) {
        // 去重
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        int target = -nums[i];
        int left = i + 1;
        int right = nums.length - 1;
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target) {
                list.add(Arrays.asList(nums[i], nums[left], nums[right]));
                // 去重复元素
                while (left < right && nums[left] == nums[left + 1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right - 1]) {
                    right--;
                }
                left++;
                right--;
            } else if (target > sum) {
                left++;
            } else {
                right--;
            }
        }
    }

    return list;
}

7. ✅接雨水

核心思路:计算每个单位积水值,然后求和。在获取每个单位积水值的时候,需要知道该单位左侧和右侧的最大值,方能知道能否积水。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public int trap(int[] height) {
    int sum = 0;
    // 获取每个下标的左侧最大值
    int[] leftMax = new int[height.length];
    for (int i = 0; i < height.length; i++) {
        if (i == 0) {
            leftMax[i] = 0;
        } else {
            leftMax[i] = Math.max(height[i - 1], leftMax[i - 1]);
        }
    }
    // 获取每个下标的右侧最大值
    int[] rightMax = new int[height.length];
    for (int i = height.length - 1; i >= 0; i--) {
        if (i == height.length - 1) {
            rightMax[i] = 0;
        } else {
            rightMax[i] = Math.max(height[i + 1], rightMax[i + 1]);
        }
    }
    // 依次计算每个小标的积水,并求总和
    for (int i = 0; i < height.length; i++) {
        int area = Math.min(leftMax[i], rightMax[i]) - height[i];
        if (area > 0) {
            sum += area;
        }
    }
    return sum;
}

三、滑动窗口

8. ✅无重复字符的最长子串

核心思路:设置一个容器,将所有不重复的字符存入。当发现新来的字符重复时,就把第一个该字符及其前面字符移除掉。最蠢的办法,就是设置一个List,然后对List进行截取。但该容器的本质是需要维护一个左右可滑动窗口,所以List只是容器的很麻烦的一种实现。

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public int lengthOfLongestSubstring(String s) {
    if (s.length() <= 1) {
        return s.length();
    }
    int max = 0, left = 0;
    char[] chars = s.toCharArray();
    for (int right = 1; right < chars.length; right++) {
        // 维护一个滑动窗口,右侧新来的值,依次跟滑动窗口中的值进行比较
        int index = left;
        while (index < right) {
            if (chars[index] == chars[right]) {
                left = index + 1;
            }
            ++index;
        }
        if ((right - left + 1) > max) {
            max = right - left + 1;
        }
    }
    return max;
}

9. ✅找到字符串中所有字母异位词

核心思路:控制一个长度固定的滑动窗口,依次向前滑动,并将值排序后与排序后的 p 进行比较。这算是最简单直观的暴力解法了。排序在数据量大时,性能会特别低,其实也不需要关心 p 串中的字符顺序,只关心其中各字母出现的频次,频次一样就是字母异位词

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public List<Integer> findAnagrams(String s, String p) {
    List<Integer> list = new ArrayList<>();
    if (s.length() < p.length()) {
        return list;
    }
    // 最多只有26个字母,用数组来表示滑动窗口
    int[] sa = new int[26];
    int[] pa = new int[26];
    for (int i = 0; i < p.length(); i++) {
        pa[p.charAt(i) - 'a']++;
        sa[s.charAt(i) - 'a']++;
    }
    // 定长滑动窗口,每次左侧指针移动一次,若不匹配,则将数组的最左侧移除
    for (int left = 0; left <= s.length() - p.length(); left++) {
        if (left != 0) {
            sa[s.charAt(left + p.length() - 1) - 'a']++;
        }
        if (Arrays.equals(pa, sa)) {
            list.add(left);
        }
        sa[s.charAt(left) - 'a']--;

    }
    return list;
}

四、子串

10. ✅和为k的子数组

核心思路:

  • 暴力解法,T=O(n²)。每个元素依次和后面的元素累加,记录和为k的次数。
  • 前缀和解法,T=O(n)。可以通过计算前缀和来解决该问题
    • 前缀和定义:prefixSum[i] = nums[0] + nums[1] + ... + nums[i]
    • 任意子数组 [j+1 ... i] 的和:sum(j, i) = prefixSum[i] - prefixSum[j]

暴力解法

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static int subarraySum(int[] nums, int k) {
    int sum = 0;
    for (int i = 0; i < nums.length; i++) {
        int total = 0, index = i;
        while (index < nums.length) {
            total += nums[index++];
            if (total == k) {
                sum++;
            }
        }
    }
    return sum;
}

前缀和解法

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public int subarraySum(int[] nums, int k) {
    int sum = 0, prefixSum = 0;
    //前缀和:该和出现的次数
    Map<Integer, Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        prefixSum += nums[i];
        if (prefixSum == k) {
            sum++;
        }
        sum += map.getOrDefault(prefixSum - k, 0);
        map.put(prefixSum, map.getOrDefault(prefixSum, 0) + 1);
    }
    return sum;
}

11. ✅滑动窗口最大值

核心思路

  • 暴力解法,T=O(nk):超时
  • 优先队列解法,T=O(nlogn):比暴力解法略优。优先队列支持通过自定义排序的方式,int compare(Integer o1, Integer o2)前-后=从小到大,后-前=从大到小。
  • 双端队列解法,T=O(n):双端队列,新增元素时,依次对比队列尾部,若原尾部小,就踢掉。如此就能实现队首始终最大值

优先队列解法

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public int[] maxSlidingWindow(int[] nums, int k) {
    if (nums.length < k) {
        return new int[0];
    }
    int[] arr = new int[nums.length - k + 1];
    // 使用一个自排序的优先队列。队列存储数组 int[]{值, 下标}
    // 值相同,按下标升序;否则,按值降序
    PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> {
        return o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0];
    });
    for (int i = 0; i < nums.length - k + 1; i++) {
        if (i != 0) {
            pq.add(new int[]{nums[i + k - 1], i + k - 1});
            // 保证队首的元素在滑动窗口内
            while (pq.peek()[1] < i) {
                pq.poll();
            }
        } else {
            for (int j = 0; j < k; j++) {
                pq.add(new int[]{nums[j], j});
            }
        }
        arr[i] = pq.peek()[0];
    }
    return arr;
}

单调队列解法:

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public int[] maxSlidingWindow(int[] nums, int k) {
    if (nums.length < k) {
        return new int[0];
    }
    int[] arr = new int[nums.length - k + 1];
    // 双端队列,新增元素时,依次对比队列尾部,若原尾部小,就踢掉
    // 如此就能实现队首始终最大值
    Deque<Integer> deque = new LinkedList<>();
    for (int i = 0; i < nums.length - k + 1; i++) {
        if (i == 0) {
            for (int j = 0; j < k; j++) {
                while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[j]) {
                    deque.pollLast();
                }
                deque.offerLast(j);
            }
        } else {
            while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i + k - 1]) {
                deque.pollLast();
            }
            deque.offerLast(i + k - 1);
        }
        while (!deque.isEmpty() && deque.peekFirst() < i) {
            deque.pollFirst();
        }
        arr[i] = nums[deque.peekFirst()];
    }
    return arr;
}

12. ✅最小覆盖字串

双指针,右边一直扩,直到满足条件,左边开始缩,缩到刚好不满足,再继续右扩

java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public String minWindow(String s, String t) {
    // 没有明确说明只有小写或者大写,所以字符范围直接 ASCII 字符(大小写字母、数字、符号),也就是128
    int[] sa = new int[128];
    int[] ta = new int[128];
    for (char c : t.toCharArray()) {
        ta[c]++;
    }
    // 右边一直扩 → 直到满足条件 → 左边开始缩 → 缩到刚好不满足 → 再继续右扩
    int left = 0, right = 0, required = t.length();
    int minLenStart = 0, minLen = Integer.MAX_VALUE;
    while (right < s.length()) {
        char rc = s.charAt(right);
        if (ta[rc] > 0) {
            sa[rc]++;
            if (sa[rc] <= ta[rc]) {
                required--;
            }
        }
        while (required == 0) {
            if (minLen > right - left + 1) {
                // 匹配到当前的最小值了,记录下来
                minLen = right - left + 1;
                minLenStart = left;
            }
            char lc = s.charAt(left);
            if (ta[lc] > 0) {
                sa[lc]--;
                if (sa[lc] < ta[lc]) {
                    required++;
                }
            }
            left++;
        }
        right++;
    }
    return minLen == Integer.MAX_VALUE ? "" : s.substring(minLenStart, minLenStart + minLen);
}

五、普通数组

13. 最大子数组和