算法闭关修炼百题计划(三)

news2024/11/29 2:46:47

减轻复习压力,一篇只有十题左右

  • 1.反转链表II
  • 2.LRU缓存
  • 3.合并区间
  • 4.快速排序
  • 5.数字中的第k个最大元素
  • 6.归并排序
  • 7.每种字符至少取k个
  • 8.螺旋矩阵II
  • 9.旋转图像
  • 10.删除数组中重复的元素II

1.反转链表II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

假如left == 1,需要反转的部分从链表的第一个节点就开始,那么翻转后链表的头结点将发生变化,使用dummy可以特殊处理这种情况。

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* p0 = dummy;
        for(int i = 1; i < left; i ++) p0 = p0->next;
        ListNode* pre = nullptr;
        ListNode* cur = p0->next;
        for(int i = 0; i < right - left + 1; i ++){
            ListNode* tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        p0->next->next = cur;
        p0->next = pre;
        return dummy->next;
        
        
    }
};

2.LRU缓存

请你设计并实现一个满足最近最少使用缓存约束的数据结构,LRUCache类:

  • LRUCache(int capacity)以正整数作为容量capacity初始化LRU缓存
  • int get(int key)如果关键字key存在于缓存中,则返回关键字的值,否则返回-1
  • void put(int key,int value)如果关键字key已经存在,则变更其数据值value;如果不存在,则向缓存中插入该组key-value。如果插入操作导致关键字数量超过capacity,则应该逐出最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

题目中提到key-value,所以涉及哈希表
题目的难点在于维护出入,首先想到栈,但是题目要求get和put还必须是O(1),所以栈、队列都不行,要用链表

链表+哈希,用双向链表
为什么是双向链表,因为双向链表在删除的时候是O(1),而不需要前驱节点

//双向链表结构体的实现
class Node{
public:
    int key, val;
    Node* next;
    Node* pre;
    Node(int k, int v){
        key = k;
        val = v;
    }
};
class DoubleList{
private:
    Node* head;
    Node* tail;
    int size;//链表的元素数
public:
    DoubleList(){
        //初始化
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head->next = tail;
        tail->pre = head;
        size = 0;
    }

    //在链表尾部添加节点x,O(1)
    void addLast(Node* x){
        x->pre = tail->pre;
        x->next = tail;
        tail->pre->next = x;
        tail->pre = x;
        size ++;
    }

    //删除链表中的x节点,(x一定存在)
    void remove(Node* x){
        x->pre->next = x->next;
        x->next->pre = x->pre;
        size --;
    }

    //删除链表第一个节点,并返回
    Node* removeFirst(){
        if(head->next == tail) return nullptr;

        Node* first = head->next;
        remove(first);
        return first;
    }

    int getsize(){
        return size;
    }
};
class LRUCache {
private:
    // key -> Node(key, val)
    unordered_map<int, Node*> map;
    // Node(k1, v1) <-> Node(k2, v2)...
    DoubleList cache;
    // 最大容量
    int cap;
public:
    LRUCache(int capacity) {
        cap = capacity;

    }
    // 将某个 key 提升为最近使用的
    void makeRevently(int key){
        // 先拿到这个key对应的指针
        Node* x = map[key];
        // 在原本的位置删除它
        cache.remove(x);
        // 最近使用的加入到队尾
        cache.addLast(x);

    }

    // 添加最近使用的元素
    void addRecently(int key, int val){
        Node* x = new Node(key, val);
        cache.addLast(x);// 将最近使用的添加到链表中
        map[key] = x;// 在map中添加key的映射
    }
    // 删除某一个key
    void deleteKey(int key){
        Node* x = map[key];
        cache.remove(x);
        map.erase(key);
    }
    //删除最久未用的元素
    void removeLeastRecently(){
        // 链表的第一个元素就是最久没用的
        Node* deletedNode = cache.removeFirst();
        // 先得到节点的key,然后从map里删除
        int deletedKey = deletedNode->key;
        map.erase(deletedKey);
    }
    
    int get(int key) {
        if(!map.count(key)){
            return -1;
        }
        makeRevently(key);
        return map[key]->val;
    }
    
