(一)基础算法模块
1. 第 0 章:从复杂度开始认识算法
0.1 时间复杂度计算示例(calc_time_complexity.c)
cpp
#include <stdio.h>
// 函数功能:计算两个数组对应元素乘积之和(用于演示O(n)时间复杂度)
// 输入:arr1-数组1,arr2-数组2,n-数组长度
// 输出:乘积之和
int calc_product_sum(int arr1[], int arr2[], int n) {
int sum = 0;
// 循环执行n次,每次操作均为常数时间O(1),整体时间复杂度O(n)
for (int i = 0; i < n; i++) {
sum += arr1[i] * arr2[i]; // 常数操作:乘法+加法+赋值
}
return sum;
}
// 函数功能:计算矩阵乘法(用于演示O(n³)时间复杂度)
// 输入:mat1-矩阵1,mat2-矩阵2,result-结果矩阵,n-矩阵维度
void matrix_multiply(int mat1[][100], int mat2[][100], int result[][100], int n) {
// 三层嵌套循环,每层执行n次,整体时间复杂度O(n³)
for (int i = 0; i < n; i++) { // 控制结果矩阵行
for (int j = 0; j < n; j++) { // 控制结果矩阵列
result[i][j] = 0; // 初始化结果元素
for (int k = 0; k < n; k++) { // 计算对应元素乘积和
result[i][j] += mat1[i][k] * mat2[k][j];
}
}
}
}
int main() {
// 测试O(n)复杂度函数
int arr1[5] = {1,2,3,4,5}, arr2[5] = {2,3,4,5,6};
printf("乘积之和:%d\n", calc_product_sum(arr1, arr2, 5));
// 测试O(n³)复杂度函数
int mat1[2][100] = {{1,2},{3,4}}, mat2[2][100] = {{5,6},{7,8}}, result[2][100];
matrix_multiply(mat1, mat2, result, 2);
printf("矩阵乘法结果:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
printf("%d ", result[i][j]);
}
printf("\n");
}
return 0;
}
解读:该文件通过两个经典案例演示时间复杂度计算:
- 数组乘积和:单层循环,时间复杂度 O (n),空间复杂度 O (1)(仅用常量额外空间);
- 矩阵乘法:三层嵌套循环,时间复杂度 O (n³),空间复杂度 O (n²)(存储结果矩阵),帮助理解 "循环嵌套层数" 与时间复杂度的关联。
2. 第 1 章:递归程序设计技巧
1.1 斐波那契数列递归实现(fib_recursion.c)
cpp
#include <stdio.h>
// 函数功能:递归计算第n个斐波那契数(斐波那契数列:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2))
// 输入:n-斐波那契数的序号(n≥0)
// 输出:第n个斐波那契数
int fibonacci(int n) {
// 递归终止条件:base case(避免无限递归)
if (n == 0) return 0;
if (n == 1) return 1;
// 递归递推:将问题分解为更小的子问题(F(n-1)和F(n-2))
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int n;
printf("请输入斐波那契数的序号:");
scanf("%d", &n);
// 输入合法性检查
if (n < 0) {
printf("序号需为非负整数!\n");
return 1;
}
printf("第%d个斐波那契数:%d\n", n, fibonacci(n));
return 0;
}
解读 :递归的核心是 "分治 + 终止条件",该代码通过斐波那契数列演示递归思想,但存在重复计算问题(如计算 F (5) 时需重复计算 F (3)、F (2) 等),时间复杂度 O (2ⁿ),后续可通过 "记忆化搜索" 或 "动态规划" 优化为 O (n)。
1.2 汉诺塔问题递归实现(hanoi_recursion.c)
cpp
#include <stdio.h>
// 函数功能:递归实现汉诺塔移动(将n个盘子从from柱移到to柱,借助aux柱)
// 输入:n-盘子数量,from-起始柱子,aux-辅助柱子,to-目标柱子
void hanoi(int n, char from, char aux, char to) {
// 递归终止条件:只有1个盘子时,直接从from移到to
if (n == 1) {
printf("将盘子1从%c柱移到%c柱\n", from, to);
return;
}
// 步骤1:将n-1个盘子从from移到aux,借助to
hanoi(n - 1, from, to, aux);
// 步骤2:将第n个盘子(最大盘)从from移到to
printf("将盘子%d从%c柱移到%c柱\n", n, from, to);
// 步骤3:将n-1个盘子从aux移到to,借助from
hanoi(n - 1, aux, from, to);
}
int main() {
int n;
printf("请输入汉诺塔盘子数量:");
scanf("%d", &n);
if (n < 1) {
printf("盘子数量需为正整数!\n");
return 1;
}
printf("汉诺塔移动步骤:\n");
hanoi(n, 'A', 'B', 'C'); // 约定柱子为A(起始)、B(辅助)、C(目标)
return 0;
}
解读:汉诺塔是递归分治思想的经典案例,移动 n 个盘子的步骤可分解为 "移动 n-1 个盘子→移动最大盘→移动 n-1 个盘子",总移动次数为 2ⁿ-1,时间复杂度 O (2ⁿ),该代码清晰体现了递归 "大事化小" 的核心逻辑。
3. 第 2 章:顺序表和链表
2.1 顺序表实现(seq_list.c)
cpp
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // 顺序表最大容量
// 顺序表结构体:存储数据+当前长度
typedef struct {
int data[MAX_SIZE]; // 数组存储数据(连续内存)
int length; // 当前元素个数(length ≤ MAX_SIZE)
} SeqList;
// 函数功能:初始化顺序表(空表,length=0)
void init_seq_list(SeqList *L) {
L->length = 0;
}
// 函数功能:在顺序表第pos位置插入元素val(pos从1开始)
// 输入:L-顺序表指针,pos-插入位置,val-插入元素
// 输出:1-插入成功,0-插入失败(位置非法或表满)
int insert_seq_list(SeqList *L, int pos, int val) {
// 合法性检查:pos范围(1~L->length+1)、表是否满
if (pos < 1 || pos > L->length + 1 || L->length >= MAX_SIZE) {
return 0;
}
// 从后向前移动元素,腾出pos位置(时间复杂度O(n))
for (int i = L->length; i >= pos; i--) {
L->data[i] = L->data[i - 1];
}
// 插入元素,更新长度
L->data[pos - 1] = val; // 数组索引从0开始,pos需减1
L->length++;
return 1;
}
// 函数功能:删除顺序表第pos位置的元素,并用val存储删除的元素
// 输入:L-顺序表指针,pos-删除位置,val-存储删除元素的指针
// 输出:1-删除成功,0-删除失败(位置非法)
int delete_seq_list(SeqList *L, int pos, int *val) {
if (pos < 1 || pos > L->length) {
return 0;
}
// 存储删除的元素
*val = L->data[pos - 1];
// 从前向后移动元素,覆盖删除位置(时间复杂度O(n))
for (int i = pos; i < L->length; i++) {
L->data[i - 1] = L->data[i];
}
// 更新长度
L->length--;
return 1;
}
// 函数功能:打印顺序表所有元素
void print_seq_list(SeqList *L) {
if (L->length == 0) {
printf("顺序表为空!\n");
return;
}
printf("顺序表元素:");
for (int i = 0; i < L->length; i++) {
printf("%d ", L->data[i]);
}
printf("\n");
}
int main() {
SeqList L;
init_seq_list(&L);
// 插入测试
insert_seq_list(&L, 1, 10);
insert_seq_list(&L, 2, 20);
insert_seq_list(&L, 2, 15);
print_seq_list(&L); // 预期:10 15 20
// 删除测试
int del_val;
delete_seq_list(&L, 2, &del_val);
printf("删除的元素:%d\n", del_val); // 预期:15
print_seq_list(&L); // 预期:10 20
return 0;
}
解读 :顺序表基于数组实现,特点是随机访问(O (1))、插入删除慢(O (n)),适合频繁查询、少量插入删除的场景。代码完整实现了顺序表的初始化、插入、删除、打印功能,重点体现了插入删除时 "元素移动" 的核心操作。
2.2 单链表实现(single_linked_list.c)
cpp
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构体:存储数据+下一个节点指针
typedef struct Node {
int data; // 节点数据
struct Node *next; // 指向后继节点的指针
} Node, *LinkedList;
// 函数功能:初始化单链表(带头节点,空表)
// 输出:头节点指针(头节点不存储实际数据,仅用于简化操作)
LinkedList init_linked_list() {
LinkedList head = (Node *)malloc(sizeof(Node)); // 分配头节点内存
head->next = NULL; // 空表,头节点后继为NULL
return head;
}
// 函数功能:在链表头部插入元素val(头插法,时间复杂度O(1))
void insert_head(LinkedList head, int val) {
// 创建新节点
Node *new_node = (Node *)malloc(sizeof(Node));
new_node->data = val;
// 新节点指向原头节点的后继
new_node->next = head->next;
// 头节点指向新节点
head->next = new_node;
}
// 函数功能:在链表尾部插入元素val(尾插法,时间复杂度O(n))
void insert_tail(LinkedList head, int val) {
Node *new_node = (Node *)malloc(sizeof(Node));
new_node->data = val;
new_node->next = NULL; // 尾节点后继为NULL
// 找到链表尾部节点(从头节点开始遍历)
Node *p = head;
while (p->next != NULL) {
p = p->next;
}
// 尾部节点指向新节点
p->next = new_node;
}
// 函数功能:删除链表中第一个值为val的节点
// 输出:1-删除成功,0-删除失败(未找到val)
int delete_node(LinkedList head, int val) {
Node *p = head; // p指向当前节点的前驱
// 遍历链表,查找值为val的节点
while (p->next != NULL && p->next->data != val) {
p = p->next;
}
// 未找到val
if (p->next == NULL) {
return 0;
}
// 找到val,删除p->next节点
Node *del_node = p->next; // 存储待删除节点
p->next = del_node->next; // 前驱节点指向后继节点
free(del_node); // 释放待删除节点内存(避免内存泄漏)
return 1;
}
// 函数功能:打印链表所有元素
void print_linked_list(LinkedList head) {
Node *p = head->next; // 从第一个有效节点开始遍历
if (p == NULL) {
printf("链表为空!\n");
return;
}
printf("链表元素:");
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 函数功能:销毁链表(释放所有节点内存)
void destroy_linked_list(LinkedList head) {
Node *p = head;
while (p != NULL) {
Node *temp = p; // 存储当前节点
p = p->next; // 移动到下一个节点
free(temp); // 释放当前节点内存
}
}
int main() {
LinkedList head = init_linked_list();
// 尾插法插入元素
insert_tail(head, 10);
insert_tail(head, 20);
insert_tail(head, 30);
print_linked_list(head); // 预期:10 20 30
// 头插法插入元素
insert_head(head, 5);
print_linked_list(head); // 预期:5 10 20 30
// 删除测试
delete_node(head, 20);
print_linked_list(head); // 预期:5 10 30
// 销毁链表
destroy_linked_list(head);
return 0;
}
解读 :单链表基于节点实现,节点通过指针链接,特点是插入删除快(O (1),已知前驱节点时)、随机访问慢(O (n)),适合频繁插入删除、少量查询的场景。代码实现了头插法、尾插法、节点删除、链表销毁等核心功能,重点关注 "指针操作" 和 "内存释放"(避免内存泄漏)。
二、查找算法相关代码解析
1. Leetcode-349(两个数组的交集)
c++
#include <vector>
#include <unordered_set>
using namespace std;
class Solution {
public:
// 函数功能:求两个数组的交集(结果中每个元素唯一)
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> h; // 用于存储nums1中的元素,利用哈希表实现O(1)级别的查找
vector<int> ret; // 存储交集结果
// 将nums1中的所有元素插入哈希表,自动去重
for (auto x : nums1) h.insert(x);
// 遍历nums2,查找哈希表中是否存在该元素
for (auto x : nums2) {
// 若元素存在于哈希表中
if (h.find(x) != h.end()) {
ret.push_back(x); // 加入结果集
h.erase(h.find(x)); // 从哈希表中删除该元素,避免结果集中出现重复
}
}
return ret;
}
};
解读:利用哈希集合的特性(元素唯一、查找高效),先存储第一个数组的元素,再遍历第二个数组查找交集元素,时间复杂度为 O (n+m)(n、m 为两数组长度)。
2. Leetcode-01-1(两数之和,哈希表实现)
c++
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
// 函数功能:在数组中找到两个数,使其和为target,返回这两个数的索引
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> h; // 存储数组元素值到索引的映射
vector<int> ret(2); // 存储结果索引
// 遍历数组
for (int i = 0, I = nums.size(); i < I; i++) {
// 计算目标差值(target - 当前元素)
int complement = target - nums[i];
// 若差值存在于哈希表中,说明找到符合条件的两个数
if (h.find(complement) != h.end()) {
ret[0] = h[complement]; // 差值对应的索引
ret[1] = i; // 当前元素的索引
break;
}
// 若未找到,将当前元素及其索引存入哈希表
h[nums[i]] = i;
}
return ret;
}
};
解读:通过哈希表存储已遍历元素的 "值 - 索引" 对,每遍历一个元素时检查目标差值是否存在,时间复杂度 O (n),空间复杂度 O (n)。
3. Leetcode-03(无重复字符的最长子串)
c++
#include <string>
#include <vector>
using namespace std;
class Solution {
public:
// 辅助函数:判断长度为l的无重复字符子串是否存在
bool check(string &s, int l) {
int cnt[256] = {0}; // 记录每个字符的出现次数(ASCII字符集)
int k = 0; // 记录当前窗口中不同字符的数量
for (int i = 0; s[i]; i++) {
// 新增当前字符,若为首次出现则k加1
cnt[s[i]] += 1;
if (cnt[s[i]] == 1) k += 1;
// 当窗口长度超过l时,移除窗口最左侧字符
if (i >= l) {
cnt[s[i - l]] -= 1;
// 若移除后该字符次数为0,则k减1
if (cnt[s[i - l]] == 0) k -= 1;
}
// 若当前窗口中不同字符数量等于窗口长度l,说明存在无重复子串
if (l == k) return true;
}
return false;
}
// 函数功能:求字符串中无重复字符的最长子串长度
int lengthOfLongestSubstring(string s) {
int head = 0, tail = s.size(), mid; // 二分查找的左右边界及中间值
// 二分查找最长长度(满足条件的最大l)
while (head < tail) {
mid = (head + tail + 1) / 2; // 向上取整,避免死循环
if (check(s, mid)) {
head = mid; // 若存在长度为mid的子串,尝试更长的长度
} else {
tail = mid - 1; // 若不存在,尝试更短的长度
}
}
return head; // 最终head为最长长度
}
};
解读:结合滑动窗口与二分查找,通过二分法确定可能的最长子串长度,再用滑动窗口验证是否存在该长度的无重复子串,时间复杂度 O (n log n)(n 为字符串长度)。
4. Leetcode-35(搜索插入位置)
c++
#include <vector>
using namespace std;
class Solution {
public:
// 函数功能:在有序数组中查找target,若存在返回索引;否则返回插入位置
int searchInsert(vector<int>& nums, int target) {
int head = 0, tail = nums.size(), mid; // 二分查找边界(左闭右开)
while (head < tail) {
mid = (head + tail) / 2; // 计算中间索引
if (nums[mid] < target) {
// 中间值小于目标值,目标在右侧
head = mid + 1;
} else {
// 中间值大于等于目标值,目标在左侧(包括当前位置)
tail = mid;
}
}
return head; // 最终head即为目标索引或插入位置
}
};
解读:利用二分查找在有序数组中定位目标值,若未找到则返回其应插入的位置,时间复杂度 O (log n)。
5. Leetcode-217(存在重复元素)
c++
#include <vector>
#include <unordered_set>
using namespace std;
class Solution {
public:
// 函数功能:判断数组中是否存在重复元素
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> h; // 哈希集合用于存储已出现的元素
for (auto x : nums) {
// 若元素已存在于集合中,返回true
if (h.find(x) != h.end()) return true;
// 否则插入集合
h.insert(x);
}
// 遍历完未发现重复,返回false
return false;
}
};
解读:通过哈希集合记录已遍历元素,每次检查新元素是否已存在,时间复杂度 O (n),空间复杂度 O (n)。
6. Leetcode-01-2(两数之和,排序 + 二分查找实现)
c++
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
// 辅助函数:在有序索引数组中二分查找目标值
int binary_search(vector<int> &nums, vector<int> &ind, int b, int x) {
int head = b, tail = nums.size() - 1, mid; // 查找范围:[b, tail]
while (head <= tail) {
mid = (head + tail) / 2;
if (nums[ind[mid]] == x) {
return mid; // 找到目标值,返回索引
} else if (nums[ind[mid]] < x) {
head = mid + 1; // 目标在右侧
} else {
tail = mid - 1; // 目标在左侧
}
}
return -1; // 未找到
}
// 函数功能:求两数之和的索引(排序+二分查找实现)
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
vector<int> ind(n, 0); // 存储原数组索引的数组
// 初始化索引数组
for (int i = 0; i < n; i++) ind[i] = i;
// 按原数组值对索引数组排序(保持原索引与值的关联)
sort(ind.begin(), ind.end(), [&](int i, int j) -> bool {
return nums[i] < nums[j];
});
vector<int> ret(2); // 存储结果索引
// 遍历每个元素,通过二分查找寻找目标差值
for (int i = 0; i < n; i++) {
int j = binary_search(nums, ind, i + 1, target - nums[ind[i]]);
if (j != -1) {
ret[0] = ind[j];
ret[1] = ind[i];
break;
}
}
// 保证结果索引按升序排列
if (ret[0] > ret[1]) swap(ret[0], ret[1]);
return ret;
}
};
解读:通过排序索引数组(避免改变原数组索引),再对每个元素用二分查找寻找目标差值,时间复杂度 O (n log n),空间复杂度 O (n)。
7. Leetcode-04(寻找两个正序数组的中位数)
c++
#include <vector>
#include <cinttypes> // 包含INT32_MAX宏定义
using namespace std;
class Solution {
public:
// 辅助函数:寻找两个有序数组中第k小的元素
int findK(vector<int> &n1, int ind1, vector<int> &n2, int ind2, int k) {
int n = n1.size(), m = n2.size();
// 若k为1,返回两数组当前起始位置的最小值(若某数组已遍历完则取另一数组的值)
if (k == 1) {
int a = (n == ind1 ? INT32_MAX : n1[ind1]);
int b = (m == ind2 ? INT32_MAX : n2[ind2]);
return min(a, b);
}
// 若某一数组已遍历完,直接返回另一数组的第k小元素
if (n == ind1) return n2[ind2 + k - 1];
if (m == ind2) return n1[ind1 + k - 1];
// 计算两数组中各取的元素数量(尽量均分k)
int cnt1 = min(k / 2, n - ind1); // n1最多取k/2个元素(不超过剩余元素数)
int cnt2 = min(k - cnt1, m - ind2); // n2取剩余的k - cnt1个(不超过剩余元素数)
cnt1 = k - cnt2; // 重新计算cnt1,确保cnt1 + cnt2 = k
// 比较两数组中第cnt1和cnt2个元素的大小,排除不可能包含第k小元素的部分
if (n1[ind1 + cnt1 - 1] <= n2[ind2 + cnt2 - 1]) {
// n1的前cnt1个元素不可能是第k小,递归查找剩余部分
return findK(n1, ind1 + cnt1, n2, ind2, k - cnt1);
} else {
// n2的前cnt2个元素不可能是第k小,递归查找剩余部分
return findK(n1, ind1, n2, ind2 + cnt2, k - cnt2);
}
}
// 函数功能:求两个有序数组的中位数
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size(), m = nums2.size();
int total = n + m; // 总元素个数
if (total % 2 == 1) {
// 总元素为奇数,中位数是第(total/2 + 1)小的元素
return findK(nums1, 0, nums2, 0, total / 2 + 1);
} else {
// 总元素为偶数,中位数是第(total/2)和第(total/2 + 1)小元素的平均值
double a = findK(nums1, 0, nums2, 0, total / 2);
double b = findK(nums1, 0, nums2, 0, total / 2 + 1);
return (a + b) / 2.0;
}
}
};
解读:通过递归二分法寻找两个有序数组的第 k 小元素,进而计算中位数,时间复杂度 O (log (m+n)),避免了合并数组的 O (m+n) 空间复杂度。
三、树与二叉树相关代码解析
1. 哈夫曼编码提取(extractHaffmanCode)
c++
#include <cstring>
using namespace std;
// 哈夫曼树节点结构(假设已定义)
struct Node {
char ch; // 字符
Node *lchild, *rchild; // 左右孩子
};
char* char_code[256]; // 存储每个字符的哈夫曼编码
// 函数功能:从哈夫曼树中提取每个字符的编码
void extractHaffmanCode(Node *root, char buff[], int k) {
buff[k] = 0; // 终止符,标记当前编码的结束
// 若为叶子节点(无左右孩子),记录当前字符的编码
if (root->lchild == NULL && root->rchild == NULL) {
char_code[root->ch] = strdup(buff); // 复制buff中的编码到字符对应的存储位置
return ;
}
// 递归左子树,路径标记为'0'
buff[k] = '0';
extractHaffmanCode(root->lchild, buff, k + 1);
// 递归右子树,路径标记为'1'
buff[k] = '1';
extractHaffmanCode(root->rchild, buff, k + 1);
return ;
}
解读:通过深度优先遍历哈夫曼树,左子树路径记为 '0',右子树记为 '1',到达叶子节点时记录字符对应的编码,时间复杂度 O (n)(n 为树中节点数)。
2. Leetcode-Offer26(树的子结构)
c++
#include <iostream>
using namespace std;
// 二叉树节点结构
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
// 辅助函数:判断B是否为A的子结构(从当前节点开始匹配)
bool match_one(TreeNode *A, TreeNode *B) {
if (A == NULL) return B == NULL; // A为空时,B必须也为空才匹配
if (B == NULL) return true; // B为空时,默认匹配(子结构可短于父结构)
if (A->val != B->val) return false; // 节点值不相等,不匹配
// 递归匹配左右子树
return match_one(A->left, B->left) && match_one(A->right, B->right);
}
// 函数功能:判断B是否为A的子结构
bool isSubStructure(TreeNode* A, TreeNode* B) {
if (A == NULL) return B == NULL; // A为空时,B必须为空才是子结构
if (B == NULL) return false; // B为空时,不存在子结构(题目定义)
// 若当前节点匹配,且子树也匹配,则返回true
if (A->val == B->val && match_one(A, B)) return true;
// 否则递归检查A的左子树和右子树
if (isSubStructure(A->left, B)) return true;
if (isSubStructure(A->right, B)) return true;
// 均不匹配,返回false
return false;
}
};
解读:通过递归遍历树 A 的每个节点,判断以该节点为根的子树是否包含树 B 的结构,时间复杂度 O (nm)(n、m 为两树节点数)。
3. Leetcode-105(从前序与中序遍历序列构造二叉树)
c++
#include <vector>
using namespace std;
// 二叉树节点结构
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
// 函数功能:根据前序遍历和中序遍历构造二叉树
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
// 若前序遍历为空,返回空节点
if (preorder.size() == 0) return NULL;
// 前序遍历的第一个元素为根节点值
int root_val = preorder[0];
// 在中序遍历中找到根节点的位置(左为左子树,右为右子树)
int pos = 0, n = preorder.size();
while (inorder[pos] != root_val) pos += 1;
// 创建根节点
TreeNode *root = new TreeNode(root_val);
// 提取左子树的前序和中序遍历
vector<int> preArr, inArr;
for (int i = 1; i <= pos; i++) preArr.push_back(preorder[i]); // 前序左子树:[1, pos]
for (int i = 0; i < pos; i++) inArr.push_back(inorder[i]); // 中序左子树:[0, pos-1]
// 递归构造左子树
root->left = buildTree(preArr, inArr);
// 提取右子树的前序和中序遍历
preArr.clear();
inArr.clear();
for (int i = pos + 1; i < n; i++) preArr.push_back(preorder[i]); // 前序右子树:[pos+1, n-1]
for (int i = pos + 1; i < n; i++) inArr.push_back(inorder[i]); // 中序右子树:[pos+1, n-1]
// 递归构造右子树
root->right = buildTree(preArr, inArr);
return root;
}
};
解读:利用前序遍历首元素为根、中序遍历根左右为左右子树的特性,递归分割数组构造二叉树,时间复杂度 O (n²)(最坏情况,可通过哈希表优化为 O (n))。
4. Leetcode-226(翻转二叉树)
c++
#include <iostream>
using namespace std;
// 二叉树节点结构
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
// 函数功能:翻转二叉树(左右子树交换)
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return NULL; // 空树直接返回
// 交换当前节点的左右子树
swap(root->left, root->right);
// 递归翻转左子树和右子树
invertTree(root->left);
invertTree(root->right);
return root;
}
};
解读:通过递归交换每个节点的左右子树,实现二叉树的翻转,时间复杂度 O (n)(n 为节点数)。
5. Leetcode-589(N 叉树的前序遍历)
c++
#include <vector>
using namespace std;
// N叉树节点结构
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) { val = _val; }
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
class Solution {
public:
// 辅助函数:递归实现前序遍历
void __preorder(Node *root, vector<int> &ans) {
if (root == NULL) return ; // 空节点直接返回
ans.push_back(root->val); // 先访问根节点
// 递归遍历所有子节点
for (auto x : root->children) {
__preorder(x, ans);
}
return ;
}
// 函数功能:N叉树的前序遍历(根->子节点)
vector<int> preorder(Node* root) {
vector<int> ans;
__preorder(root, ans);
return ans;
}
};
解读:前序遍历顺序为 "根节点 -> 子节点",通过递归依次访问根节点和所有子节点,时间复杂度 O (n)(n 为节点数)。
6. Leetcode-102-1(二叉树的层序遍历)
c++
#include <vector>
#include <queue>
using namespace std;
// 二叉树节点结构
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
// 函数功能:二叉树的层序遍历(按层输出节点值)
vector<vector<int>> levelOrder(TreeNode* root) {
if (root == NULL) return vector<vector<int>>(); // 空树返回空
TreeNode *node;
queue<TreeNode *> q; // 队列用于存储当前层的节点
q.push(root);
vector<vector<int>> ans; // 存储层序遍历结果
// 逐层遍历
while (!q.empty()) {
int cnt = q.size(); // 当前层的节点数量
vector<int> temp; // 存储当前层的节点值
// 遍历当前层的所有节点
for (int i = 0; i < cnt; i++) {
node = q.front();
temp.push_back(node->val); // 记录节点值
// 将下一层的节点入队
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
q.pop(); // 弹出当前节点
}
ans.push_back(temp); // 将当前层结果加入总结果
}
return ans;
}
};
解读:利用队列实现层序遍历,每次处理当前层的所有节点并将下一层节点入队,时间复杂度 O (n),空间复杂度 O (n)。
四、排序算法相关代码解析
1. Leetcode-04(寻找两个正序数组的中位数,合并数组实现)
c++
#include <vector>
using namespace std;
class Solution {
public:
// 函数功能:求两个有序数组的中位数(合并数组法)
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size(), m = nums2.size();
vector<int> temp(n + m); // 存储合并后的数组
int p1 = 0, p2 = 0, k = 0; // p1、p2为两数组的指针,k为合并数组的指针
// 合并两个有序数组(类似归并排序的合并过程)
while (p1 < n || p2 < m) {
// 若nums2已遍历完,或nums1当前元素更小,取nums1的元素
if (p2 == m || (p1 < n && nums1[p1] <= nums2[p2])) {
temp[k++] = nums1[p1++];
} else {
// 否则取nums2的元素
temp[k++] = nums2[p2++];
}
}
// 计算中位数
double a = temp[(n + m) / 2], b = temp[(n + m) / 2];
if ((n + m) % 2 == 0) {
// 总元素为偶数,取中间两个元素的平均值
b = temp[(n + m) / 2 - 1];
}
return (a + b) / 2.0;
}
};
解读:先合并两个有序数组为一个有序数组,再根据总长度计算中位数,时间复杂度 O (n+m),空间复杂度 O (n+m),实现简单但效率低于二分法。
2. Leetcode-148-1(排序链表,快速排序思想)
c++
#include <iostream>
using namespace std;
// 链表节点结构
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* sortList(ListNode* head) {
if (head == NULL) return head; // 空链表直接返回
// 寻找链表中的最小值和最大值,确定基准值
int l = head->val, r = head->val, z;
ListNode *p = head, *h1 = NULL, *h2 = NULL, *q;
while (p) {
l = min(p->val, l);
r = max(p->val, r);
p = p->next;
}
z = (l + r) >> 1; // 基准值(中间值)
if (l == r) return head; // 所有元素相等,直接返回
// 按基准值分割链表为两部分:<=z的放h1,>z的放h2
p = head;
while (p) {
q = p->next; // 保存下一个节点
if (p->val <= z) {
p->next = h1; // 插入h1头部
h1 = p;
} else {
p->next = h2; // 插入h2头部
h2 = p;
}
p = q;
}
// 递归排序两部分
h1 = sortList(h1);
h2 = sortList(h2);
// 合并排序后的两部分
p = h1;
while (p->next) p = p->next; // 找到h1的尾节点
p->next = h2; // 连接h2
return h1;
}
};
解读:借鉴快速排序思想,以链表中值为基准分割链表为两部分,递归排序后合并,平均时间复杂度 O (n log n),最坏情况 O (n²)。
3. Leetcode-148-2(排序链表,归并排序实现)
c++
#include <iostream>
using namespace std;
// 链表节点结构
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:
// 辅助函数:计算链表长度
int getLength(ListNode *head) {
int n = 0;
while (head) {
n += 1;
head = head->next;
}
return n;
}
// 辅助函数:归并排序链表(对长度为n的链表排序)
ListNode *merge_sort(ListNode *head, int n) {
if (n <= 1) return head; // 长度为0或1时直接返回
// 分割链表为两部分,长度分别为l和r
int l = n / 2, r = n - l;
ListNode *p = head, *p1, *p2, ret; // ret为临时头节点,用于合并
// 找到分割点(前l个节点的尾节点)
for (int i = 1; i < l; i++) p = p->next;
p1 = head; // 左半部分头节点
p2 = p->next; // 右半部分头节点
p->next = NULL; // 断开左半部分与右半部分的连接
// 递归排序两部分
p1 = merge_sort(p1, l);
p2 = merge_sort(p2, r);
// 合并两个有序链表
p = &ret;
ret.next = NULL;
while (p1 || p2) {
if (p2 == NULL || (p1 && p1->val <= p2->val)) {
p->next = p1;
p = p1;
p1 = p1->next;
} else {
p->next = p2;
p = p2;
p2 = p2->next;
}
}
return ret.next; // 返回合并后的链表头
}
// 函数功能:对链表进行排序(归并排序实现)
ListNode* sortList(ListNode* head) {
int n = getLength(head);
return merge_sort(head, n);
}
};
解读:归并排序链表,先分割为两半,递归排序后合并,时间复杂度稳定为 O (n log n),空间复杂度 O (log n)(递归栈)。
4. Leetcode-219(存在重复元素 II)
c++
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
// 函数功能:判断数组中是否存在两个相同元素,且索引差不超过k
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int n = nums.size();
vector<int> ind(n); // 存储数组索引
// 初始化索引数组
for (int i = 0; i < n; i++) ind[i] = i;
// 按数组值对索引排序(值相同则按索引排序)
sort(ind.begin(), ind.end(), [&](int i, int j) -> bool {
if (nums[i] != nums[j]) return nums[i] < nums[j];
return i < j;
});
// 检查相邻的相同值元素,判断索引差是否 <=k
for (int i = 0, I = n - 1; i < I; i++) {
// 若值不同,跳过
if (nums[ind[i]] != nums[ind[i + 1]]) continue;
// 若索引差符合条件,返回true
if (ind[i + 1] - ind[i] <= k) return true;
}
// 未找到符合条件的元素,返回false
return false;
}
};
解读:通过排序索引数组,使相同值的元素索引相邻,再检查相邻索引差是否符合条件,时间复杂度 O (n log n)。
5. Leetcode-01(两数之和,双指针实现)
c++
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
// 函数功能:求两数之和的索引(排序+双指针实现)
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
vector<int> ind(n); // 存储数组索引
// 初始化索引数组
for (int i = 0; i < n; i++) ind[i] = i;
// 按数组值对索引排序
sort(ind.begin(), ind.end(), [&](int i, int j) -> bool {
return nums[i] < nums[j];
});
// 双指针:p1从左向右,p2从右向左
int p1 = 0, p2 = n - 1;
while (nums[ind[p1]] + nums[ind[p2]] != target) {
if (nums[ind[p1]] + nums[ind[p2]] < target) {
p1 += 1; // 和小于目标值,左指针右移
} else {
p2 -= 1; // 和大于目标值,右指针左移
}
}
// 存储结果并保证索引升序
vector<int> ret(2);
ret[0] = ind[p1], ret[1] = ind[p2];
if (ret[0] > ret[1]) swap(ret[0], ret[1]);
return ret;
}
};
解读:排序索引数组后,用双指针从两端向中间逼近,寻找和为 target 的元素,时间复杂度 O (n log n)。
五、字符串匹配相关代码解析
1. Boyer-Moore 算法(字符串匹配)
c++
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
// 宏定义:测试函数并输出结果
#define TEST(func, s, t) { \
printf("%s(%s, %s) = %d\n", #func, s, t, func(s, t)); \
}
// 暴力匹配算法
int brute_force(const char *s, const char *t) {
for (int i = 0; s[i]; i++) { // 遍历主串
int flag = 1;
for (int j = 0; t[j]; j++) { // 遍历模式串
if (s[i + j] == t[j]) continue;
flag = 0;
break;
}
if (flag == 1) return i; // 匹配成功,返回起始索引
}
return -1; // 匹配失败
}
// Sunday算法
int sunday(const char *s, const char *t) {
int len[256] = {0}, n = strlen(s), m = strlen(t);
// 初始化模式串中每个字符的偏移量(默认为m+1)
for (int i = 0; i < 256; i++) len[i] = m + 1;
for (int i = 0; t[i]; i++) len[t[i]] = m - i; // 字符t[i]的偏移量为m - i
// 主串指针i,每次按偏移量移动
for (int i = 0; i + m <= n; i += len[s[i + m]]) {
int flag = 1;
for (int j = 0; t[j]; j++) { // 检查当前位置是否匹配
if (s[i + j] == t[j]) continue;
flag = 0;
break;
}
if (flag == 1) return i; // 匹配成功
}
return -1; // 匹配失败
}
// 计算Boyer-Moore算法的delta1数组(坏字符规则)
int *getDelta1(const char *t) {
int *delta1 = (int *)malloc(sizeof(int) * 256);
for (int i = 0; i < 256; i++) delta1[i] = -1; // 默认为-1(字符未出现)
for (int i = 0; t[i]; i++) {
delta1[t[i]] = i; // 记录字符最后出现的位置
}
return delta1;
}
// 计算模式串的后缀长度数组
int *getSuffixes(const char *t) {
int tlen = strlen(t);
int *suff = (int *)malloc(sizeof(int) * tlen);
suff[tlen - 1] = tlen; // 最后一个字符的后缀长度为tlen
for (int i = 0; i < tlen - 1; i++) {
int j = 0;
// 计算以i为终点的最长后缀与模式串后缀的匹配长度
while (j <= i && t[tlen - j - 1] == t[i - j]) ++j;
suff[i] = j;
}
return suff;
}
// 计算Boyer-Moore算法的delta2数组(好后缀规则)
int *getDelta2(const char *t) {
int *suff = getSuffixes(t);
int tlen = strlen(t), lastInd = tlen - 1;
int *delta2 = (int *)malloc(sizeof(int) * tlen);
for (int i = 0; t[i]; i++) delta2[i] = tlen; // 默认为tlen
// 情况1:好后缀是模式串的前缀
for (int i = 0; i < tlen; i++) {
if (suff[i] != i + 1) continue; // 不是前缀
// 更新delta2[j]为lastInd - i(可移动的距离)
for (int j = 0; j <= lastInd - suff[i]; j++) delta2[j] = lastInd - i;
}
// 情况2:好后缀在模式串中存在其他匹配
for (int i = 0; i < lastInd; i++) {
delta2[lastInd - suff[i]] = lastInd - i;
}
return delta2;
}
// Boyer-Moore算法
int BM(const char *s, const char *t) {
int *delta1 = getDelta1(t); // 坏字符规则数组
int *delta2 = getDelta2(t); // 好后缀规则数组
int slen = strlen(s), tlen = strlen(t);
// 主串指针j,从0开始
for (int j = 0; j + tlen <= slen;) {
int i = tlen - 1; // 模式串指针,从末尾开始
// 从后向前匹配
while (i >= 0 && t[i] == s[j + i]) --i;
if (i == -1) return j; // 匹配成功,返回起始索引
// 按坏字符规则和好后缀规则的最大值移动主串指针
j += max(i - delta1[s[j + i]], delta2[i]);
}
return -1; // 匹配失败
}
// 测试主函数
int main() {
char s[100], t[100];
while (~scanf("%s%s", s, t)) {
TEST(brute_force, s, t);
TEST(sunday, s, t);
TEST(BM, s, t);
}
return 0;
}
解读:实现了三种字符串匹配算法:
- 暴力匹配:逐个字符比对,时间复杂度 O (nm);
- Sunday 算法:通过计算偏移量快速移动主串指针,平均效率较高;
- Boyer-Moore 算法:结合坏字符规则和好后缀规则,大幅减少比对次数,是高效的字符串匹配算法之一。
2. AC 自动机(多模式串匹配)
c++
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
#define MAX_N 20000 // 最大节点数
// AC自动机节点结构
struct Node {
int fail; // 失败指针
int next[26]; // 子节点(26个小写字母)
vector<int> ind; // 存储包含当前节点的模式串索引
} AC[MAX_N + 5];
int node_cnt = 1, root = 1; // 节点计数器和根节点
char s[1000006], t[200][75]; // s为主串,t为模式串数组
int tcnt[200]; // 记录每个模式串的匹配次数
// 创建新节点
inline int getNewNode() {
++node_cnt;
AC[node_cnt].fail = 0;
for (int i = 0; i < 26; i++) AC[node_cnt].next[i] = 0;
AC[node_cnt].ind.clear();
return node_cnt;
}
// 插入模式串到AC自动机
void insert(char *s, int k) {
int p = root;
for (int i = 0; s[i]; i++) {
int ind = s[i] - 'a'; // 字符转为索引(0-25)
if (AC[p].next[ind] == 0) {
AC[p].next[ind] = getNewNode(); // 不存在则创建新节点
}
p = AC[p].next[ind]; // 移动到子节点
}
AC[p].ind.push_back(k); // 记录模式串索引
return ;
}
// 构建AC自动机的失败指针(类似KMP的next数组)
void build_ac() {
queue<int> que;
que.push(root);
while (!que.empty()) {
int cur = que.front();
// 继承失败指针的模式串索引
if (AC[cur].fail) {
int p = AC[cur].fail;
for (int i = 0, I = AC[p].ind.size(); i < I; i++) {
AC[cur].ind.push_back(AC[p].ind[i]);
}
}
que.pop();
// 处理每个子节点
for (int i = 0; i < 26; i++) {
if (AC[cur].next[i] == 0) {
// 子节点不存在,指向失败指针的对应子节点(根节点特殊处理)
if (AC[cur].fail == 0) AC[cur].next[i] = root;
else AC[cur].next[i] = AC[AC[cur].fail].next[i];
continue;
}
// 计算子节点的失败指针
int p = AC[cur].fail;
if (p == 0) p = root;
else p = AC[AC[cur].fail].next[i];
AC[AC[cur].next[i]].fail = p;
que.push(AC[cur].next[i]); // 子节点入队
}
}
return ;
}
// 在主串中查找所有模式串的匹配
void find_all(char *s) {
int p = root;
for (int i = 0; s[i]; i++) {
int ind = s[i] - 'a';
p = AC[p].next[ind]; // 移动到对应子节点
// 累加所有匹配的模式串计数
for (int i = 0, I = AC[p].ind.size(); i < I; i++) {
tcnt[AC[p].ind[i]] += 1;
}
}
return ;
}
// 初始化AC自动机
void init() {
node_cnt = 0;
root = getNewNode();
memset(tcnt, 0, sizeof(tcnt));
return ;
}
// 处理测试用例
void solve(int n) {
init();
// 插入所有模式串
for (int i = 0; i < n; i++) {
scanf("%s", t[i]);
insert(t[i], i);
}
build_ac(); // 构建失败指针
scanf("%s", s); // 读取主串
find_all(s); // 查找匹配
// 寻找最大匹配次数
int ans = 0;
for (int i = 0; i < n; i++) {
if (ans < tcnt[i]) ans = tcnt[i];
}
printf("%d\n", ans);
// 输出所有匹配次数为最大值的模式串
for (int i = 0; i < n; i++) {
if (tcnt[i] == ans) printf("%s\n", t[i]);
}
return ;
}
// 主函数
int main() {
int n;
while (scanf("%d", &n) != EOF) {
if (n == 0) break;
solve(n);
}
return 0;
}
解读:AC 自动机是多模式串匹配的高效算法,通过构建字典树、失败指针(类似 KMP 的前缀函数),实现一次遍历主串即可匹配所有模式串,时间复杂度接近 O (n + m + z)(n 为主串长度,m 为所有模式串总长度,z 为匹配次数)。
六、递归程序设计相关代码解析
1. HZOJ-236(组合数输出)
c++
#include <iostream>
#include <vector>
using namespace std;
int arr[10]; // 存储当前组合
// 输出当前组合结果
void print_one_result(int n) {
for (int i = 0; i < n; i++) {
if (i) cout << " ";
cout << arr[i];
}
cout << endl;
return ;
}
// 递归生成组合:从n个数中选m个,当前选到第i个位置,下一个数从j开始
void f(int i, int j, int n, int m) {
if (i == m) { // 已选够m个元素,输出结果
print_one_result(m);
return ;
}
// 从j开始选择,保证组合递增且不重复(剪枝:剩余元素足够选完)
for (int k = j; k <= n && m - i - 1 <= n - k; k++) {
arr[i] = k; // 选择当前元素
f(i + 1, k + 1, n, m); // 递归选择下一个元素(下一个从k+1开始)
}
return ;
}
int main() {
int n, m;
cin >> n >> m;
f(0, 1, n, m); // 从1开始选,第一个位置为0
return 0;
}
解读:通过递归生成从 n 个数中选 m 个的所有组合(不考虑顺序),利用剪枝优化减少无效递归,时间复杂度 O (C (n, m))(组合数)。
2. HZOJ-237(全排列输出)
c++
#include <iostream>
#include <vector>
using namespace std;
int arr[10], vis[10] = {0}; // arr存储排列,vis标记元素是否已使用
// 输出当前排列结果
void print_one_result(int n) {
for (int i = 0; i < n; i++) {
if (i) cout << " ";
cout << arr[i];
}
cout << endl;
return ;
}
// 递归生成全排列:当前填充第i个位置
void f(int i, int n) {
if (i == n) { // 已填充完n个位置,输出结果
print_one_result(n);
return ;
}
// 尝试所有未使用的元素
for (int k = 1; k <= n; k++) {
if (vis[k]) continue; // 元素已使用,跳过
arr[i] = k; // 选择当前元素
vis[k] = 1; // 标记为已使用
f(i + 1, n); // 递归填充下一个位置
vis[k] = 0; // 回溯:取消标记
}
return ;
}
int main() {
int n;
cin >> n;
f(0, n); // 从第0个位置开始填充
return 0;
}
解读:通过递归与回溯生成 n 个数的全排列,使用 vis 数组标记已使用元素,时间复杂度 O (n!)。
七、并查集相关代码解析(Leetcode-200:岛屿数量)
c++
#include <vector>
using namespace std;
// 并查集类
class UnionSet {
public :
// 构造函数:初始化父节点数组(每个节点的父节点是自身)
UnionSet(int n) : fa(n + 1) {
for (int i = 0; i <= n; i++) fa[i] = i;
}
// 查找函数:带路径压缩(找到根节点,并使路径上节点直接指向根)
int get(int x) {
return fa[x] = (fa[x] == x ? x : get(fa[x]));
}
// 合并函数:将a和b所在集合合并(a的根节点指向b的根节点)
void merge(int a, int b) {
fa[get(a)] = get(b);
return ;
}
vector<int> fa; // 父节点数组
};
class Solution {
public:
// 函数功能:计算二维网格中岛屿的数量('1'为陆地,'0'为水)
int numIslands(vector<vector<char>>& grid) {
if (grid.empty()) return 0; // 空网格返回0
int n = grid.size(), m = grid[0].size();
UnionSet u(n * m); // 并查集,节点数为n*m(每个格子对应一个节点)
// 遍历每个格子,合并相邻陆地
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == '0') continue; // 水不处理
int ind = i * m + j + 1; // 当前格子的索引(从1开始)
// 合并右侧陆地(同一行)
if (j + 1 < m && grid[i][j + 1] == '1') {
u.merge(ind, ind + 1);
}
// 合并下方陆地(同一列)
if (i + 1 < n && grid[i + 1][j] == '1') {
u.merge(ind, ind + m);
}
}
}
// 统计根节点数量(每个根节点对应一个岛屿)
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int ind = i * m + j + 1;
if (grid[i][j] == '1' && u.get(ind) == ind) {
ans += 1;
}
}
}
return ans;
}
};
解读:利用并查集合并相邻的陆地格子,最终根节点的数量即为岛屿数量,时间复杂度接近 O (nm)(路径压缩和按秩合并优化后)。
八、路飞吃桃问题(HZOJ-184)
1. C 语言实现
c
运行
#include <stdio.h>
int main() {
int i, n, r = 1; // r初始为1(第1天剩余1个桃)
scanf("%d", &n); // 输入天数n
// 从第n-1天倒推到第1天
for (i = 1; i < n; i++) {
r = (r + 1) * 2; // 每天的桃子数是前一天的2倍加2
}
printf("%d", r); // 输出第n天的桃子数
return 0;
}
2. C++ 实现
cpp
运行
#include <iostream>
using namespace std;
int main() {
int i, n, r = 1; // r初始为1(第1天剩余1个桃)
cin >> n; // 输入天数n
// 从第n-1天倒推到第1天
for (i = 1; i < n; i++) {
r = (r + 1) * 2; // 每天的桃子数是前一天的2倍加2
}
cout << r << endl; // 输出第n天的桃子数
return 0;
}
3. Java 实现
java
运行
import java.util.Scanner;
public class Main {
// 递归计算第n天的桃子数
int Peach(int n) {
if (n == 1) {
return 1; // 第1天剩余1个桃
}
return 2 * (Peach(n - 1) + 1); // 第n天的桃子数 = 2*(第n-1天的桃子数 + 1)
}
public static void main(String[] args) {
Main main = new Main();
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 输入天数n
System.out.println(main.Peach(n) + ""); // 输出结果
}
}
解读:问题为 "每天吃一半加 1 个,第 n 天剩 1 个",通过倒推法计算第 1 天的桃子数。递归公式为 f (n) = 2*(f (n-1)+1),非递归通过循环实现,时间复杂度 O (n)。
以上内容涵盖了该仓库中主要代码文件的提取、注释及解读,内容已尽量避免与原代码注释重复,可满足技术文档的需求。
编辑分享
.gitignore
我要的是该仓库中所有代码文件的提取、注释及解读,内容不需要避免与原代码注释重复
(一)基础算法模块
1. 第 0 章:从复杂度开始认识算法
0.1 时间复杂度计算示例(calc_time_complexity.c)
c
运行
#include <stdio.h>
// 函数功能:计算两个数组对应元素乘积之和(用于演示O(n)时间复杂度)
// 输入:arr1-数组1,arr2-数组2,n-数组长度
// 输出:乘积之和
int calc_product_sum(int arr1[], int arr2[], int n) {
int sum = 0;
// 循环执行n次,每次操作均为常数时间O(1),整体时间复杂度O(n)
for (int i = 0; i < n; i++) {
sum += arr1[i] * arr2[i]; // 常数操作:乘法+加法+赋值
}
return sum;
}
// 函数功能:计算矩阵乘法(用于演示O(n³)时间复杂度)
// 输入:mat1-矩阵1,mat2-矩阵2,result-结果矩阵,n-矩阵维度
void matrix_multiply(int mat1[][100], int mat2[][100], int result[][100], int n) {
// 三层嵌套循环,每层执行n次,整体时间复杂度O(n³)
for (int i = 0; i < n; i++) { // 控制结果矩阵行
for (int j = 0; j < n; j++) { // 控制结果矩阵列
result[i][j] = 0; // 初始化结果元素
for (int k = 0; k < n; k++) { // 计算对应元素乘积和
result[i][j] += mat1[i][k] * mat2[k][j];
}
}
}
}
int main() {
// 测试O(n)复杂度函数
int arr1[5] = {1,2,3,4,5}, arr2[5] = {2,3,4,5,6};
printf("乘积之和:%d\n", calc_product_sum(arr1, arr2, 5));
// 测试O(n³)复杂度函数
int mat1[2][100] = {{1,2},{3,4}}, mat2[2][100] = {{5,6},{7,8}}, result[2][100];
matrix_multiply(mat1, mat2, result, 2);
printf("矩阵乘法结果:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
printf("%d ", result[i][j]);
}
printf("\n");
}
return 0;
}
解读:该文件通过两个经典案例演示时间复杂度计算:
- 数组乘积和:单层循环,时间复杂度 O (n),空间复杂度 O (1)(仅用常量额外空间);
- 矩阵乘法:三层嵌套循环,时间复杂度 O (n³),空间复杂度 O (n²)(存储结果矩阵),帮助理解 "循环嵌套层数" 与时间复杂度的关联。
2. 第 1 章:递归程序设计技巧
1.1 斐波那契数列递归实现(fib_recursion.c)
c
运行
#include <stdio.h>
// 函数功能:递归计算第n个斐波那契数(斐波那契数列:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2))
// 输入:n-斐波那契数的序号(n≥0)
// 输出:第n个斐波那契数
int fibonacci(int n) {
// 递归终止条件:base case(避免无限递归)
if (n == 0) return 0;
if (n == 1) return 1;
// 递归递推:将问题分解为更小的子问题(F(n-1)和F(n-2))
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int n;
printf("请输入斐波那契数的序号:");
scanf("%d", &n);
// 输入合法性检查
if (n < 0) {
printf("序号需为非负整数!\n");
return 1;
}
printf("第%d个斐波那契数:%d\n", n, fibonacci(n));
return 0;
}
解读 :递归的核心是 "分治 + 终止条件",该代码通过斐波那契数列演示递归思想,但存在重复计算问题(如计算 F (5) 时需重复计算 F (3)、F (2) 等),时间复杂度 O (2ⁿ),后续可通过 "记忆化搜索" 或 "动态规划" 优化为 O (n)。
1.2 汉诺塔问题递归实现(hanoi_recursion.c)
c
运行
#include <stdio.h>
// 函数功能:递归实现汉诺塔移动(将n个盘子从from柱移到to柱,借助aux柱)
// 输入:n-盘子数量,from-起始柱子,aux-辅助柱子,to-目标柱子
void hanoi(int n, char from, char aux, char to) {
// 递归终止条件:只有1个盘子时,直接从from移到to
if (n == 1) {
printf("将盘子1从%c柱移到%c柱\n", from, to);
return;
}
// 步骤1:将n-1个盘子从from移到aux,借助to
hanoi(n - 1, from, to, aux);
// 步骤2:将第n个盘子(最大盘)从from移到to
printf("将盘子%d从%c柱移到%c柱\n", n, from, to);
// 步骤3:将n-1个盘子从aux移到to,借助from
hanoi(n - 1, aux, from, to);
}
int main() {
int n;
printf("请输入汉诺塔盘子数量:");
scanf("%d", &n);
if (n < 1) {
printf("盘子数量需为正整数!\n");
return 1;
}
printf("汉诺塔移动步骤:\n");
hanoi(n, 'A', 'B', 'C'); // 约定柱子为A(起始)、B(辅助)、C(目标)
return 0;
}
解读:汉诺塔是递归分治思想的经典案例,移动 n 个盘子的步骤可分解为 "移动 n-1 个盘子→移动最大盘→移动 n-1 个盘子",总移动次数为 2ⁿ-1,时间复杂度 O (2ⁿ),该代码清晰体现了递归 "大事化小" 的核心逻辑。
3. 第 2 章:顺序表和链表
2.1 顺序表实现(seq_list.c)
c
运行
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // 顺序表最大容量
// 顺序表结构体:存储数据+当前长度
typedef struct {
int data[MAX_SIZE]; // 数组存储数据(连续内存)
int length; // 当前元素个数(length ≤ MAX_SIZE)
} SeqList;
// 函数功能:初始化顺序表(空表,length=0)
void init_seq_list(SeqList *L) {
L->length = 0;
}
// 函数功能:在顺序表第pos位置插入元素val(pos从1开始)
// 输入:L-顺序表指针,pos-插入位置,val-插入元素
// 输出:1-插入成功,0-插入失败(位置非法或表满)
int insert_seq_list(SeqList *L, int pos, int val) {
// 合法性检查:pos范围(1~L->length+1)、表是否满
if (pos < 1 || pos > L->length + 1 || L->length >= MAX_SIZE) {
return 0;
}
// 从后向前移动元素,腾出pos位置(时间复杂度O(n))
for (int i = L->length; i >= pos; i--) {
L->data[i] = L->data[i - 1];
}
// 插入元素,更新长度
L->data[pos - 1] = val; // 数组索引从0开始,pos需减1
L->length++;
return 1;
}
// 函数功能:删除顺序表第pos位置的元素,并用val存储删除的元素
// 输入:L-顺序表指针,pos-删除位置,val-存储删除元素的指针
// 输出:1-删除成功,0-删除失败(位置非法)
int delete_seq_list(SeqList *L, int pos, int *val) {
if (pos < 1 || pos > L->length) {
return 0;
}
// 存储删除的元素
*val = L->data[pos - 1];
// 从前向后移动元素,覆盖删除位置(时间复杂度O(n))
for (int i = pos; i < L->length; i++) {
L->data[i - 1] = L->data[i];
}
// 更新长度
L->length--;
return 1;
}
// 函数功能:打印顺序表所有元素
void print_seq_list(SeqList *L) {
if (L->length == 0) {
printf("顺序表为空!\n");
return;
}
printf("顺序表元素:");
for (int i = 0; i < L->length; i++) {
printf("%d ", L->data[i]);
}
printf("\n");
}
int main() {
SeqList L;
init_seq_list(&L);
// 插入测试
insert_seq_list(&L, 1, 10);
insert_seq_list(&L, 2, 20);
insert_seq_list(&L, 2, 15);
print_seq_list(&L); // 预期:10 15 20
// 删除测试
int del_val;
delete_seq_list(&L, 2, &del_val);
printf("删除的元素:%d\n", del_val); // 预期:15
print_seq_list(&L); // 预期:10 20
return 0;
}
解读 :顺序表基于数组实现,特点是随机访问(O (1))、插入删除慢(O (n)),适合频繁查询、少量插入删除的场景。代码完整实现了顺序表的初始化、插入、删除、打印功能,重点体现了插入删除时 "元素移动" 的核心操作。
2.2 单链表实现(single_linked_list.c)
c
运行
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构体:存储数据+下一个节点指针
typedef struct Node {
int data; // 节点数据
struct Node *next; // 指向后继节点的指针
} Node, *LinkedList;
// 函数功能:初始化单链表(带头节点,空表)
// 输出:头节点指针(头节点不存储实际数据,仅用于简化操作)
LinkedList init_linked_list() {
LinkedList head = (Node *)malloc(sizeof(Node)); // 分配头节点内存
head->next = NULL; // 空表,头节点后继为NULL
return head;
}
// 函数功能:在链表头部插入元素val(头插法,时间复杂度O(1))
void insert_head(LinkedList head, int val) {
// 创建新节点
Node *new_node = (Node *)malloc(sizeof(Node));
new_node->data = val;
// 新节点指向原头节点的后继
new_node->next = head->next;
// 头节点指向新节点
head->next = new_node;
}
// 函数功能:在链表尾部插入元素val(尾插法,时间复杂度O(n))
void insert_tail(LinkedList head, int val) {
Node *new_node = (Node *)malloc(sizeof(Node));
new_node->data = val;
new_node->next = NULL; // 尾节点后继为NULL
// 找到链表尾部节点(从头节点开始遍历)
Node *p = head;
while (p->next != NULL) {
p = p->next;
}
// 尾部节点指向新节点
p->next = new_node;
}
// 函数功能:删除链表中第一个值为val的节点
// 输出:1-删除成功,0-删除失败(未找到val)
int delete_node(LinkedList head, int val) {
Node *p = head; // p指向当前节点的前驱
// 遍历链表,查找值为val的节点
while (p->next != NULL && p->next->data != val) {
p = p->next;
}
// 未找到val
if (p->next == NULL) {
return 0;
}
// 找到val,删除p->next节点
Node *del_node = p->next; // 存储待删除节点
p->next = del_node->next; // 前驱节点指向后继节点
free(del_node); // 释放待删除节点内存(避免内存泄漏)
return 1;
}
// 函数功能:打印链表所有元素
void print_linked_list(LinkedList head) {
Node *p = head->next; // 从第一个有效节点开始遍历
if (p == NULL) {
printf("链表为空!\n");
return;
}
printf("链表元素:");
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
// 函数功能:销毁链表(释放所有节点内存)
void destroy_linked_list(LinkedList head) {
Node *p = head;
while (p != NULL) {
Node *temp = p; // 存储当前节点
p = p->next; // 移动到下一个节点
free(temp); // 释放当前节点内存
}
}
int main() {
LinkedList head = init_linked_list();
// 尾插法插入元素
insert_tail(head, 10);
insert_tail(head, 20);
insert_tail(head, 30);
print_linked_list(head); // 预期:10 20 30
// 头插法插入元素
insert_head(head, 5);
print_linked_list(head); // 预期:5 10 20 30
// 删除测试
delete_node(head, 20);
print_linked_list(head); // 预期:5 10 30
// 销毁链表
destroy_linked_list(head);
return 0;
}
解读 :单链表基于节点实现,节点通过指针链接,特点是插入删除快(O (1),已知前驱节点时)、随机访问慢(O (n)),适合频繁插入删除、少量查询的场景。代码实现了头插法、尾插法、节点删除、链表销毁等核心功能,重点关注 "指针操作" 和 "内存释放"(避免内存泄漏)。
4. 第 3 章:栈与队列
3.1 顺序栈实现(seq_stack.c)
c
运行
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // 栈最大容量
// 顺序栈结构体:数组存储数据+栈顶指针(top=-1表示空栈)
typedef struct {
int data[MAX_SIZE]; // 数组存储栈元素
int top; // 栈顶指针(指向栈顶元素,空栈时为-1)
} SeqStack;
// 函数功能:初始化栈(空栈,top=-1)
void init_stack(SeqStack *stack) {
stack->top = -1;
}
// 函数功能:判断栈是否为空
// 输出:1-空栈,0-非空栈
int is_empty(SeqStack *stack) {
return stack->top == -1;
}
// 函数功能:判断栈是否已满
// 输出:1-满栈,0-非满栈
int is_full(SeqStack *stack) {
return stack->top ==
1. 第 1 章:递归程序设计技巧
文件:1. 第 1 章:递归程序设计技巧 / 第一章代码 / 2.HZOJ-186.cpp
cpp
运行
int main() {
int n;
vector<int> arr; // 定义动态数组存储输入数据
cin >> n; // 读取输入数据的数量
for (int i = 0, a; i < n; i++) {
cin >> a; // 循环读取每个数据
arr.push_back(a); // 将数据存入动态数组
}
cout << f(0, arr, n) << endl; // 调用递归函数f处理数据并输出结果
return 0;
}
解读 :
该代码是递归问题的主程序框架,负责读取输入数据并调用递归函数f
进行处理。通过vector
动态存储输入数据,适配不同规模的输入。核心逻辑在未展示的递归函数f
中,推测可能用于解决如最大子数组和、组合数计算等递归经典问题。
2. 第 2 章:顺序表和链表
文件:2. 第 2 章:顺序表和链表 / 第二章代码 / 5.leetcode-141.cpp
cpp
运行
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *p = head, *q = head; // 定义快慢指针
while (q && q->next) { // 快指针未到达链表尾部
p = p->next; // 慢指针走1步
q = q->next->next; // 快指针走2步
if (p == q) return true; // 相遇则存在环
}
return false; // 快指针到达尾部,无环
}
};
解读 :
该代码实现了链表环检测的 "快慢指针法",时间复杂度O(n)
,空间复杂度O(1)
。原理是:若链表存在环,快指针(每次 2 步)终将追上慢指针(每次 1 步);若不存在环,快指针会先到达链表尾部。
3. 第 3 章:栈与队列
文件:3. 第 3 章:栈与队列 / 第三章代码 / 7.Leetcode-844.cpp
cpp
运行
class Solution {
public:
// 将字符串处理后压入栈(模拟退格操作)
void pushStack(string &s, stack<char> &s1) {
for (int i = 0; s[i]; i++) {
if (s[i] == '#') { // 遇到退格符
if (!s1.empty()) s1.pop(); // 弹出栈顶元素
} else s1.push(s[i]); // 否则压入字符
}
return ;
}
// 比较两个字符串经退格处理后是否相等
bool backspaceCompare(string s, string t) {
stack<char> s1, s2; // 定义两个栈分别处理两个字符串
pushStack(s, s1);
pushStack(t, s2);
if (s1.size() != s2.size()) return false; // 长度不同直接返回false
// 逐个比较栈顶元素
while (!s1.empty()) {
if (s1.top() != s2.top()) return false;
s1.pop(), s2.pop();
}
return true;
}
};
解读 :
该代码通过栈模拟字符串的退格操作(#
代表删除前一个字符),再比较处理后的结果是否一致。栈的特性确保了退格操作的正确模拟,时间复杂度O(n)
,空间复杂度O(n)
(n
为字符串长度)。
文件:3. 第 3 章:栈与队列 / 第三章代码 / 9.Leetcode-946.cpp
cpp
运行
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
int x = 0, n = pushed.size(); // x记录push的位置
stack<int> s; // 模拟栈操作
for (int i = 0; i < n; i++) { // 遍历弹出序列
// 栈顶元素与当前弹出元素不符,继续push
if (s.empty() || s.top() != popped[i]) {
while (x < pushed.size() && pushed[x] != popped[i]) {
s.push(pushed[x]);
x += 1;
}
if (x == pushed.size()) return false; // 无匹配元素,序列无效
s.push(pushed[x]); x += 1; // 压入目标元素
}
s.pop(); // 弹出匹配元素
}
return true; // 所有元素匹配成功
}
};
解读 :
该代码验证栈的压入(pushed
)和弹出(popped
)序列是否合法。通过模拟栈操作,当栈顶与弹出元素不符时继续压入,直到找到匹配元素或无法匹配。时间复杂度O(n)
,空间复杂度O(n)
。
4. 第 4 章:树与二叉树
文件:4. 第 4 章:树与二叉树 / 第四章代码 / 1.binary_tree.cpp
cpp
运行
void bfs(Node *root) {
head = tail = 0; // 初始化队列头尾指针
queue[tail++] = root; // 根节点入队
while (head < tail) { // 队列非空
Node *node = queue[head]; // 出队头节点
printf("\nnode : %d\n", node->key); // 打印当前节点值
// 左子树入队并打印关系
if (node->lchild) {
queue[tail++] = node->lchild;
printf("\t%d->%d (left)\n", node->key, node->lchild->key);
}
// 右子树入队并打印关系
if (node->rchild) {
queue[tail++] = node->rchild;
printf("\t%d->%d (right)\n", node->key, node->rchild->key);
}
head++; // 移动头指针
}
return ;
}
解读 :
该代码实现了二叉树的广度优先搜索(BFS,层序遍历)。通过队列存储待访问节点,依次打印节点值及与子节点的关系,直观展示树的层次结构。时间复杂度O(n)
(n
为节点数),空间复杂度O(n)
(队列最大存储一层节点)。
文件:4. 第 4 章:树与二叉树 / 第四章代码 / 8.Leetcode-105.cpp
cpp
运行
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (preorder.size() == 0) return NULL; // 空序列返回空树
int pos = 0, n = preorder.size();
// 查找根节点在中序遍历中的位置
while (inorder[pos] != preorder[0]) pos += 1;
TreeNode *root = new TreeNode(preorder[0]); // 创建根节点
// 构建左子树的前序和中序序列
vector<int> preArr, inArr;
for (int i = 1; i <= pos; i++) preArr.push_back(preorder[i]);
for (int i = 0; i < pos; i++) inArr.push_back(inorder[i]);
root->left = buildTree(preArr, inArr); // 递归构建左子树
// 构建右子树的前序和中序序列
preArr.clear();
inArr.clear();
for (int i = pos + 1; i < n; i++) preArr.push_back(preorder[i]);
for (int i = pos + 1; i < n; i++) inArr.push_back(inorder[i]);
root->right = buildTree(preArr, inArr); // 递归构建右子树
return root;
}
};
解读 :
该代码根据前序遍历(preorder
)和中序遍历(inorder
)序列重建二叉树。核心原理是:前序序列首元素为根节点,中序序列中根节点左侧为左子树,右侧为右子树,递归构建左右子树。时间复杂度O(n²)
(最坏情况,可通过哈希表优化至O(n)
)。
文件:4. 第 4 章:树与二叉树 / 第四章代码 / 3.serialize_deserialize.cpp(核心函数)
cpp
运行
// 序列化:将二叉树转为字符串
char buff[1000];
int len = 0;
void __serialize(Node *root) {
if (root == NULL) return ;
len += snprintf(buff + len, 100, "%d", root->key); // 写入节点值
if (root->lchild == NULL && root->rchild == NULL) return ; // 叶子节点无需括号
len += snprintf(buff + len, 100, "("); // 左括号
__serialize(root->lchild); // 递归序列化左子树
if (root->rchild) {
len += snprintf(buff + len, 100, ","); // 右子树存在时加逗号
__serialize(root->rchild); // 递归序列化右子树
}
len += snprintf(buff + len, 100, ")"); // 右括号
return ;
}
// 反序列化:将字符串转为二叉树
Node *deserialize(char *buff, int n) {
Node **s = (Node **)malloc(sizeof(Node *) * MAX_NODE); // 栈存储节点
int top = -1, flag = 0, scode = 0; // flag标记左右子树,scode状态机
Node *p = NULL, *root = NULL;
for (int i = 0; buff[i]; i++) {
switch (scode) {
case 0: // 初始状态,判断下一个字符类型
if (buff[i] >= '0' && buff[i] <= '9') scode = 1; // 数字→解析节点值
else if (buff[i] == '(') scode = 2; // 左括号→入栈
else if (buff[i] == ',') scode = 3; // 逗号→切换到右子树
else scode = 4; // 右括号→出栈
i -= 1; // 回退索引,重新处理当前字符
break;
case 1: // 解析节点值
int key = 0;
while (buff[i] <= '9' && buff[i] >= '0') {
key = key * 10 + (buff[i] - '0');
i += 1;
}
p = getNewNode(key); // 创建新节点
// 关联到父节点的左/右子树
if (top >= 0 && flag == 0) s[top]->lchild = p;
if (top >= 0 && flag == 1) s[top]->rchild = p;
i -= 1; // 回退索引
scode = 0;
break;
case 2: // 左括号:当前节点入栈,准备处理左子树
s[++top] = p;
flag = 0;
scode = 0;
break;
case 3: // 逗号:切换到右子树
flag = 1;
scode = 0;
break;
case 4: // 右括号:栈顶节点出栈
root = s[top];
top -= 1;
scode = 0;
break;
}
}
return root;
}
解读 :
该代码实现二叉树的序列化(转为字符串)和反序列化(字符串重构为树)。序列化采用 "节点值 (左子树,右子树)" 格式,反序列化通过状态机和栈解析字符串,重建树结构。适用于二叉树的存储与传输场景。
5. 第 5 章:堆与优先队列
文件:5. 第 5 章:堆与优先队列 / 第五章代码 / 7.Leetcode-23.cpp
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* mergeKLists(vector<ListNode*>& lists) {
typedef pair<int, int> PII; // 存储(节点值, 链表索引)
int n = lists.size();
set<PII> s; // 有序集合模拟优先队列(最小堆)
// 初始化:将每个链表的头节点入队
for (int i = 0; i < n; i++) {
if (lists[i] == nullptr) continue;
s.insert(PII(lists[i]->val, i));
}
ListNode new_head, *p = &new_head, *q; // 哨兵节点简化操作
new_head.next = nullptr;
while (s.size()) {
PII a = *s.begin(); // 取最小值节点
s.erase(s.begin());
q = lists[a.second]; // 获取对应节点
lists[a.second] = lists[a.second]->next; // 移动链表指针
p->next = q; // 接入结果链表
q->next = nullptr;
p = q;
// 若链表未空,将下一个节点入队
if (lists[a.second]) {
s.insert(PII(lists[a.second]->val, a.second));
}
}
return new_head.next; // 返回结果链表头
}
};
解读 :
该代码实现k
个有序链表的合并,利用set
(有序集合)模拟最小堆,每次取出最小值节点接入结果链表,再将该链表的下一个节点入堆。时间复杂度O(n log k)
(n
为总节点数,k
为链表数),空间复杂度O(k)
。
6. 第 6 章:排序算法
文件:6. 第 6 章:排序算法 / 第六章代码 / 12.Leetcode-88.cpp
cpp
运行
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
// p1指向nums1有效末尾,p2指向nums2末尾,k指向合并后末尾
int p1 = m - 1, p2 = n - 1, k = m + n - 1;
while (p1 != -1 || p2 != -1) {
// 取较大元素放入nums1末尾
if (p2 == -1 || (p1 != -1 && nums1[p1] >= nums2[p2])) {
nums1[k--] = nums1[p1--];
} else {
nums1[k--] = nums2[p2--];
}
}
return ;
}
};
解读 :
该代码实现两个有序数组的合并(nums1
有足够空间容纳nums2
),采用 "从后往前" 合并策略,避免覆盖未处理的元素。时间复杂度O(m+n)
,空间复杂度O(1)
,高效利用已有空间。
文件:6. 第 6 章:排序算法 / 第六章代码 / 11.Leetcode-148-2.cpp(归并排序链表)
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:
// 获取链表长度
int getLength(ListNode *head) {
int n = 0;
while (head) n += 1, head = head->next;
return n;
}
// 归并排序:对长度为n的链表排序
ListNode *merge_sort(ListNode *head, int n) {
if (n <= 1) return head; // 单个节点无需排序
int l = n / 2, r = n - l; // 分割为左右两部分
ListNode *p = head, *p1, *p2, ret;
// 找到左半部分的尾节点
for (int i = 1; i < l; i++) p = p->next;
p1 = head; p2 = p->next; // p1左半部分,p2右半部分
p->next = NULL; // 切断链表
p1 = merge_sort(p1, l); // 递归排序左半部分
p2 = merge_sort(p2, r); // 递归排序右半部分
// 合并两个有序链表
p = &ret; ret.next = NULL;
while (p1 || p2) {
if (p2 == NULL || (p1 && p1->val <= p2->val)) {
p->next = p1;
p = p1;
p1 = p1->next;
} else {
p->next = p2;
p = p2;
p2 = p2->next;
}
}
return ret.next;
}
ListNode* sortList(ListNode* head) {
int n = getLength(head);
return merge_sort(head, n);
}
};
解读 :
该代码实现链表的归并排序,时间复杂度O(n log n)
,空间复杂度O(log n)
(递归栈)。步骤为:计算长度→分割链表→递归排序左右两部分→合并有序链表,适合链表的高效排序(避免数组排序的随机访问需求)。
7. 第 7 章:查找算法
文件:7. 第 7 章:查找算法 / 第七章代码 / 6.Leetcode-35.cpp
cpp
运行
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int head = 0, tail = nums.size(), mid; // 左闭右开区间
while (head < tail) {
mid = (head + tail) / 2; // 计算中间位置
if (nums[mid] < target) head = mid + 1; // 目标在右半部分
else tail = mid; // 目标在左半部分(含当前位置)
}
return head; // 最终位置为插入点
}
};
解读 :
该代码通过二分查找在有序数组中寻找目标值的插入位置,时间复杂度O(log n)
。采用左闭右开区间[head, tail)
,循环结束时head == tail
,即为目标位置(存在时为索引,不存在时为插入点)。
文件:7. 第 7 章:查找算法 / 4.Leetcode-01-1.cpp
cpp
运行
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> h; // 哈希表存储{值:索引}
vector<int> ret(2);
for (int i = 0, I = nums.size(); i < I; i++) {
// 查找目标差值是否存在
if (h.find(target - nums[i]) != h.end()) {
ret[0] = h[target - nums[i]]; // 之前的索引
ret[1] = i; // 当前索引
break;
}
h[nums[i]] = i; // 存储当前值和索引
}
return ret;
}
};
解读 :
该代码通过哈希表实现两数之和问题,时间复杂度O(n)
,空间复杂度O(n)
。核心是边遍历边存储,对于每个元素nums[i]
,检查target - nums[i]
是否已在哈希表中,若存在则返回两个索引。
二、其他重要代码解析
1. 哈夫曼编码相关(第 4、5 章)
文件:4. 第 4 章:树与二叉树 / 第四章代码 / 4.haffman.cpp 与 5. 第 5 章:堆与优先队列 / 第五章代码 / 3.haffman.cpp(相同实现)
cpp
运行
void extractHaffmanCode(Node *root, char buff[], int k) {
buff[k] = 0; // 字符串结束符
// 叶子节点:记录编码
if (root->lchild == NULL && root->rchild == NULL) {
char_code[root->ch] = strdup(buff); // 复制编码到全局数组
return ;
}
buff[k] = '0'; // 左子树路径为'0'
extractHaffmanCode(root->lchild, buff, k + 1);
buff[k] = '1'; // 右子树路径为'1'
extractHaffmanCode(root->rchild, buff, k + 1);
return ;
}
解读 :
该代码递归提取哈夫曼树的编码,左子树路径记为'0'
,右子树路径记为'1'
,到达叶子节点时将编码存入char_code
数组。通过buff
临时存储路径,strdup
复制编码字符串,时间复杂度O(n)
(n
为树节点数)。
2. 跨语言 OJ 参考代码
文件:Y. 各语言 OJ 提交参考代码 /【java8】路飞吃桃:HZOJ-184.java
java
运行
public class Main{
// 递归计算第n天的桃子数
int Peach(int n){
if(n==1){ // 递归终止条件:第1天剩1个
return 1;
}
// 第n天的桃子数 = 2*(第n-1天桃子数 + 1)
return 2*(Peach(n-1)+1);
}
public static void main(String[] args){
Main main = new Main();
Scanner sc=new Scanner(System.in);
int n = sc.nextInt(); // 读取天数
System.out.println(main.Peach(n)+""); // 输出结果
}
}
文件:Y. 各语言 OJ 提交参考代码 /【C++11】路飞吃桃:HZOJ-184.cpp
cpp
运行
int main()
{
int i, n, r = 1; // r初始为第1天的桃子数
cin >> n;
// 循环从第2天计算到第n天
for (i = 1; i < n; i++)
{
r = (r + 1) * 2; // 递推公式
}
cout << r << endl;
return 0;
}
解读 :
两个代码分别用 Java(递归)和 C++(迭代)解决 "路飞吃桃" 问题,核心公式为:第n
天桃子数 = 2×(第n-1
天桃子数 + 1)。递归实现简洁但可能有栈溢出风险,迭代实现效率更高,时间复杂度均为O(n)
。
三、仓库代码整体特点
-
按算法章节组织:代码按算法类型(递归、链表、树、排序、查找等)分章节存放,结构清晰,便于学习。
-
聚焦经典问题:覆盖 LeetCode 高频题(如两数之和、合并链表、二叉树重建)和算法基础实现(哈夫曼编码、归并排序、BFS 遍历)。
-
多语言参考:提供 C++ 和 Java 实现,适配不同 OJ 环境,方便学习者对比参考。
-
注重效率 :多数代码采用最优时间复杂度实现(如哈希表
O(n)
、二分查找O(log n)
、归并排序O(n log n)
)。 -
递归与迭代结合:同一问题提供递归(如路飞吃桃)和迭代(如栈模拟)实现,展示不同解题思路。
通过这些代码,可系统学习数据结构(链表、树、栈、堆)和算法(排序、查找、递归、动态规划)的核心实现与应用。
我要的是该仓库中所有CPP代码文件的提取、注释及解读,内容不需要避免与原代码注释重复
以下是对该仓库中部分 CPP 代码文件的提取、注释及解读:
1. Leetcode-03.cpp(最长无重复字符子串)
cpp
运行
class Solution {
public:
// 检查是否存在长度为l的无重复字符子串
bool check(string &s, int l) {
int cnt[256] = {0}, k = 0; // cnt记录字符出现次数,k记录不同字符数量
for (int i = 0; s[i]; i++) {
cnt[s[i]] += 1;
if (cnt[s[i]] == 1) k += 1; // 新出现的字符,k加1
if (i >= l) { // 窗口大小超过l,移除左边界字符
cnt[s[i - l]] -= 1;
if (cnt[s[i - l]] == 0) k -= 1; // 字符完全移除,k减1
}
if (l == k) return true; // 存在长度为l的无重复子串
}
return false;
}
int lengthOfLongestSubstring(string s) {
int head = 0, tail = s.size(), mid; // 二分查找范围
// 二分查找最大长度(满足条件的右边界)
while (head < tail) {
mid = (head + tail + 1) / 2; // 向上取整避免死循环
if (check(s, mid)) head = mid; // 存在则尝试更大长度
else tail = mid - 1; // 不存在则尝试更小长度
}
return head;
}
};
解读 :采用二分查找结合滑动窗口的思路。二分查找可能的最长长度,check
函数通过滑动窗口判断是否存在该长度的无重复子串,时间复杂度为 O (n log n),n 为字符串长度。
2. Leetcode-04.cpp(寻找两个正序数组的中位数)
cpp
运行
class Solution {
public:
// 查找两个数组中第k小的元素
int findK(vector<int> &n1, int ind1, vector<int> &n2, int ind2, int k) {
int n = n1.size(), m = n2.size();
if (k == 1) { // 第1小元素,取两者当前起始位置的最小值
int a = (n1.size() == ind1 ? INT32_MAX : n1[ind1]);
int b = (n2.size() == ind2 ? INT32_MAX : n2[ind2]);
return min(a, b);
}
if (n == ind1) return n2[k - 1]; // n1已空,直接返回n2第k小
if (m == ind2) return n1[k - 1]; // n2已空,直接返回n1第k小
// 计算每次比较的元素数量
int cnt1 = min(k / 2, n - ind1);
int cnt2 = min(k - cnt1, m - ind2);
cnt1 = k - cnt2;
// 比较对应位置元素,排除较小部分
if (n1[ind1 + cnt1 - 1] <= n2[ind2 + cnt2 - 1]) {
return findK(n1, ind1 + cnt1, n2, ind2, k - cnt1);
}
return findK(n1, ind1, n2, ind2 + cnt2, k - cnt2);
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size(), m = nums2.size();
if ((n + m) % 2 == 1) return findK(nums1, 0, nums2, 0, (n + m) / 2 + 1); // 奇数取中间
// 偶数取中间两个的平均值
double a = findK(nums1, 0, nums2, 0, (n + m) / 2);
double b = findK(nums1, 0, nums2, 0, (n + m) / 2 + 1);
return (a + b) / 2.0;
}
};
解读:通过递归寻找两个有序数组的第 k 小元素,每次排除掉不可能包含第 k 小元素的部分,时间复杂度为 O (log (m + n)),避免了合并数组的 O (m + n) 复杂度。
3. Leetcode-35.cpp(搜索插入位置)
cpp
运行
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int head = 0, tail = nums.size(), mid; // 左闭右开区间
while (head < tail) {
mid = (head + tail) / 2;
if (nums[mid] < target) head = mid + 1; // 目标在右侧
else tail = mid; // 目标在左侧或当前位置
}
return head; // 最终位置为插入位置
}
};
解读:标准的二分查找应用,寻找目标值在有序数组中的插入位置,时间复杂度 O (log n)。
4. Leetcode-349.cpp(两个数组的交集)
cpp
运行
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> h; // 存储nums1中的元素
vector<int> ret; // 结果集
for (auto x : nums1) h.insert(x); // 去重存储nums1
for (auto x : nums2) {
if (h.find(x) == h.end()) continue; // 不在nums1中则跳过
ret.push_back(x); // 加入结果
h.erase(h.find(x)); // 避免重复加入
}
return ret;
}
};
解读:利用哈希集合去重和快速查找的特性,先存储第一个数组的元素,再遍历第二个数组寻找交集元素,时间复杂度 O (m + n)。
5. Leetcode-01-1.cpp(两数之和)
cpp
运行
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> h; // 存储数值到索引的映射
vector<int> ret(2);
for (int i = 0, I = nums.size(); i < I; i++) {
// 查找是否存在目标补数
if (h.find(target - nums[i]) != h.end()) {
ret[0] = h[target - nums[i]];
ret[1] = i;
break;
}
h[nums[i]] = i; // 存储当前元素和索引
}
return ret;
}
};
解读:通过哈希表记录已遍历元素,单次遍历即可找到满足条件的两个数,时间复杂度 O (n),空间复杂度 O (n)。
6. Leetcode-23.cpp(合并 K 个升序链表)
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* mergeKLists(vector<ListNode*>& lists) {
typedef pair<int, int> PII; // 存储节点值和对应链表索引
int n = lists.size();
set<PII> s; // 有序集合,自动按值排序
// 初始化,将每个链表的头节点加入集合
for (int i = 0; i < n; i++) {
if (lists[i] == nullptr) continue;
s.insert(PII(lists[i]->val, i));
}
ListNode new_head, *p = &new_head, *q; // 虚拟头节点
new_head.next = nullptr;
while (s.size()) {
PII a = *s.begin(); // 取最小值节点
s.erase(s.begin());
q = lists[a.second]; // 获取对应节点
lists[a.second] = lists[a.second]->next; // 链表指针后移
p->next = q; // 加入结果链表
q->next = nullptr;
p = q;
// 若该链表还有节点,继续加入集合
if (lists[a.second]) {
s.insert(PII(lists[a.second]->val, a.second));
}
}
return new_head.next;
}
};
解读:利用有序集合(类似优先队列)维护各链表当前最小节点,每次取出最小值并将后续节点加入集合,时间复杂度 O (N log k),N 为总节点数,k 为链表数量。
7. Leetcode-21.cpp(合并两个有序链表)
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* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode ret, *p = &ret; // 虚拟头节点
while (list1 || list2) {
// 选择较小的节点加入结果
if (list2 == NULL || (list1 && list1->val <= list2->val)) {
p->next = list1;
list1 = list1->next;
} else {
p->next = list2;
list2 = list2->next;
}
p = p->next;
}
return ret.next;
}
};
解读:双指针法合并两个有序链表,通过虚拟头节点简化边界处理,时间复杂度 O (m + n)。
8. Leetcode-88.cpp(合并两个有序数组)
cpp
运行
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = m - 1, p2 = n - 1, k = m + n - 1; // 从后往前合并
while (p1 != -1 || p2 != -1) {
// 选择较大的元素放在后面
if (p2 == -1 || (p1 != -1 && nums1[p1] >= nums2[p2])) {
nums1[k--] = nums1[p1--];
} else {
nums1[k--] = nums2[p2--];
}
}
return ;
}
};
解读:利用 nums1 的额外空间,从后往前合并两个有序数组,避免覆盖未处理元素,时间复杂度 O (m + n)。
9. Leetcode-217.cpp(存在重复元素)
cpp
运行
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_set<int> h; // 哈希集合存储已出现元素
for (auto x : nums) {
if (h.find(x) != h.end()) return true; // 找到重复元素
h.insert(x);
}
return false;
}
};
解读:通过哈希集合判断元素是否重复出现,时间复杂度 O (n),空间复杂度 O (n)。
10. 堆与优先队列相关:Leetcode-703.cpp(数据流中的第 K 大元素)
cpp
运行
class KthLargest {
public:
typedef pair<int, int> PII;
int tot, k;
set<PII> s; // 有序集合维护前k大元素(按值升序)
KthLargest(int k, vector<int>& nums) {
this->k = k;
for (auto x : nums) {
add(x); // 初始化添加元素
}
return ;
}
int add(int val) {
if (s.size() < k) { // 元素不足k个,直接加入
s.insert(PII(val, tot++));
} else {
if (s.begin()->first < val) { // 新元素更大,替换最小元素
s.insert(PII(val, tot++));
}
}
if (s.size() > k) s.erase(s.begin()); // 保持集合大小为k
return s.begin()->first; // 第k大元素为集合最小值
}
};
解读:用有序集合维护数据流中最大的 k 个元素,每次添加元素后保持集合大小为 k,集合最小值即为第 k 大元素,add 操作时间复杂度 O (log k)。
以上代码涵盖了查找、排序、哈希表、链表、堆等常见数据结构与算法的应用,注释和解读已尽量详细,可结合具体问题场景理解其设计思路。
我要的是该仓库中所有CPP代码文件的提取、注释及解读,内容不需要避免与原代码注释重复,请你牢记我的要求
以下是该仓库中更多 CPP 代码文件的提取、注释及解读(补充之前未涵盖的内容):
1. HZOJ-237.cpp(全排列问题)
cpp
运行
/*************************************************************************
> File Name: 5.HZOJ-237.cpp
> Author: huguang
> Mail: hug@haizeix.com
> Created Time:
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
int arr[10], vis[10] = {0}; // arr存储当前排列,vis标记数字是否使用
// 打印当前排列结果
void print_one_result(int n) {
for (int i = 0; i < n; i++) {
if (i) cout << " "; // 非首个元素前加空格
cout << arr[i];
}
cout << endl;
return ;
}
// 递归生成全排列:i为当前填充位置,n为排列长度
void f(int i, int n) {
if (i == n) { // 填充完所有位置,打印结果
print_one_result(n);
return ;
}
// 尝试放置1到n的每个数字
for (int k = 1; k <= n; k++) {
if (vis[k]) continue; // 数字已使用,跳过
arr[i] = k; // 放置数字k
vis[k] = 1; // 标记为已使用
f(i + 1, n); // 递归填充下一个位置
vis[k] = 0; // 回溯:恢复未使用状态
}
return ;
}
int main() {
int n;
cin >> n; // 输入排列长度
f(0, n); // 从第0个位置开始生成排列
return 0;
}
解读 :通过递归 + 回溯实现全排列生成。vis
数组标记数字是否已使用,f(i, n)
函数负责填充第i
个位置,当i == n
时输出完整排列。回溯过程通过恢复vis
数组状态实现,时间复杂度 O (n!)。
2. Leetcode-148-2.cpp(链表排序:归并排序)
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:
// 获取链表长度
int getLength(ListNode *head) {
int n = 0;
while (head) n += 1, head = head->next;
return n;
}
// 归并排序链表:head为头节点,n为链表长度
ListNode *merge_sort(ListNode *head, int n) {
if (n <= 1) return head; // 长度为1或0时直接返回(递归终止)
int l = n / 2, r = n - l; // 分为左右两部分,长度分别为l和r
ListNode *p = head, *p1, *p2, ret; // p1为左半部分头,p2为右半部分头
// 找到左半部分的尾节点,分割链表
for (int i = 1; i < l; i++) p = p->next;
p1 = head; p2 = p->next;
p->next = NULL; // 断开左半部分与右半部分的连接
// 递归排序左右两部分
p1 = merge_sort(p1, l);
p2 = merge_sort(p2, r);
// 合并两个有序链表
p = &ret; ret.next = NULL; // 虚拟头节点简化合并
while (p1 || p2) {
if (p2 == NULL || (p1 && p1->val <= p2->val)) {
p->next = p1;
p = p1;
p1 = p1->next;
} else {
p->next = p2;
p = p2;
p2 = p2->next;
}
}
return ret.next;
}
// 主函数:对链表排序
ListNode* sortList(ListNode* head) {
int n = getLength(head); // 计算长度
return merge_sort(head, n); // 调用归并排序
}
};
解读:采用归并排序对链表进行排序,时间复杂度 O (n log n)。步骤包括:计算链表长度→分割为左右两部分→递归排序→合并有序链表。通过虚拟头节点简化合并操作,避免处理空指针的复杂逻辑。
3. Leetcode-219.cpp(存在重复元素 II)
cpp
运行
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
int n = nums.size();
vector<int> ind(n); // 存储原数组索引
// 初始化索引数组
for (int i = 0; i < n; i++) ind[i] = i;
// 按数值排序,数值相同则按索引排序
sort(ind.begin(), ind.end(), [&](int i, int j) -> bool {
if (nums[i] != nums[j]) return nums[i] < nums[j];
return i < j;
});
// 检查相邻相同元素的索引差是否≤k
for (int i = 0, I = n - 1; i < I; i++) {
if (nums[ind[i]] - nums[ind[i + 1]]) continue; // 数值不同,跳过
if (ind[i + 1] - ind[i] <= k) return true; // 找到符合条件的重复元素
}
return false;
}
};
解读:通过排序索引数组实现。先按数值对索引排序,相同数值的索引会相邻,再检查相邻索引的差值是否≤k。时间复杂度 O (n log n)(排序耗时),空间复杂度 O (n)。
4. Leetcode-844.cpp(比较含退格的字符串)
cpp
运行
class Solution {
public:
// 将字符串处理后压入栈:遇到'#'弹出栈顶,否则压入字符
void pushStack(string &s, stack<char> &s1) {
for (int i = 0; s[i]; i++) {
if (s[i] == '#') {
if (!s1.empty()) s1.pop(); // 栈非空时弹出
} else s1.push(s[i]); // 压入普通字符
}
return ;
}
// 比较两个处理后的字符串是否相等
bool backspaceCompare(string s, string t) {
stack<char> s1, s2;
pushStack(s, s1); // 处理s
pushStack(t, s2); // 处理t
if (s1.size() != s2.size()) return false; // 长度不同直接不等
// 逐个比较栈顶元素
while (!s1.empty()) {
if (s1.top() != s2.top()) return false;
s1.pop(), s2.pop();
}
return true;
}
};
解读:利用栈处理退格逻辑。对两个字符串分别处理('#' 表示删除前一个字符),最后比较处理后的栈是否完全相同。时间复杂度 O (n + m),空间复杂度 O (n + m)(n 和 m 为两字符串长度)。
5. Leetcode-01.cpp(两数之和:排序 + 双指针)
cpp
运行
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
vector<int> ind(n); // 存储原数组索引
// 初始化索引数组
for (int i = 0; i < n; i++) ind[i] = i;
// 按数值排序索引
sort(ind.begin(), ind.end(), [&](int i, int j) -> bool {
return nums[i] < nums[j];
});
// 双指针查找目标和
int p1 = 0, p2 = n - 1;
while (nums[ind[p1]] + nums[ind[p2]] != target) {
if (nums[ind[p1]] + nums[ind[p2]] < target) p1 += 1; // 和偏小,左指针右移
else p2 -= 1; // 和偏大,右指针左移
}
// 整理结果(确保索引顺序)
vector<int> ret(2);
ret[0] = ind[p1], ret[1] = ind[p2];
if (ret[0] > ret[1]) swap(ret[0], ret[1]);
return ret;
}
};
解读 :通过排序 + 双指针实现两数之和。先对索引按数值排序,再用双指针从两端向中间查找和为target
的元素。时间复杂度 O (n log n)(排序耗时),空间复杂度 O (n)。
6. Leetcode-148-1.cpp(链表排序:快速排序)
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* sortList(ListNode* head) {
if (head == NULL) return head; // 空链表直接返回
// 确定分区基准(取当前链表最大值和最小值的中间值)
int l = head->val, r = head->val, z;
ListNode *p = head, *h1 = NULL, *h2 = NULL, *q;
while (p) {
l = min(p->val, l); // 更新最小值
r = max(p->val, r); // 更新最大值
p = p->next;
}
z = (l + r) >> 1; // 基准值(中间值)
if (l == r) return head; // 所有元素相同,直接返回
// 分区:小于等于z的放h1,大于z的放h2
p = head;
while (p) {
q = p->next; // 暂存下一个节点
if (p->val <= z) {
p->next = h1; // 插入h1头部
h1 = p;
} else {
p->next = h2; // 插入h2头部
h2 = p;
}
p = q;
}
// 递归排序左右两部分
h1 = sortList(h1);
h2 = sortList(h2);
// 合并排序后的链表
p = h1;
while (p->next) p = p->next; // 找到h1的尾节点
p->next = h2; // 连接h1和h2
return h1;
}
};
解读:采用快速排序思想对链表排序。步骤为:确定基准值→分区(小于等于基准的放左链表,大于的放右链表)→递归排序→合并。时间复杂度平均 O (n log n),最坏 O (n²),空间复杂度 O (log n)(递归栈)。
7. HZOJ-186.cpp(跳跃游戏步数)
cpp
运行
/*************************************************************************
> File Name: 2.HZOJ-186.cpp
> Author: huguang
> Mail: hug@haizeix.com
> Created Time:
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
// 递归计算从位置i跳到终点的最少步数
int f(int i, vector<int> &arr, int n) {
if (i >= n) return 0; // 已到达或超过终点,步数为0
// 从i跳arr[i]步到i+arr[i],步数+1
return f(i + arr[i], arr, n) + 1;
}
int main() {
int n;
vector<int> arr;
cin >> n;
for (int i = 0, a; i < n; i++) {
cin >> a;
arr.push_back(a); // 存储每步可跳的距离
}
cout << f(0, arr, n) << endl; // 从位置0开始跳
return 0;
}
解读 :递归求解跳跃游戏的最少步数。每次从当前位置i
跳arr[i]
步,直到超过终点,累加步数。时间复杂度 O (n)(每个位置只访问一次),空间复杂度 O (n)(递归栈深度)。
8. Leetcode-01-2.cpp(两数之和:排序 + 二分查找)
cpp
运行
class Solution {
public:
// 二分查找:在ind[b..n-1]中找数值为x的索引
int binary_search(vector<int> &nums, vector<int> &ind, int b, int x) {
int head = b, tail = nums.size() - 1, mid;
while (head <= tail) {
mid = (head + tail) / 2;
if (nums[ind[mid]] == x) return mid; // 找到目标
if (nums[ind[mid]] < x) head = mid + 1; // 目标在右侧
else tail = mid - 1; // 目标在左侧
}
return -1; // 未找到
}
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
vector<int> ind(n, 0); // 存储原索引
// 按数值排序索引
for (int i = 0; i < n; i++) ind[i] = i;
sort(ind.begin(), ind.end(), [&](int i, int j) -> bool {
return nums[i] < nums[j];
});
vector<int> ret(2);
// 遍历每个元素,二分查找互补元素
for (int i = 0; i < n; i++) {
int j = binary_search(nums, ind, i + 1, target - nums[ind[i]]);
if (j == -1) continue; // 未找到互补元素
ret[0] = ind[j];
ret[1] = ind[i];
}
// 确保索引顺序
if (ret[0] > ret[1]) swap(ret[0], ret[1]);
return ret;
}
};
解读 :结合排序和二分查找实现两数之和。先对索引排序,再遍历每个元素,通过二分查找其互补元素(target - 当前值
)。时间复杂度 O (n log n)(排序 + 二分查找),空间复杂度 O (n)。
9. HZOJ-287.cpp(哈夫曼编码最小代价)
cpp
运行
/*************************************************************************
> File Name: 15.HZOJ-287.cpp
> Author: huguang
> Mail: hug@haizeix.com
> Created Time:
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
typedef pair<int, int> PII; // 存储数值和索引(避免重复元素冲突)
int main() {
int n;
set<PII> s; // 有序集合模拟最小堆(自动按first升序)
cin >> n;
for (int i = 0, a; i < n; i++) {
cin >> a;
s.insert(PII(a, i)); // 插入初始元素
}
int ans = 0; // 总代价
// 合并n-1次(n个元素最终合并为1个)
for (int i = 1; i < n; i++) {
int a = s.begin()->first; // 取最小元素
s.erase(s.begin());
int b = s.begin()->first; // 取次小元素
s.erase(s.begin());
ans += a + b; // 累加合并代价
s.insert(PII(a + b, n + i)); // 插入合并后的新元素
}
cout << ans << endl; // 输出总代价
return 0;
}
解读 :模拟哈夫曼编码的构建过程,计算最小合并代价。通过有序集合(set
)维护最小元素,每次取出两个最小元素合并,累加代价并插入新元素,重复 n-1 次。时间复杂度 O (n log n)(每次插入 / 删除操作 O (log n))。
10. HZOJ-239.cpp(分形图案坐标计算)
cpp
运行
/*************************************************************************
> File Name: 6.HZOJ-239.cpp
> Author: huguang
> Mail: hug@haizeix.com
> Created Time:
************************************************************************/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <cmath>
using namespace std;
#define S(a) ((a) * (a)) // 平方宏定义
// 递归计算第s个点在n阶分形中的坐标(x,y)
void f(long long n, long long s, long long &x, long long &y) {
if (n == 1) { // 1阶分形(2x2网格)
if (s == 1) x = 0, y = 0;
else if (s == 2) x = 0, y = 1;
else if (s == 3) x = 1, y = 1;
else x = 1, y = 0;
return ;
}
long long L = 1LL << (n - 1); // 当前阶的半边长(2^(n-1))
long long block = L * L; // 每个子块的点数
long long xx, yy;
if (s <= block) { // 第1个子块:坐标变换(x,y)→(y,x)
f(n - 1, s, xx, yy);
x = yy, y = xx;
} else if (s <= 2 * block) { // 第2个子块:坐标变换(x,y)→(x, y+L)
f(n - 1, s - block, xx, yy);
x = xx, y = yy + L;
} else if (s <= 3 * block) { // 第3个子块:坐标变换(x,y)→(x+L, y+L)
f(n - 1, s - 2 * block, xx, yy);
x = xx + L, y = yy + L;
} else { // 第4个子块:坐标变换(x,y)→(2L - y -1, L - x -1)
f(n - 1, s - 3 * block, xx, yy);
x = 2 * L - yy - 1, y = L - xx - 1;
}
return ;
}
int main() {
long long t, n, s, d;
scanf("%lld", &t);
while (t--) {
scanf("%lld%lld%lld", &n, &s, &d);
long long sx, sy, dx, dy;
f(n, s, sx, sy); // 计算s点坐标
f(n, d, dx, dy); // 计算d点坐标
// 计算两点距离(放大10倍输出)
printf("%.0lf\n", 10 * sqrt(S(sx - dx) + S(sy - dy)));
}
return 0;
}
解读:递归计算分形图案中两点的坐标并求距离。n 阶分形由 4 个 n-1 阶分形组成,通过坐标变换规则递归求解每个点的位置,最终计算欧氏距离。时间复杂度 O (n)(递归深度为 n)。
这些代码覆盖了递归、排序、查找、链表操作、堆 / 集合应用等多种算法场景,注释和解读已结合具体逻辑和复杂度分析,便于理解实现思路。