一、两数之和
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6 输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6 输出:[0,1]
提示:
2 <= nums.length <= 104-109 <= nums[i] <= 109-109 <= target <= 109- 只会存在一个有效答案
方法一:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len = nums.size();
int i, j;
for(i = 0; i < len; i++){
for(j = i + 1; j < len; j++){
if(nums[i] + nums[j] == target){
return {i,j};
}
}
}
return {};
}
};
方法二:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int> hashtable;
for(int i = 0; i < nums.size(); i++){
auto it = hashtable.find(target - nums[i]);
if(it != hashtable.end()){
return {it->second, i};
}
hashtable[nums[i]] = i;
}
return {};
}
};
对于方法二若是不理解,可以查看下面有注释的版本:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
// 1. 创建一个哈希表(键是数字,值是数字在数组中的索引)
unordered_map<int, int> hashtable;
// 2. 遍历数组中的每个元素
for (int i = 0; i < nums.size(); i++) {
// 3. 计算当前需要的“互补数”:目标值 - 当前数字
int complement = target - nums[i];
// 4. 在哈希表里查找这个互补数是否存在
auto it = hashtable.find(complement);
// 5. 如果找到了(it不等于哈希表的末尾),说明之前见过这个互补数
// 原因:如果没找到,find 会返回一个特殊的迭代器 ——hashtable.end()
// 它表示 “哈希表的末尾”(本质上是一个 “超出最后一个元素” 的标记)。
if (it != hashtable.end()) {
// 返回互补数的索引(it->second)和当前数字的索引(i)
return {it->second, i};
}
// 6. 如果没找到,就把当前数字和它的索引存入哈希表(作为“备忘录”)
hashtable[nums[i]] = i;
}
// 7. 题目保证有答案,这里只是为了语法完整(实际不会执行到)
return {};
}
};
延申问题:为什么it->second就是互补数的索引了?
这个问题的核心在于理解哈希表(unordered_map)中 “键” 和 “值” 的存储关系。
在这段代码里,哈希表的定义是 unordered_map<int, int> hashtable,它的规则是:
- 键(key):存储数组中的数字(
nums[i]) - 值(value):存储这个数字在数组中的索引(
i)
当我们执行 hashtable[nums[i]] = i 时,其实是在做这样的操作:把 “当前数字” 作为键,“当前数字的索引” 作为值,存到哈希表里。比如数字 2 在索引 0,就会存成 {2:0};数字 7 在索引 1,就会存成 {7:1}。
再看查找的过程:当我们用 hashtable.find(complement) 查找 “互补数” 时,得到的 it 是一个指向哈希表中对应键值对的迭代器。
it->first表示这个键值对中的 “键”(也就是我们找到的互补数本身)it->second表示这个键值对中的 “值”(也就是这个互补数在数组中的索引)
举个例子:如果当前数字是 7(索引 1),互补数是 2。我们在哈希表里找到了键为 2 的键值对,这个键值对是之前存的 {2:0}。所以 it->first 就是 2(互补数本身),it->second 就是 0(互补数的索引)。
简单说:因为我们存的时候就规定了 “值” 是索引,所以取的时候 it->second 自然就是互补数的索引啦。
二、两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:

