代码随想录算法训练营第8天 | 344.反转字符串 541. 反转字符串II 卡码网.替换数字 151.翻转字符串里的单词 卡码网.右旋字符串

反转字符串

代码随想录算法训练营第8天 | 344.反转字符串 541. 反转字符串II 卡码网.替换数字 151.翻转字符串里的单词 卡码网.右旋字符串_第1张图片
C++ 中直接用reverse库函数就可以解决问题。但是需要注意的是,在leetcode面试时,遇到直接就能用库函数解决的问题不要用库函数;如果库函数只是解题步骤中的一小步,而且对库函数的内部实现原理比较熟悉时,可以使用

class Solution{
public:
	void reverseString(vector<char>& s){
		for(int i = 0, j = s.size() - 1;i < s.size()/2; i++, j--){
			swap(s[i], s[j]);
		}
	} 
};

注意这里的swap是库函数,而且交换的实现也很简单,直接交换也可,位运算下通过连续的异或也可。

s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];

反转字符串II

代码随想录算法训练营第8天 | 344.反转字符串 541. 反转字符串II 卡码网.替换数字 151.翻转字符串里的单词 卡码网.右旋字符串_第2张图片
花式反转,但仍然可以用双指针来解决。我们已经在上一题实现了反转字符串的函数,这里可以用,也可以直接用库函数。分析题意,是每到第 2k 个数时,反转前 k 个,因此我们可以直接一次 2k 步地向前遍历,不必一个一个地遍历,代码会简洁很多。
所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。

class Solution{
public:
	// **** 注意这里要用指针,保证会改变传入的字符串,而不是只改变形参 ****
	void reverse(string& s, int begin, int end){
		int len = end - begin + 1;
		for(int i = begin, j = end; i < len / 2; i++, j--){
			swap(s[i], s[j]);
		}
	}
	string reverseStr(string s, int k){
		// i一次前进2k,每一次都是字符串新起点
		for(int i = 0; i < s.size(); i += (2 * k)){
			// [i,i+k)左闭右开区间内的字符串,一共k个字符
			if(i + k <= s.size()){
				reverse(s, i, i + k - 1);
			}
			else{
				reverse(s, i, s.size() - 1);
			}
		}
		return s;
	} 
};
class Solution{
public:
	string reverseStr(string s, int k){
		for(int i = 0; i < s.size(); i += (2 * k)){
			if(i + k <= s.size()){
				reverse(s.begin() + i, s.begin() + i + k);
			}
			else{
				reverse(s.begin() + i, s.end());
			}
		}
		return s;
	}
};
class Solution{
public:
	string reverseStr(string s, int k){
		int pos = 0;
		while(pos < s.size()){
			if(pos + k <= s.size()) 
			reverse(s.begin() + pos, s.begin() + pos + k);
			else reverse(s.begin() + pos, s.end());
			pos += (2 * k);
		}
		return s;
	}
};

替换数字

代码随想录算法训练营第8天 | 344.反转字符串 541. 反转字符串II 卡码网.替换数字 151.翻转字符串里的单词 卡码网.右旋字符串_第3张图片
其实用暴力解法是可以做的,我们知道新输出字符串的长度,遇到数字就将后面的字符向后移动,再写入number,但是这样做时间复杂度太高 O(n^2),可以采用双指针换个方向遍历来解决问题,太妙了!!!

#include 
using namespace std;
int main(){
	string s;
	while(cin >> s){
		int count = 0;
		int Oldlen = s.size();
		for(int i = 0; i < Oldlen; i++){
			if(s[i] <= '9' && s[i] >= '0'){
				count++;
			}
		}
		s.resize(Oldlen + count * 5);
		int Newlen = s.size();
		for(int i = Oldlen - 1, j = Newlen - 1; i >= 0; i--, j--){
			if(s[i] > '9' || s[i] < '0'){
				s[j] = s[i];
			}
			else{
				s[j] = 'r';
				s[j - 1] = 'e';
				s[j - 2] = 'b';
				s[j - 3] = 'm';
				s[j - 4] = 'u';
				s[j - 5] = 'n';
				j -= 5;
			}
		}
		cout << s << endl;
	}
}

