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

news2024/11/16 10:50:02

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());
			else
				ans.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.coloradmin.cn/o/2154365.html

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

相关文章

【小沐学GIS】blender导入OpenStreetMap城市建筑(blender-osm、blosm)

文章目录 1、简介1.1 blender1.2 osm地图 2、OpenStreetMap下载方式2.1 Simple2.2 Overpass API2.3 OSM星球2.4 Geofabrik下载2.5 其他方法2.6 BBBike 3、blender-osm插件3.1 简介3.2 操作 结语 1、简介 1.1 blender https://www.blender.org/ Blender 是一款免费的开源 3D …

机器学习周报第五十七周 GATRes

文章目录 week56 HA-GNN摘要Abstract一、大数据相关1. 单机zookeeper2. 与hbase对接的技术2.1 使用hive操作hbase——直接与hbase对接&#xff08;非接口技术&#xff09;使用 pyhive 操作 Hive 2.2 mysql操作hbase&#xff08;非接口技术&#xff09;导出 HBase 数据到 MySQL导…

【技术调研】三维(7)-Unity基础笔记

安装 ​ 最好使用长期维护版本。 创建项目 ​ 略 窗口布局 Hierarchy:层级面板,展示当前打开的场景里面有哪些物体。 Scene:场景面板,显示当前场景的样子 Game:游戏面板,场景运行的时候的样子 Inspector:检视面板(或属性面板),查看一个游戏物体由哪些组件组成。 …

有关shell指令练习2

写一个shell脚本&#xff0c;将以下内容放到脚本中 在家目录下创建目录文件&#xff0c;dir dir下创建dir1和dir2 把当前目录下的所有文件拷贝到dir1中&#xff0c; 把当前目录下的所有脚本文件拷贝到dir2中 把dir2打包并压缩为dir2.tar.xz 再把dir2.tar.xz移动到dir1中 …

Golang | Leetcode Golang题解之第423题从英文中重建数字

题目&#xff1a; 题解&#xff1a; func originalDigits(s string) string {c : map[rune]int{}for _, ch : range s {c[ch]}cnt : [10]int{}cnt[0] c[z]cnt[2] c[w]cnt[4] c[u]cnt[6] c[x]cnt[8] c[g]cnt[3] c[h] - cnt[8]cnt[5] c[f] - cnt[4]cnt[7] c[s] - cnt[6]…

uni-app页面调用接口和路由(四)

文章目录 一、路由二、页面调用接口二、路由跳转1.uni.navigateTo(OBJECT)2.uni.redirectTo(OBJECT)3.uni.reLaunch(OBJECT)4.uni.switchTab(OBJECT)5.uni.navigateBack(OBJECT) 总结 一、路由 路由配置 uni-app页面路由为框架统一管理&#xff0c;开发者需要在pages.json里配…

VBA技术资料MF199:屏蔽单元格右键菜单复制剪切按钮

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

Matlab simulink建模与仿真 第十九章(生成C代码)

一、Configuration Parameters模型参数配置 1、仿真时间 &#xff08;1&#xff09;在Solver选项卡中可以设置仿真的起始时间和结束时间&#xff0c;一般起始时间设为0&#xff0c;而结束时间按需设置。 &#xff08;2&#xff09;如果希望仿真不会自动暂停&#xff08;也就…

通过springcloud gateway优雅的进行springcloud oauth2认证和权限控制

代码地址 如果对你有帮助请给个start&#xff0c;本项目会持续更新&#xff0c;目标是做一个可用的快速微服务开发平台&#xff0c;成为接私活&#xff0c;毕设的开发神器&#xff0c; 欢迎大神们多提意见和建议 使用的都是spring官方最新的版本&#xff0c;版本如下&#xff1…

通过UV快速计算品牌独立站网络流量

背景&#xff1a; 品牌独立站项目交付过程中&#xff0c;我们需要为客户提供“云资源” 成本报价&#xff0c;其中“计算资源” 及CPU、内存、存储 参数相对固定&#xff0c;而互联网网络成本需要进行评估报价&#xff0c;以海外TOP云平台 AWS、AZURE、GCP 为例都是以“不限带…

