Leetcode Hot 100 (C语言版) 1-21

博主搞嵌入式的,走底层纯C的,为了找实习/工作刷点题,平时AI coding习惯了,怕手撕代码写不出来,这里刷题顺便分享下思路,代码能跑就行,有什么问题欢迎指出,博主是新手入门;

这里按照难度排序依次进行。

在刷leetcode前建议可以先练一练牛客的算法入门,效果会更好。


1.两数之和

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

分析:经典劝退题,难度不高但是能看出来平时写不写代码;直接暴力双循环解决,也有用哈希表来做的,咱这能跑就行。

int* twoSum(int* nums, int numsSize, int target, int* returnSize) { int *a =(int*)malloc(sizeof(int)*2); int i,j; for(i=0;i<numsSize;i++){ for(j=i+1;j<numsSize;j++){ if(nums[i]+nums[j]==target) { a[0]=i; a[1]=j; *returnSize=2; return a; } } } return 0; }

20.有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

分析:很经典的堆栈问题,主要是判断下标top是否为0

bool isValid(char* s) { char stack[10001]; int top=0; for(int i=0;s[i]!='\n'&&s[i]!='\0';i++) { char c=s[i]; if (c == '(' || c == '{' || c == '[') { stack[++top] = c; } else { if(top==0){return false;} char topChar = stack[top]; if ((c == ')' && topChar != '(') || (c == '}' && topChar != '{') || (c == ']' && topChar != '[')) { return false; } top--; } } if(top!=0){return false;} return true; }

21.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

分析:有两种做法,递归和尾插。递归不是太好理解,这里用更直观的尾插法,比较大小

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { struct ListNode dummy; struct ListNode *p=&dummy; while(list1!=NULL && list2!=NULL) { if(list1->val<=list2->val) { p->next=list1; list1=list1->next; } else { p->next=list2; list2=list2->next; } p=p->next; } p->next=list1?list1:list2; return dummy.next; }

70.爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

分析:这道题最大的难度是看出它是斐波那契数列

