Studying-CodeTop | 3. 无重复字符的最长子串、206. 反转链表、146. LRU 缓存

news2024/11/25 16:28:58

目录

3. 无重复字符的最长子串

206. 反转链表

146. LRU 缓存 

解题过程: 


3. 无重复字符的最长子串

题目:3. 无重复字符的最长子串 - 力扣(LeetCode)

学习:本题题意很好理解,我们需要从所有不含有重复字符的子串中,找到长度最长的那个。首先明确子串的概念:子串不是子序列,子串中的元素是连续的,因此对于示例3来说,wke连续的三个字符是子串,而pwke中间跳掉了一个字符w,因此不是子串,是子序列。

本题我们可以使用回溯算法,遍历所有字符串的所有子串,并判断是否还有重复字符,如果没有则记录一次答案,最后找到最长的长度。这是一种暴力解法,显然时间复杂度极高。

我们其实可以从实际模拟出发,采用滑动窗口的方式进行本题的求解。

以示例1为例,我们可以从头开始遍历数组,先把‘a’加入窗口,然后b与a不同,再把b加入窗口,接着再把c加入窗口。再接着就到a了,此时滑动窗口里面已经有a了,出现了重复的字符。此时我们需要将滑动窗口缩窄,也就是将窗口左边界向右移动,这里要注意,这个过程我们需要使用while进行,虽然本题第四个字符是a,我们只需要左边界移动一次,但如果第四个字符是c,则我们需要把左边界一直移动到第四个字符下标,其实也就是我们需要一直缩减滑动窗口,直到其中不包含重复字符。

统计滑动窗口内字符和找到重复字符的方法,我们可以使用哈希表进行。滑动窗口的移动则可以通过for循环来进行。

代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if(s.size() <= 1) return s.size();
        //滑动窗口,使用哈希表来求解
        unordered_set<char> ans;
        int result = 1;
        ans.insert(s[0]); //初始化哈希表
        for(int i = 0, j = 1; j < s.size(); j++) {
            while(i < j && ans.find(s[j]) != ans.end()) {
                ans.erase(s[i]);
                i++;
            }
            ans.insert(s[j]);
            result = max(result, j - i + 1);
        }
        return result;
    }
};

206. 反转链表

题目:206. 反转链表 - 力扣(LeetCode)

学习:本题是一道经典的使用双指针方法来解决的题型。

 

我们可以定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。然后开始进行反转。

首先我们要把cur->next节点用tmp指针保存一下,也就是保存一下这个节点。这是因为接下来要改变cur->next的指向,后续我们还需要回到原先的cur->next的节点进行下一步操作。

接着就是将cur->next = pre;然后pre = cur; cur = next 进行下一轮反转。

代码:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp;
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur != nullptr) {
            tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
        
    }
};

146. LRU 缓存 

题目:146. LRU 缓存 - 力扣(LeetCode)

学习:本题很难,我们需要从两个函数出发。

1. get() 函数

该函数,要求我们判断关键字key是否在缓存中,如果在返回关键字的值,如果不在返回-1。显然这是一个map的结构,并且要求我们时间复杂度为O(1),因此,我们应该采取哈希表来进行存储。同时这里还包含了,如果存在,则代表该节点被访问了的信息。

2.put() 函数

该函数除了要求我们判断key是否存在以外。还要求我们在不存在的时候,将一个新的结点插入到该组中,如果导致节点数量超过了capacity,则需要删除最久未使用的关键字。这里就两个任务需要我们去实现:①找到最久为使用的节点,并将其删除;②将新节点插入到组中,并且是最新的。

上述两个函数的操作都要求O(1)的时间复杂度进行,而对于删除节点和插入节点,我们能想到的O(1)时间复杂度的结构就是链表(数组的插入删除时间复杂度是O(n)) 。

因此本题需要使用 哈希表 和 链表来进行解决。

解题过程:

链表我们需要使用双向链表结构,这样更方便我们进行节点的删除了插入。

