Solutions to Chapter 1 | Arrays and Strings

本文提供了一系列字符串操作算法的实现,包括判断字符串中字符是否唯一、反转C风格字符串、去除字符串中的重复字符、判断两个字符串是否互为字谜、替换字符串中的空格、旋转图像矩阵、标记矩阵中的零行零列以及检测旋转字符串。

1.1 

Implement an algorithm to determine if a string has all unique characters. What if you can not use additional data structures?

译文:

实现一个算法来判断一个字符串中的字符是否唯一(即没有重复).不能使用额外的数据结构。 (即只使用基本的数据结构)

解答

         面试的时候遇到这种题,第一反应就是要去除模糊性,可以通过提问的方式:

                     1 请问,构成字符串的字符集多大?ASCII还是只有字母和数字组成?

我们假设是ASCII码,那么ASCII码除去128 - 255 扩展的特殊字符,我们假设只有0-127


第一种方法:标记数组法


用  bool a[128]表示字符出现情况,false 为 没有出现过,true表示出现过。初始化为false。遍历字符串的字符,以ascii码为下标进行判断。

时间复杂度 O(n)   时间复杂度已达最低,能优化的就是空间复杂度了,往下看


第二种方法:位操作        


128位 我们用4个int就可以表示(32 bit os),我们用 一个长度为4的int数组即可。
关键是要把字符对应的数字,映射到正确的位上去,      字符的ascii码值 整除 32 得到数组下标   
int val = str.at(i);
int index = val/32;//数组下标
int offset = val%32//相应位


整体代码如下:vs 2008 已测试通过
#include <iostream>
#include <string>
using namespace std;

bool boolArrays(string str,bool *a)
{
	int len = str.length();
	for (int i = 0; i < len; i++)
	{
		int val = str.at(i);
		if (a[val])
		{
			return false;
		}else{
			a[val] = true;
		}
	}
	return true;
}

bool bitOperation(string str,int* a)//int 为32位 128个 128/32 = 4
{
	int len = str.length();
	for (int i = 0; i < len; i++)
	{
		int val = str.at(i);
		int index = val/32;
		int offset = val%32;

		int j = 1<<(32 - offset);           //关键是这里  别糊涂 我看见有的人的文章这里不对

		if (a[index]&j)
		{
			return false;
		}else{
			a[index] |= (j);
		}
	}
	return true;
}

int main()
{
	bool a[128];
	int  b[4];
	string str = "";

	memset(a,0,sizeof(a));
	memset(b,0,sizeof(b));
    
	while(cin>>str){
		if (boolArrays(str,a))
		{
			cout<<"boolArrays true"<<endl;
		}else{
			cout<<"boolArrays false"<<endl;
		}
		memset(a,0,sizeof(a));

		if (bitOperation(str,b))
		{
			cout<<"bitOperation true"<<endl;
		}else{
			cout<<"bitOperation false"<<endl;
		}
		memset(b,0,sizeof(b));
	}

	return 0;
}


1.2 

Write code to reverse a C-Style String. (C-String means that “abcd” is represented as five characters, including the null character.)

译文:

写代码翻转一个C风格的字符串。(C风格的意思是"abcd"需要用5个字符来表示,包含末尾的 结束字符)

解答

这道题如果就是要考察你有没有注意到C风格字符串最后的那个结束符,那我觉得还是像书 上写的那样,在代码中有所体现。代码如下:

#include <iostream>
using namespace std;

void Reverse(char *a){
	char c;
	for (int i = 0,j = strlen(a) - 1; i < j; i++,j-- )
	{
		c    = a[i];
		a[i] = a[j];
		a[j] = c;
	}
}

int main()
{
	char str[] = "123456789";
	Reverse(str);
	cout<<str<<endl;
	return 0;
}



1.3 

Design an algorithm and write code to remove the duplicate characters in a string without using any additional buffer. NOTE: One or two additional variables are fine. An extra copy of the array is not.

FOLLOW UP
Write the test cases for this method.

译文:

设计算法并写出代码移除字符串中重复的字符,不能使用额外的缓存空间。注意: 可以使用额外的一个或两个变量,但不允许额外再开一个数组拷贝。

进一步地,

为你的程序写测试用例。

解答

