《代码随想录》刷题笔记——字符串篇【java实现】

news2024/11/19 21:20:49

反转字符串

https://leetcode.cn/problems/reverse-string/description/

【双指针法:一前一后】

public void reverseString(char[] s) {
    for (int i = 0; i < s.length / 2; i++) {
        char temp = s[i];
        s[i] = s[s.length - 1 - i];
        s[s.length - 1 - i] = temp;
    }
}

反转字符串Ⅱ

https://leetcode.cn/problems/reverse-string-ii/

每计数至2k个字符,就反转这2k个字符中的前k个字符,因此循环的步长是2k

public static String reverseStr(String s, int k) {
    // 将字符串转化为 字符数组 ,方便遍历
    char[] chars = s.toCharArray();
    for (int i = 0; i < chars.length; i += 2 * k) {
        if (i + k > chars.length) {
            // 将剩余的元素反转
            for (int j = 0; j < (chars.length - i) / 2; j++) {
                char temp = chars[j + i];
                chars[j + i] = chars[chars.length  - j - 1];
                chars[chars.length  - j - 1] = temp;
            }
        } else {
            // 反转前面k个
            for (int j = 0; j < k / 2; j++) {
                char temp = chars[j + i];
                chars[j + i] = chars[i + k - j - 1];
                chars[i + k - j - 1] = temp;
            }
        }
    }
    return new String(chars);
}

反转字符串中的单词

https://leetcode.cn/problems/reverse-words-in-a-string/

【移除无效的空格 迭代过程】

在这里插入图片描述

public String reverseWordsFromStartToEnd(String s) {
    char[] charArr = s.toCharArray();
    // 去除无效的空格
    int arrLen = removeSpace(charArr);
    // 将整个字符串反转
    reverse(charArr, 0, arrLen - 1);
    // 反转每个单词
    reverseWordsFromStartToEnd(charArr, 0, arrLen - 1);

    return new String(charArr, 0, arrLen);
}

/**
 * 反转字符串里面的每一个单词
 *
 * @param charArr
 * @param startIndex
 * @param endIndex
 */
private void reverseWordsFromStartToEnd(char[] charArr, int startIndex, int endIndex) {
    // fast 比 slow 多一位,因为 slow 和 fast 不同时,才需要反转
    int slow = startIndex, fast = startIndex + 1;
    while (fast <= endIndex) {
        if (charArr[fast] == ' ') {
            // 当fast走到空格时,slow到fast-1的就是一个单词,需要反转
            reverse(charArr, slow, fast - 1);
            // slow直接跳过空格,来到下一个单词的开头
            slow = fast + 1;
            fast = slow + 1;
        } else if (fast == endIndex) {
            // 当fast走到末尾时,slow到fast的就是一个单词,需要反转
            reverse(charArr, slow, fast);
            break;
        } else {
            fast++;
        }
    }
}

/**
 * 反转 startIndex与endIndex之间的字符
 *
 * @param charArr
 * @param startIndex
 * @param endIndex
 */
private void reverse(char[] charArr, int startIndex, int endIndex) {
    for (int i = 0; i <= (endIndex - startIndex) / 2; i++) {
        char temp = charArr[i + startIndex];
        charArr[i + startIndex] = charArr[endIndex - i];
        charArr[endIndex - i] = temp;
    }
}

/**
 * 移除无效的空格,如前后的空格 以及 单词与单词中间的空格
 *
 * @param charArr
 * @return 移除无效空格之后的数组长度
 */
private int removeSpace(char[] charArr) {
    int slow = 0, fast = 0;
    // 表示是否已经增加了一个空格,如果增加了,就改成true
    boolean addSpace = false;
    while (fast < charArr.length) {
        if (slow == fast && charArr[slow] != ' ') {
            // 如果不是空格,不需要替换,fast和slow同时走
            slow++;
            fast++;
            addSpace = false;
        } else if (charArr[fast] != ' ') {
            charArr[slow++] = charArr[fast++];
            addSpace = false;
        } else if (charArr[fast] == ' ') {
            if (addSpace == false && slow > 0) {
                // 单词与单词之间保留一个空格
                charArr[slow++] = ' ';
                addSpace = true;
            }
            fast++;
        }
    }
    // 如果最后增加了一个空格,就将这个空格删掉
    if (charArr[slow - 1] == ' ') {
        slow -= 1;
    }
    return slow;
}