struct Linknode {
    Linknode* prev;
    Linknode* next;
    int key;
    int val;
    Linknode(): key(0), val(0), prev(nullptr), next(nullptr) {}
    Linknode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr) {}
};

接着我们需要一个哈希表,来进行节点的映射。同时可以设置两个虚拟头结点和尾节点,方便我们插入最新的节点,和找到最久的节点。当然我们也需要保存该组的容量和当前大小。

private:
    unordered_map<int, Linknode*> map;
    Linknode* dummyhead; //虚拟头尾节点
    Linknode* dummytail;
    int _capacity; //容量
    int _size; //当前所用容量

接着在实现get() 和 put() 函数之前,我们需要实现两个额外的函数,插入头节点的函数和删除节点的函数。 

void addtohead(Linknode* node) { //将节点加入到头结点
    node->prev = dummyhead;
    node->next = dummyhead->next;
    dummyhead->next->prev = node;
    dummyhead->next = node;
}
void remove(Linknode* node) { //删除一个节点
    node->prev->next = node->next; //将该节点的前一个节点的next指向该节点的next
    node->next->prev = node->prev; //将该节点的下一个节点的prev指向该节点的prev
}

我们还可以对其进行封装,以实现,将访问过的节点,重新插入到头结点(最新的节点)的方法:

void movetohead(Linknode* node) {
    remove(node);
    addtohead(node);
}

下面我们就可以对get()函数进行实现了,判断两种情况,存在key或者不存在key

int get(int key) {
    if(!map.count(key)) {
        return -1;
    }
    Linknode* node = map[key];
    movetohead(node);
    return node->val;
}

put()函数则还需要插入新的节点,并判断容量:

void put(int key, int value) {
    if(!map.count(key)) {
        Linknode* node = new Linknode(key, value);
        map[key] = node;
        addtohead(node);
        ++_size;
        if(_size > _capacity) {
            Linknode* removenode = dummytail->prev;
            remove(removenode);
            map.erase(removenode->key);
            delete removenode;
            removenode = nullptr;
            --_size;
        }
    }
    else {
        Linknode* node = map[key];
        node->val = value;
        movetohead(node);
    }
}

最后得到代码:

class LRUCache {
public:
    //定义双向链表结构体
    struct Linknode {
        Linknode* prev;
        Linknode* next;
        int key;
        int val;
        Linknode(): key(0), val(0), prev(nullptr), next(nullptr) {}
        Linknode(int _key, int _val): key(_key), val(_val), prev(nullptr), next(nullptr) {}
    };

    LRUCache(int capacity) : _capacity(capacity), _size(0) {
        dummyhead = new Linknode();
        dummytail = new Linknode();
        dummyhead->next = dummytail;
        dummytail->prev = dummyhead;
    }
    
    int get(int key) {
        if(!map.count(key)) {
            return -1;
        }
        Linknode* node = map[key];
        movetohead(node);
        return node->val;
    }
    
    void put(int key, int value) {
        if(!map.count(key)) {
            Linknode* node = new Linknode(key, value);
            map[key] = node;
            addtohead(node);
            ++_size;
            if(_size > _capacity) {
                Linknode* removenode = dummytail->prev;
                remove(removenode);
                map.erase(removenode->key);
                delete removenode;
                removenode = nullptr;
                --_size;
            }
        }
        else {
            Linknode* node = map[key];
            node->val = value;
            movetohead(node);
        }
    }

    void addtohead(Linknode* node) { //将节点加入到头结点
        node->prev = dummyhead;
        node->next = dummyhead->next;
        dummyhead->next->prev = node;
        dummyhead->next = node;
    }
    void remove(Linknode* node) { //删除一个节点
        node->prev->next = node->next; //将该节点的前一个节点的next指向该节点的next
        node->next->prev = node->prev; //将该节点的下一个节点的prev指向该节点的prev
    }
    void movetohead(Linknode* node) {
        remove(node);
        addtohead(node);
    }
private:
    unordered_map<int, Linknode*> map;
    Linknode* dummyhead; //虚拟头尾节点
    Linknode* dummytail;
    int _capacity; //容量
    int _size; //当前所用容量
};

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

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

