给定一个字符串,请你找出其中不含有重复字符的最长子串的长度

2023-10-26

示例 1:

输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:

输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:

输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

解决方案:

方法一:暴力法
思路

逐个检查所有的子字符串,看它是否不含有重复的字符。

算法

假设我们有一个函数 boolean allUnique(String substring) ,如果子字符串中的字符都是唯一的,它会返回true,否则会返回false。 我们可以遍历给定字符串 s 的所有可能的子字符串并调用函数 allUnique。 如果事实证明返回值为true,那么我们将会更新无重复字符子串的最大长度的答案。

现在让我们填补缺少的部分:

为了枚举给定字符串的所有子字符串,我们需要枚举它们开始和结束的索引。假设开始和结束的索引分别为 ii 和 jj。那么我们有 0≤i<j≤n (这里的结束索引 j是按惯例排除的)。因此,使用 i 从0到 n - 1 以及 j 从 i+1 到 n 这两个嵌套的循环,我们可以枚举出 s 的所有子字符串。

要检查一个字符串是否有重复字符,我们可以使用集合。我们遍历字符串中的所有字符,并将它们逐个放入 set 中。在放置一个字符之前,我们检查该集合是否已经包含它。如果包含,我们会返回 false。循环结束后,我们返回 true。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int ans = 0;
        for (int i = 0; i < n; i++)
            for (int j = i + 1; j <= n; j++)
                if (allUnique(s, i, j)) ans = Math.max(ans, j - i);
        return ans;
    }

    public boolean allUnique(String s, int start, int end) {
        Set<Character> set = new HashSet<>();
        for (int i = start; i < end; i++) {
            Character ch = s.charAt(i);
            if (set.contains(ch)) return false;
            set.add(ch);
        }
        return true;
    }
}

复杂度分析

时间复杂度:O(n^3) 。

空间复杂度:O(min(n, m)),我们需要 O(k)O(k) 的空间来检查子字符串中是否有重复字符,其中 kk 表示 Set 的大小。而 Set 的大小取决于字符串 n 的大小以及字符集/字母 m 的大小。

方法二:滑动窗口
算法

暴力法非常简单。但它太慢了。那么我们该如何优化它呢?

在暴力法中,我们会反复检查一个子字符串是否含有有重复的字符,但这是没有必要的。如果从索引 i 到 j - 1 之间的子字符串 s_{ij}已经被检查为没有重复字符。我们只需要检查 s[j] 对应的字符是否已经存在于子字符串 s_{ij}中。

要检查一个字符是否已经在子字符串中,我们可以检查整个子字符串,这将产生一个复杂度为 O(n^2)的算法,但我们可以做得更好。

通过使用 HashSet 作为滑动窗口,我们可以用 O(1) 的时间来完成对字符是否在当前的子字符串中的检查。

滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [i, j)(左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [i, j)向右滑动 1 个元素,则它将变为 [i+1, j+1)(左闭,右开)。

回到我们的问题,我们使用 HashSet 将字符存储在当前窗口 [i, j)(最初 j = i)中。 然后我们向右侧滑动索引 j,如果它不在 HashSet 中,我们会继续滑动 j。直到 s[j] 已经存在于 HashSet 中。此时,我们找到的没有重复字符的最长子字符串将会以索引 i 开头。如果我们对所有的 i这样做,就可以得到答案。

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Set<Character> set = new HashSet<>();
        int ans = 0, i = 0, j = 0;
        while (i < n && j < n) {
            // try to extend the range [i, j]
            if (!set.contains(s.charAt(j))){
                set.add(s.charAt(j++));
                ans = Math.max(ans, j - i);
            }
            else {
                set.remove(s.charAt(i++));
            }
        }
        return ans;
    }
}

复杂度分析

时间复杂度:O(2n) = O(n),在最糟糕的情况下,每个字符将被 i 和 j 访问两次。

空间复杂度:O(min(m, n)),与之前的方法相同。滑动窗口法需要 O(k)的空间,其中 kk 表示 Set 的大小。而Set的大小取决于字符串 n 的大小以及字符集/字母 m 的大小。

