开始搓题
1、两数之和 = 目标值
利用哈希表key=数值,value=索引
使用目标值减去当前值,然后检测这个数是否再哈希表中?若有则找到:若无则将当前值存入哈希表;
vector<int> twoSum(vector<int>& nums, int target) {
// 创建哈希表,key = 数值,value = 索引
unordered_map<int, int> mp;
for (int i = 0; i < nums.size(); i++) {
// 计算所需的另一个数
int complement = target - nums[i];
// 检查这个数是否已经在哈希表中
if (mp.find(complement) != mp.end()) {
// 找到了!返回索引
return {mp[complement], i};
}
// 将当前数字和索引存入哈希表
mp[nums[i]] = i;
}
// 没找到,返回空数组
return {};
}
2、最佳时机购买股票
给定一个数组 prices,其中 prices[i] 是第 i 天的股票价格。 你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。
动态规划思想(一次遍历)
记录目前为止的最小价格;记录最大利润
// 方案1:动态规划思想(一次遍历)- 最优解
// O(n) 时间,O(1) 空间
int maxProfit_Optimal(vector<int>& prices) {
// 特殊情况处理
if (prices.empty()) return 0;
int minPrice = prices[0]; // 记录到目前为止的最小价格
int maxProfit = 0; // 记录最大利润
// 从第二天开始遍历
for (int i = 1; i < prices.size(); i++) {
// 如果今天卖出,能获得多少利润
int profit = prices[i] - minPrice;
// 更新最大利润
maxProfit = max(maxProfit, profit);
// 更新最小价格
minPrice = min(minPrice, prices[i]);
}
return maxProfit;
}
3、反转列表
题目:给你单链表的头节点 head,请你反转链表,并返回反转后的链表。
反转前:1 -> 2 -> 3 -> 4 -> 5 -> NULL
反转后:5 -> 4 -> 3 -> 2 -> 1 -> NULL
核心思路:维护三个指针,逐个反转指向关系
步骤:
-
prev = NULL, curr = head
-
循环处理:
-
保存下一个节点(防止丢失链表)
-
当前节点指向前一个节点(反转)
-
三个指针都向前移动
初始:NULL <- [1] [2] [3] [4] [5]
prev curr第1步:NULL <- [1] [2] [3] [4] [5]
prev curr第2步:NULL <- [1] <- [2] [3] [4] [5]
prev curr第3步:NULL <- [1] <- [2] <- [3] [4] [5]
prev curr...最终:NULL <- [1] <- [2] <- [3] <- [4] <- [5]
prev curr(NULL)ListNode* reverseList_Iterative(ListNode* head) {
ListNode* prev = nullptr; // 前一个节点(初始为NULL)
ListNode* curr = head; // 当前节点while (curr != nullptr) { // 1. 保存下一个节点(防止丢失) ListNode* next = curr->next; // 2. 反转:当前节点指向前一个节点 curr->next = prev; // 3. 向前移动 prev = curr; curr = next; } // prev 现在指向新的头节点 return prev;}
4、两数相加(逆序链表)
题目:给你两个非空的链表,表示两个非负整数。
这些数字以逆序存储在链表中,每个节点包含单个数字。
将这两个数相加,并以链表形式返回一个代表和的链表。
关键点:数字以逆序存储在链表中!
链表 [2,4,3] 表示数字 342(逆序!)
链表 [5,6,4] 表示数字 465(逆序!)
相加结果:342 + 465 = 807 → [7,0,8]
核心思想:模拟竖式加法,处理进位
2 4 3
+ 5 6 4
-------
7 0 8
(+ 1 进位)
逆序的好处:从链表头开始就能进行加法运算!
1️⃣ 创建虚拟头节点 dummy
- 简化代码(不需要特殊处理第一个节点)
- 最后返回 dummy.next 即为真实结果
2️⃣ 初始化 carry = 0(进位)
3️⃣ 循环处理:
while (l1 != nullptr || l2 != nullptr || carry != 0)
为什么要检查 carry?
- 最后两个数相加可能产生进位
- 例如 [5] + [7] = [2,1],最后的 1 是进位
4️⃣ 每次迭代:
- 获取当前值(NULL 当作 0)
- 计算 sum = val1 + val2 + carry
- 新节点值 = sum % 10(个位数)
- 进位 = sum / 10
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
// 创建虚拟头节点,简化代码
ListNode* dummy = new ListNode(0);
ListNode* curr = dummy;
int carry = 0; // 进位
// 遍历两个链表
while (l1 != nullptr || l2 != nullptr || carry != 0) {
// 获取当前节点的值(如果为NULL则为0)
int val1 = (l1 != nullptr) ? l1->val : 0;
int val2 = (l2 != nullptr) ? l2->val : 0;
// 计算和
int sum = val1 + val2 + carry;
// 提取个位数和进位
int digit = sum % 10;
carry = sum / 10;
// 创建新节点
curr->next = new ListNode(digit);
curr = curr->next;
// 移动指针
l1 = (l1 != nullptr) ? l1->next : nullptr;
l2 = (l2 != nullptr) ? l2->next : nullptr;
}
// 返回结果(跳过虚拟头节点)
return dummy->next;
}
5、最长不重复子字符串
题目:给定一个字符串 s,请你找出其中不含有重复字符的最长连续子字符串的长度。
**滑动窗口原理:**维护一个"滑动窗口",窗口内没有重复字符
字符串:a b c a b c b b
索引: 0 1 2 3 4 5 6 7
第1步:[0,0] = "a" ✓
第2步:[0,1] = "ab" ✓
第3步:[0,2] = "abc" ✓ 长度最大!
第4步:[0,3] = "abca" ✗ 'a'重复了!左指针移到1
第5步:[1,3] = "bca" ✓
... 继续...
最终:最长长度 = 3
窗口移动规则:
🟢 右指针:一直向右扩展(添加新字符)
🔴 左指针:只有遇到重复时才向右收缩
int lengthOfLongestSubstring_Optimal(string s) {
// 用哈希表存储每个字符最后出现的位置
unordered_map<char, int> charIndex;
int left = 0; // 窗口左边界
int maxLen = 0; // 最长子字符串长度
// 右指针遍历字符串
for (int right = 0; right < s.length(); right++) {
char ch = s[right];
// 如果字符已经存在,且在当前窗口内
if (charIndex.find(ch) != charIndex.end() &&
charIndex[ch] >= left) {
// 左指针移动到重复字符之后
left = charIndex[ch] + 1;
}
// 更新字符的最后出现位置
charIndex[ch] = right;
// 更新最长长度
maxLen = max(maxLen, right - left + 1);
}
return maxLen;
}