    void put(int key, int val) {
        if(map.count(key)){
            deleteKey(key);
            addRecently(key, val);
            return;
        }
        if(cap == cache.getsize()){
            removeLeastRecently();
        }
        addRecently(key, val);

    }
};

3.合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

注意他的for是怎么遍历的

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        //按照start排序
        sort(intervals.begin(), intervals.end(),[](vector<int> a, vector<int> b){return a[0] < b[0];});
        vector<vector<int>> res;
        //把第一个元素push进去
        res.push_back(intervals[0]);
        for(int i = 1; i < intervals.size(); i ++){
            //区间重叠
            if(res.back()[1] >= intervals[i][0]){
                res.back()[1] = max(res.back()[1], intervals[i][1]);
            }
            //区间不重叠
            else{
                res.push_back(intervals[i]);
            }
        }
        return res;
    }
};

4.快速排序

快排就是分治,给一个基准值,左边都小于这个基准值,右边都大于这个基准值
分别排序左右,合并就是有序数组了
注意mid = nums[(l + r) / 2],而不是mid = (l + r) / 2,不要用索引直接用值

class Solution {
public:
    void quick_sort(vector<int>& nums, int l, int r){
        if(l >= r) return;
        int left = l - 1, right = r + 1, mid = nums[(l + r) / 2];
        while(left < right){
            do left ++; while(nums[left] < mid);
            do right--;while(nums[right] > mid);
            if(left < right) swap(nums[left], nums[right]);
        }
        quick_sort(nums, l, right);
        quick_sort(nums, right + 1, r);

    }
    vector<int> sortArray(vector<int>& nums) {
        quick_sort(nums, 0, nums.size() - 1);
        return nums;
    }
};

5.数字中的第k个最大元素

真的很想sort,但是这道题考频这么高,不可能让你调用库函数…

如果对原数组排序,再返回倒数第k个位置,这样复杂度是O(nlogn)
其实可以更快
在快排中,每次经过划分后,一定可以确定一个元素的最终位置,即x的最终位置是q,并且a[l…q - 1]中的每个元素小于等于a[q],且a[q]小于等于a[q + 1…r]中的每个元素,所以只要某次划分的q为倒数第k个下标的时候,我们就找到了答案,只需关注这点。

class Solution {
public:
    int quickselect(vector<int> &nums, int l, int r, int k) {
        if (l == r)
            return nums[k];
        int mid = nums[(l + r)/2], i = l - 1, j = r + 1;
        while (i < j) {
            do i++; while (nums[i] < mid);
            do j--; while (nums[j] > mid);
            if (i < j)
                swap(nums[i], nums[j]);
        }
        if (k <= j) return quickselect(nums, l, j, k);
        else return quickselect(nums, j + 1, r, k);
    }

    int findKthLargest(vector<int> &nums, int k) {
        int n = nums.size();
        return quickselect(nums, 0, n - 1, n - k);
    }
};

6.归并排序

如果说快排是前序,那么归并就是后序,一个是先计算再递归,一个是先递归再计算
两个都属于分治算法

void merge_sort(vector<int>& nums, int lo, int hi){
	if(lo >= hi) return;
	int mid = (lo + hi) >> 1;
	merge_sort(nums, lo, mid);
    merge_sort(nums, mid + 1, hi);

    vector<int> tmp(hi - lo + 1); // 创建一个新的 tmp 数组
    int k = 0;
    int i = lo, j = mid + 1;
    while(i <= mid && j <= hi){
        if(nums[i] > nums[j]){
                tmp[k++] = nums[j++];
            }
            else{
                tmp[k++] = nums[i++];
            }
     }
    while(i <= mid) tmp[k++] = nums[i++];
    while(j <= hi) tmp[k++] = nums[j++];

    for(int i = lo, j = 0; i <= hi && j < k; i ++, j ++){
        nums[i] = tmp[j];
    }
}

7.每种字符至少取k个

给你一个由字符 ‘a’、‘b’、‘c’ 组成的字符串 s 和一个非负整数 k 。每分钟,你可以选择取走 s 最左侧 还是 最右侧 的那个字符。

你必须取走每种字符 至少 k 个,返回需要的 最少 分钟数;如果无法取到,则返回 -1 。