左旋转字符串

该题的链接变成其他的题目了,这里就不再放链接了,题目介绍如下:

在这里插入图片描述

这道题的思路非常灵活,对于lrlose | umgh,首先反转前6个元素,变成esolrl | umgh,再反转后面的剩余字符esolrl | hgmu,最后在对整一个字符串进行反转,就得到最终的结果了umghlrlose

public String reverseLeftWords(String s, int n) {
    char[] chars = s.toCharArray();
    // 反转前n个单词
    reverse(chars, 0, n - 1);
    // 反转后面的单词
    reverse(chars, n,  chars.length - 1);
    // 反转整个字符串
    reverse(chars, 0,  chars.length - 1);
    return new String(chars);
}

/**
 * 反转两个指定索引之间的字符
 * @param charArr
 * @param startIndex
 * @param endIndex
 */
private void reverse(char[] charArr, int startIndex, int endIndex) {
    for (int i = 0; i <= (endIndex - startIndex) / 2; i++) {
        char temp = charArr[i + startIndex];
        charArr[i + startIndex] = charArr[endIndex - i];
        charArr[endIndex - i] = temp;
    }
}

找出字符串中第一个匹配项的下标

题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/

已知有一个文本串aabaabaaf和一个模式串aabaaf

暴力求解

/**
 * 暴力破解
 *
 * @param haystack
 * @param needle
 * @return
 */
public int bruteForce(String haystack, String needle) {
    for (int i = 0; i < haystack.length(); i++) {
        boolean found = true;
        for (int j = 0; j < needle.length(); j++) {
            if (haystack.charAt(i + j) != needle.charAt(j)) {
                found = false;
                break;
            }
        }
        if (found) {
            return i;
        }
    }
    return -1;
}

时间复杂度是O(m*n),m是haystack的长度,n是needle的长度

KMP算法

KMP思想:当出现字符串不匹配时,通过利用部分已经匹配过的内容信息来避免从头开始匹配

模式串前缀

包括模式串的第一个字母,不包括模式串的最后一个字母的所有子串,如aabaaf有如下前缀:

  • a
  • aa
  • aab
  • aaba
  • aabaa

模式串后缀

包括模式串的最后一个字母,不包括模式串的第一个字母的所有子串,如aabaaf有如下后缀:

  • f
  • af
  • aaf
  • baaf
  • abaaf

最长相等前后缀(前缀表)

子串最长相等前后缀原因
a0只有一个字符,没有前缀,也没有后缀,所以长度是0
aa1a与a
aab0
aaba1a与a
aabaa2aa与aa
aabaaf0

[0,1,0,1,2,0]就是前缀表,用来记录模式串与主串不匹配的时候,模式串应该从哪里开始重新匹配

如下图,模式串和主串在前面的a a b a a都是匹配的,到i=5,j=5的时候开始不匹配了

在这里插入图片描述

这时并不需要从头开始匹配,因为②和③之前已经匹配过了,①和②又相同,因此①和③无须再匹配,直接从i=5,j=2开始匹配即可。使用了前缀表就知道aabaa的最长相等前后缀是2,由此可以知道下一个需要匹配的元素是模式串的第三个元素

在这里插入图片描述

如何计算填充前缀表(next数组)

在这里插入图片描述

时间复杂度O(n+m)

假设n为文本串长度,m为模式串长度,在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n)。除此之外,还生成next数组,时间复杂度是O(m)

代码