First, ask yourself, what does the interviewer mean by an additional buffer? Can we use an additional array of constant size?
Algorithm—No (Large) Additional Memory:

1. For each character, check if it is a duplicate of already found characters.
2. Skip duplicate characters and update the non duplicate characters.

Time complexity is O(N2).

首先,自己默默问一下自己,面试官口中所谓的额外的缓存空间是什么?可以使用具有固定大小的数组吗?

下面是一个不使用额外内存(几个变量而已)版本的算法:

1 对每一个字符,检查是否与前面字符重复

2 跳过(覆盖)重复的字符并且更新不重复的字符

算法复杂度是O(n)

#include <iostream>
#include <cstring>
using namespace std;

void removeDuplicate(char* s)
{
	if (s == NULL)
	{
		return ;
	}
	if (strlen(s) < 2)
	{
		return ;
	}
	int tail = 1;                 //跟踪不重复的元素,也可以理解为当前不重复字符的个数,这样方便覆盖,不用挪动元素
	int len = strlen(s);
	for (int i = 1; i < len; ++i) //直接从第二个字符开始检查
	{
		int j;
		for (j = 0;j < tail; ++j)
		{
			if (s[j] == s[i])
			{
				break;
			}
		}
		if ( j == tail)           //j等于tail 说明没有被break,当前检测字符与之前的字符没有重复
		{
			s[tail] = s[i];
			++tail;
		}
	}
	s[tail] = '\0';
	return ;
}

int main()
{
 	char s[]  = "ababababa";
        cout<<s<<endl;
	removeDuplicate(s);
   	cout<<s<<endl;
	return 0;
}

1. String does not contain any duplicates, e.g.: abcd
2. String contains all duplicates, e.g.: aaaa
3. Null string
4. String with all continuous duplicates, e.g.: aaabbb
5. String with non-contiguous duplicate, e.g.: abababa

测试用例:

  1. 不包含重复字符的字符串,比如:abcd
  2. 字符串全是重复字符,比如:aaaa
  3. 空字符串
  4. 重复字符连续出现,比如:aaabbb
  5. 重复字符不连续出现,比如:abababa

另外一种方法:用到上第一题中的方法,标记数组,假设可以用固定长度的数组

void removeDuplicate1(char* s)
{
	if(s == NULL) return;
	int len = strlen(s);
	if(len < 2)   return;

	bool boolArray[128];
	memset(boolArray,0,sizeof(boolArray));

	int tail = 0;
	for (int i = 0; i < len; i++)
	{
		int val = s[i];
		if (!boolArray[val])
		{
			boolArray[val] = true;
			s[tail] = s[i];
			++tail;
		}
	}
	s[tail] = '\0';
	return;
}

int main()
{
	char str[]  = "abcdeffdsfagdfbfdew";
	cout<<str<<endl;
	removeDuplicate1(str);
	cout<<str<<endl;
	return 0;
}

1.4 

Write a method to decide if two strings are anagrams or not.

译文:

写一个函数判断两个字符串是否由相同的字符构成(回文)。

解答

有两种方法解决这个问题:

O(nlogn)的解法

先分别对两个字符串进行排序,排序后的字符串若相等,则是回文,否则不是

bool isAnagram1(string s1, string s2){
	if(s1 == "" || s2 == "") return false;
	if(s1.length() != s2.length()) return false;

	sort(&s1[0], &s1[0]+s1.length());
	sort(&s2[0], &s2[0]+s2.length());
	if(s1 == s2) return true;
	else return false;
}

O(n)的解法

检查两个字符串中的每一个字符是否有相同的出现次数,一个++,一个--。若是回文,则最终抵消,否则不是

bool anagrams(string &s1,string &s2)
{
	if (s1 == ""||s2 == "") return false;

	if (s1.length() != s2.length())
	{
		return false;
	}

	int ascii[128];
	memset(ascii,0,sizeof(ascii));

	int len = s1.length();
    int val1 = 0,val2 = 0,i;
	for ( i = 0; i < len; i++)
	{
		val1 = s1.at(i);
		val2 = s2.at(i);
		++ascii[val1];
		--ascii[val2];
	}

	for (i = 0; i < 128; i++)
	{
		if (ascii[i] != 0)
		{
			return false;
		}
	}
	return true;
}