输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807.
示例 2:
输入:l1 = [0], l2 = [0] 输出:[0]
示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] 输出:[8,9,9,9,0,0,0,1]
提示:
- 每个链表中的节点数在范围
[1, 100]内 0 <= Node.val <= 9- 题目数据保证列表表示的数字不含前导零
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head = nullptr;
ListNode *p = nullptr;
int carry = 0;
while(l1 || l2){
int n1 = l1?l1->val:0;
int n2 = l2?l2->val:0;
int sum = n1 + n2 + carry;
if(!head){
head = p = new ListNode(sum%10);
}else{
p->next = new ListNode(sum%10);
p = p->next;
}
carry = sum / 10;
if(l1){
l1 = l1->next;
}
if(l2){
l2 = l2->next;
}
}
if(carry > 0){
p->next = new ListNode(carry);
}
return head;
}
};
若是看不懂则看下面的注释版本:
/*
struct ListNode {
int val; // 节点存储的数字(0-9)
ListNode *next; // 指向下一个节点的指针
// 下面是构造函数,方便创建节点
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
*/
ListNode *head = nullptr; // 结果链表的头节点(最终要返回的)
ListNode *p = nullptr; // 临时指针,用来构建结果链表
int carry = 0; // 进位(比如个位相加满10,carry=1)
while(l1 || l2) { // 只要有一个链表还有节点,就继续循环
// 取当前节点的值:如果链表已经遍历完,就用0代替
int n1 = l1 ? l1->val : 0; // l1有节点就取val,否则0
int n2 = l2 ? l2->val : 0; // l2有节点就取val,否则0
// 计算当前位的总和:两个数 + 上一位的进位
int sum = n1 + n2 + carry;
// 构建结果链表的节点
if (!head) { // 第一次循环:初始化头节点
head = p = new ListNode(sum % 10); // sum%10是当前位的结果
} else { // 非第一次循环:在链表末尾新增节点
p->next = new ListNode(sum % 10);
p = p->next; // 指针后移,指向新节点
}
// 更新进位(sum//10:比如sum=15,carry=1)
carry = sum / 10;
// 移动两个链表的指针(如果没遍历完的话)
if (l1) l1 = l1->next;
if (l2) l2 = l2->next;
}
对于某部分代码的具体解释:
1.
int n1 = l1?l1->val:0;
这行代码的意思是:定义一个整数变量 n1,并根据指针 l1 是否为空来为其赋值:
- 如果
l1不是空指针(即l1 != nullptr),则n1的值为l1->val(即指针l1所指向对象的val成员); - 如果
l1是空指针(即l1 == nullptr),则n1的值为0。
这本质上是利用了 三目运算符(条件运算符) ?: 实现的简洁判断,等价于以下 if-else 语句:
cpp
运行
int n1;
if (l1) {
n1 = l1->val;
} else {
n1 = 0;
}
这种写法在处理链表等场景中很常见,用于避免空指针访问(当 l1 为空时,直接访问 l1->val 会导致程序崩溃),同时为变量提供一个默认值(这里是 0)。
2.
if(!head){
head = p = new ListNode(sum%10);
}else{
p->next = new ListNode(sum%10);
p = p->next;
}
先明确两个指针的作用
head:结果链表的 “头节点”,是整个链表的起点(最后要返回的就是它)。p:“当前指针”,始终指向结果链表的最后一个节点,用来在链表末尾 “新增节点”。
分两种情况理解代码
这段代码的逻辑是:第一次循环时创建头节点,之后的循环在链表末尾追加新节点。
1. 第一次循环:if (!head)
当我们刚进入循环时,head 还是 nullptr(空指针),说明结果链表还没创建。此时:
cpp
运行
head = p = new ListNode(sum % 10);
new ListNode(sum % 10):创建一个新节点,存储当前位的计算结果(sum%10是当前位的数字,比如 sum=15 时,当前位是 5)。head = ...:让head指向这个新节点,从此它成为结果链表的第一个节点(头节点)。p = ...:让p也指向这个新节点(因为此时链表只有一个节点,它既是头也是尾)。
举例:第一次计算出当前位是 7,就会创建一个存 7 的节点,head 和 p 都指向这个节点,链表现在是 [7]。
2. 非第一次循环:else
当 head 已经有值(不是空指针),说明结果链表已经有节点了,接下来需要在链表的末尾追加新节点。此时:
p->next = new ListNode(sum % 10); // 步骤1:在当前末尾节点后加新节点
p = p->next; // 步骤2:让p指向新的末尾节点
- 步骤 1:
p此时指向链表的最后一个节点,通过p->next让这个最后节点的next指针指向新创建的节点(新节点存当前位结果)。这样就把新节点 “挂” 到了链表末尾。 - 步骤 2:
p移动到新节点(因为新节点现在是链表的最后一个节点了),方便下一次循环继续追加节点。
举例:假设之前链表是 [7](p 指向 7),现在计算出当前位是 0:
- 步骤 1:
p->next = 新节点(0)→ 链表变成7 -> 0。 - 步骤 2:
p = p->next→p现在指向 0(新的末尾)。
为什么要这样设计?
- 用
head记录链表起点,保证最后能返回整个链表。 - 用
p始终指向末尾,避免每次新增节点时都要从头遍历到尾(提高效率)。 - 第一次循环单独处理是因为此时链表为空,需要先创建头节点,之后才能 “追加”。
总结
这段代码的核心是 “逐步构建链表”:从无到有创建头节点,之后每计算出一位结果,就往链表末尾加一个新节点,同时让指针 p 始终保持在末尾,为下一次追加做准备。这样就能把所有计算结果串联成一个完整的链表。



2万+

被折叠的 条评论
为什么被折叠?