两边不好想,用滑动窗口想中间
怎么看外面字符呢,用一个tar数组,初始化为-k,for遍历++,tar中存的就是比k多的数量
滑动窗口内的数字,和tar里面留的数字比较,滑动窗口的数字小于tar中的,说明两侧这个字母的数量>k,滑动窗口的数字大于tar中的,说明两侧这个字母小于k

什么时候缩小窗口:因为当窗口中某个字符的数量 超过 tar 时,就意味着在剩余的字符串中,该字符的数量将 少于 k,这不符合题目要求。tar[i] 表示的是在可以移除的字符中,每种字符最多能移除的数量(即总数量减去 k)。因此,在滑动窗口中,我们需要确保窗口中每种字符的数量 不超过 tar[i]。

class Solution {
public:
    int takeCharacters(string s, int k) {
        vector<int> tar(3, -k);  // 初始化目标数组为-k
        for (char c : s) tar[c - 'a']++;  // 计数数组

        // 如果任意字符计数小于0,则无法形成有效子串
        if (tar[0] < 0 || tar[1] < 0 || tar[2] < 0) return -1;
        
        // 如果所有字符恰好达到k次,返回整个字符串长度
        if (tar[0] == 0 && tar[1] == 0 && tar[2] == 0) return s.length();

        int l = 0, r = 0, res = 0;
        vector<int> cnt(3, 0);  // 计数当前窗口中各字符的数量
        while (r < s.length()) {
            cnt[s[r++] - 'a']++;  // 扩展窗口右边界
            // 当窗口中任一字符数量超过tar时,收缩窗口左边界
            while (cnt[0] > tar[0] || cnt[1] > tar[1] || cnt[2] > tar[2]) {
                cnt[s[l++] - 'a']--;
            }
            // 更新最大子串长度
            res = max(res, r - l);
        }
        // 返回除去最短有效子串后的剩余长度
        return s.length() - res;
    }
};

8.螺旋矩阵II

给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

四个方向循环的表达式 dirIdx = (dirIdx + 1) % 4; 是一种非常高效且常用的方式来实现这种循环控制:

  • dirIdx + 1:这部分是将当前的方向索引增加1,以便移动到下一个方向。
  • 取模运算符 % 用来实现循环效果。因为有四个方向,所以取模4。这意味着当 dirIdx 从3增加1变为4时,4 % 4 的结果是0,从而将方向索引重置为0(即向右方向),形成一个循环。
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if (matrix.empty()) return {}; // 处理空矩阵的情况

        int n = matrix.size();
        int m = matrix[0].size();
        vector<int> res;
        vector<vector<int>> direction = {
            {0, 1},  // 向右移动
            {1, 0},  // 向下移动
            {0, -1}, // 向左移动
            {-1, 0}  // 向上移动
        };
        vector<vector<bool>> seen(n, vector<bool>(m, false));
        int i = 0, j = 0, dirIdx = 0; // dirIdx 用来控制方向

        for (int k = 0; k < m * n; k++) {
            res.push_back(matrix[i][j]);
            seen[i][j] = true;
            int nexti = i + direction[dirIdx][0];
            int nextj = j + direction[dirIdx][1];
            
            // 检查是否需要转向:下一步是否出界或已访问
            if (nexti >= 0 && nexti < n && nextj >= 0 && nextj < m && !seen[nexti][nextj]) {
                i = nexti;
                j = nextj;
            } else {
                // 调整方向
                dirIdx = (dirIdx + 1) % 4; // 四个方向循环
                i += direction[dirIdx][0];
                j += direction[dirIdx][1];
            }
        }
        return res;
    }
};

9.旋转图像

90度旋转
在这里插入图片描述
要求原地旋转,不另开数组

其实是个数学问题
先上下对称翻转,再主对角线翻转
在这里插入图片描述

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size() ;
        //首先进行上下翻转
        for( int i = 0 ; i < n/2 ; i ++ ){
            swap(matrix[ i ], matrix[ n - i - 1]) ; 
        }
        //然后进行对角线翻转
        for( int i = 0 ; i < n ; i ++ ){
            for( int j = i ; j < n ;j ++ ){
                swap(matrix[i][j],matrix[j][i]) ;
            }
        }

    }
};