方法三:优化的滑动窗口
上述的方法最多需要执行 2n 个步骤。事实上,它可以被进一步优化为仅需要 n 个步骤。我们可以定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。 当我们找到重复的字符时,我们可以立即跳过该窗口。

也就是说,如果 s[j] 在 [i, j)范围内有与 j’ 重复的字符,我们不需要逐渐增加 i 。 我们可以直接跳过 [i,j’]范围内的所有元素,并将 i 变为 j’ + 1。

Java(使用 HashMap)

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>(); // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            if (map.containsKey(s.charAt(j))) {
                i = Math.max(map.get(s.charAt(j)), i);
            }
            ans = Math.max(ans, j - i + 1);
            map.put(s.charAt(j), j + 1);
        }
        return ans;
    }
}

Java(假设字符集为 ASCII 128)

以前的我们都没有对字符串 s 所使用的字符集进行假设。

当我们知道该字符集比较小的时侯,我们可以用一个整数数组作为直接访问表来替换 Map。

常用的表如下所示:

int [26] 用于字母 ‘a’ - ‘z’或 ‘A’ - ‘Z’
int [128] 用于ASCII码
int [256] 用于扩展ASCII码

public class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        int[] index = new int[128]; // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {
            i = Math.max(index[s.charAt(j)], i);
            ans = Math.max(ans, j - i + 1);
            index[s.charAt(j)] = j + 1;
        }
        return ans;
    }
}

复杂度分析

时间复杂度:O(n),索引 j 将会迭代 n 次。

空间复杂度(HashMap):O(min(m, n)),与之前的方法相同。

空间复杂度(Table):O(m),m 是字符集的大小。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

给定一个字符串,请你找出其中不含有重复字符的最长子串的长度 的相关文章

