经典算法题

第一部分:数组(Array)—— 核心:下标变换与双指针

数组面试题主要考查对边界的控制和对时间复杂度的压榨(通常要求 $O(n)$)。

1. 两数之和 (Two Sum)

  • 题目: 给定一个数组和一个目标值,寻找索引 $i, j$ 使得 nums[i] + nums[j] == target

  • 核心思维: 空间换时间。不要两层循环,遍历数组时用一个 Dictionary/HashMap{ target - nums[i] : i }

  • 举一反三: 看到“寻找特定组合”,优先想哈希表。

算法基地动画.gif

class Solution { 
List<int>? twoSum(List<int> nums, int target) { 
	final Map<int, int> resultMap = {}; 
	for (int i = 0; i < nums.length; i++) { 
		int tempNum = target - nums[i]; 
		if (resultMap.containsKey(tempNum)) { 
			return [resultMap[tempNum]!, i]; 
		} 
		resultMap[nums[i]] = i; 
	} 
	return null; 
	// 如果没找到,返回 null 
	} 
}

1.1 两数之和 有序数组 O(1)时间复杂度

题目: 给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列  ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。 Kapture 2025-12-25 at 14.07.02.gif

class Solution {
List<int>? twoSum(List<int> numbers, int target) {
	int left = 0;
	int right = numbers.length;
	while (left < right) {
		int sum = numbers[left]+ numbers[right-1];
		if (target == sum) {
			return [left+1,right];
		}
		if (target < sum) {
			right--;
		} else {
			left++;
		}
	}
		return null;
	}
}

2. 三数之和 (3Sum)

  • 题目: 找出所有和为 0 的不重复三元组。

  • 核心思维: 排序 + 双指针。先排序,固定一个数 i,剩下的部分用左指针 L 和右指针 R 向中间夹。

  • 举一反三: 在有序数组里找组合,双指针是不二法门。

3. 移动零 (Move Zeroes)

  • 题目: 将所有 0 移到末尾,保持非零元素相对顺序。

  • 核心思维: 快慢指针。慢指针 j 指向“下一个非零数该放的位置”,快指针 i 遍历。

  • 举一反三: “原地修改数组”、“去重”、“移动元素”类问题,都用快慢指针。


第二部分:字符串(String)—— 核心:滑动窗口

字符串本质是字符数组,但常涉及子串问题。

1. 无重复字符的最长子串

  • 题目: 找到其中不含有重复字符的 最长子串 的长度。

  • 核心思维: 滑动窗口 (Sliding Window)。用一个 Set 或字典维护窗口内的字符,右边界往后移,遇到重复的,左边界被迫收缩。

  • 举一反三: 只要题目出现“子串”(连续的),第一时间想滑动窗口。

2. 验证回文串 (Valid Palindrome)

  • 题目: 忽略大小写和非字母数字字符,判断是否回文。

  • 核心思维: 头尾双指针。注意过滤掉非字符(在 Swift 中用 isLetterNumber)。

  • 举一反三: 判断对称性问题,用头尾指针。

3. 翻转字符串里的单词

  • 题目: "the sky is blue" -> "blue is sky the"

  • 核心思维: 整体反转 + 局部反转。先把整个字符串反转,再逐个反转每个单词。

  • 举一反三: 涉及到位置调整但不想申请额外大空间的,考虑多次反转。


第三部分:链表(Linked List)—— 核心:指针操作与哨兵节点

链表题不难,但极其容易在指针指向(死循环)和边界条件(头节点变化)上出错。

1. 反转链表 (Reverse Linked List)

  • 题目: 将链表反转。

  • 核心思维: 迭代三指针。维护 prev, curr, next 三个指针,一边遍历一边改指向。

  • 举一反三: 这是所有链表题的基础。

2. 环形链表 (Linked List Cycle)

  • 题目: 判断链表中是否有环。

  • 核心思维: 快慢指针 (Floyd 判圈算法)。快指针走两步,慢指针走一步,只要相遇就有环。

  • 举一反三: 寻找链表的中点、寻找倒数第 K 个节点,都用这种“步长差”思维。

3. 合并两个有序链表

  • 题目: 将两个升序链表合并为一个新的升序链表。

  • 核心思维: 虚拟头节点 (Dummy Node)。先创建一个假的头节点,然后像拉链一样对比两个链表,谁小谁挂上去。

  • 举一反三: 当你需要创建一个新链表,但又不确定头节点是谁时,必须用 Dummy Node,它能极大简化代码逻辑。