4道经典算法题代码详解:从两数之和到链表两两交换

4道经典算法题代码详解:从两数之和到链表两两交换

一、前言

在算法面试与刷题过程中,有几道题堪称「入门必刷、面试高频」的经典题目,它们覆盖了哈希表、链表操作、数组模拟哈希等核心算法思想,是打牢算法基础的关键。

本文将对4道经典题目(两数之和、两数相加、两两交换链表中的节点、判定是否互为字符重排)进行完整的思路拆解、代码详解与优化分析,所有代码均为C++实现,可直接在LeetCode上通过测试,适合算法入门与面试复习。


文章目录

二、题目1:两数之和(LeetCode 1)

题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

思路分析

  • 暴力解法:两层循环遍历数组,时间复杂度O(n²),空间复杂度O(1),但数据量较大时效率极低,不推荐。
  • 哈希表优化 :使用unordered_map存储「数值→下标」的映射,遍历数组时,计算target - nums[i],若该值已在哈希表中,则直接返回两个下标;否则将当前数值与下标存入哈希表。
  • 时间复杂度:O(n),空间复杂度:O(n),是最优解法。

完整代码

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        for(int i = 0; i < nums.size(); i++)
        {
            int x = target - nums[i];
            if(hash.count(x)) return {hash[x], i};
            hash[nums[i]] = i;
        }
        return {-1, -1};
    }
};

代码详解

  1. 哈希表初始化unordered_map<int, int> hash,用于存储已经遍历过的数值及其下标。
  2. 遍历数组for(int i = 0; i < nums.size(); i++),逐个处理数组元素。
  3. 计算目标差值int x = target - nums[i],计算需要匹配的另一个数值。
  4. 哈希表查询if(hash.count(x)),判断差值是否已存在于哈希表中,若存在则直接返回{hash[x], i}(两个下标)。
  5. 存入哈希表 :若不存在,则将当前数值nums[i]与下标i存入哈希表,供后续元素匹配。
  6. 兜底返回return {-1, -1},题目保证有解,仅为语法兜底。

三、题目2:两数相加(LeetCode 2)

题目描述

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

思路分析

  • 链表逆序存储数字,天然适合从低位到高位相加,直接模拟加法运算即可。
  • 核心要点:处理进位,即使两个链表都遍历完,若进位不为0,仍需新增节点存储进位。
  • 使用虚拟头节点简化链表操作,避免处理头节点为空的特殊情况。
  • 时间复杂度:O(max(m,n)),空间复杂度:O(1)(不计入结果链表)。

完整代码

