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

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

相关推荐
We་ct3 小时前
LeetCode 5. 最长回文子串:DP + 中心扩展
前端·javascript·算法·leetcode·typescript
王老师青少年编程7 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:合并果子
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·合并果子
叼烟扛炮7 小时前
C++第二讲:类和对象(上)
数据结构·c++·算法·类和对象·struct·实例化
天疆说8 小时前
【哈密顿力学】深入解读航天器交会最优控制中的Hamilton函数
人工智能·算法·机器学习
wuweijianlove8 小时前
关于算法设计中的代价函数优化与约束求解的技术7
算法
leoufung9 小时前
LeetCode 149: Max Points on a Line - 解题思路详解
算法·leetcode·职场和发展
样例过了就是过了9 小时前
LeetCode热题100 最长公共子序列
c++·算法·leetcode·动态规划
HXDGCL9 小时前
矩形环形导轨:自动化循环线的核心运动单元解析
运维·算法·自动化
谭欣辰9 小时前
C++ 排列组合完整指南
开发语言·c++·算法
代码中介商9 小时前
银行管理系统的业务血肉 —— 流程、状态机、输入校验与持久化(下篇)
c语言·算法