public int strStr(String haystack, String needle) {
    if ("".equals(needle)) {
        return 0;
    }
    int[] next = getNext(needle);
    int i = 0, j = 0;
    while (i < haystack.length() && j < needle.length()) {
        if (haystack.charAt(i) == needle.charAt(j)) {
            if (j == needle.length() - 1) {
                // --if-- needle全部匹配完成,返回索引
                return i - j;
            }
            // 匹配成功,i和j同时++,匹配下一个字符
            i++;
            j++;
        } else {
            if (j > 0) {
                // --if-- 如果匹配不成功,而且j>0,对j进行回退
                j = next[j - 1];
            } else {
                // --if-- 回退不了的话,i++,重新开始匹配
                i++;
            }
        }
    }

    return -1;
}

private int[] getNext(String needle) {
    int[] next = new int[needle.length()];
    // j是前缀的末尾索引,i是后缀的末尾索引
    int i, j = 0;
    for (i = 1; i < needle.length(); i++) {
        //--for-- 从next的第二个元素开始填充,next[0]为 0,因为一个字符的最长相等前后缀为 0
        while (j > 0 && needle.charAt(i) != needle.charAt(j)) {
            // 当后缀的末尾字符与前缀末尾字符不相等时,将j回退
            j = next[j - 1];
        }
        if (needle.charAt(i) == needle.charAt(j)) {
            //--if-- 前缀末尾字符和后缀末尾字符相等时,j++
            j++;
        }
        next[i] = j;
    }
    return next;
}

重复的子字符串

题目链接:https://leetcode.cn/problems/repeated-substring-pattern/description/

感悟

这道题目的定位是简单,做起来却让我感到我的头脑简单つ﹏⊂。确实,使用暴力法很容易做出结果,但是时间复杂度太高。能想出移动匹配和用kmp方法求解的真乃大神,我连理解都需要花费好长时间/(ㄒoㄒ)/~~

暴力法

一层循环遍历模板子串的尾部元素,即[0,end]的元素为一个模板子串;另一层循环用来判断模板子串后面的字符串是否都由模板子串重复构成

时间复杂度:O(n^2)

空间复杂度:O(1)

/**
 * 暴力方法求解
 *
 * @param s
 * @return
 */
public static boolean bruteForce(String s) {
    // 假如 s.length()=10,循环到索引4即可 [0,1,2,3,4] [5,6,7,8,9]
    // 假如 s.length()= 9,循环到索引3即可 [0,1,2,3] [4,5,6,7,8]
    for (int end = 0; end < s.length() / 2; end++) {
        // 如果字符串的长度不是end+1的整数倍,说明肯定不是当前子串的循环重复
        if (s.length() % (end + 1) != 0) {
            continue;
        }
        // 从[0,end]的元素表示一个子串
        int index = 0;
        for (int i = end + 1; i < s.length(); i++) {
            if (s.charAt(index) == s.charAt(i)) {
                // 必须i == s.length() - 1 && index == end 再 return true,
                // 如果缺少index == end。 aabaaba 会出现问题
                if (i == s.length() - 1 && index == end) {
                    return true;
                }
                // (end+1)才是子串的数量
                index = ++index % (end + 1);
            } else {
                break;
            }
        }
    }
    return false;
}

移动匹配(我愿称之为双S法)

方法

假设有一个字符串s= abcdabcd,首先获取ss= s+s = abcdabcdabcdabcd,然后去掉ss的首尾元素,ss=bcdabcdabcdabc,然后再判断ss里面是否包含s,如果包含,返回true,ss=bcdabcdabcdabc里面很明显是包含s的,因此字符串s里面包含重复的子字符串。如果s不包含重复子串,s自己就是一个子串,s+s去头去尾就一定不包含自己。

字符串是由子串循环重复而成→双S法返回true,如何证明充分必要性

充分性

因为字符串是由子串循环重复而成,假设子串为S’,则S=S’S’…S’S’,S至少由两个S’组成,SS=S+S,去掉SS的首尾元素只是毁掉首尾的S’,SS里面还会包含S。那为什么要毁掉首位的S’呢,因为这样会让SS中的两个S都不在成形,否则包含S也可能只是包含原来的S