1.5 

Write a method to replace all spaces in a string with ‘%20’.

译文:

写一个函数,把字符串中所有的空格替换为%20 。

解答

简单题。

剑指offer中也提到了此题,在网络编程(服务器)中,URL中含有特殊字符 如 空格 ‘#’ 服务器端无法获取正确的参数。所以要替换为相应ascii码的十六进制

最笨的方法是逐个判断,若为空格,则向后移动。但这样的时间复杂度很高O(n2)



The algorithm is as follows:
1. Count the number of spaces during the first scan of the string.
2. Parse the string again from the end and for each character:
»»If a space is encountered, store “%20”.
»»Else, store the character as it is in the newly shifted location.

另外一种算法如下:
1 先遍历一遍字符串,记录其中空格的个数
2 从后向前判断并塑造取代空格后的字符串

代码:
#include <iostream>

using namespace std;

void replaceBlank(char* str)
{
	if (str == NULL) return;

	int count = 0,len = strlen(str);

	for (int i = 0; i < len; i++)
	{
		if (str[i] == ' ')
		{
			count++;
		}
	}

	int size = len + 2*count;
	str[size] = '\0';
	int start = 0,end = size - 1;

	for (int j = len - 1; j >= 0; j--)
	{
		if (str[j] == ' ')
		{
			str[end]     = '0';
			str[end - 1] = '2';
			str[end - 2] = '%';
			end -= 3;
		}else{
			str[end--] = str[j];
		}
	}
}

int main()
{
   char a[20] = "a b c d";
   replaceBlank(a);
   cout<<a<<endl;
   return 0;
}

1.6 

Given an image represented by an NxN matrix, where each pixel in the image is 4 bytes, write a method to rotate the image by 90 degrees. Can you do this in place?

译文:

一张图像表示成NxN的矩阵,图像中每个像素是4个字节,写一个函数把图像旋转90度。 你能原地进行操作吗?(即不开辟额外的存储空间)

解答

先问是顺时针还是逆时针,假设顺时针。
The rotation can be performed in layers, where you perform a cyclic swap on the edges on each layer. In the first for loop, we rotate the first layer (outermost edges). We rotate the edges by doing a four-way swap first on the corners, then on the element clockwise from the edges, then on the element three steps away.

Once the exterior elements are rotated, we then rotate the interior region’s edges.

图片的旋转可以将像素划分成一圈一圈,然后从最外层一圈一圈上来旋转。旋转某一圈的某个元素的时候,相当于对应的上下左右依次交换。

#include <iostream>

using namespace std;

void rotate90(int a[][4],int n)
{
	for (int layer = 0; layer < n / 2; ++layer) {
		 int first = layer;
		 int last = n - 1 - layer;
		 for(int i = first; i < last; ++i) {
			 int offset = i - first;
			 int top = a[first][i]; // save top
			 // left -> top
				 a[first][i] = a[last-offset][first];
			
			 // bottom -> left
				 a[last-offset][first] = a[last][last - offset];
			
			 // right -> bottom
				 a[last][last - offset] = a[i][last];
			
			 // top -> right
				 a[i][last] = top; // right <- saved top
			 }
		 }
	for(int i=0; i<4; ++i){
		for(int j=0; j<4; ++j)
			cout<<a[i][j]<<" ";
		cout<<endl;
	}
	cout<<endl;;
}
int main()
{
	int a[][4] = {1,2,3,4,
		          5,6,7,8,
				  9,10,11,12,
				  13,14,15,16};
	rotate90(a,4);
	return 0;
}




1.7 

Write an algorithm such that if an element in an MxN matrix is 0, its entire row and column is set to 0.

译文:

写一个函数处理一个MxN的矩阵,如果矩阵中某个元素为0,那么把它所在的行和列都置为0.

解答

简单题。