10.删除数组中重复的元素II

有重复的全删掉,一个也不留

需要dummy node
初始化cur指向dummy node
每次循环看下一个节点和下下节点的值是不是一样的,如果是一样的,就嵌套一个while,不一样,cur就移动

这里嵌套while的方法无敌,仔细学

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* cur = dummy;
        while(cur && cur->next && cur->next->next){
            //先把这个值存下来
            int num = cur->next->val;
            if(cur->next->next->val == num){
                //可能不止2个
                while(cur->next && cur->next->val == num){
                    cur->next = cur->next->next;
                }
            }
            else cur = cur->next;
        }
        return dummy->next;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2192104.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

洗车行软件系统有哪些 佳易王洗车店会员管理系统操作教程#洗车店会员软件试用版下载

一、前言 【试用版软件下载可点击本文章最下方官网卡片】 洗车行软件系统有哪些 佳易王洗车店会员管理系统操作教程#洗车店会员软件试用版下载 洗车管理软件应用是洗车业务的得力助手&#xff0c;实现会员管理及数据统计一体化&#xff0c;助力店铺高效、有序运营。 洗车项…

年薪96w!这才是运营人未来5年最好的就业方向!

运营人&#xff0c;终于被逼疯了&#xff01; 一个人一个部门&#xff01;文案、策划、拍摄、剪辑、运营、销售什么都做。企业利润为王&#xff0c;阅读量、粉丝量要是不能转化为业绩&#xff0c;注定拿不到高薪…… **活干了一大堆&#xff0c;一看工资8000块&#xff0c;**…

【黑马点评】 使用RabbitMQ实现消息队列——2.使用RabbitMQ监听秒杀下单

2 使用RabbitMQ实现消息队列 2.1 修改\hm-dianping\pom.xmlpom.xml文件 添加RabbitMQ的环境 <!-- RabbitMQ--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </depe…

从零开始,她如何为客户创建语义知识图谱?

在这篇文章中&#xff0c;Capgemini 的知识图谱负责人 Veronika Heimsbakk 分享了她为客户创建语义知识模型的方法。阅读本指南&#xff0c;了解她如何与客户合作&#xff0c;从头开始构建语义知识模型&#xff0c;并发现可以应用于您自己的语义建模项目的实践。 如何为客户构…

微积分-反函数6.5(指数增长和衰减)

在许多自然现象中&#xff0c;数量的增长或衰减与其大小成正比。例如&#xff0c;如果 y f ( t ) y f(t) yf(t) 表示在时间 t t t 时某种动物或细菌种群的个体数量&#xff0c;那么似乎可以合理地假设增长速率 f ’ ( t ) f’(t) f’(t) 与种群 f ( t ) f(t) f(t) 成正比…

在实际芯片里,电阻电容电感是怎么制作的

一、电阻 以前的半导体工艺中&#xff0c;使用图形化和掺杂后的硅制作电阻&#xff0c;电阻值的高低取决于长度、线宽、结深和掺杂浓度。现在一般都使用多晶硅制作IC芯片上的电阻&#xff0c;多晶硅的线宽高度、宽度和掺杂浓度决定了电阻值大小。 为什么用多晶硅做电阻呢&am…

【IO】多路转接Select

一、初识 select 系统提供 select 函数来实现多路复用输入/输出模型. select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的;程序会停在 select 这里等待&#xff0c;直到被监视的文件描述符有一个或多个发生了状态改变; select 函数原型 C #include <sys/…

u盘拷贝文件管控如何实现?4个方法一举搞定,一文详解!100%纯干货,赶快码住!

数字化办公日益普及&#xff0c;U盘作为便携的数据存储设备&#xff0c;在文件传输和备份中扮演着重要角色。 然而&#xff0c;U盘的使用也带来了数据泄露的风险&#xff0c;如何有效管控U盘拷贝文件呢&#xff1f;u盘拷贝文件管控如何实现&#xff1f; 本文&#xff0c;将详细…

winforms基本操作-将datagridview内容保存为excel文件

