13.面试算法-字符串常见算法题(二)

1. 字符串反转专题

我们知道反转是链表的一个重要考点,反转同样是字符串的重要问题。常见问题也就是在LeetCode中列举的相关题目:

【1】LeetCode344. 反转字符串:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
【2】LeetCode541. K个一组反转:给定一个字符串 s 和一个整数 k ,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
【3】LeetCode.917. 仅仅反转字母:给定一个字符串 S ,返回 “反转后的” 字符串,其中不是字母的字符都保留在原地,而所有字母的位置发生反转。
【4】LeetCode151. 反转字符串里的单词:给你一个字符串 s ,逐个反转字符串中的所有单词。
【5】LeetCode.557. 反转字符串中的单词 III:给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
这几个题目你是否发现前三道就是要么反转字符,要么反转里面的单词。针对字符的反转又可以变换条件造出多问题。我们就从基本问题出发,各个击破。

1.1 LeetCode344. 反转字符串

题目要求

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例1:
输入:s = [“h”,“e”,“l”,“l”,“o”]
输出: [“o”,“l”,“l”,“e”,“h”]
示例2:
输入:s = [“H”,“a”,“n”,“n”,“a”,“h”]
输出: [“h”,“a”,“n”,“n”,“a”,“H”]

这是最基本的反转题,也是最简单的问题,使用双指针方法最直接。具体做法是:

对于长度为 N 的待被反转的字符数组,我们可以观察反转前后下标的变化,假设反转前字符数组为 s[0] s[1] s[2] … s[N - 1],那么反转后字符数组为 s[N - 1] s[N - 2] … s[0]。比较反转前后下标变化很容易得出 s[i] 的字符与 s[N - 1 - i] 的字符发生了交换的规律,因此我们可以得出如下双指针的解法:

  • 将 left 指向字符数组首元素, right 指向字符数组尾元素。
  • 当 left < right:
    • 交换 s[left] 和 s[right];
    • left 指针右移一位,即 left = left + 1;
    • right 指针左移一位,即 right = right - 1 。
  • 当 left >= right ,反转结束,返回字符数组即可。
class Solution {public void reverseString(char[] s) {  if (s == null || s.length() == 0) {return s;}int n = s.length;for (int left = 0, right = n - 1; left < right; ++left, --right) {char tmp = s[left];s[left] = s[right];s[right] = tmp;}}
}

这里使用for循环貌似条件过于复杂了,我们使用while也可以:

class Solution {public void reverseString(char[] s) {//采用双指针反转即可,左右指针元素互换,然后慢慢靠近 int left=0,right=s.length-1;while(left<=right){char tmp=s[left];s[left]=s[right];s[right]=tmp;left++;right--;}}
}

1.2 LeetCode541. K个一组反转

这个题,我感觉有点没事找事,先看一下要求:

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前k 个字符,其余字符保持原样。

示例1:
输入:s = “abcdefg”, k = 2
输出: “bacdfeg”
示例2:
输入:s = “abcd”, k = 2
输出: “bacd”

我们直接按题意进行模拟就可以:反转每个下标从 2k的倍数开始的,长度为 k的子串。若该子串长度不足 k,则反转整个子串。

class Solution {public String reverseStr(String s, int k) { if (s == null || s.length() == 0) {return s;}int n = s.length();char[] arr = s.toCharArray();for (int i = 0; i < n; i += 2 * k) {reverse(arr, i, Math.min(i + k, n) - 1);}return new String(arr);}public void reverse(char[] arr, int left, int right) {while (left < right) {char temp = arr[left];arr[left] = arr[right];arr[right] = temp;left++;right--;}}
}

1.3 LeetCode.917. 仅仅反转字母

这个题有点难度,我们来看一下:

给定一个字符串 S ,返回 “反转后的” 字符串,其中不是字母的字符都保留在原地,而所有字母的位置发生反转。

示例 1:
输入:s = “ab-cd”
输出:“dc-ba”
示例 2:
输入:s = “a-bC-dEf-ghIj”
输出:“j-Ih-gfE-dCba”
示例 3:
输入:s = “Test1ng-Leet=code-Q!”
输出:“Qedo1ct-eeLg=ntse-T!”

这里第一眼感觉不是特别复杂,同样从两头向中间即可,但问题是"-"不是均匀的有些划分的段长,有的短,这就增加了处理的难度。

方法1:使用栈

将 s 中的所有字母单独存入栈中,所以出栈等价于对字母反序操作。(或者,可以用数组存储字母并反序数组。) 然后,遍历 s 的所有字符,如果是字母我们就选择栈顶元素输出。

class Solution {public String reverseOnlyLetters(String S) {Stack<Character> letters = new Stack();for (char c: S.toCharArray())if (Character.isLetter(c))letters.push(c);StringBuilder ans = new StringBuilder();for (char c: S.toCharArray()) {if (Character.isLetter(c))ans.append(letters.pop());elseans.append(c);}return ans.toString();}
}

方法2:拓展 双转指针

一个接一个输出 s 的所有字符。当遇到一个字母时,我们希望找到逆序遍历字符串的下一个字母。 所以我们这么做:维护一个指针 j从后往前遍历字符串,当需要字母时就使用它。

class Solution {public String reverseOnlyLetters(String S) { if (S == null || S.length() == 0) {return S;}StringBuilder ans = new StringBuilder();int j = S.length() - 1;for (int i = 0; i < S.length(); ++i) {if (Character.isLetter(S.charAt(i))) {while (!Character.isLetter(S.charAt(j)))j--;ans.append(S.charAt(j--));} else {ans.append(S.charAt(i));}}return ans.toString();}
}

1.4 LeetCode151. 反转字符串里的单词

题目要求

给你一个字符串 s ,逐个反转字符串中的所有单词 。
单词是由非空格字符组成的字符串。 s 中使用至少一个空格将字符串中的单词分隔开。
请你返回一个反转 s 中单词顺序并用单个空格相连的字符串。

说明:

  • 输入字符串 s 可以在前面、后面或者单词间包含多余的空格。
  • 反转后单词间应当仅用一个空格分隔。
  • 反转后的字符串中不应包含额外的空格。

示例 1:
输入:s = “the sky is blue”
输出:“blue is sky the”
示例 2:
输入:s = " hello world "
输出:“world hello”
解释:反转后的字符串中不能存在前导空格和尾随空格。
示例 3:
输入:s = “a good example”
输出:“example good a”
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

这个题也经常出现在很多面试题中,我记得曾经见过有个题是这样出的,要你按照同样的方式反转“ I love youzan”。

这个题的关键在于如何处理单词。很多语言提供了相关的特性,因此我们可以首先使用语言的特性来实现:

很多语言对字符串提供了split(拆分),reverse(反转)和 join(连接)等方法,因此我们可以简单的调用内置的API 完成操作:

  • 使用 split 将字符串按空格分割成字符串数组;
  • 使用 reverse 将字符串数组进行反转;
  • 使用 join 方法将字符串数组拼成一个字符串。

如图:
在这里插入图片描述

class Solution {public String reverseWords(String s) {  if (s == null || s.length() == 0) {return s; }// 除去开头和末尾的空白字符 s = s.trim();// 正则匹配连续的空白字符作为分隔符分割List<String> wordList = Arrays.asList(s.split("\\s+"));Collections.reverse(wordList);return String.join(" ", wordList);}
}

如果我们要自行编写实现函数,对于字符串不可变的语言,例如java中的String,首先得把字符串转化成其他可变的数据结构,同时还需要在转化的过程中去除空格。

第二种解法

对于字符串可变的语言,就不需要再额外开辟空间了,直接在字符串上原地实现。在这种情况下,反转字符和去除空格可以一起完成。
在这里插入图片描述
实现方法:

class Solution {public String reverseWords(String s) { if (s == null || s.length() == 0) {return s;}StringBuilder sb = trimSpaces(s); // 反转字符串reverse(sb, 0, sb.length() - 1);// 反转每个单词reverseEachWord(sb);  return sb.toString();}public StringBuilder trimSpaces(String s) { int left = 0, right = s.length() - 1;// 去掉字符串开头的空白字符while (left <= right && s.charAt(left) == ' ') {++left;}// 去掉字符串末尾的空白字符while (left <= right && s.charAt(right) == ' ') {--right;}// 将字符串间多余的空白字符去除StringBuilder sb = new StringBuilder();while (left <= right) {char c = s.charAt(left);if (c != ' ') {sb.append(c);} else if (sb.charAt(sb.length() - 1) != ' ') {sb.append(c);}++left;}return sb;}public void reverse(StringBuilder sb, int left, int right) {while (left < right) {char tmp = sb.charAt(left);sb.setCharAt(left++, sb.charAt(right));sb.setCharAt(right--, tmp);}}public void reverseEachWord(StringBuilder sb) {int n = sb.length();int start = 0, end = 0;while (start < n) {// 循环至单词的末尾while (end < n && sb.charAt(end) != ' ') {++end;}// 反转单词reverse(sb, start, end - 1); // 更新start,去找下一个单词start = end + 1;++end;}}
}

另外本题还可以使用双端队列来解决。由于双端队列支持从队列头部插入的方法,因此我们可以沿着字符串一个一个单词处理,然后将单词压入队列的头部,再将队列转成字符串即可。
在这里插入图片描述

class Solution {public String reverseWords(String s) {int left = 0, right = s.length() - 1; // 去掉字符串开头的空白字符while (left <= right && s.charAt(left) == ' ') {++left;}// 去掉字符串末尾的空白字符while (left <= right && s.charAt(right) == ' ') {--right;}Deque<String> d = new ArrayDeque<String>();StringBuilder word = new StringBuilder();while (left <= right) {char c = s.charAt(left);if ((word.length() != 0) && (c == ' ')) { // 将单词  push 到队列的头部d.offerFirst(word.toString());word.setLength(0);} else if (c != ' ') {word.append(c);}++left;}d.offerFirst(word.toString());return String.join(" ", d);}
}

1.5 LeetCode557. 反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例 1:
输入:s = “Let’s take LeetCode contest”
输出:“s’teL ekat edoCteeL tsetnoc”
示例 2:
输入: s = “Mr Ding”
输出:“rM gniD”

提示

