力扣LeetCode刷题C++ 两数之和 两数相加

一、两数之和

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 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指向新的末尾节点

  • 步骤 1p 此时指向链表的最后一个节点,通过 p->next 让这个最后节点的 next 指针指向新创建的节点(新节点存当前位结果)。这样就把新节点 “挂” 到了链表末尾。
  • 步骤 2p 移动到新节点(因为新节点现在是链表的最后一个节点了),方便下一次循环继续追加节点。

举例:假设之前链表是 [7]p 指向 7),现在计算出当前位是 0:

  • 步骤 1:p->next = 新节点(0) → 链表变成 7 -> 0
  • 步骤 2:p = p->next → p 现在指向 0(新的末尾)。
为什么要这样设计?
  • 用 head 记录链表起点,保证最后能返回整个链表。
  • 用 p 始终指向末尾,避免每次新增节点时都要从头遍历到尾(提高效率)。
  • 第一次循环单独处理是因为此时链表为空,需要先创建头节点,之后才能 “追加”。

总结

这段代码的核心是 “逐步构建链表”:从无到有创建头节点,之后每计算出一位结果,就往链表末尾加一个新节点,同时让指针 p 始终保持在末尾,为下一次追加做准备。这样就能把所有计算结果串联成一个完整的链表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值