LeetCode笔记(C) | 代码随想录

数组

 二分查找

二分查找有两种写法:

1.左闭右闭:[left, right]
  • 在这种写法中,【left,right】在left==right依然有意义。所以while循环条件写成(left<=right)
  • 然后用三种条件的if/else语句写。if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

704.二分查找

2.左闭右开:[left, right)
  • 循环条件就要写成while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle。因为下一个查询区间不会去比较nums[middle](左闭右开)

704.二分查找1

查找成功的条件放最后,时间复杂度低一点

数组创建

二维数组

int**可以理解为二维数组。在创建int** 类型的变量时,需要用malloc分配内存,同时对每一行数组分配内存。以int[6][3]为例:

    int** ans = (int**)malloc(sizeof(int*) * 6);
    for(int m = 0; m < 6; m++){
        ans[m] = (int *)malloc(sizeof(int) * 3);
    }

char**数组可以理解为字符串数组。例如

tokens = ["2","1","+","3","*"]

这个数组 tokens 中的每个元素都是字符指针(char*),指向一个以 null 结尾的字符串。虽然有些元素可能只包含一个字符,但在 C 语言中,字符也被表示为以 null 结尾的字符串。因此,在这个数组中,每个元素都是一个指向字符数组的指针,表示一个字符串。

对于该数组,"2" 是一个指向以字符 '2' 和 null 终止符组成的字符数组的指针(即指向字符串 "2" 的指针)。后面以此类推。

calloc函数

calloc函数用法:

int arraySize = 10; // 数组大小
    char *charArray;    // 指向char数组的指针

    // 使用calloc分配内存,并自动初始化为零
    charArray = (char *)calloc(arraySize, sizeof(char));

和malloc函数的区别是,calloc函数会将所有元素初始化为0,这导致在大块内存分配时,calloc要慢于malloc。

无论使用哪种函数,记得在使用完内存后使用free来释放分配的内存,以避免内存泄漏问题。

char数组

char数组初始化

快速排序

qsort

qsort 是 C 标准库中提供的一个快速排序函数。函数用法如下:

qsort(nums, numsSize, sizeof(int), cmp);
  • nums:指向要排序的数组的第一个元素的指针(数组名)。
  • numsize:数组中的元素数量。
  • sizeof:每个元素的大小(以字节为单位)。
  • cmp:辅助函数,比较函数的指针,用于指定元素之间的比较规则。

cmp函数如下(升序写法):

int cmp(const void* a, const void* b){
    return *((int*)a) > *((int*)b);
}

如果 compar 返回一个负值,表示第一个元素小于第二个元素;如果返回零,表示两个元素相等;如果返回一个正值,表示第一个元素大于第二个元素。

数组长度

在C语言中:

int类型数组s,可以用sizeof(s) / sizeof(int)来计算。int数组不以 '\0' 结尾。

char类型数组s,可以用strlen(s)来计算(不包括‘\0’)。因为char数组以 '\0' 结尾。在使用malloc函数为char数组分配内存时,还需要考虑加一个 '\0' 的大小。

在C++中:

提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用'\0'来判断是否结束。

例如这段代码:

string a = "asd";
for (int i = 0; i < a.size(); i++) {
}

在LeetCode中数组返回长度用*returnSize来传递,注意通过循环递增*returnSize的时候,要写成:

(*returnsize)++

不然会报错stack-buffer-overflow

链表

虚拟头节点(增删元素)

删除链表中的第一个元素和中间的元素逻辑是不一样的。因为删除第一个元素还要改变链表的头节点。
为了统一逻辑,需要虚拟一个头节点。如果删除的是第一个元素,在代码最后改变头节点的位置为虚拟头节点的下一个即可。

struct ListNode* removeElements(struct ListNode* head, int val){
    typedef struct ListNode ListNode;
    ListNode* shead = (struct ListNode*)malloc(sizeof(struct ListNode));
    shead->next = head;
    ListNode* cur = shead;
    while(cur->next != NULL){
        if(cur->next->val == val){
            ListNode* tmp = cur->next;
            cur->next = cur->next->next;
            free(tmp);
        }else{
            cur = cur->next;
        }
    }
    head = shead->next;
    free(shead);
    return head;
}

哈希表