必要性

下图证明来源于官方解答(我水平太低,证不出来/(ㄒoㄒ)/~~),我在图中做了一些批注,希望可以帮助大家理解
在这里插入图片描述

时间复杂度:O(m+n),因为使用contain方法的底层实现方法的复杂度是O(m+n),可以参考上面的KMP算法

/**
 * 双 ss 法
 * ss = s + s
 * 掐头去尾,再判断剩下的字符串是否包含 s
 *
 * @param s
 * @return
 */
public static boolean repeatedSubstringPattern(String s) {
    String ss = s + s;
    ss = ss.substring(1, ss.length() - 1);
    return ss.contains(s);
}

使用KMP的next数组

知识点1:为什么最小重复子串一定是原字符串的最长相等前后缀所不包含的部分

在这里插入图片描述

如上图所示:

  • 因为前缀k=后缀t,所以k[0]=t[0]k[1]=t[1]k[2]=t[2]
  • 又因为k[3]=t[0]k[4]=t[1]k[5]=t[2](很容易理解,他们对应于原字符串的相同位置),所以k[0:2]=k[3:5]=t[3:5]
  • k[0:2]=k[3:5]=t[3:5]=>s[0:2]=s[3:5]=s[6:8](还是将k和t分别对应到原字符串s的相同位置来理解),因此s可以看成由s[0:2]重复多次组成

从上面的推导只能证明s[0:2]是s的重复子串,那如何进一步证明s[0:2]就是s的最小重复子串?

这里使用反证法来证明:

  • 假设s[0:2]不是最小重复子串,那它一定可以拆分成更小的重复子串,如s[0:2]=aaa,更小的重复子串为a,那么s=aaaaaaaaa,很明显最长相等前后缀的长度就不是6了,而应该更长。因此s[0:2]只能是最小重复子串,前缀k和后缀t才是现在的形式

知识点2:怎么根据next数组判断一个字符串是否由子串重复而成

  • 条件1:如果一个字符串由子串重复而成,那其最长相等前后缀的长度一定是大于0的。假设s为子串,字符串为ss,那么最长相等前后缀长度一定大于等于s的长度,因为最少有前缀s和后缀s相同。
  • 条件2:从上面的证明可知,最小重复子串一定是原字符串的最长相等前后缀所不包含的部分,如果字符串由子串重复而成,那么字符串长度一定是子串长度的整数倍,因此s.length() % (s.length() - next[next.length - 1])一定等于0。

知识点3:为什么最长相等前后缀长度>0s.length() % (s.length() - next[next.length - 1])==0就能推出字符串由重复子串组成

  • 因为一旦满足这个条件,就可以参考知识点1推出类似s[0:2]=s[3:5]=s[6:8]的关系
/**
 * 使用KMP的next数组
 * @param s
 * @return
 */
public static boolean kmp(String s) {
    int[] next = getNext(s);
    // 如果最长相等前后缀长度>0 且 字符串长度 % (字符串长度 - 最长相等前后缀长度) == 0 则存在循环
    if (next[next.length - 1] > 0 && s.length() % (s.length() - next[next.length - 1]) == 0) {
        return true;
    }
    return false;
}

private static int[] getNext(String s) {
    int[] next = new int[s.length()];
    // j是前缀的末尾索引,i是后缀的末尾索引
    int i, j = 0;
    for (i = 1; i < s.length(); i++) {
        //--for-- 从next的第二个元素开始填充,next[0]为0,因为一个字符的最长相等前后缀为0
        while (j > 0 && s.charAt(i) != s.charAt(j)) {
            // 当后缀的末尾字符与前缀末尾字符不相等时,将j回退
            j = next[j - 1];
        }
        if (s.charAt(i) == s.charAt(j)) {
            //--if-- 前缀末尾字符和后缀末尾字符相等时,j++
            j++;
        }
        next[i] = j;
    }
    return next;
}

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

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