  • 在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。

上一个题是将单词本身不变,单词之间的关系反转。而本题就是单词反转,单词之间的关系不变。

分析

我们可以使用额外的空间来执行,开辟一个新字符串。然后从头到尾遍历原字符串,直到找到空格为止,此时找到了一个单词,并能得到单词的起止位置。随后,根据单词的起止位置,可以将该单词逆序放到新字符串当中。如此循环多次,直到遍历完原字符串,就能得到反转后的结果。

class Solution {public String reverseWords(String s) { if (s == null || s.length() == 0) {return s;}StringBuffer ret = new StringBuffer();int length = s.length();int i = 0;while (i < length) {int start = i;while (i < length && s.charAt(i) != ' ') {i++;}for (int p = start; p < i; p++) {ret.append(s.charAt(start + i - 1 - p));}while (i < length && s.charAt(i) == ' ') {i++;ret.append(' ');}}return ret.toString();}
}

此题也可以直接在原字符串上进行操作,避免额外的空间开销。当找到一个单词的时候,我们交换字符串第一个字符与倒数第一个字符,随后交换第二个字符与倒数第二个字符……如此反复,就可以在原空间上反转单词。

需要注意的是,原地解法在某些语言(比如 Java,JavaScript)中不适用,因为在这些语言中 String 类型是一个不可变的类型,需要先转换。在写转换的时候有一个更大的问题经常会被忽略,下面这段代码是有问题的,执行之后会发现无法完成反转,你能找到问题在哪里吗?

class Solution {public String reverseWordsError(String s) {int length = s.length();char[] charArray = s.toCharArray();int i = 0;while (i < length) {int start = i;while (i < length && charArray[i] != ' ') {i++;}int left = start, right = i - 1;while (left < right) {swap(charArray[left], charArray[right]);left++;right--;}while (i < length && charArray[i] == ' ') {i++;}}return String.valueOf(charArray);}public void swap(char a ,char b){char c=a;a=b;b=c;}
}

这里的问题在于swap方法只是在局部反转了a和b,而并没有调整数组charArray中的元素,那该怎么写呢?这个留给读者好好思考。

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

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

相关文章

【多维动态规划】64. 最小路径和(面试真题+面试官调整后的题目)

64. 最小路径和 难度&#xff1a;中等 力扣地址&#xff1a;https://leetcode.cn/problems/minimum-path-sum/description/ 1. 原题以及解法 1.1 题目 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和…

Redis——redispluspls库list及set类型相关接口使用

文章目录 list 类型相关接口lpush和lrangerpushlpop和rpopblpop和brpopllen set 类型相关接口sadd和smemberssismemberscardspopsinstersinterstore list 类型相关接口 lpush和lrange void lrange_lpush(sw::redis::Redis& redis){std::cout<<"lpush 和 lrang…

Windows控制台中文乱码怎么解决?(nes,一些exe窗口程序)

当我们打开一些Window窗口程序出现中文乱码时&#xff0c;可以像这样设置一下&#xff01; 1、打开 设置-->时间和语言-->语言和区域 2、 管理语言设置 3、更改系统区域设置 4、取消勾选 Beta版&#xff1a;UTF-8 5、效果演示 这下中文不乱码了&#xff01;

多维系统下单点登录之生产实践(2种方案3种实践)

1、基于 Cookie 跨域与分布式 Session 的技术实践 1、XXL-SSO 整体价格 2、实现原理剖析 首次请求 第二次请求 跨域请求 注销流程 3、案例演示 首次登陆跳转至统一认证中心 访问&#xff1a;http://xxlssoclient1.com:8081/ 登陆成功&#xff0c;写入 Cookie&#…

MySQL record 06 part

事务、存储过程 事务&#xff1a; MySQL的同步&#xff0c;同步是指 together done&#xff0c;要么一起前进&#xff0c;要么一起后退的意思。 注意&#xff0c;回滚 rollback 对已经提交 commit 的数据是无效的&#xff0c;也就是说&#xff0c;只能对没有被提交 commit …

【iOS】KVC的学习

【iOS】KVC的学习 文章目录 【iOS】KVC的学习前言KVC定义KVC设值KVC取值KVC使用keyPathKVC处理异常处理nil异常 KVC的一些应用修改动态的设置值实现高阶的消息传递 小结 前言 笔者简单学习了有关与KVC的相关内容&#xff0c;这里写一篇博客简单介绍一下相关内容。 KVC 定义 KV…

saas收银系统源码

1. 线下门店多样化收银 ①门店有社区小店、也会有大店&#xff0c;甚至还会有夫妻店&#xff0c;同时还要有Windows版和安卓版&#xff0c;需满足不同门店的收银需求。 ②支持Windows收银、安卓收银、无人自助收银、聚合码收银等&#xff0c;支持ai智能称重、收银称重一体机等…

『功能项目』QFrameWorkBug拖拽功能【66】

我们打开上一篇65QFrameWork道具栏物品生成的项目&#xff0c; 本章要做的事情是实现物品的拖拽功能 修改脚本&#xff1a;UISlot.cs 实现接口后编写脚本&#xff1a; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace QFramework {publi…

Netty+HTML5+Canvas 网络画画板实时在线画画

采用Html5的canvas做前端画画板&#xff0c;发送数据到后端Netty服务&#xff0c;实时转发笔迹数据&#xff0c;在线实时同步画笔轨迹&#xff0c;单击绿色小方块&#xff0c;保存画板的图片 页面&#xff1a; <!-- index.html --><!DOCTYPE html> <html> …

[Linux]:信号(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. 信号的阻塞 1.1 基本概念 信号被操作系统发送给进程之后&#xff0c;进程…

机器学习05-聚类算法(python)SC(轮廓系数)详解

# 导入必要的库 from sklearn.cluster import KMeans # 导入 KMeans 聚类算法 import matplotlib.pyplot as plt # 导入 matplotlib 用于绘图 from sklearn.datasets import make_blobs # 导入 make_blobs 用于生成模拟数据 from sklearn.metrics import silhouette_score …

react:组件通信

组件通信 父组件向子组件通信 function App() {return (<div><div>这是父组件</div><Child name"这是子组件" /></div>); }// 子组件 function Child(props) {return <div>{props.name}</div>; }props说明 props可以传…

浅谈计算机视觉的学习路径1

计算机视觉&#xff08;Computer Vision, CV&#xff09;是人工智能领域的一个重要分支&#xff0c;它的目标是使计算机能够像人类一样理解和处理图像和视频数据。 面向想要从事该方向的大学生&#xff0c;笔者这里给出以下是关于计算机视觉的学习路径建议&#xff1a; 简要了解…

Linux开发工具(git、gdb/cgdb)--详解

目录 一、Linux 开发工具分布式版本控制软件 git1、背景2、使用 git&#xff08;1&#xff09;预备工作——安装 git&#xff1a;&#xff08;2&#xff09;克隆远程仓库到本地&#xff08;3&#xff09;把需要提交的代码拷贝到本地仓库&#xff08;4&#xff09;提交本地仓库文…

一种新的电子邮件攻击方式:AiTM

新的攻击组利用合作伙伴组织之间的信任关系来绕过多重身份验证。 一种新的攻击方式开始出现&#xff0c;它利用合作伙伴组织之间的信任关系绕过多重身份验证。在一个利用不同组织之间关系的攻击中&#xff0c;攻击者成功地对四家或更多组织进行了商业电子邮件欺诈(BEC)攻击&…

VM-Ubantu中使用vscode头文件报错——解决办法

问题 系统中头文件明明存在但是却报错 解决方法 在报错的文件中点击&#xff0c;shift ctrl p选择Edit Configurations(JSON) 修改文件内容 原文件内容 修改之后的内容 {"configurations": [{"name": "Linux","includePath":…

计算机毕业设计推荐-基于python大数据的个性化图书数据可视化分析

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、个性化图书数据可视化分析-项…

828华为云征文 | 云服务器Flexus X实例:开源项目 LangChain 部署,实例测试

目录 一、LangChain 介绍 二、部署 LangChain 2.1 安装 langchain 2.2 安装 langchain_community 2.3 安装 qianfan 三、实例运行 3.1 Chat Models 3.2 LLMs 3.3 Embedding Models 四、总结 本篇文章主要通过 Flexus云服务器X实例 部署开源项目 LangChain&#xff0c…

【每日一题】LeetCode 2374.边积分最高节点(图、哈希表)

【每日一题】LeetCode 2374.边积分最高节点&#xff08;图、哈希表&#xff09; 题目描述 给定一个有向图&#xff0c;图中包含 n 个节点&#xff0c;节点编号从 0 到 n - 1。每个节点都有一个出边&#xff0c;指向图中的另一个节点。图由一个长度为 n 的整数数组 edges 表示…

【Linux学习】基本指令其一

命令行界面 命令行终端是一个用户界面&#xff0c;允许用户通过输入文本命令与计算机系统进行交互。 比如Windows下&#xff0c; 键入winR&#xff0c;然后输入cmd&#xff0c;就可以输入文本指令与操作系统交互了。 Windows有另一个命令行界面Powershell,它的功能比cmd更强大…