title: winforms基本操作-将datagridview内容保存为excel文件 tags: [winforms, windows, datagridview] categories: [客户端, windows, winforms] 这里记录一下将winforms展示的datagridview&#xff0c;导出或保存为excel文件。 这里说一下环境、版本信息&#xff1a; win系…

在线教育系统开发:SpringBoot框架的实战应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

什么软件能指定usb端口禁用?五款电脑USB端口禁用软件!(热门分享)

什么软件能指定usb端口禁用&#xff1f; USB端口&#xff0c;作为电脑与外部设备连接的重要接口&#xff0c;其安全性日益受到企业的重视。 为了有效防止数据泄露和未经授权的设备接入&#xff0c;指定USB端口禁用成为了许多企业的迫切需求。 本文&#xff0c;将介绍五款热门…

京东云主机怎么用?使用京东云服务器建网站(图文教程)

京东云主机怎么用&#xff1f;非常简单&#xff0c;本文京东云服务器网jdyfwq.com使用以使用京东云服务器搭建WordPress博客网站为例&#xff0c;来详细说下京东云主机的使用方法。使用京东云服务器快速搭建WordPress网站教程&#xff0c;3分钟基于应用镜像一键搞定&#xff0c…

程序传入单片机的过程,以Avrdude为例分析

在市场上有各式各样的单片机&#xff0c;例如Arduino&#xff0c;51单片机&#xff0c;STM等。通常&#xff0c;我们都用其对应的IDE软件进行单片机的编程。这些软件既负责将程序代码转写成二进制代码&#xff0c;即机器语言&#xff0c;也负责将该二进制代码导入单片机。与此同…

YOLO11改进|卷积篇|引入空间通道重组卷积ScConv

目录 一、【SCConv】卷积1.1【SCConv】卷积介绍1.2【SCConv】核心代码 二、添加【SCConv】卷积2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【SCConv】卷积 1.1【SCConv】卷积介绍 SCConv 模块提供了一种新的视角来看待CNNs的特征提取…

无人机企业必备运营合格证及甲级服务能力等级证书详解

无人机企业在运营过程中&#xff0c;需要取得一系列资质证书以确保其合法、安全、高效地开展业务。其中&#xff0c;运营合格证和甲级服务能力等级证书是两个重要的资质认证。以下是这两个证书的详细解析&#xff1a; 无人机企业运营合格证 无人机企业运营合格证是由国家相关…

10个令人惊叹的AI工具

AI 确实改变了游戏规则&#xff1b;它彻底改变了我们工作、创造和与技术互动的方式。虽然 ChatGPT、DALLE 和 Midjourney 等巨头占据了大部分头条新闻&#xff0c;但还有很多其他不为人知的 AI 工具和技术&#xff0c;大多数都同样令人惊叹。 以下是十种你可能没有听说过但绝对…

【AI知识点】正则化(Regularization)

正则化&#xff08;Regularization&#xff09; 是机器学习和统计学中的一种技术&#xff0c;用于防止模型过拟合。在训练模型时&#xff0c;模型可能会过度拟合训练数据&#xff0c;导致在新数据上的表现较差。正则化通过在优化过程中引入额外的约束或惩罚项&#xff0c;使模型…

python如何比较字符串

Python可使用cmp()方法来比较两个对象&#xff0c;相等返回 0 &#xff0c;前大于后&#xff0c;返回 1&#xff0c;小于返回 -1。 a "abc" b "abc" c "aba" d "abd" print cmp(a,b) print cmp(a,c) print cmp(a,d) //返回 0 1 …

pWnos1.0 靶机渗透 (Perl CGI 的反弹 shell 利用)

靶机介绍 来自 vulnhub 主机发现 ┌──(kali㉿kali)-[~/testPwnos1.0] …

解决 OpenCloudOS 中 yum 安装 yum-utils 命令报错的问题

目录 前言1. 问题背景与错误分析2. 深入分析错误原因2.1 OpenCloudOS 与 CentOS 之间的区别2.2 文件冲突的具体分析 3. 解决方案3.1 使用 --replacefiles 强制安装3.2 使用 yum swap 替换冲突包3.3 手动调整冲突包 4. 预防与优化建议4.1 确保软件源的兼容性4.2 定期更新系统 结…