目录
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;
}