相关文章

【软考中级】网络工程师:10.组网技术(理论篇)

交换机基础 交换机分类 > 根据交换方式分      - 存储转发式交换&#xff08;Store and Forward&#xff09;完整接收数据帧&#xff0c;缓存、验证、碎片过滤&#xff0c;然后转发。   优点&#xff1a;可以提供差错校验和非对称交换。   缺点&#xff1a;延迟大。…

【Qt控件之QTabWidget】介绍及使用

描述 QTabWidget类提供了一个带有选项卡的小部件堆栈。 选项卡小部件提供了一个选项卡栏&#xff08;参见QTabBar&#xff09;和一个“页面区域”&#xff0c;用于显示与每个选项卡相关联的页面。默认情况下&#xff0c;选项卡栏显示在页面区域的上方&#xff0c;但可以使用…

2023年拼多多双11百亿补贴新增单件立减玩法介绍

2023年拼多多双11百亿补贴新增单件立减玩法介绍 拼多多启动了11.11大促活动&#xff0c;主题为“天天11.11&#xff0c;天天真低价”。消费者享受多重优惠&#xff0c;包括满减、百亿补贴和单件立减等。百亿补贴新增玩法&#xff0c;有超过20000款品牌商品参与单件立减活动。 …

【内网穿透】五分钟搭建全球领先开源ERP:Odoo,并实现公网访问

目录 前言 1. 下载安装Odoo&#xff1a; 2. 实现公网访问Odoo本地系统&#xff1a; 3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件&#xff0c;是一个一站式全功能ERP及电商平台。 开源性质&#xff1a;Odoo是一个开源的ERP软件&#xff0c;这意味着…

Vue3踩坑指南

vue.config.ts不起作用 关于项目 项目使用的还是vue-cli搭建的&#xff0c;底层还是webpack&#xff0c;没有使用新的vite搭建。 踩坑1&#xff1a;vue.config.ts不起作用 我本着既然是vue3 ts的项目&#xff0c;那么为了规范&#xff0c;项目中所有的js文件都得替换成ts文…

HanLP集成到Springboot及使用自定义词典

前言 HanLP集成到Springboot及使用自定义词典 文章目录 前言简介集成Springboot扩展使用自定义词典路径易错问题 简介 开源工具包&#xff0c;提供词法分析、句法分析、文本分析和情感分析等功能&#xff0c;具有功能完善、性能高效、架构清晰、语料时新、可自定义等特点。 官…

【小白专用 已验证】PHP连接SQLServer数据库

PHP是一门强大的服务器端脚本语言&#xff0c;而SQL Server是Microsoft开发的一款关系型数据库管理系统。为了在PHP中直接操纵SQL Server数据库&#xff0c;需要通过安装SQL Server扩展来实现。这篇文章将详细介绍如何在PHP中使用SQL Server扩展来操作数据库。 首先&#xff0…

那些你面试必须知道的webpack知识点

目录 1、webpack介绍和简单使用1.1 什么是webpack&#xff1f;1.2 安装webpack1.3 简单使用一下webpack 2、webpack的入口与输出2.1 入口(entry)2.2 输出(output) 3、入口多种配置方法3.1 多文件打包成一个文件3.2 多文件打包成多文件 4、loader的概念5、压缩打包HTML5.1 使用步…

DP基础相关笔记

基础 DP LIS LIS&#xff08;Longest Increasing Subsequence&#xff09;&#xff0c;顾名思义&#xff0c;就是最长上升子序列问题。 在这里我们要区分一下子串和子序列的区别&#xff0c;很简单&#xff0c;子串连续&#xff0c;子序列可以不连续。然而就在几小时之前本蒟…

分布式应用开发的核心技术系列之——基于TCP/IP的原始消息设计

本文由葡萄城技术团队原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 本文的内容主要围绕以下几个部分&#xff1a; TCP/IP的简单介绍。消息的介绍。基于消息分类的传输格式&…

