代码随想录算法训练营第十三天| 239. 滑动窗口最大值 、347.前 K 个高频元素

239. 滑动窗口最大值

题目链接/文章讲解/视频讲解:代码随想录

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 

单调队列

分析及思路

入队出队返回最大值,就是该题目的思路,我们再次思路优化即可。使用双链表进。

插入时判断尾部元素是否小于插入值,若小于则弹出,若大于则直接插入。我们用的是双端队列能插入也能输出。 呈现出递减趋势。头部是用来出栈的,尾部是用来实现单调递减的。

代码及注释 

// 双链表结点结构体定义
typedef struct Node {
    int data;  // 数据
    struct Node* prev;  // 前驱指针
    struct Node* next;  // 后继指针
} Node;

// 双链表结构体定义
typedef struct DoubleLinkedList {
    Node* head;  // 头结点
    Node* tail;  // 尾结点
    int size;    // 链表大小
} DoubleLinkedList;

// 创建双链表
DoubleLinkedList* createDoubleLinkedList() {
    // 分配内存空间
    DoubleLinkedList* list = (DoubleLinkedList*)malloc(sizeof(DoubleLinkedList));
    // 初始化头结点、尾结点和链表大小
    list->head = NULL;
    list->tail = NULL;
    list->size = 0;
    return list;
}

// 在链表头部插入结点
void insertAtHead(DoubleLinkedList* list, int data) {
    // 创建新结点
    Node* newNode = (Node*)malloc(sizeof(Node));
    // 设置新结点的数据和指针
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = list->head;
    // 更新链表头结点和尾结点
    if (list->head != NULL) {
        list->head->prev = newNode;
    } else {
        list->tail = newNode;
    }
    list->head = newNode;
    list->size++;
}

// 在链表尾部插入结点
void insertAtTail(DoubleLinkedList* list, int data) {
    // 创建新结点
    Node* newNode = (Node*)malloc(sizeof(Node));
    // 设置新结点的数据和指针
    newNode->data = data;
    newNode->next = NULL;
    newNode->prev = list->tail;
    // 更新链表头结点和尾结点
    if (list->tail != NULL) {
        list->tail->next = newNode;
    } else {
        list->head = newNode;
    }
    list->tail = newNode;
    list->size++;
}

// 从链表头部删除结点
void deleteAtHead(DoubleLinkedList* list) {
    // 判断链表是否为空
    if (list->head == NULL) {
        return;
    }
    // 移除头结点并更新链表头结点和尾结点
    Node* temp = list->head;
    list->head = list->head->next;
    if (list->head != NULL) {
        list->head->prev = NULL;
    } else {
        list->tail = NULL;
    }
    free(temp);
    list->size--;
}

// 从链表尾部删除结点
void deleteAtTail(DoubleLinkedList* list) {
    // 判断链表是否为空
    if (list->tail == NULL) {
        return;
    }
    // 移除尾结点并更新链表头结点和尾结点
    Node* temp = list->tail;
    list->tail = list->tail->prev;
    if (list->tail != NULL) {
        list->tail->next = NULL;
    } else {
        list->head = NULL;
    }
    free(temp);
    list->size--;
}

// 判断双链表是否为空
int isDoubleLinkedListEmpty(DoubleLinkedList* list) {
    return list->size == 0;
}

// 销毁双链表
void destroyDoubleLinkedList(DoubleLinkedList* list) {
    // 释放链表内存空间
    while (list->head != NULL) {
        deleteAtHead(list);
    }
    free(list);
}

// 弹出链表头部指定值的结点
void pop(DoubleLinkedList* list,int value){
    if( (!isDoubleLinkedListEmpty(list)) && (value == list->head->data) )
        deleteAtHead(list);
}

// 将值插入链表尾部
void push(DoubleLinkedList* list,int value) {
    while( (!isDoubleLinkedListEmpty(list)) && (value > list->tail->data ) )
        deleteAtTail(list);
    insertAtTail(list,value);
}

// 获取链表头部的值
int front(DoubleLinkedList* list){
    return list->head->data;
}

// 滑动窗口最大值
int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize) {
    // 分配结果数组内存空间
    int* result = (int*)malloc(sizeof(int)*(numsSize-k+1));
    // 创建双链表
    DoubleLinkedList* list =  createDoubleLinkedList();
    // 将前k个元素插入双链表
    for(int i=0;i<k;i++)
        push(list,nums[i]);
    result[0] = front(list);
    int count = 1;
    // 滑动窗口,更新结果数组
    for(int i=k;i<numsSize;i++){
        pop(list,nums[i-k]);
        push(list,nums[i]);
        result[count++] = front(list);
    }
    // 更新返回结果的大小
    *returnSize = count;
    return result;
}