At first glance, this problem seems easy: just iterate through the matrix and every time we see a 0, set that row and column to 0. There’s one problem with that solution though: we will “recognize” those 0s later on in our iteration and then set their row and column to zero. Pretty soon, our entire matrix is 0s!
One way around this is to keep a second matrix which flags the 0 locations. We would then do a second pass through the matrix to set the zeros. This would take O(MN) space.
Do we really need O(MN) space? No. Since we’re going to set the entire row and column to zero, do we really need to track which cell in a row is zero? No. We only need to know that row 2, for example, has a zero.
The code below implement this algorithm. We keep track in two arrays all the rows with zeros and all the columns with zeros. We then make a second pass of the matrix and set a cell to zero if its row or column is zero.

乍一看题目,先遍历矩阵,出现0元素,就将所在的行列置零。但是这样的方法执行下来的话整个矩阵都变成了0了。
一个变通的方法,在另外一个MxN的矩阵中记录是否出现零的情况,然后根据记录对原来的矩阵中对相应的行列进行置零。可是这样方法的空间复杂度为O(MxN)。有没有改进的空间呢?
经过观察,其实我们只需要记录哪一列和哪一行需要置零即可。那么我们新的记录方法即为:使用两个数组rows[]和col[]来记录需要置零的行列。更具这样的方法算法代码如下:

#include <iostream>

using namespace std;

void setZeros(int (*a)[4],int m,int n)
{
  bool* row = new bool[m],*col = new bool[n];

  memset(row,0,sizeof(row));
  memset(col,0,sizeof(col));
  // Store the row and column index with value 0
  for (int i = 0; i < m; i++)
  {
	  for (int j = 0; j < n; j++)
	  {
		  if (a[i][j] == 0)
		  {
			  row[i] = true;
			  col[j] = true;
		  }
	  }
  }
 
  // Set arr[i][j] to 0 if either row i or column j has a 0
  for (int i = 0; i < m; i++)
  {
	  for (int j = 0; j < n; j++)
	  {
		  if (row[i]||col[j])
		  {
			  a[i][j] = 0;
		  }
	  }
  }
  delete row;
  delete col;
}
int main()
{
	int a[][4] = {1,2,3,2,
	              4,5,6,7,
	              8,9,0,4,
	              3,5,2,3};

	for(int i=0; i<4; ++i){
		for(int j=0; j<4; ++j)
			cout<<a[i][j]<<" ";
		cout<<endl;
	}
	cout<<endl;
    setZeros(a,4,4);
	for(int i=0; i<4; ++i){
		for(int j=0; j<4; ++j)
			cout<<a[i][j]<<" ";
		cout<<endl;
	}
	cout<<endl;
	return 0;
}


1.8 

Assume you have a method isSubstring which checks if one word is a substring of another. Given two strings, s1 and s2, write code to check if s2 is a rotation of s1 using only one call to isSubstring (i.e., “waterbottle” is a rotation of “erbottlewat”).

译文:

假设你有一个isSubstring函数,可以检测一个字符串是否是另一个字符串的子串。 给出字符串s1和s2,只使用一次isSubstring就能判断s2是否是s1的旋转字符串, 请写出代码。旋转字符串:"waterbottle"是"erbottlewat"的旋转字符串。

解答

Just do the following checks
1. Check if length(s1) == length(s2). If not, return false.
2. Else, concatenate s1 with itself and see whether s2 is substring of the result.
input: s1 = apple, s2 = pleap ==> apple is a substring of pleappleap
input: s1 = apple, s2 = ppale ==> apple is not a substring of ppaleppale

很巧妙的算法:

算法描述:
1 如果length(s1)!= length(s2) 返回 false
2 将是s1和本身连接,得到新字符串s1',调用isSubstring(s2,s1')判断s2是否为s1'的字符串。

#include <iostream>
#include <string>
using namespace std;

bool isSubstring(string s1, string s2){
	if(s1.find(s2) != string::npos) return true;
	else return false;
}
bool isRotation(string s1, string s2){
	if(s1.length() != s2.length() || s1.length()<=0)
		return false;
	return isSubstring(s1+s1, s2);
}

int main(){
	string s1 = "apple";
	string s2 = "pleap";
	cout<<isRotation(s1, s2)<<endl;
	//cout<<string::npos<<endl;
	return 0;
}