嵌入式养成计划-46----QT--简易版网络聊天室实现

一百一十九、简易版网络聊天室实现 119.1 QT实现连接TCP协议 119.1.1 基于TCP的通信流程 119.1.2 QT中实现服务器过程 使用QTcpServer实例化一个服务器对象设置监听状态&#xff0c;通过listen()函数&#xff0c;可以监听特定的主机&#xff0c;也可以监听所有客户端&#x…

二维码智慧门牌管理系统升级解决方案:高效、便捷、安全的外业数据管理方法

文章目录 前言一、背景与需求二、升级解决方案三、方案优势 前言 在当今的信息化社会&#xff0c;数据管理的重要性日益凸显。尤其对于像二维码智慧门牌管理系统这样的复杂系统&#xff0c;如何实现高效、便捷、安全的数据管理&#xff0c;成为了系统升级的重要议题。本文将详…

计算机数据库中了malloxx勒索病毒怎么解决,勒索病毒解密,数据恢复

随着网络技术的不断发展&#xff0c;越来越多的网络安全威胁也不断增加&#xff0c;最近&#xff0c;云天数据恢复中心接到一些企业的求助&#xff0c;企业的计算机数据库遭到了malloxx勒索病毒攻击&#xff0c;导致企业所有计算机服务器无法正常使用&#xff0c;针对此次勒索病…

51单片机定时器和中断(03)

eg1&#xff1a;数码管如何显示出字符 51单片机40个引脚的功能需要记住** RXD&#xff1a;表示的是串行输入口INT0&#xff1a;外部中断0INT1&#xff1a;外部中断1TO : 外部中断0T1 &#xff1a;外部中断1WR: 外部输入存储器写RD: 外部输出存储器读XTK2/XTL1 单片机晶振的输…

微信公众号迁移详细步骤

公众号迁移有什么作用&#xff1f;只能变更主体吗&#xff1f;很多小伙伴想做公众号迁移&#xff0c;但是不知道公众号迁移有什么作用&#xff0c;今天跟大家具体讲解一下。首先公众号迁移最主要的就是修改公众号的主体了&#xff0c;比如我们公众号原来是A公司的&#xff0c;现…

Ubuntu 22.04 中安装 fcitx5

Ubuntu 22.04 中安装 fcitx5 可以按照以下步骤进行&#xff1a; 添加 fcitx5 的 PPA 首先&#xff0c;添加 fcitx5 的官方 PPA&#xff1a; sudo add-apt-repository ppa:fcitx-team/fcitx5更新软件包列表 sudo apt update安装 fcitx5 sudo apt install fcitx5 fcitx5-conf…

基于SSM的文化培训学校网站的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

030-第三代软件开发-密码输入框

第三代软件开发-密码输入框 文章目录 第三代软件开发-密码输入框项目介绍密码输入框总结一下 关键字&#xff1a; Qt、 Qml、 echoMode、 TextInput、 Image 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Meta-Object Language…

【Qt控件之QMdiArea】介绍及使用

描述 QMdiArea小部件提供了一个区域&#xff0c;用于显示MDI窗口。QMdiArea的功能类似于MDI窗口的窗口管理器。例如&#xff0c;它在自身上绘制和排列管理的窗口&#xff0c;可以按级联或平铺模式排列它们。通常&#xff0c;QMdiArea被用作QMainWindow的中心小部件&#xff0c…

YOLOv5算法改进(17)— 手把手教你去更换损失函数(IoU/GIoU/DIoU/CIoU/EIoU/AlphaIoU/SIoU)

前言:Hello大家好,我是小哥谈。损失函数(loss function)是机器学习中用来衡量模型预测值与真实值之间差异的函数。它用于度量模型在训练过程中的性能,以便优化模型参数。在训练过程中,损失函数会根据模型的预测结果和真实标签计算出一个标量值,代表了模型预测的错误程度…