随机推荐

  • 【位图&&布隆过滤器&&海量数据面试题】

    文章目录 1 位图 2 布隆过滤器 1 位图 首先我们来看看一个腾讯的面试题 给40亿个不重复的无符号整数 没排过序 给一个无符号整数 如何快速判断一个数是否在这40亿个数中 分析 40亿个不重复整形数据 大概有160亿字节 也就是16GB
  • 数据结构单链表的创建以及简单操作

    在数据结构中 目录 一 数据节点类型结构体封装 二 创建单链表 1 创建链表 2 头部插入 3 遍历链表 4 尾部插入 5 释放链表 链表可以解决顺序表无法开辟连续空间的问题 大大提高了内存的利用率 这使我们在开发中不再局限于小型数据量的项
  • 开发岗校招求职攻略——面试准备(7.2胸有成竹-技术面技巧)

    1 前言 当你踏入面试房间的第一只脚开始 你的一举一动就都在面试官眼里和心里了 从最开始的自我介绍 到最后结束面试时的提问 都不能草率对待 下面 我根据技术面的几种常见面试形式 分别介绍一些特有的技巧 并且会在此基础上再额外介绍一些通用技巧
  • 查找已安装 npm 包的版本

    问 如何查找已安装的 node js npm 包的版本 这将打印 npm 本身的版本 npm v 这会打印一个神秘的错误 npm version 这会在注册表上打印软件包版本 即可用的最新版本 npm view version 如何获取已安
  • IDEA 连接 数据库

    IDEA 连接 数据库 一 首先确保数据库服务是打开的 使用 mysql u root p 连接数据库服务器 若不能进入到 mysql 里面则说明 没有启动服务器 使用 net start mysql 命令启动 如果 net start m
  • 攻防世界Web赛题记录

    Cat 题目 https adworld xctf org cn task answer type web number 3 grade 1 id 4658 page 2 Writeup 攻防世界 web Cat XCTF 4th WHCT
  • QT tabWidget样式表

    背景设置 QTabWidget pane border 1px solid rgba 125 250 250 160 border radius 3px background transparent 透明 margin top 1px cl
  • npm报错:xxx packages are looking for funding run `npm fund` for details(解决办法)

    报错信息 30 packages are looking for funding run npm fund for details 报错原因 这里是开发者捐赠支持的提示 打开一个github的链接之后 会显示是否需要打赏捐赠的信息 解决方案
  • LPDDR4 JEDEC标准测试实例解析--地址总线写操作

    说完DQ信号的读写测试 接下来 再来聊一聊命令及地址总线 CA Bus 的测试 由于CA bus只有一个信号流向 因此 只需要进行写操作的测试即可 如下图所示 为JEDEC标准中定义的CA相关的测试参数 接下来 将对测试项逐一进行解析 tC
  • 【直接收藏】分享 42 个常用前端布局方案

    对 CSS 布局掌握程度决定你在Web开发中的开发页面速度 随着Web技术的不断革新 实现各种布局的方式已经多得数不胜数了 本篇文章总结了四十二种CSS的常见布局 这四十二种布局可以细分为如下几类 水平居中 垂直居中 水平垂直居中 两列布局
  • centos 64 位系统安装postgresql odbc 方法

    1 64位系统下 postgresql 的psqlodbc驱动下载地址 http www postgresql org ftp odbc versions src 2 64位系统下 安装psqlodbc需要的安装包 unixODBC 2 3
  • 机器学习实验(一)—Linear Regression

    前几天做了几个机器学习的简单实验 机器学习实验二 Logistic Regression 实验一是关于简单的线性回归的实验 下面是我的实验报告的截图 直接把word的内容撸过来 格式就全乱了 没有找到解决办法 直接上图吧 也是一种办法 后面
  • 关于差速移动机器人的运动学模型推导

    预备 在机器人的运动中 经常会涉及到航向推演 下面这篇博客写的挺好的 https blog csdn net heyijia0327 article details 44983551 在学习机器人运动模型推导的时候 有看到 网上别人的推导过
  • Rabbitmq的五种模式和案例

    消息生产者p将消息放入队列 消费者监听队列 如果队列中有消息 就消费掉 消息被拿走后 自动从队列删除 隐患 消息可能没有被消费者正确处理 已经消失了 无法恢复 应用场景 聊天室 案例 1 gt 首先准备依赖
  • linux查找nigux得路径,Dzongkha localization in Linux operating

    This message was created automatically by mail delivery software A message that you sent could not be delivered to one o
  • 网关(gateway)简介与作用

    网关的英文名称 gateway 又叫做网间连接器 协议转换器 网关是在采用不同体系结构或协议的网络之间进行互通时 用于提供协议转换 路由选择 数据交换等网络兼容功能的设施 网关在传输层上以实现网络互连 是最复杂的网络互连设备 仅用于两个高层
  • Lerp 实现匀速运动

    Lerp函数在Mathf Vector3 等类中都有 用法都类似 作用都是按照百分比取得从一个值过度到另外一个值的中间值 下面说的内容针对各中类的Lerp函数都是通用的 Lerp的常见 误用 是 Update Transform posit
  • 快速排序与快速选择

    快速排序算法就是将一列无序的数字排成有序 通过使用分治法 快速排序能够在O nlog n 的时间内完成 相比堆排序等其他也是O nlog n 复杂度的排序算法 快速排序的基数更小 因此效率也就越高 快速选择是在快速排序的基础上 在一列无序数
  • C语言中getchar()的用法详谈

    大多数人只看getchar 名字 以为其返回值是char 类型 但是getchar 的确不是char 类型 而是int 类型 其原型如下 int getchar void getchar有一个int型的返回值 当程序调用getchar时 程
  • 给定一个字符串,请你找出其中不含有重复字符的最长子串的长度

    示例 1 输入 abcabcbb 输出 3 解释 因为无重复字符的最长子串是 abc 所以其长度为 3 示例 2 输入 bbbbb 输出 1 解释 因为无重复字符的最长子串是 b 所以其长度为 1 示例 3 输入 pwwkew 输出 3 解