前言
第一次写博客好像效果还不错,所以我会持续坚持下去,也会努力写得更好。最近好像又会变忙:课程的增多、蓝桥杯竞赛、英语四级,看来又得忙了(笑)
目录
正文
数学
21.合并两个有序链表(easy)
题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4] 示例 2:
输入:l1 = [], l2 = [] 输出:[] 示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
自写版本:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
struct ListNode dummy={0};//正确写法:struct LisNode *dummy={0,NULL};
struct ListNode *c=dummy;//c用tail取代更加规范
while(list1&&list2){
if(list1->val<list2->val){
c->next=list1->val;
list1=list1->next;
}else{
c->next=list2->val;
list2=list2->next;
}
c=c->next;
}
return dummy.next;
}//无法通过
解题思路
- 分析题目:题目会给两个升序链表,将它们合并返回一个新列表。
- 创建虚拟头节点:这里有点忘记代码怎么写了,但还是记得有这么个步骤
- 定义指针:用于合并过程构建新链表
- 合并两个链表:只要list1和list2都不为NULL,就每次都去比较list1和list2当前节点的值,若list1->val < list2->val,就把list1当前节点连接到新链表上,然后list1指针向后移动一位。 反之,把list2当前节点连接到新链表上,接着list2指针向后移动一位。每次连接节点之后,c指针也向后移动一位。
- 处理剩余节点:当while循环结束时,list1或者list2其中之一可能还存在剩余节点。这里使用三目运算符把剩余节点直接连接到新链表的末尾。
6返回合并后链表的头节点:返回虚拟头节点dummy的next指针,也就是合并后新链表的头节点。
代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
struct ListNode dummy={0,NULL};
struct ListNode* tail=&dummy;
while(l1&&l2){
if(l1->val<l2->val){
tail->next=l1;
l1=l1->next;
}else{
tail->next=l2;
l2=l2->next;
}
tail=tail->next;
}
tail->next=l1?l1:l2;
return dummy.next;
}
345.反转字符串中的元音字母(easy)
给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。
元音字母包括 ‘a’、‘e’、‘i’、‘o’、‘u’,且可能以大小写两种形式出现不止一次。
示例 1:
输入:s = “IceCreAm”
输出:“AceCreIm”
解释:
s 中的元音是 [‘I’, ‘e’, ‘e’, ‘A’]。反转这些元音,s 变为 “AceCreIm”.
示例 2:
输入:s = “leetcode”
输出:“leotcede”
自写版本:
int Vowels(char c){
tolower(c);//只是返回了一个为小写后的字符,并没有赋值,正确写法: c=tolower(c);
return c=='a'||c=='e'||c=='i'||c=='o'||c=='u';
}
char* reverseVowels(char* s) {
int left=0;
int right=strlen(s)-1;
while(left<right){
if(left<right&&!Vowels(s[left])){ //这里不能用if,if只动一次,要用while循环
left++;
}
if(left<right&&!Vowels(s[right])){ //同上
right--;
}
//最好加上if(left<right)
char temp=s[left];
s[left]=s[right];
s[right]=temp;
left++;
right--;
}
return s;
}
解题思路
1.定义 Vowels 函数:此函数用于判断一个字符是否为元音字母
-
借助 tolower 函数把字符转换为小写,这样就能不区分大小写地判断元音字母。
-
运用逻辑或运算符 || 来判定字符是否为 ‘a’、‘e’、‘i’、‘o’ 或者 ‘u’ 中的一个。若为元音字母则返回 1,反之返回 0。
2**.定义 reverseVowels 函数**: -
初始化指针:left 指针初始化为 0,指向字符串的开头;right 指针初始化为 strlen(s) - 1,指向字符串的末尾。
-
外层循环:运用 while 循环,条件是 left < right,保证两个指针不会交叉。
-
移动指针:借助内层 while 循环,在 left < right 且 s[left] 不是元音字母的情况下,持续将 left 指针右移right 指针左移。
-
交换字符:当 left < right 时,意味着两个指针都指向了元音字母,此时交换 s[left] 和 s[right] 的值,接着将 left 指针右移,right 指针左移。
-
返回结果:最后返回反转后的字符串。
代码实现
int Vowels(char c){
c=tolower(c);
return c=='a'||c=='e'||c=='i'||c=='o'||c=='u';
}
char* reverseVowels(char* s) {
int left=0,right=strlen(s)-1;
while(left<right){
while(left<right&&!Vowels(s[left])){
left++;
}
while(left<right&&!Vowels(s[right])){
right--;
}
if(left<right){
char temp;
temp=s[left];
s[left]=s[right];
s[right]=temp;
left++;
right--;
}
}
return s;
}
1523.在区间范围内统计奇数数目(easy)
给你两个非负整数 low 和 high 。请你返回 low 和 high 之间(包括二者)奇数的数目。
示例 1:
输入:low = 3, high = 7 输出:3 解释:3 到 7 之间奇数数字为 [3,5,7] 。 示例 2:
输入:low = 8, high = 10 输出:1 解释:8 到 10 之间奇数数字为 [9] 。
自写版本:
int countOdds(int low, int high) {
if((high-low+1)%2==0){
return (high-low+1)/2;
}else{
if(high%2!=0){
return (high-low)/2+1;
}else{
return (high-low)/2;
}
}
}
解题思路
- 若元素总数为偶数:区间内奇数和偶数的数量是相等的。
- 当元素总数为奇数且high 是偶数:那么区间内奇数的数量就等于元素总数减 1 之后再除以 2
- 当元素总数为奇数时且high 是奇数:那么区间内奇数的数量就等于元素总数减 1 之后除以 2 再加 1
代码实现
本题较为简单,自写版本已经正确。
数组
2011.执行操作后的变量值(easy)
存在一种仅支持 4 种操作和 1 个变量 X 的编程语言:
++X 和 X++ 使变量 X 的值 加 1
–X 和 X-- 使变量 X 的值 减 1 最初,X 的值是 0给你一个字符串数组 operations ,这是由操作组成的一个列表,返回执行所有操作后, X 的 最终值 。
示例 1:
输入:operations = [“–X”,“X++”,“X++”] 输出:1 解释:操作按下述步骤执行: 最初,X = 0
–X:X 减 1 ,X = 0 - 1 = -1 X++:X 加 1 ,X = -1 + 1 = 0 X++:X 加 1 ,X = 0 + 1 = 1 示例 2:输入:operations = [“++X”,“++X”,“X++”] 输出:3 解释:操作按下述步骤执行: 最初,X = 0
++X:X 加 1 ,X = 0 + 1 = 1
++X:X 加 1 ,X = 1 + 1 = 2 X++:X 加 1 ,X = 2 + 1 = 3
自写版本:
int finalValueAfterOperations(char** operations, int operationsSize) {
int X=0;
for(int i=0;i<operationsSize;i++){
if(strcmp(operations[i],"X++")==0||strcmp(operations[i],"++X")==0){
X++;
}else{
X--;
}
}
return X;
}
解题思路
- 设置变量X:把变量 X 初始化为 0。
- 遍历数组:对数组 operations 里的每个字符进行遍历。
- 判断操作类型:针对每个字符,使用strcmp函数判定它是自增操作(++X 或者 X++)还是自减操作(–X 或者 X–)。
- 返回最终结果:遍历完所有字符之后,对变量 X 的值进行更新,返回变量 X 的最终值。
代码实现
本题比较简单,自写版本已是正确答案,要注意其中的X为大写。
1822.数组元素积的符号(easy)
已知函数 signFunc(x) 将会根据 x 的正负返回特定值:
如果 x 是正数,返回 1 。 如果 x 是负数,返回 -1 。 如果 x 是等于 0 ,返回 0 。 给你一个整数数组 nums 。令
product 为数组 nums 中所有元素值的乘积。返回 signFunc(product) 。
示例 1:
输入:nums = [-1,-2,-3,-4,3,2,1] 输出:1 解释:数组中所有值的乘积是 144 ,且 signFunc(144)
= 1 示例 2:输入:nums = [1,5,0,2,-3] 输出:0 解释:数组中所有值的乘积是 0 ,且 signFunc(0) = 0 示例 3:
输入:nums = [-1,1,-1,1,-1] 输出:-1 解释:数组中所有值的乘积是 -1 ,且 signFunc(-1) = -1
自写版本:
int signFunc(int x){
if(x>0){
return 1;
}else if(x==0){
return 0;
}else{
return -1;
}
}
int arraySign(int* nums, int numsSize) {
int product=signFunc(nums[0]);
for(int i=1;i<numsSize;i++){
product*=signFunc(nums[i]);
}
return product;
}
解题思路
- signFunc 函数:此函数的作用是判断一个整数的符号。
- arraySign 函数:该函数用于判断数组元素乘积的符号,使用累乘的方法实现
代码实现
本题较为简单,自写已经正确
1502.判断能否形成等差数列(easy)
给你一个数字数组 arr 。
如果一个数列中,任意相邻两项的差总等于同一个常数,那么这个数列就称为 等差数列 。
如果可以重新排列数组形成等差数列,请返回 true ;否则,返回 false 。
示例 1:
输入:arr = [3,5,1] 输出:true 解释:对数组重新排序得到 [1,3,5] 或者 [5,3,1] ,任意相邻两项的差分别为
2 或 -2 ,可以形成等差数列。 示例 2:输入:arr = [1,2,4] 输出:false 解释:无法通过重新排序得到等差数列。
自写版本:
int compare(void const*a,void const*b){
return (*(int*)a-*(int*)b);
}
bool canMakeArithmeticProgression(int* arr, int arrSize) {
qsort(arr,arrSize,sizeof(int),compare);
int m=arr[1]-arr[0];
for(int i=1;i<arrSize-1;i++){
if(arr[i+1]-arr[i]!=m){
return 0;
}
}
return 1;
}
解题思路
- 设置比较函数compare:此函数是为 qsort 函数提供的比较规则。
- 数组排序:调用 qsort 函数对数组 arr 进行排序。
- 计算公差:在等差数列里,任意相邻两项的差值是固定的,这个固定差值就是公差。
- 检查是否为等差数列:对于每个索引 i,检查 arr[i + 1] - arr[i] 是否等于公差 m。若不相等,说明数组不满足等差数列的条件,函数返回 0 表示 false。
- 返回结果
代码实现
此题较为简单,自己所写代码已经正确。注意i设置的范围应该是(i<arrSize-1)防止超出范围。
链表
206.反转链表(easy)
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1] 示例 2:
输入:head = [1,2] 输出:[2,1] 示例 3:
输入:head = [] 输出:[]
自写版本:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* current=head;
struct ListNode* pro=NULL;
struct ListNode* temp=NULL;
while(current==NULL){//while循环条件不熟,是当条件不等于括号里面时,才会跳出循环
temp=current->next;
current->next=pro;
pro=current;
head=temp;//正确写法:current=temp;
}
return pro;
}
解题思路
1.初始化指针:
- 定义三个指针:current、pro 和 temp。
- current 指针初始化为链表的头节点 head,用于遍历链表。
- pro 指针初始化为 NULL,它代表当前节点的前一个节点,在反转过程中,当前节点的 next 指针将指向这个节点。
- temp 指针初始化为 NULL,用于临时保存当前节点的下一个节点,防止在改变 current 的 next 指针时丢失后续节点的引用。
2.遍历链表并反转节点:
- 使用一个 while 循环来遍历链表,循环条件为 current != NULL,即只要当前节点不为空,就继续进行反转操作。
- 保存下一个节点:将 current 的下一个节点保存到 temp 中,即 temp = current->next。这样做是为了在后续操作中不会丢失当前节点的下一个节点的引用。
- 反转当前节点的指针:将 current 的 next 指针指向 pro,即 current->next = pro。这一步是反转链表的关键操作,它改变了当前节点的指向,使其指向前一个节点。
- 移动 pro 指针:将 pro 指针更新为 current,即 pro = current。因为在下次循环中,当前节点将成为下一个节点的前一个节点。
- 移动 current 指针:将 current 指针更新为 temp,即 current = temp。这样就将 current 移动到了下一个节点,继续进行反转操作。
3.返回新的头节点:
当 current 为 NULL 时,说明已经遍历完整个链表,此时 pro 指针指向的就是反转后链表的头节点。因此,返回 pro 作为结果。
代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* current=head;
struct ListNode* pro=NULL;
struct ListNode* temp=NULL;
while(current!=NULL){
temp=current->next;
current->next=pro;
pro=current;
current=temp;
}
return pro;
}
203.移除链表元素(easy)
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5] 示例 2:
输入:head = [], val = 1 输出:[] 示例 3:
输入:head = [7,7,7,7], val = 7 输出:[]
自写版本:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* dummy=(struct ListNode*)malloc(sizeof(struct ListNode));
dummy->next=head;
struct ListNode* cur=dummy;
while(cur->next!=NULL){
if(cur->next==val){ //应该是下一节点的值等于val,修改为:cur->next-val==val
struct ListNode* temp=cur->next;
cur->next=cur->next->next;
free(temp);
}else{
cur=cur->next;
}
}
struct ListNode* newNode;
newNode=dummy;应该是返回头结点:newNode=dummy->next
free(dummy);
return newNode;
}
解题思路
- 创建虚拟头节点:
- 运用 malloc 函数为虚拟头节点分配内存。
- 把虚拟头节点的 next 指针指向原链表的头节点 head。
- 初始化当前指针:定义一个指针 cur 并将其初始化为虚拟头节点 dummy,此指针用于遍历链表。
- 遍历链表并删除符合条件的节点:
-
借助 while 循环遍历链表,循环条件为 cur->next != NULL,即只要当前节点的下一个节点存在,就继续遍历。
-
若当前节点的下一个节点的值等于 val,就执行以下操作:
用临时指针 temp 指向当前节点的下一个节点。
将当前节点的 next 指针指向当前节点的下下个节点,从而跳过值为 val 的节点。
利用 free 函数释放 temp 所指向节点的内存,防止内存泄漏。 -
若当前节点的下一个节点的值不等于 val,则将 cur 指针向后移动一位。
- 获取新的头节点并释放虚拟头节点:新的头节点是虚拟头节点的下一个节点,将其赋值给 newHead。释放虚拟头节点的内存,避免内存泄漏。
代码实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val) {
struct ListNode* dummy=(struct ListNode*)malloc(sizeof(struct ListNode));
dummy->next=head;
struct ListNode* cur=dummy;
while(cur->next!=NULL){
if(cur->next->val==val){
struct ListNode* temp=cur->next;
cur->next=cur->next->next;
free(temp);
}else{
cur=cur->next;
}
}
struct ListNode*newHead;
newHead=dummy->next;
free(dummy);
return newHead;
}
字符串
520.检测大写字母(easy)
我们定义,在以下情况时,单词的大写用法是正确的:
全部字母都是大写,比如 “USA” 。 单词中所有字母都不是大写,比如 “leetcode” 。 如果单词不只含有一个字母,只有首字母大写,
比如 “Google” 。 给你一个字符串 word 。如果大写用法正确,返回 true ;否则,返回 false 。示例 1:
输入:word = “USA” 输出:true 示例 2:
输入:word = “FlaG” 输出:false
自写版本:
bool detectCapitalUse(char* word) {
int m=strlen(word);//m改为len更符合规范
int count=0; //upperCount更好
for(int i=0;i<m;i++){
if(supper((using char)word[i])){ //函数名为isupper unsigned char意思是无符号类型的字符,不要拼错
count++;
}
}
if(count==0){
return 1;
}
if(count==m){
return 1;
}
if(count==1&&isupper(using char)word[0]){
return 1;
}
return 0;
}
解题思路
1.计算字符串长度与初始化大写字母计数器:
-
len:借助strlen函数算出字符串word的长度。
-
upperCount:用于记录字符串里大写字母的数量,初始值设为0。
2.遍历字符串并统计大写字母数量: -
利用for循环遍历字符串中的每个字符。
-
借助isupper函数检查当前字符是否为大写字母。
-
若当前字符为大写字母,upperCount就加1。
3.判断大写字母使用是否符合规则: -
if(upperCount==len):若upperCount等于字符串长度,表明所有字母都是大写,返回true。
-
if(upperCount==0):若upperCount为0,表明所有字母都是小写,返回true。
-
if(upperCount==1&&isupper((unsigned char)word[0])):若upperCount为1且首个字母是大写,表明只有首个字母是大写,返回true。
-
若以上条件都不满足,返回false。
代码实现
#include<stdio.h>
#include<string.h>
#include<ctype.h>
int detectCapitalUse(char* word) {
int len=strlen(word);
int upperCount=0;
for(int i=0;i<len;i++){
if(isupper((unsigned char)word[i])){
upperCount++;
}
}
if(upperCount==len){
return true;
}
if(upperCount==0){
return true;
}
if(upperCount==1&&isupper((unsigned char)word[0])){
return true;
}
return false;
}
1768.交替合并字符串(easy)
给你两个字符串 word1 和 word2 。请你从 word1
开始,通过交替添加字母来合并字符串。如果一个字符串比另一个字符串长,就将多出来的字母追加到合并后字符串的末尾。返回 合并后的字符串 。
示例 1:
输入:word1 = “abc”, word2 = “pqr” 输出:“apbqcr” 解释:字符串合并情况如下所示: word1: a
b c word2: p q r 合并后: a p b q c r 示例 2:输入:word1 = “ab”, word2 = “pqrs” 输出:“apbqrs” 解释:注意,word2 比 word1 长,“rs”
需要追加到合并后字符串的末尾。 word1: a b word2: p q r s 合并后: a p b q
r s
自写版本:
char* mergeAlternately(char* word1, char* word2) {
int len1=strlen(word1);
int len2=strlen(word2);
char* result=(char*)malloc((len1+len2+1)*sizeof(char));
int i=0,j=0,k=0;
while(i<len1&&j<len2){
result[k++]=word1[i++];
result[k++]=word2[j++];
}
while(i<len1){
result[k++]=word1[i++];
}
while(j<len2){
result[k++]=word2[j++];
}
result[k]='\0';
return result;
}
解题思路
- 计算字符串长度:借助 strlen 函数分别算出 word1 和 word2 的长度,存储在 len1 和 len2 里。
- 为结果字符串分配内存:使用 malloc 函数为结果字符串动态分配内存。
- 交替合并字符串:
- 运用三个变量 i、j 和 k 分别作为 word1、word2 和 result 的索引。
- 借助 while 循环,只要 word1 和 word2 都还有字符未处理,就交替将 word1 和 word2 的字符添加到 result 中。
- 每添加一个字符,对应的索引 i、j 和 k 就加 1。
- 处理剩余字符:第一个 while 循环用于处理 word1 剩余的字符,第二个 while 循环用于处理 word2 剩余的字符。
- 添加字符串结束符并返回结果字符串:在结果字符串的末尾添加字符串结束符 ‘\0’,以此保证它是一个合法的 C 字符串,返回结果。
代码实现
自写版本代码已经正确
小结
为期两个星期的写博客时间又结束了,感谢自己每天的坚持。以后每个月底的博客我都会附上一篇小说的读后感,希望看点书对自己能有一定的提高。
读《呐喊》有感
- 以往接触鲁迅作品时,我常因文字晦涩、内容难懂,对其敬而远之。然而,当再度品味,我真切感受到,鲁迅的文字犀利如劲风,横扫一切陈旧迂腐的观念,以排山倒海之势,冲击着传统思想的堡垒。
- 《呐喊》这部短篇小说集,收录了鲁迅于 1918 年至 1922 年间创作的 14 部作品。篇幅虽短,却宛如一面照妖镜,将辛亥革命至五四运动时期的社会万象,淋漓尽致地展现出来。书中既有对封建礼教 “吃人” 本质的批判。在《狂人日记》中,“我自己被人吃了,可仍然是吃人的人的兄弟!” 这一语句,写出了 “狂人” 发现自己身处 “吃人” 环境时的无奈与悲哀,同时也揭示出封建礼教对人性的扭曲 —— 即便身为受害者,也难以摆脱与吃人者的关联。书中亦有对民众麻木愚昧的深切痛心。《药》中有这样一幕:“老栓也向那边看,却只见一堆人的后背;颈项都伸得很长,仿佛许多鸭,被无形的手捏住了的,向上提着。” 看似简单的一笔,却将看客们的麻木与愚昧刻画得入木三分。
- 《孔乙己》一文,尤其让我印象深刻。孔乙己作为科举制度下的失败者,满口之乎者也,既麻木又懒惰。即便穷困潦倒,甚至沦落到偷别人家东西换酒喝的地步,他仍始终放不下读书人的自尊,不愿通过劳动换取安稳生活。最终,在众人的奚落与嘲笑声中,他拖着伤残的身躯,消失在冰冷的世界里,只留给时代一个落寞、悲惨的背影。
- 鲁迅曾说:“既然是呐喊,则当然须听将令的了。”《呐喊》正是顺应 “五四” 反帝反封建和新民主主义革命浪潮的 “遵命文学”。鲁迅以笔为利剑,刺向旧社会的腐朽与黑暗,一心想要唤醒沉睡在铁屋子里的国人。他用生动鲜活、入木三分的语言,将一个个故事娓娓道来,让读者仿若置身其中,深切地感受到那个时代令人窒息的压抑与无尽悲哀。
- 如今,距离《呐喊》问世已过去百余年,可书中所揭示的问题,依旧如黄钟大吕,振聋发聩,值得我们深入思考。它时刻提醒着我们,要始终保持清醒的头脑,不被陈旧观念裹挟,勇于打破常规,执着地追求真理。同时,也让我们更加珍视当下来之不易的和平与自由,为创造更美好的未来全力以赴。
- 掩卷沉思,《呐喊》中的声音依旧在耳边回响,它跨越了时空的界限,激励着一代又一代人为了理想与正义,勇敢地发出属于自己的声音。