相关文章

Linux 软件编程学习第十七天

1.select的缺点&#xff1a; 1.select监听的文件描述符集合是一个数组&#xff0c;有上限&#xff08;1024个&#xff09; 2.select监听的文件描述符集合在应用层&#xff0c;内核层监听事件后需要传递给用户层带来资源开销 3.select需要用户手动查找产生事件的文件…

数据复制一(主从复制详解)

目录 一、主从复制 二、同步复制和异步复制 三、节点失效处理方案 四、复制日志的实现 五、复制滞后问题 读自己的写 单调读 前缀一致读 数据复制就是相同的数据在多台机器上传输&#xff0c;多台机器可以在一个机房也不可以跨区域。通过数据复制有以下好处&#xff1a…

「数组」希尔排序 / 区间增量优化(C++)

目录 概述 思路 核心概念&#xff1a;增量d 算法过程 流程 Code 优化方案 区间增量优化 Code(pro) 复杂度 概述 我们在「数组」冒泡排序|选择排序|插入排序 / 及优化方案&#xff08;C&#xff09;中讲解了插入排序。 它有这么两个特点&#xff1a; ①待排序元素较…

<数据集>无人机航拍不同高度牧羊识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6065张 标注数量(xml文件个数)&#xff1a;6065 标注数量(txt文件个数)&#xff1a;6065 标注类别数&#xff1a;1 标注类别名称&#xff1a;[sheep] 序号类别名称图片数框数1sheep6065149785 使用标注工具&…

【Spring】初识Spring MVC

文章目录 前言一、MVC是什么&#xff1f;二、学习Spring MVC建立连接RequestMapping注解注解的使用细节 三、传递参数的情况传递单个参数1.传递String2.传递包装类/基本类型3.参数重命名(RequestParam) 传递多个参数传递对象传递数组传递集合参数为变量传递文件小细节 四、JSON…

MCAL--MCU (S32K144)

AutoSAR中MCU Driver主要提供了用于基本的控制器初始化、下电、复位功能的服务,同时也为其它MCAL层需要的功能提供对应的服务函数。通常来说在AutoSAR的架构中MCU主要支持以下几个功能: 1.初始化控制器的外设时钟、系统时钟、PLL等,对所有控制器内各个外设模块用到的时钟提供…

Spring之@Bean注解