哈希表和链表的联系

  • 理想情况下,插入、删除和查找操作的时间复杂度都是 O(1)
  • 一般哈希表都是用来快速判断一个元素是否出现集合里
  • 遍历哈希表其实可以理解成遍历链表。

哈希表结构

struct HashTable{
    int key;
    int val;
    UT_hash_handle hh;
};

我的个人习惯,为了后续方便,可以在最前面加上:

typedef struct HashTable hashtable;

哈西表处理常用的宏

在使用这些宏之前需要引入‘uthash’库函数

#include "uthash.h"

1.HASH_FIND_INT:查找具有整数键的元素(参数解释如下)

 HASH_FIND_INT(hashTable, &searchKey, result);
  • 哈希表的指针(在本例中是 hashTable)。
  • 指向要查找的整数键的指针(在本例中是 &searchKey)。
  • 一个指针变量,用于存储查找结果。

2.HASH_ADD_INT:添加

HASH_ADD_INT(hashTable, key, newElement);

3.HASH_ITER:用于迭代遍历整个哈希表,执行自定义操作。

struct HashTable *currentElement, *tmp;
HASH_ITER(hh, hashTable, currentElement, tmp) {
    // 对当前元素执行操作
}

4.HASH_DEL:用于从哈希表中删除一个元素。

HASH_DEL(hashTable, elementToDelete);

三数之和

关键:

  1. 使用快排+双指针
  2. 遍历排序后的数组,确定第一个数,移动指针确定后两个数
  3. 最终结果的几种可能可以看作排列组合,n个里面选3个,但不是Cn3,而是Cn2,因为在确定前两个数值后,最后一个数值是固定的。而这里申请内存的时候理论值应该是 :numsSize * (numsSize -1);代码中的设计:
int **ans = (int **)malloc(sizeof(int *) * numsSize  *numsSize);

去重的写法:

a:

问题在于,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同

如果写法是前者,以{-1, -1 ,2} 这组数据为例,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。

我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!

所以写法应该是后者,我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到结果集里。

if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}

b、c:

while(middle < right && nums[middle] == nums[++middle]);
while(middle < right && nums[right] == nums[--right]);

字符串

反转字符串 ||

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

思路:

因为要找的也就是每2 * k 区间的起点,所以在遍历字符串的过程中,只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。

当需要固定规律一段一段去处理字符串的时候,要想想在for循环的表达式上做做文章。

替换空格

很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后再从后向前进行操作。

这就需要确定扩充后数组的长度。

优点:

  1. 不用申请新数组。
  2. 从后向前填充元素,避免了从前向后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题。

KMP算法

力扣链接:28. 找出字符串中第一个匹配项的下标

i:后缀末尾, j:最长共同前缀末尾

如何理解求next数组中 j 要一直回退的问题

以下为next数组不统一减1的代码:

void GetNext(int* next, char* p){
    next[0] = 0;
    int j = 0;
    int len = strlen(p);

    for(int i = 1; i < len; i++){

        j = next[i - 1];
//不断递归判断是否存在子对称,k=0说明不再有子对称,Pattern[i] != Pattern[k]说明虽然对称,但是对称后面的值和当前的字符值不相等,所以继续递推

        while(p[i] != p[j] && j > 0){
            j = next[j - 1];        //继续递归
        }

        if(p[i] == p[j]){
            next[i] = j + 1;        //找到了这个子对称,j又表示最大共同前后缀长度,在此基础上长度+1
        }else{
            next[i] = 0;         //如果遍历了所有子对称都无效,说明这个新字符不具有对称性,清0
        }
    }
}

对应的kmp代码(返回第一次出现p的第一个字母下标):

int strStr(char * s, char * p){
    int lens = strlen(s);
    int lenp = strlen(p);
    int* next = (int*)malloc(sizeof(int) * lenp);
    GetNext(next, p);

    int i = 0, j = 0;
    for(; i < lens; i++){
        while(j > 0 && s[i] != p[j]){        //同样的循环条件
            j = next[j - 1];        //一直往前找到最大相同前后缀
        }

        if(s[i] == p[j]){
            j++;
        }
        
        if(j == lenp) return i - j + 1;
    }

    return -1;
}

常见错误及解决办法:

1.control reaches end of non-void function

解决方法:没有默认返回值,在函数末尾添加返回值如return 0;

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值