每日练习——leetcode438和203

目录

438. 找到字符串中所有字母异位词

题目描述

解题思路

代码实现

203. 移除链表元素

题目描述

解题思路

代码实现


438. 找到字符串中所有字母异位词

题目描述

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

 示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • s 和 p 仅包含小写字母

解题思路

利用哈希表来记录字符频率,并使用滑动窗口的方法来遍历字符串s。

1.初始化

首先,获取字符串s和p的长度,并检查p的长度是否大于s的长度。如果大于,那么s中不可能有与p构成异位词的子串,所以直接返回空数组。

初始化两个哈希表shash和phash,用于存储s和p中字符出现的次数。因为题目中说s 和 p 仅包含小写字母,所以哈希表的大小为26。

2.计算p中字符的出现次数

遍历p中的每个字符,并在phash中对应的位置增加计数。

3.初始化结果数组和滑动窗口

分配内存给结果数组ans,大小为slen - plen + 1。这是因为最多可能有slen - plen + 1个与p等长的子串存在于s中。

初始化计数器count为0,用于记录找到的异位词子串的数量。

初始化滑动窗口的左边界left为0。

4.使用滑动窗口遍历s

使用变量right作为滑动窗口的右边界,从0开始遍历s。

在每次迭代中,更新shash中对应s[right]字符的计数。

当滑动窗口的大小达到p的长度时,检查shash和phash是否相等。可以通过memcmp函数实现,它比较两个内存区域的内容。

如果哈希值匹配,说明找到了一个与p构成异位词的子串,将左边界的索引left添加到结果数组ans中,并增加计数器count。

然后,移动滑动窗口的左边界,即将left右移一位,并更新shash中对应s[left]字符的计数。

5.返回结果

设置结果数组的大小为count。

返回结果数组ans。

【补充】C 库<string.h>函数 - memcmp()

作用:把存储区 str1 和存储区 str2 的前 n 个字节进行比较。

函数声明:int memcmp(const void *str1, const void *str2, size_t n)

参数:str1 -- 指向内存块的指针。

str2 -- 指向内存块的指针。

n -- 要被比较的字节数。

返回值:

如果str1 小于 str2,返回值 < 0。

如果str1 大于 str2,返回值 > 0。

如果str1 等于 str2,返回值 = 0。

代码实现

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* findAnagrams(char * s, char * p, int* returnSize){
    int slen = strlen(s), plen = strlen(p);  
    // 如果p的长度大于s的长度,直接返回空数组
    if (plen > slen) {  
        *returnSize = 0;  
        return NULL;  
    }  
    // 初始化两个哈希表,用于存储p和s中字符出现的次数
    int shash[26] = {0}, phash[26] = {0};  
    // 计算p中字符出现的次数
    for (int i = 0; i < plen; i++) {  
        phash[p[i] - 'a']++;  
    }  
    // 为结果数组分配内存
    int *ans = (int*)malloc(sizeof(int) * (slen - plen + 1));  
    // 初始化结果数组的计数器和滑动窗口的左边界
    int count = 0, left = 0;  
    // 遍历字符串s
    for (int right = 0; right < slen; right++) {  
        // 维护滑动窗口的哈希值
        shash[s[right] - 'a']++;  
        // 当窗口大小达到p的长度时,开始检查哈希值是否匹配
        if (right - left + 1 == plen) {  
            // 如果哈希值匹配,将左边界的索引添加到结果数组中
            if (memcmp(shash, phash, sizeof(phash)) == 0) {  
                ans[count++] = left;  
            }  
            // 移动窗口左边界,并更新哈希值
            shash[s[left] - 'a']--;  
            left++;  
        }  
    }  
    // 设置结果数组的大小
    *returnSize = count;  
    // 返回结果数组
    return ans;  
}

203. 移除链表元素

题目描述

给你一个链表的头节点 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
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

解题思路

法一:原链表删除

1.处理头节点:

首先要考虑特殊情况,即头节点的值就是要删除的值val。在这种情况下,需要不断地删除头节点,直到头节点的值不再等于val,或者链表变为空。

使用临时节点temp来保存要删除的节点,这样可以释放其内存。

更新头节点head为下一个节点,并释放temp所指向的节点内存。

2.遍历链表:

初始化一个指针current指向当前的节点,开始遍历链表。

在遍历过程中,检查current的下一个节点current->next是否是要删除的节点(即其值是否等于val)。

如果current->next的值等于val,那么需要删除它。为此,再次使用temp来保存要删除的节点,并更新current->next为current->next->next,即跳过要删除的节点。然后释放temp所指向的节点内存。

如果current->next的值不等于val,则继续向后移动,将current更新为current->next。

3.返回结果:

遍历完成后,链表中的所有值为val的节点都已被删除。

返回处理后的链表头节点head。

法二:虚拟头节点

为什么要使用虚拟头节点(dummy head)?

在处理链表问题时,经常遇到需要删除头节点的情况。如果直接使用头节点来处理,可能需要单独处理头节点被删除的情况,这会使代码变得复杂。引入虚拟头节点可以简化这个问题,因为这样可以保证链表始终有一个头节点(即虚拟头节点),然后就可以统一处理删除节点的逻辑。

1.创建虚拟头节点

首先创建一个新的节点dummyhead,作为虚拟头节点。

将虚拟头节点的next指针指向原链表的头节点head。

2.初始化当前节点

将current指针初始化为虚拟头节点dummyhead。这样,就可以从虚拟头节点开始遍历链表。

3.遍历链表

使用while循环遍历链表,直到current->next为NULL,即遍历到链表的末尾。

在循环内部,检查current->next节点的值是否等于要删除的值val。

4.删除节点:

如果current->next的值等于val,需要删除这个节点。

使用temp临时保存current->next节点。

将current->next更新为current->next->next,即跳过要删除的节点。释放temp所指向的节点内存。

如果current->next的值不等于val,则不需要删除节点,只需要将current移动到下一个节点。

5.返回处理后的链表:

当遍历完成后,所有值为val的节点都已被删除。

由于使用了虚拟头节点,因此处理后的链表头节点实际上是虚拟头节点的next节点。

所以,返回dummyhead->next作为处理后的链表头节点。

代码实现

//原链表删除元素

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
     struct ListNode* temp; // 临时节点,用于释放内存
    // 当链表头节点值为val时,删除头节点
    while (head != NULL && head->val == val) {
        temp = head;
        head = head->next;
        free(temp);
    }
    // 初始化当前节点为头节点
    struct ListNode* current = head;
    // 遍历链表
    while (current != NULL && current->next != NULL) {
        // 如果当前节点的下一个节点值为val,删除下一个节点
        if (current->next->val == val) {
            temp = current->next;
            current->next = current->next->next;
            free(temp);
        } else {
            // 否则,移动到下一个节点
            current = current->next;
        }
    }
    // 返回处理后的链表头节点
    return head;
}
//虚拟头结点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* removeElements(struct ListNode* head, int val) {
      struct ListNode* temp; // 临时节点,用于释放内存
    struct ListNode* dummyhead; // 虚拟头节点,用于简化边界条件处理
    // 创建虚拟头节点,并将其next指针指向原链表头节点
    dummyhead = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummyhead->next = head;
    // 初始化当前节点为虚拟头节点
    struct ListNode* current = dummyhead;
    // 遍历链表
    while (current->next != NULL) {
        // 如果当前节点的下一个节点值为val,删除下一个节点
        if (current->next->val == val) {
            temp = current->next;
            current->next = current->next->next;
            free(temp);
        } else {
            // 否则,移动到下一个节点
            current = current->next;
        }
    }
    // 返回处理后的链表头节点(虚拟头节点的下一个节点)
    return dummyhead->next;
}

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值