cpp 复制代码
/**
 * 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* cur1 = l1, *cur2 = l2;

        ListNode* newnode = new ListNode(0);
        ListNode* prev = newnode;
        int t = 0;

        while(cur1 || cur2 || t)
        {
            if(cur1)
            {
                t += cur1->val;
                cur1 = cur1->next;
            }
            if(cur2)
            {
                t += cur2->val;
                cur2 = cur2->next;
            }

            prev->next = new ListNode(t % 10);
            prev = prev->next;
            t /= 10;
        }

        prev = newnode->next;
        delete newnode;
        return prev;
    }
};

代码详解

  1. 指针初始化cur1cur2分别指向两个链表的头节点,用于遍历。
  2. 虚拟头节点newnode为虚拟头节点,prev指向当前结果链表的尾节点,用于新增节点。
  3. 进位变量t存储当前位的和与进位,初始为0。
  4. 循环相加while(cur1 || cur2 || t),只要有一个链表未遍历完,或进位不为0,就继续循环。
    • cur1不为空,将cur1->val加入t,并移动cur1指针。
    • cur2不为空,将cur2->val加入t,并移动cur2指针。
    • 新增节点存储t % 10(当前位的结果),移动prev指针。
    • t /= 10,更新进位(仅保留十位,即进位值)。
  5. 处理虚拟头节点prev = newnode->next,跳过虚拟头节点,得到结果链表的头节点。
  6. 内存释放delete newnode,释放虚拟头节点,避免内存泄漏。
  7. 返回结果 :返回结果链表的头节点prev

四、题目3:两两交换链表中的节点(LeetCode 24)

题目描述

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

思路分析

  • 核心要求:仅交换节点,不修改节点值,因此需要操作指针,调整节点的指向。
  • 使用虚拟头节点简化操作,统一处理头节点交换的特殊情况。
  • 遍历链表,每次处理两个节点的交换,更新指针指向,直到链表末尾。
  • 时间复杂度:O(n),空间复杂度:O(1)。

完整代码

cpp 复制代码
/**
 * 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* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr) return head;

        ListNode* newhead = new ListNode(0);
        newhead->next = head;

        ListNode* prev = newhead, *cur = prev->next, *next = cur->next, *nnext = next->next;

        while(cur && next)
        {
            prev->next = next;
            next->next = cur;
            cur->next = nnext;

            //修改指针,准备下一轮交换
            prev = cur;
            cur = nnext;
            if(cur) next = cur->next;
            if(next) nnext = next->next;
        }

        cur = newhead->next;
        delete newhead;
        return cur;
    }
};

代码详解

  1. 边界判断if(head == nullptr || head->next == nullptr) return head,链表为空或只有一个节点,直接返回。
  2. 虚拟头节点newhead为虚拟头节点,newhead->next = head,将原链表接入虚拟头节点后。
  3. 指针初始化
    • prev:当前待交换的两个节点的前驱节点(初始为虚拟头节点)。
    • cur:待交换的第一个节点。
    • next:待交换的第二个节点。
    • nnext:待交换的第二个节点的后继节点(下一轮的cur)。
  4. 循环交换while(cur && next),只要有两个可交换的节点,就继续循环。
    • prev->next = next:前驱节点指向第二个节点。
    • next->next = cur:第二个节点指向第一个节点,完成两个节点的交换。
    • cur->next = nnext:第一个节点指向后继节点,连接后续链表。
    • 更新指针prev = cur(下一轮的前驱节点为当前交换后的第一个节点),cur = nnext(下一轮的第一个节点),next = cur->next(下一轮的第二个节点),nnext = next->next(下一轮的后继节点),注意空指针判断。
  5. 处理虚拟头节点cur = newhead->next,得到交换后的链表头节点。
  6. 内存释放delete newhead,释放虚拟头节点,避免内存泄漏。
  7. 返回结果 :返回交换后的链表头节点cur

五、题目4:判定是否互为字符重排(面试题 01.02)

题目描述

给定两个由小写字母组成的字符串 s1s2,请编写一个程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。

思路分析

  • 核心逻辑:两个字符串互为重排,当且仅当它们的字符种类与数量完全相同
  • 优化方案:由于仅包含小写字母,使用长度为26的数组模拟哈希表,统计s1的字符出现次数,再遍历s2减去对应次数,若出现负数则直接返回false,最终返回true
  • 时间复杂度:O(n),空间复杂度:O(1)(数组长度固定为26),是最优解法。

完整代码

cpp 复制代码
class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        if(s1.size() != s2.size()) return false;

        //使用两个哈希表,数组模拟哈希表,又刷上力扣了,小朋友
        int hash[26] = {0};

        for(auto ch : s1)
        {
            hash[ch - 'a']++;
        }

        for(auto ch : s2)
        {
            hash[ch - 'a']--;
            if(hash[ch - 'a'] < 0) return false;
        }

        return true;
    }
};

代码详解

  1. 长度判断if(s1.size() != s2.size()) return false,长度不同直接返回false,是最基础的剪枝。
  2. 数组模拟哈希表int hash[26] = {0},长度为26的数组,对应26个小写字母,初始值为0。
  3. 统计s1字符for(auto ch : s1),遍历s1hash[ch - 'a']++,将字符转换为数组下标(a对应0,b对应1,...,z对应25),统计出现次数。
  4. 校验s2字符for(auto ch : s2),遍历s2hash[ch - 'a']--,减去对应字符的次数。
    • hash[ch - 'a'] < 0,说明s2中该字符数量多于s1,直接返回false
  5. 返回结果 :遍历完成后,所有字符次数均为0,返回true

六、总结与拓展

核心算法思想总结

题目 核心思想 时间复杂度 空间复杂度
两数之和 哈希表优化 O(n) O(n)
两数相加 链表模拟加法 O(max(m,n)) O(1)
两两交换链表中的节点 链表指针操作 O(n) O(1)
判定是否互为字符重排 数组模拟哈希表 O(n) O(1)

面试高频考点

  1. 两数之和:哈希表的应用,是面试中最常考的入门题,需掌握暴力解法与哈希表优化的对比。
  2. 两数相加:链表操作与进位处理,考察对链表的理解与边界情况的处理(如链表长度不同、最终进位)。
  3. 两两交换链表中的节点:链表指针操作,考察对链表结构的理解,是链表操作的经典题。
  4. 判定是否互为字符重排:哈希表的优化应用,考察对空间复杂度的优化(数组替代哈希表)。

七、结语

这4道题目是算法入门的「基石题」,覆盖了哈希表、链表、数组等核心数据结构,是面试刷题的必刷内容。本文不仅提供了可直接通过的代码,更详细拆解了思路与代码逻辑,帮助读者真正理解算法本质,而非死记硬背。

如果对你有帮助,欢迎点赞、收藏、关注,后续会持续更新更多算法题解与技术干货!

相关推荐
cmpxr_2 小时前
【C】隐式类型转换
c语言·c++·算法
W23035765732 小时前
【C++ 高性能日志系统实战】第三篇:异步日志系统的实现与优化
网络·数据结构·算法·日志
y = xⁿ2 小时前
【LeetCode】哈希表
算法·leetcode·散列表
智者知已应修善业2 小时前
【51单片机独立按键控制数码管动态显示和LED间隔闪烁并清零】2023-5-28
c语言·经验分享·笔记·算法·51单片机
北顾笙9802 小时前
day22-数据结构力扣
数据结构·算法·leetcode
IT枫斗者2 小时前
AI Agent 设计模式全景解析:从单体智能到分布式协作的架构演进
人工智能·redis·分布式·算法·spring·缓存·设计模式
2301_822703202 小时前
鸿蒙flutter三方库适配——笔记与知识管理应用:Flutter Markdown实战
笔记·算法·flutter·华为·图形渲染·harmonyos·鸿蒙
人道领域2 小时前
【LeetCode刷题日记】454:四数相加Ⅱ
算法·leetcode