347.前 K 个高频元素 

题目链接/文章讲解/视频讲解:代码随想录

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

分析及思路

统计频率我们就可以使用map来实现,然后使用小根堆把小的频率排除,剩下的都是大的。然后输出即可。

堆排序可以看小根堆与大根堆思路代码解释-CSDN博客

代码及注释

// 定义一个结构体map,包含键值对和哈希表的句柄
typedef struct {
    int key;
    int value;
    UT_hash_handle hh;
} map;

// 哈希表指针
map* hashMap = NULL;

// 向哈希表中添加键值对
void hashMapAdd(int key, int value) {
    map* s;
    HASH_FIND_INT(hashMap, &key, s); // 在哈希表中查找指定键
    if (s == NULL) {
        s = (map*)malloc(sizeof(map)); // 如果找不到则分配内存
        s->key = key; // 设置键
        HASH_ADD_INT(hashMap, key, s); // 向哈希表中添加键值对
    }
    s->value = value; // 设置值
}

// 在哈希表中查找指定键的值
map* hashMapFind(int key) {
    map* s;
    HASH_FIND_INT(hashMap, &key, s); // 在哈希表中查找指定键
    return s; // 返回找到的键值对
}

// 清空哈希表
void hashMapCleanup() {
    map* cur, *tmp;
    HASH_ITER(hh, hashMap, cur, tmp){ // 遍历哈希表
        HASH_DEL(hashMap, cur); // 从哈希表中删除当前节点
        free(cur); // 释放内存
    }
}

// 打印哈希表中的键值对
void hashPrint(){
    map* s; // 定义一个map类型的指针s
    for(s = hashMap; s != NULL; s=(map*)(s -> hh.next)){ // 遍历哈希表
        printf("key %d, value %d\n", s -> key, s -> value); // 打印键和值
    }
}

// 调整小根堆,使以k为根的子树成为小根堆
void HeadAdjust(map* MapHash,int k,int len){
    int i;
    MapHash[0] = MapHash[k]; // 保存根节点的值
    for(i=2*k;i<=len;i*=2){ // 遍历子节点
        if(i<len&&MapHash[i].value>MapHash[i+1].value) // 找出子节点中较小的节点
            i++;
        if(MapHash[0].value<=MapHash[i].value) break; // 如果根节点小于等于子节点,则退出循环
        else{
            MapHash[k] = MapHash[i]; // 将子节点的值赋给根节点
            k = i; // 更新k的值
        }
        MapHash[k] = MapHash[0]; // 将根节点的值赋给最终位置
    }
}

// 建立小根堆
void BuildMinHeap(map* MapHash,int len){
    int i;
    for(i=len/2;i>0;i--) // 从最后一个非叶子节点开始调整堆
        HeadAdjust(MapHash,i,len);
}

// 返回前k个出现频率最高的元素
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize) {
    map* MapHash = (map*)malloc(sizeof(map)*(k+1)); // 分配存储堆的空间

    // 设置哈希表
    for (int i = 0; i < numsSize; i++) {
        map* found = hashMapFind(nums[i]);
        if (found) {
            found->value++;
        } else {
            hashMapAdd(nums[i], 1);
        }
    }

    // 初始化建立小根堆
    map* TempHash = hashMap;
    for(int i=1;i<k+1;i++){
        MapHash[i].key = TempHash->key;
        MapHash[i].value = TempHash->value;
        TempHash = (map*)(TempHash -> hh.next);
    }

    // 建立小根堆
    BuildMinHeap(MapHash,k);
    map* s;
    for(s = TempHash; s != NULL; s=(map*)(s -> hh.next)){ // 遍历哈希表
            if(MapHash[1].value < s->value){
                MapHash[1].key = s->key;
                MapHash[1].value = s->value;
                BuildMinHeap(MapHash,k);
            }
    }

    int* elements = (int*)malloc(sizeof(int) * numsSize); // 分配存储返回结果的空间
    int j=k;
    for(int i=0;i<k;i++){ // 将堆中的元素存入结果数组
        elements[i] = MapHash[j--].key;
    }

    *returnSize = k; // 设置返回结果的大小
    hashMapCleanup(); // 清空哈希表
    free(MapHash); // 释放堆的内存
    return elements; // 返回结果数组
}

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值