专业的屏幕录像和视频编辑的软件Camtasia 2024安装激活图文教程

‌Camtasia 2024是一款专业的屏幕录像和视频编辑的软件套装。它由TechSmith公司开发‌&#xff0c;提供了强大的屏幕录像、视频剪辑和编辑、视频菜单制作、视频剧场、视频播放等功能。 Camtasia Studio 2024是该软件套装的核心部分&#xff0c;支持在PC和Mac平台上运行&#xf…

SpringBoot3核心特性-核心原理

目录 传送门前言一、事件和监听器1、生命周期监听2、事件触发时机 二、自动配置原理1、入门理解1.1、自动配置流程1.2、SPI机制1.3、功能开关 2、进阶理解2.1、 SpringBootApplication2.2、 完整启动加载流程 三、自定义starter1、业务代码2、基本抽取3、使用EnableXxx机制4、完…

针对 Linux SSH 服务器的新攻击:Supershell 恶意软件危害易受攻击的系统

ASEC 研究人员发现了针对保护不善的 Linux SSH 服务器的新攻击。 在其中&#xff0c;黑客使用了用Go编写的 Supershell恶意软件。 该后门使攻击者能够远程控制受感染的系统。 初次感染后&#xff0c;黑客启动扫描仪来寻找其他易受攻击的目标。 据信这些攻击是使用从已受感…

构建高可用和高防御力的云服务架构:从DDoS高防到PolarDB

引言 随着互联网技术的飞速发展&#xff0c;网络环境已经成为我们日常生活和商业活动中不可或缺的一部分。然而&#xff0c;这种依赖也带来了新的挑战&#xff0c;尤其是在网络安全领域。其中&#xff0c;分布式拒绝服务&#xff08;DDoS&#xff09;攻击因其破坏性强、难以防…

OpenStack Yoga版安装笔记(十三)neutron安装

1、官方文档 OpenStack Installation Guidehttps://docs.openstack.org/install-guide/ 本次安装是在Ubuntu 22.04上进行&#xff0c;基本按照OpenStack Installation Guide顺序执行&#xff0c;主要内容包括&#xff1a; 环境安装 &#xff08;已完成&#xff09;OpenStack…

VS2022中的调试

目录 前言&#xff1a; 使用&#xff1a; 调试&#xff1a; 前言&#xff1a; 在日益繁忙的工作环境中&#xff0c;选择合适的编程工具已成为提升开发者工作效率的关键。不同的工具能够帮助我们简化代码编写、自动化任务、提升调试速度&#xff0c;甚至让团队协作更加顺畅。…

【Linux学习】基本指令其一

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

关于有源蜂鸣器及无源蜂鸣器的区别及驱动各类单片机案例

关于有源蜂鸣器及无源蜂鸣器的区别及驱动各类单片机案例 有源蜂鸣器与无源蜂鸣器区别有源蜂鸣器无源蜂鸣器模块化有源蜂鸣器及无源蜂鸣器驱动方式的说明 有源、无源蜂鸣器代码驱动总结 有源蜂鸣器与无源蜂鸣器区别 有源蜂鸣器与无源蜂鸣器区别在于是否有振荡源。 有源蜂鸣器即…

Redis——redispluspls库通用命令以及String类型相关接口使用

文章目录 通用命令get&#xff0c;setkeys插入迭代器 expire和ttltype string 类型接口set和getset NX和XXmset 和 mgetgetrange 和 setrangeincr 和 decr 通用命令 get&#xff0c;set void get_set_test(sw::redis::Redis& redis){//bool set(const sw::redis::StringV…

Hadoop的安装

文章目录 一. 到Hadoop官网下载安装文件hadoop-3.4.0.tar.gz。二. 环境变量三. 配置 一. 到Hadoop官网下载安装文件hadoop-3.4.0.tar.gz。 随后点击下载即可 由于Hadoop不直接支持Windows系统&#xff0c;因此&#xff0c;需要修改一些配置才能运行 二. 环境变量 三. 配置 进…