字符串与数组的区别,在 python 中字符串是不可变类型,如果要修改字符串,需要将字符串转化为list,然后''.join()。在 C++ 中当字符串存入数组时,必须以 \0 来标志结束。而 string 类型则提供了其他接口来获得字符串信息和操作字符串,其实基本操作上 vectorstring差不多,只是 string 提供了更多字符串处理的接口。

翻转字符串里的单词

代码随想录算法训练营第8天 | 344.反转字符串 541. 反转字符串II 卡码网.替换数字 151.翻转字符串里的单词 卡码网.右旋字符串_第4张图片
分析问题:其实有split这样的库函数,定义一个新字符串,然后通过split分出单词,将单词逆序放进新字符串中。这里我们追求不使用辅助空间,可以将这个问题分解为:

  1. 去除字符串中的多余空格
  2. 翻转整个字符串
  3. 分别翻转每一个单词
    翻转我们之前已经实现过了,不难,主要在于实现去除多余空格。比较直接的就是这样,遇到重的空格就erase
void removeExtraSpaces(string& s){
	// 删除字符串中重复出现的空格
	for(int i = s.size() - 1; i > 0; i--){
		if(s[i] == s[i - 1] && s[i] == ' '){
			s.erase(s.begin() + i);
		}
	}
	// 删除开头的空格
	if(s[0] == ' ')  s.erase(s.begin());
	if(s[s.size() - 1] == ' ')  s.erase(s.begin() + s.size() - 1);
}

这种写法看上去是 O(n),但其实erase函数是 O(n) 的(移除元素),这个算法其实是 O(n^2) 的。而且这种erase的写法要注意移除一个元素之后索引下标会有变化,所以不能从前向后移除,那样下标就乱了。直接采用移除元素中的双指针法进行这一步的处理,时间复杂度 O(n)。

class Solution{
public:
	void reverse(string& s, int start, int end){
		for(int i = start, j = end; i < j; i++, j--){
			swap(s[i], s[j]);
		}
	}
	void removeExtraSpaces(string& s){
		int fast = 0, slow = 0;
		for(; fast < s.size(); fast++){
			if(s[fast] != ' '){
				if(slow != 0){
					s[slow++] = ' ';
				}
				while(fast < s.size() && s[fast] != ' ') s[slow++] = s[fast++];
			}
		}
		s.resize(slow);
	}
	string reverseWords(string s){
		removeExtraSpaces(s);
		reverse(s, 0, s.size() - 1);
		int start = 0;
		for(int i = 0; i <= s.size(); i++){
			if(i == s.size() || s[i] == ' '){
				reverse(s, start, i - 1);
				start = i + 1;
			}
		}
		return s;
	}
};

右旋字符串

先贴一份自己的,每次将最前面未确定的字符与后 k 个字符做交换,最终实现整体的移动。

#include 
#include 
using namespace std;
int main(){
	string s;
	int k = 0;
	cin >> k;
	cin >> s
	int start = 0;
	int len = s.size() - k;  // 当前剩余需要交换的字符数目
	while(len > 0){
		for(int i = start, j = s.size() - k; i < start + k; i++, j++){
			swap(s[i], s[j]);
		}
		len -= k;
		start += k;
	}
	cout << s << endl;
}

题解中给的方法是从今天翻转字符串的题目中发散而来的,我们可以把问题转换成这样:一个字符串分为 len-n 和 n 两段,将后面那段放到前面去。这就和刚刚的翻转字符串中的单词很像了,都是都是翻转一段字符的顺序:先翻转整个字符串,再分别翻转两段字符串。

#include 
#include 
using namespace std;
int main(){
	string s;
	int k = 0;
	cin >> k;
	cin >> s;
	int len = s.size();
	reverse(s.begin(), s.end());
	reverse(s.begin(), s.begin() + k);
	reverse(s.begin() + k, s.end());
	cout << s << endl;
}

反转的顺序也可以很灵活,先翻转局部,再翻转整体也可,但需要注意区间的起止点

你可能感兴趣的:(算法)