int climbStairs(int n) { if (n <= 2) return n; int prev2 = 1; // f(n-2) int prev1 = 2; // f(n-1) int curr; for (int i = 3; i <= n; i++) { curr = prev1 + prev2; // f(n) = f(n-1) + f(n-2) prev2 = prev1; prev1 = curr; } return prev1; }

94.二叉树的中序遍历

给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。

分析:中序是”左根右“,另外还有先序的”根左右“以及后序的”左右根“;root在哪就是什么序,然后左右;这次的话用递归就好办不少了,写代码的时候注意指针操作最好加个括号;

void inorder(struct TreeNode* root, int* tree, int* returnSize) { if(root == NULL) { return; // void函数直接return } inorder(root->left, tree, returnSize); // 左 tree[*returnSize] = root->val; // 根 (*returnSize)++; // 括号包裹,不加的话++的优先级高于* inorder(root->right, tree, returnSize); // 右 } int* inorderTraversal(struct TreeNode* root, int* returnSize) { *returnSize = 0; int *tree = malloc(sizeof(int) * 300); inorder(root, tree, returnSize); return tree; }

101.对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

分析:想明白递归思路,就稍微好写了

 1. 两个都为空,对称 2. 只有一个为空,不对称 3. 值不同,不对称 4. 递归检查:左的左 vs 右的右,左的右 vs 右的左
bool judge(struct TreeNode* left, struct TreeNode* right) { if(right==NULL && left==NULL){return true;} if(right==NULL || left==NULL){return false;} if(right->val != left->val){return false;} return judge(left->left,right->right) && judge(left->right,right->left); } bool isSymmetric(struct TreeNode* root) { if(root == NULL) { return true; } return judge(root,root); }

104.二叉树的最大深度

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

分析:这里leetcode官方给的解释很不错,

如果我们知道了左子树和右子树的最大深度 l 和 r,那么该二叉树的最大深度即为

max(l,r)+1 根据这个递归就好了

int maxDepth(struct TreeNode* root) { if (root == NULL) { return 0; } int leftDepth = maxDepth(root->left); int rightDepth = maxDepth(root->right); return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1; }

121.买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

分析:(leetcode评论区)

每天的最大获利 = 今天 - 前面 n 天的最小值
最大获利 = max(每天最大获利)

int maxProfit(int* prices, int pricesSize) { int buy = prices[0]; int money = 0; for(int i=0;i<pricesSize;i++) { if(prices[i]-buy>money){money=prices[i]-buy;} if(prices[i]<buy){buy=prices[i];} } return money; }

136.只出现一次的数字

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。

分析:刚上来就想到哈希表做法,但官方答案用异或来做更好一些

#define OFFSET 30000 // 偏移量,覆盖负数范围 #define SIZE 60001 int singleNumber(int* nums, int numsSize) { int hash[SIZE] = {0}; for(int i = 0; i < numsSize; i++) { int idx = nums[i] + OFFSET; // 负数变正数 hash[idx] ^= 1; // 0变1,1变0 } for(int i = 0; i < SIZE; i++) { if(hash[i] == 1) { return i - OFFSET; } } return 0; }
int singleNumber(int* nums, int numsSize) { int result = 0; for(int i = 0; i < numsSize; i++) { result ^= nums[i]; // 成对的数会抵消,只剩单个的 } return result; }

141.环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

分析:使用快慢指针,如果有环,快的一定能追上慢的

bool hasCycle(struct ListNode *head) { struct ListNode *fast=head; struct ListNode *slow=head; while(fast!=NULL && fast->next!=NULL) { fast=fast->next->next; slow=slow->next; if(fast==slow) { return true; } } return false; }

160.相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

分析:这道题,我最开始想的就是固定A,让B轮询,然后A再往前走,这样的话时间复杂度是O(m×n),而且一点也不优雅;后来看评论区用双指针(两个指针都走 m+n 步,必然在交点或终点相遇),走过你的来时路,只为与你相遇,还挺浪漫。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { struct ListNode *curA = headA; while (curA != NULL) { struct ListNode *curB = headB; while (curB != NULL) { if (curA == curB) { return curA; } curB = curB->next; } curA = curA->next; } return NULL; }
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { if (headA == NULL || headB == NULL) return NULL; struct ListNode *pA = headA, *pB = headB; while (pA != pB) { pA = (pA == NULL) ? headB : pA->next; pB = (pB == NULL) ? headA : pB->next; } return pA; }

169.多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

分析:这道题又想用哈希表,后来一看范围,觉得肯定不是最优解,看了看评论区,一命抵一命,人数最多的那个肯定活到最后,直接给程序写出来了

  • -10^9 <= nums[i] <= 10^9
  • 1 <= n <= 5 * 10^4
int majorityElement(int* nums, int numsSize) { int a=nums[0]; int blood=1; for(int i=1;i<numsSize;i++) { int b=nums[i]; if(a==b) { blood++; } else { blood--; if(blood<0) { a=nums[i]; blood=0; } } } return a; }

206.翻转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

分析:很基础的问题,一般直接修改原链表,还可以生成新链表方式(n次malloc,不推荐);

struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev = NULL; struct ListNode *curr = head; struct ListNode *next = NULL; while (curr != NULL) { next = curr->next; // 保存下一个节点 curr->next = prev; // 反转当前节点指向 prev = curr; // prev前移 curr = next; // curr前移 } return prev; // 新头节点 }
struct ListNode* reverseList(struct ListNode* head) { struct ListNode* newHead = NULL; // 新链表的头 while (head != NULL) { // 创建新节点,复制值 struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode)); newNode->val = head->val; // 头插法:新节点指向原来的新头 newNode->next = newHead; newHead = newNode; // 更新新头 head = head->next; // 遍历原链表(只读,不修改) } return newHead; // 全新的反转链表 }

226.反转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

分析:经典递归做法;有趣的是,homebrew(一个很有名的苹果软件)的作者因为不能在白板上反转二叉树(就这道题)就被Google给拒了

struct TreeNode* invertTree(struct TreeNode* root) { // 1. 递归终止条件 if (root == NULL) { return NULL; } // 2. 递归翻转左右子树 struct TreeNode* left = invertTree(root->left); struct TreeNode* right = invertTree(root->right); // 3. 交换左右子树 root->left = right; root->right = left; return root; }

234.回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

分析:第一直觉是生成一个反转链表然后逐个比对;

           在长度不大的情况下也可以直接转成数组(时间n,空间n)

struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev = NULL, *cur = head; while (cur) { struct ListNode* next = cur->next; cur->next = prev; prev = cur; cur = next; } return prev; } bool isPalindrome(struct ListNode* head) { if (head == NULL || head->next == NULL) return true; // 1. 快慢指针找中点(慢指针走到前半部分末尾) struct ListNode *slow = head, *fast = head; while (fast->next && fast->next->next) { slow = slow->next; fast = fast->next->next; } // 2. 反转后半部分 struct ListNode* secondHalfStart = reverseList(slow->next); // 3. 比较两部分 struct ListNode *firstHalfStart = head; struct ListNode *secondHalfCopy = secondHalfStart; // 保存用于恢复 bool result = true; while (secondHalfStart) { if (firstHalfStart->val != secondHalfStart->val) { result = false; break; } firstHalfStart = firstHalfStart->next; secondHalfStart = secondHalfStart->next; } // 4. 恢复链表(可选,良好习惯) slow->next = reverseList(secondHalfCopy); return result; }
bool isPalindrome(struct ListNode* head) { if (head == NULL || head->next == NULL) return true; int arr[100001]; int n = 0; // 1. 链表转数组 struct ListNode* cur = head; while (cur) { arr[n++] = cur->val; cur = cur->next; } // 2. 双指针判断回文 int left = 0, right = n - 1; while (left < right) { if (arr[left] != arr[right]) return false; left++; right--; } return true; }

283.移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

分析:记录元素之前有多少个零就行了,直接和前面第一个零交换位置就好

void moveZeroes(int* nums, int numsSize) { int cnt=0; for(int i=0;i<numsSize;i++) { if(nums[i]==0) { cnt++; } else { int temp=nums[i-cnt]; nums[i-cnt]=nums[i]; nums[i]=temp; } } }

338.比特位计数

给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。

分析:这道题的关键在于怎样去找一个数对应的二进制,我最开始想到就是除二取余这种课本上的方法;这道题最优解应该是位运算(Brian Kernighan 算法)不过一般人(包括我)应该想不到吧

int countOnes(int n) { int count = 0; while (n > 0) { if (n % 2 == 1) { count++; } n = n / 2; } return count; } int* countBits(int n, int* returnSize) { int*a=malloc(sizeof(int)*(n+1)); *returnSize=n+1; for (int i = 0; i <= n; i++) { a[i] = countOnes(i); } return a; }
int countOnes(int x) { int ones = 0; while (x > 0) { x &= (x - 1); ones++; } return ones; }

448.找到所有数组中消失的数字

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

分析:第一时间用哈希表暴力解出来了;官方答案是拿数组本身当作哈希,效率更高

int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) { int hash[100001]={0}; int *arr=malloc(sizeof(int)*100000); int top=0; for(int i=0;i<numsSize;i++) { if(hash[nums[i]]==0) { hash[nums[i]]=1; } } for(int i=1;i<=numsSize;i++) { if(hash[i]==0) { arr[top]=i; top++; } } *returnSize=top; return arr; }
int* findDisappearedNumbers(int* nums, int numsSize, int* returnSize) { for (int i = 0; i < numsSize; i++) { int index = abs(nums[i]) - 1; if (nums[index] > 0) { nums[index] = -nums[index]; // 标记为负 } } int* result = malloc(sizeof(int) * numsSize); int count = 0; for (int i = 0; i < numsSize; i++) { if (nums[i] > 0) { // 正数表示缺失 result[count++] = i + 1; } } *returnSize = count; return result; }

461.汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

给你两个整数 x 和 y,计算并返回它们之间的汉明距离。

分析:通过x^y,不同比特位变成1,然后求1的个数,可以用Brian Kernighan 算法;有趣的是,找到了一个GCC内置函数,得到了作弊方案

int hammingDistance(int x, int y) { unsigned int xor = x ^ y; int count = 0; while (xor) { xor &= xor - 1; count++; } return count; } 
int hammingDistance(int x, int y) { return __builtin_popcount(x ^ y); }

543.二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径 。

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。

两节点之间路径的 长度 由它们之间边数表示。

分析:这道题大家普遍觉得不算简单题,有坑;首先想到的是前面做的二叉树最大深度题,左深度+右深度,但是需要注意,直径并不是百分百过根节点的,每个节点都可能是最长直径的转折点,需要遍历所有节点

int maxDepth(struct TreeNode* root, int* diameter) { if (root == NULL) return 0; int left = maxDepth(root->left, diameter); int right = maxDepth(root->right, diameter); // 更新全局最大直径(经过当前节点的路径) *diameter = (*diameter > left + right) ? *diameter : left + right; // 返回当前子树的深度(给父节点用) return (left > right ? left : right) + 1; } int diameterOfBinaryTree(struct TreeNode* root) { int diameter = 0; maxDepth(root, &diameter); return diameter; }

617.合并二叉树

给你两棵二叉树: root1 和 root2 。

想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。

返回合并后的二叉树。

注意: 合并过程必须从两个树的根节点开始。

分析:这里直接用官方答案,十分干脆的递归大法

struct TreeNode* mergeTrees(struct TreeNode* t1, struct TreeNode* t2) { if (t1 == NULL) { return t2; } if (t2 == NULL) { return t1; } struct TreeNode* merged = malloc(sizeof(struct TreeNode)); merged->val = t1->val + t2->val; merged->left = mergeTrees(t1->left, t2->left); merged->right = mergeTrees(t1->right, t2->right); return merged; }

Read more

【Linux篇章】再续传输层协议TCP:用技术隐喻重构网络世界的底层逻辑,用算法演绎‘网络因果律’的终极推演(通俗理解TCP协议,这一篇就够了)!

【Linux篇章】再续传输层协议TCP:用技术隐喻重构网络世界的底层逻辑,用算法演绎‘网络因果律’的终极推演(通俗理解TCP协议,这一篇就够了)!

📌本篇摘要 * 本篇将根据TCP协议报文的格式来对TCP更深入的了解,学习它的三次握手,四次挥手,滑动窗口等等,到最后能更加深入理解之前写TCP通信的时候,底层到底是如何进行的,读完本篇将会对之前TCP网络通信编程有更深入的认识。 🏠欢迎拜访🏠:点击进入博主主页 📌本篇主题📌:再续TCP协议 📅制作日期📅:2025.12.20 🧭隶属专栏🧭:点击进入所属Linux专栏 一.TCP协议格式 -TCP 全称为 传输控制协议(Transmission Control Protocol). 人如其名, 要对数据的传输进行一个详细的控制。 下面看TCP报文的格式: 下面我们来一个个介绍下这些字段及作用: 1. 🔍十六位窗口大小 * 这里我们知道对于tcp来说,如果接收缓冲区满了,再发送机会被丢弃,因此发送前需要知道对的的接收缓冲区的剩余长度。 * 按量按需发送,必须知道对方的接受缓冲区中剩余空间的大小,因此每次发送的tcp报文都要带有自己剩余接收缓冲区的长度! 2.🔍4位首部长度 * 首先我们要知道tcp光报头就至少20字节(不包含

【无人机路径规划】无人机三维路径规划中蚁群算法、A* 与 RRT* 算法对比(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭:行百里者,半于九十。 📋📋📋本文目录如下:🎁🎁🎁 💥1 概述 随着无人机技术的快速发展,其在军事侦察、物流配送、环境监测等众多领域的应用日益广泛。在实际应用场景中,无人机需要在复杂的三维空间内规划出一条安全、高效的飞行路径,以避开障碍物并满足任务需求。蚁群算法、A* 算法和 RRT* 算法是目前无人机三维路径规划中常用的算法,它们各自具有独特的原理和特点,对其进行详细对比有助于根据具体应用场景选择最合适的算法。 蚁群算法 蚁群算法是一种模拟蚂蚁觅食行为的启发式优化算法。蚂蚁在寻找食物的过程中,会在走过的路径上释放信息素,信息素浓度越高的路径对其他蚂蚁的吸引力越大。在无人机路径规划中,将三维空间划分为多个节点,每只“虚拟蚂蚁”从起点开始,根据信息素浓度和启发式信息选择下一个节点,不断迭代更新信息素浓度,最终找到一条从起点到终点的最优路径。 A* 算法 A*

C语言指针与数组的深度关联及实战应用

C语言指针与数组的深度关联及实战应用

C语言指针与数组的深度关联及实战应用 💡 学习目标:掌握指针与数组的内在联系,熟练运用指针操作数组元素,解决实际开发中的数组遍历、数据交换等问题;学习重点:数组名的本质、指针算术运算操作数组、指针数组与数组指针的区别及应用。 38.1 数组名与指针的关系 在C语言中,数组和指针有着密不可分的联系。很多初学者会混淆数组名和指针变量的概念,其实二者既有关联,又有本质区别。 38.1.1 数组名的本质 💡 数组名在大多数情况下会被编译器隐式转换为指向数组首元素的常量指针。 我们来看一段简单的代码: #include<stdio.h>intmain(){int arr[5]={10,20,30,40,50};printf("数组首元素地址:%p\n", arr);printf("数组首元素地址:%p\n&