内容概要:本文详细介绍了“秒杀商城”微服务架构的设计与实战全过程,涵盖系统从需求分析、服务拆分、技术选型到核心功能开发、分布式事务处理、容器化部署及监控链路追踪的完整流程。重点解决了高并发场景下的超卖问题,采用Redis预减库存、消息队列削峰、数据库乐观锁等手段保障数据一致性,并通过Nacos实现服务注册发现与配置管理,利用Seata处理跨服务分布式事务,结合RabbitMQ实现异步下单,提升系统吞吐能力。同时,项目支持Docker Compose快速部署和Kubernetes生产级编排,集成Sleuth+Zipkin链路追踪与Prometheus+Grafana监控体系,构建可观测性强的微服务系统。; 适合人群:具备Java基础和Spring Boot开发经验,熟悉微服务基本概念的中高级研发人员,尤其是希望深入理解高并发系统设计、分布式事务、服务治理等核心技术的开发者;适合工作2-5年、有志于转型微服务或提升架构能力的工程师; 使用场景及目标:①学习如何基于Spring Cloud Alibaba构建完整的微服务项目;②掌握秒杀场景下高并发、超卖控制、异步化、削峰填谷等关键技术方案;③实践分布式事务(Seata)、服务熔断降级、链路追踪、统一配置中心等企业级中间件的应用;④完成从本地开发到容器化部署的全流程落地; 阅读建议:建议按照文档提供的七个阶段循序渐进地动手实践,重点关注秒杀流程设计、服务间通信机制、分布式事务实现和系统性能优化部分,结合代码调试与监控工具深入理解各组件协作原理,真正掌握高并发微服务系统的构建能力。
### 代码理解 在 HTML 中根据特定条件刷新表格,通常会结合 JavaScript 来实现。一般的思路是,首先获取表格元素,然后当特定条件满足时,更新表格的数据并重新渲染表格内容。 ### 代码优化建议 以下是一些优化 HTML 中根据特定条件刷新表格代码的常见方法: - **减少 DOM 操作**:频繁的 DOM 操作会导致页面性能下降。可以先在 JavaScript 中构建好新的表格内容,然后一次性将其插入到 DOM 中,而不是每次更新一个单元格就进行一次 DOM 操作。 - **使用事件委托**:如果表格有很多行,为每一行添加事件监听器会消耗大量内存。可以将事件监听器添加到表格的父元素上,利用事件冒泡机制来处理事件。 - **缓存 DOM 元素**:避免在每次需要操作 DOM 元素时都使用 `document.getElementById` 或其他查询方法。可以在页面加载时将需要的 DOM 元素缓存起来,以便后续使用。 ### 示例代码 以下是一个简单的示例,展示了如何根据特定条件(点击按钮)刷新表格: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Refresh Table</title> </head> <body> <button id="refreshButton">Refresh Table</button> <table id="myTable"> <thead> <tr> <th>Name</th> <th>Age</th> </tr> </thead> <tbody> <tr> <td>John</td> <td>25</td> </tr> </tbody> </table> <script> // 缓存 DOM 元素 const refreshButton = document.getElementById('refreshButton'); const tableBody = document.querySelector('#myTable tbody'); // 模拟新数据 const newData = [ { name: 'Jane', age: 30 }, { name: 'Bob', age: 35 } ]; // 定义刷新表格的函数 function refreshTable() { // 清空表格内容 tableBody.innerHTML = ''; // 构建新的表格内容 const newRows = newData.map(item => { const row = document.createElement('tr'); const nameCell = document.createElement('td'); const ageCell = document.createElement('td'); nameCell.textContent = item.name; ageCell.textContent = item.age; row.appendChild(nameCell); row.appendChild(ageCell); return row; }); // 一次性插入新的表格内容 newRows.forEach(row => tableBody.appendChild(row)); } // 为按钮添加点击事件监听器 refreshButton.addEventListener('click', refreshTable); </script> </body> </html> ``` ### 代码解释 - 首先,通过 `document.getElementById` 和 `document.querySelector` 方法缓存了按钮和表格的 tbody 元素。 - 定义了一个新的数据数组 `newData`,用于模拟新的表格数据。 - `refreshTable` 函数用于刷新表格内容。在函数内部,先清空表格的 tbody 内容,然后根据新数据构建新的表格行,并一次性将这些行插入到 tbody 中。 - 最后,为按钮添加了一个点击事件监听器,当按钮被点击时,调用 `refreshTable` 函数。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值