1. 使用方式 1.1 Configuration Bean 1.1.1 创建实体类 User Data NoArgsConstructor public class User {private String name;public User(String name) {this.name name;} } 1.1.2 创建配置类 UserConfig Configuration public class UserConfig {Beanpublic User us…

Web客户端软件测试

目录 1.测试分类 按照软件产生的阶段划分 按照代码可见度划分 其他测试 2.质量模型&#xff1a;衡量一个软件质量的维度 3.软件测试 1.单功能测试 等价类划分法&#xff1a;一种用少量数据获得较好测试效果的工具 边界值分析法&#xff1a;一个边界范围限制选取测试数…

最近云计算领域有哪些重大进展?

在云计算领域&#xff0c;近期确实涌现出了一系列令人瞩目的重大进展。以下是一些关键点&#xff0c;为您概述了当前的科技动态&#xff1a; 中国云计算市场迅猛发展&#xff1a; 中国云计算市场正处于快速发展期&#xff0c;年复合增长率超过40%。公有云市场规模增长49.3%至32…

深信达反向沙箱:构筑内网安全与成本效益的双重防线

# 深信达反向沙箱&#xff1a;内网安全与成本控制的双重保障 在数字化时代&#xff0c;企业面临着日益复杂的网络安全挑战。内网安全尤其关键&#xff0c;因为它涉及到企业的核心数据和运营。深信达的反向沙箱技术&#xff0c;作为一种创新的安全解决方案&#xff0c;为政企单…

厦门凯酷全科技有限公司:抖音小店前沿的探索者与引领者

在数字经济浪潮席卷全球的今天&#xff0c;电商平台已成为推动消费升级、激发市场活力的关键力量。其中&#xff0c;抖音作为短视频与直播电商的佼佼者&#xff0c;正以其独特的内容生态和庞大的用户基础&#xff0c;重新定义着零售行业的边界。在这一背景下&#xff0c;厦门凯…

学习记录——day33 HTTP

目录 一、HTTP相关概念 二、客服端请求 1、请求首部 2、 响应首部 三、线程实现HTTP并发服务器 一、HTTP相关概念 1、HTTP&#xff0c;全称Hyper Text Transfer Protocol&#xff0c;用于万维网&#xff08;world wide web&#xff09;进行超文本学习的传输协议 2、HTTP属…

如何使用 Java 记录简化 Spring Data 中的数据实体

Java 开发人员一直依赖 Spring Data 来实现高效的数据访问。但是&#xff0c;随着 Java Records 的引入&#xff0c;数据实体的管理方式发生了重大变化。 在本文中&#xff0c;我们将讨论 Java Records 在 Spring Data 应用程序中的集成。我们将探讨使用 Java 记录创建健壮的数…

CGAL 线性插值(自然邻近坐标)

文章目录 一、简介二、实现代码三、实现效果一、简介 自然邻近坐标插值(Natural Neighbor Interpolation)是一种基于Voronoi图的插值方法。它被广泛应用于地理信息系统(GIS)、计算几何以及科学计算领域。该方法的核心思想是利用数据点的Voronoi单元来确定插值点的权重,从而…

基于java的酒店管理系统设计与实现

系统分析与设计 需求分析 1.系统概要 根据餐饮系统的流程&#xff0c;完成从用户登录到开台点菜&#xff0c;到结账收银&#xff0c;到统计一条线的信息化管理&#xff0c;因此整个餐饮管理信息系统的研发内容就是开发一整套餐饮管理信息系统&#xff0c;实现餐饮业务的计算…

【三维重建】2D Gaussian Splatting:几何准确的2D辐射场(更新中)

标题&#xff1a;2D Gaussian Splatting for Geometrically Accurate Radiance Fields 项目地址&#xff1a;https://github.com/hbb1/2d-gaussian-splatting 文章目录 功能输入输出 一、摘要二、引言深入分析解读 三、相关工作3.1新视角合成3.2 3D重建3.3 可微分基于点的图形…

Manim动画:相机的移动(MovingCameraScene)

1.相机的移动(MovingCameraScene) MovingCameraScene 是 Manim 中的一个类&#xff0c;用于创建可以移动的相机场景。这个类继承自 Scene&#xff0c;并提供了关于相机移动的额外功能。 MovingCameraScene(camera_class<class manim.camera.moving_camera.MovingCamera>…

搭建ELK日志采集与分析系统

SpringCloud微服务实战——企业级开发框架 &#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您…

5.Linux_Shell编程

概述 什么是shell脚本&#xff1a; Shell脚本是利用shell的功能所写的一个程序。这个程序是使用纯文本文件&#xff08;后缀为.sh&#xff09;&#xff0c;将一些shell的语法与命令&#xff08;含外部命令&#xff09;写在里面&#xff0c;搭配正则表达式、管道命令与数据流重…

【QT代码控制Linux开发板】QT控制嵌入式Linux开发板运行shell脚本

一. 前言 最近遇到了一个很冲突的问题&#xff0c;我想让比如qt代码控制传感器读取的数值大于某个阈值时控制板子的灯亮进行报警。 但是当我在Linux开发板上./运行交叉编译后的qt文件时&#xff0c;想运行开发板的其他shell语句必须先退出qt代码的执行&#xff0c;当然开发板…