刷题记录
本笔记按题意抽象、核心思路、代码实现、复杂度和易错点记录题目。代码默认使用 Java,所有结论均以给定约束为前提。
1 2 3 4 5 6 7 8
| flowchart TD A[读取题目] --> B[提取约束和目标] B --> C[手工推演样例] C --> D[选择数据结构] D --> E[证明核心步骤] E --> F[实现代码] F --> G[检查空值 边界 溢出] G --> H[分析复杂度]
|
求出胜利玩家的数目
题意
共有 n 名玩家,pick[i] 表示某名玩家获得某种颜色的球。若玩家编号为 i,只要其任意一种颜色的球数量严格大于 i,该玩家就是胜利玩家。颜色编号范围为 0 到 10。
思路
使用二维数组记录每名玩家拥有的各色球数量。统计完成后,逐个玩家检查是否存在数量大于玩家编号的颜色。每名玩家最多计数一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution { public int winningPlayerCount(int n, int[][] pick) { int[][] count = new int[n][11]; for (int[] item : pick) { count[item[0]][item[1]]++; }
int winners = 0; for (int player = 0; player < n; player++) { for (int color = 0; color <= 10; color++) { if (count[player][color] > player) { winners++; break; } } } return winners; } }
|
时间复杂度为 O(m + nC),其中 m 是拾取记录数,C 固定为 11。空间复杂度为 O(nC)。若颜色范围未知或非常大,应改用嵌套哈希表,避免分配巨大稀疏数组。
两数之和
题意
给定数组 nums 与目标值 target,返回两个不同元素的下标,使它们的和等于目标值。题目保证存在唯一答案。
暴力枚举
1 2 3 4 5 6 7 8 9 10 11 12
| class Solution { public int[] twoSum(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { for (int j = i + 1; j < nums.length; j++) { if (nums[i] + nums[j] == target) { return new int[] {i, j}; } } } throw new IllegalArgumentException("no solution"); } }
|
暴力法时间复杂度为 O(n²),额外空间复杂度为 O(1)。
哈希表优化
遍历当前数字 nums[i] 时,先查询此前是否出现过补数 target - nums[i]。先查询再写入可以避免同一元素被使用两次,同时正确处理重复值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import java.util.HashMap; import java.util.Map;
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> indexByValue = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; Integer previous = indexByValue.get(complement); if (previous != null) { return new int[] {previous, i}; } indexByValue.put(nums[i], i); } throw new IllegalArgumentException("no solution"); } }
|
平均时间复杂度为 O(n),空间复杂度为 O(n)。
斐波那契数
题意
F(0) = 0,F(1) = 1,且 F(n) = F(n - 1) + F(n - 2)。计算给定 n 的斐波那契数。
迭代解法
直接递归会重复计算大量子问题,时间复杂度接近指数级。只保留前两项即可把空间降为常数级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Solution { public int fib(int n) { if (n < 2) { return n; }
int previous = 0; int current = 1; for (int i = 2; i <= n; i++) { int next = previous + current; previous = current; current = next; } return current; } }
|
时间复杂度为 O(n),空间复杂度为 O(1)。若 n 较大,需要使用 long、BigInteger 或快速倍增法,并先确认题目是否要求取模。
使数组元素达到阈值的最少操作数
题意
每次取出数组中最小的两个数 x 和 y,加入 2 × min(x, y) + max(x, y)。求使所有元素都不小于 k 的最少操作次数。
思路
每一步都必须操作当前最小的元素,否则它仍然低于阈值。为了让新值尽可能有效且遵循题目指定操作,持续取出最小的两个元素。最小堆可以在对数时间内完成取出与插入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.util.PriorityQueue;
class Solution { public int minOperations(int[] nums, int k) { PriorityQueue<Long> heap = new PriorityQueue<>(); for (int value : nums) { heap.offer((long) value); }
int operations = 0; while (heap.peek() < k) { long first = heap.poll(); long second = heap.poll(); heap.offer(first * 2 + second); operations++; } return operations; } }
|
由于堆中先取出的 first 不大于 second,新值可直接写为 first * 2 + second。使用 long 是为了防止中间结果超过 int 范围。若题目不保证一定可达,应在循环中检查堆大小。
建堆与连续操作的总时间复杂度可写为 O((n + t) log n),其中 t 为操作次数。空间复杂度为 O(n)。
复盘要点
- 当值域很小且固定时,数组计数通常比哈希表简单。
- 需要边遍历边寻找配对时,哈希表常用于把查找降到平均常数时间。
- 递归定义不等于必须使用递归,先检查是否存在重叠子问题。
- 题目反复要求获取极值时,应优先考虑堆、单调结构或排序。
- 涉及乘法、累加和中间状态时,应主动检查整数溢出。