1.无重复字符的最长子串
知识点:滑动窗口
基本概念
- 窗口:窗口是一个连续的子序列,可以是固定长度或可变长度。
- 滑动:窗口在数据序列上移动,可以是向左或向右。
- 边界:窗口的起始和结束位置。
应用场景
- 字符串匹配:如KMP算法中的部分匹配表就是利用滑动窗口来优化字符串搜索。
- 最大/最小子数组:例如Kadane算法,通过滑动窗口找到数组中的最大子数组和。
- 窗口内元素的统计:如统计窗口内元素的个数、总和、平均值等。
- 数据流的实时处理:在数据流中,滑动窗口可以用来处理固定时间范围内的数据。
- 滑动窗口协议:在网络通信中,滑动窗口用于流量控制和拥塞避免。
实现方式
- 固定窗口:窗口大小不变,通过移动窗口的起始或结束位置来遍历整个数据序列。
- 可变窗口:窗口大小可以根据需要变化,通常用于处理动态数据集合。
示例代码(Python)
def max_subarray_sum(nums):
max_sum = float('-inf')
current_sum = 0
start = 0
for end in range(len(nums)):
current_sum += nums[end]
while current_sum > max_sum and start <= end:
max_sum = current_sum
current_sum -= nums[start]
start += 1
return max_sum if max_sum != float('-inf') else 0
# 使用示例
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print(max_subarray_sum(nums)) # 输出应该是6,对应子数组[4, -1, 2, 1]
代码:
#include <stdio.h>
#include <string.h>
int lengthOfLongestSubstring(char *s) {
int n = strlen(s);
int charIndex[128]; // ASCII 128字符的索引表
for (int i = 0; i < 128; i++) {
charIndex[i] = -1; // 初始化索引表为-1
}
int left = 0, maxLength = 0;
for (int right = 0; right < n; right++) {
char currentChar = s[right];
if (charIndex[currentChar] >= left) {
left = charIndex[currentChar] + 1; // 更新左边界
}
charIndex[currentChar] = right; // 更新当前字符的最新索引
int currentLength = right - left + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
}
}
return maxLength;
}
int main() {
char s[] = "abcabcbb";
int length = lengthOfLongestSubstring(s);
printf("最长不重复子串长度是: %d\n", length);
return 0;
}
代码解释
初始化:
charIndex
:一个数组,用于存储每个字符的最新出现位置。我们假定字符串只包含ASCII字符(共128个)。- 将
charIndex
数组初始化为-1,表示字符尚未出现。
滑动窗口:
left
:滑动窗口的左边界。maxLength
:记录最长子串的长度。- 遍历字符串
s
的每个字符,right
表示滑动窗口的右边界。 - 如果当前字符
s[right]
在charIndex
中的值大于等于left
,说明遇到了重复字符,需要更新左边界left
。 - 将当前字符的索引更新到
charIndex
中。 - 计算当前窗口的长度
right - left + 1
,并更新maxLength
。
返回结果:
- 遍历完成后,
maxLength
即为最长不含重复字符的子串的长度。
- 遍历完成后,
提交结果
2.前k个高频元素
知识点: 哈希表和快速排序算法
哈希表(Hash Table)
哈希表是一种数据结构,它提供了快速的数据插入和查找功能。以下是哈希表的一些关键点:
- 基本思想:哈希表通过使用哈希函数将输入(例如字符串或者数字)映射到一个大数组的索引上,这个数组称为哈希表的“桶”。
- 哈希函数:哈希函数的设计对于哈希表的性能至关重要,它应该能够均匀地分布元素,以减少冲突。
- 冲突解决:当两个不同的输入通过哈希函数映射到同一个索引时,称为“冲突”。解决冲突的常见方法包括链地址法(每个桶包含一个链表来存储具有相同哈希值的元素)和开放寻址法(寻找空的数组位置来存储元素)。
- 动态扩容:随着元素的增加,哈希表可能需要扩容以保持操作的效率,扩容通常涉及到重新计算现有元素的哈希值并将它们重新分配到新的更大的数组中。
- 时间复杂度:理想情况下,哈希表的插入和查找操作可以达到平均时间复杂度O(1),但在最坏情况下(例如,所有元素都映射到同一个桶中)可能退化到O(n)。
快速排序算法(Quick Sort)
快速排序是一种高效的排序算法,采用分治法的策略来把一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列。以下是快速排序的一些关键点:
- 选择基准:算法首先选择一个元素作为“基准”(pivot),然后重新排列数组,使得所有比基准小的元素都在基准的左边,所有比基准大的元素都在基准的右边。
- 分区操作:通过分区操作,数组被分为两个部分,然后递归地对这两个部分进行快速排序。
- 递归:递归是快速排序的核心,它将问题分解为更小的子问题,直到子问题足够小,可以直接解决。
- 时间复杂度:在平均情况下,快速排序的时间复杂度为O(n log n),但在最坏情况下(例如,数组已经排序或所有元素相等)时间复杂度会退化到O(n^2)。
- 原地排序:快速排序是一种原地排序算法,它不需要额外的存储空间,除了递归调用的栈空间。
- 稳定性:快速排序不是稳定的排序算法,相同的元素在排序后可能会改变它们原来的顺序。
代码解释
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int key;
int value;
struct Node* next;
} Node;
Node** createHashTable(int size) {
Node** hashTable = (Node**)malloc(sizeof(Node*) * size);
for (int i = 0; i < size; i++) {
hashTable[i] = NULL;
}
return hashTable;
}
int getHashValue(int key, int size) {
return abs(key % size);
}
void insert(Node** hashTable, int key, int size) {
int index = getHashValue(key, size);
Node* node = hashTable[index];
Node* prev = NULL;
while (node != NULL) {
if (node->key == key) {
node->value++;
return;
}
prev = node;
node = node->next;
}
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->key = key;
newNode->value = 1;
newNode->next = NULL;
if (prev == NULL) {
hashTable[index] = newNode;
} else {
prev->next = newNode;
}
}
int partition(Node** nums, int left, int right) {
int pivot = nums[right]->value;
int i = left - 1;
for (int j = left; j < right; j++) {
if (nums[j]->value >= pivot) {
i++;
Node* temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
Node* temp = nums[i+1];
nums[i+1] = nums[right];
nums[right] = temp;
return i + 1;
}
void quickSort(Node** nums, int left, int right) {
if (left < right) {
int pivotIndex = partition(nums, left, right);
quickSort(nums, left, pivotIndex - 1);
quickSort(nums, pivotIndex + 1, right);
}
}
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) {
int hashSize = numsSize * 2;
Node** hashTable = createHashTable(hashSize);
for (int i = 0; i < numsSize; i++) {
insert(hashTable, nums[i], hashSize);
}
int uniqueNums = 0;
for (int i = 0; i < hashSize; i++) {
Node* node = hashTable[i];
while (node != NULL) {
uniqueNums++;
node = node->next;
}
}
Node** frequencyArray = (Node**)malloc(sizeof(Node*) * uniqueNums);
int index = 0;
for (int i = 0; i < hashSize; i++) {
Node* node = hashTable[i];
while (node != NULL) {
frequencyArray[index++] = node;
node = node->next;
}
}
quickSort(frequencyArray, 0, uniqueNums - 1);
*returnSize = k;
int* result = (int*)malloc(sizeof(int) * k);
for (int i = 0; i < k; i++) {
result[i] = frequencyArray[i]->key;
}
return result;
}
包含标准输入输出头文件
stdio.h
和标准库头文件stdlib.h
。定义了一个结构体
Node
,包含一个整数key
,一个整数value
和一个指向Node
的指针next
。定义了一个函数
createHashTable
,它接受一个整数size
作为参数,创建一个大小为size
的哈希表,并初始化所有元素为NULL
。定义了一个辅助函数
getHashValue
,它接受一个整数key
和一个整数size
,返回哈希表中对应的索引位置。定义了一个
insert
函数,它接受一个哈希表指针、一个整数key
和一个整数size
,用于将键值对插入到哈希表中。如果键已经存在,则增加其值。定义了一个
partition
函数,用于快速排序中的分区操作。定义了一个
quickSort
函数,用于对节点数组进行快速排序。定义了主要的函数
topKFrequent
,它接受一个整数数组nums
、数组的大小numsSize
、需要找出的前k
个最频繁元素的数量以及一个指向整数的指针returnSize
。这个函数首先创建一个哈希表,然后统计每个元素出现的次数,接着将统计结果放入一个数组中,对数组进行快速排序,最后返回出现次数最多的前k
个元素。
提交结果
3.百马百担
100 匹马驮 100 担货,已知一匹大马驮 3 担,一匹中马驮 2 担,两匹小马驮 1 担。试编写 程序计算大、中、小马的所有可能组合数目。
#include <stdio.h>
int main() {
int count = 0;
for (int big = 0; big <= 100 / 3; big++) {
for (int medium = 0; medium <= 100 / 2; medium++) {
int small = 100 - big - medium;
if ((3 * big + 2 * medium + small) == 100) {
// Valid combination found
printf("大马:%d 匹,中马:%d 匹,小马:%d 匹\n", big, medium, small);
count++;
}
}
}
printf("共有 %d 种可能的组合。\n", count);
return 0;
}
代码解释
定义了一个
count
变量,用于统计有效组合的数量。外层循环变量
big
从0开始,到100 / 3
结束,因为每匹大马的重量是3单位,所以最多有33匹大马(100 / 3
向上取整)。内层循环变量
medium
从0开始,到100 / 2
结束,因为每匹中马的重量是2单位,所以最多有50匹中马。计算
small
的值,它是100减去big
和medium
的总和。使用一个
if
语句检查3 * big + 2 * medium + small
是否等于100。如果等于,说明找到了一个有效的组合,并打印出来。每次找到有效组合时,
count
变量增加1。循环结束后,打印出所有有效组合的总数。