【Cracking the Code Interview(5th edition)】一、数组与字符串(C++)

1.1 实现一个算法,确定一个字符串的所有字符是否全都不同。不允许使用额外的数据结构。

解答:这里假定字符集为ASCII码,可以与面试官沟通确认字符串使用的字符集。由于字符集是有限的,建立一个数组模拟的Hash表记录每个字符是否出现,线性扫描一次字符串即可,复杂度O(len(s)).如果字符集较大,需要考虑空间开销,则可以用bitset来实现。


 1 bool isUnique(string s) {
2   bool record[256];
3   memset(record, 0, sizeof(record));
4   size_t size = s.size();
5   for(size_t i = 0; i < size; ++i) {
6     if (record[s[i]] == true)
7       return false;
8     else
9       record[s[i]] = false;
10   }
11   return true;
12 }

可以加一个剪枝:如果字符串的大小大于字符集的大小了,那么该字符串肯定是存在重复字符的。


 1 bool isUnique(string s) {
2 size_t size = s.size();
3 if (size > 256)
4 return false;
5    bool record[256];
6    memset(record, 0, sizeof(record));
7    for(size_t i = 0; i < size; ++i) {
8      if (record[s[i]] == true)
9        return false;
10      else
11        record[s[i]] = false;
12    }
13    return true;
14 }

补充:假设对空间开销非常严苛,可以考虑先对字符串中的字符排序,然后进行一次线性扫描;如果还不允许改变字符串原有的内容,那么就只能暴力搜查了吧。


1.2 用C或C++实现void reverse(char *
str)函数,即反转一个‘\0’结尾的字符串。

解答


 1 void reverse(char *str) {
2 if (str == nullptr) return;
3 size_t length = strlen(str);
4 for (size_t i = 0; i < length / 2; ++i) {
5 // swap str[i] and str[length - 1 - i];
6 char temp = str[i];
7 str[i] = str[length - 1 - i];
8 str[length - 1 - i] = temp;
9 }
10 }


1.3 给定两个字符串,请写程序,确定其中一个字符串的重新排列后,能否变成另一个字符串。

解答:这里同样假定字符集是ASCII码,假定判断字符串相等时,大小写是区分的。在开始时通过检查两个字符串长度是否相等进行剪枝,然后,用一个数组记录第一个字符串中每个字符出现的次数,然后判断第二个字符串中每个字符出现的次数是否与第一个字符串相等即可。


 1 bool canConvert(string s1, string s2) {
2 if (s1.size() != s2.size())
3 return false;
4 int record[256];
5 memset(record, 0, sizeof(record));
6 for (auto &c : s1) {
7 ++record[c];
8 }
9 for (auto &c : s2) {
10 if (--record[c] < 0)
11 return false;
12 }
13 return true;
14 }


1.4
编写一个方法,将字符串中的空格全部替换为"%20".假定字符串尾部有足够的空间存放新增字符,并且知道字符串的真实长度。示例:

输入: "Mr John Smith"

输出: "Mr%20John%20Smith"

解答:需要改变字符串长度的问题,通常从尾部开始处理比较高效,因为可以避免字符的重复移动。


 1 void replaceSpace(char str[], int length) {
2 //length为字符串实际长度,不包括结尾的‘\0‘
3 if(length <= 0) return;
4 int space_cnt = 0;
5 for (int i = 0; i < length; ++i) {
6 if (str[i] == ‘ ‘) {
7 ++space_cnt;
8 }
9 }
10 if (space_cnt > 0) {
11 int j = length + space_cnt * 2;
12 str[j] = ‘\0‘;
13 for (int i = length - 1; i >= 0; --i) {
14 if (str[i] == ‘ ‘) {
15 //用"%20"替换空格
16 str[--j] = ‘0‘;
17 str[--j] = ‘2‘;
18 str[--j] = ‘%‘;
19 } else {
20 str[--j] = str[i];
21 }
22 }
23 }
24 }


1.5
利用字符重复出现的次数,编写一个方法,实现基本的字符串压缩功能。比如:字符串"aabbcccccaaa"会变为"a2b1c5a3".若压缩后的字符串没有变短,则返回原先的字符串。

解答:重复长度编码(run length encoding,
RLE).按照题述模拟即可,但要注意细节实现。(其中to_string(int)函数为C++11新增)


 1 string compress(string s) {
2 string ret;
3 int length = s.size();
4 if (length > 0) {
5 char buf = s[0];
6 int cnt = 1;
7 for (size_t i = 1; i < length; ++i) {
8 if (s[i] != buf) {
9 ret = ret + buf + to_string(cnt);
10 buf = s[i];
11 cnt = 1;
12 }
13 else {
14 ++cnt;
15 }
16 }
17 //开始忘记了加这一句,导致最后一组计数结果没有加入ret
18 ret = ret + buf + to_string(cnt);
19 }
20 //注意这里要加等号
21 return ret.size() >= s.size() ? s : ret;
22 }


1.6
给定一副NxN矩阵表示的图像,其中每个图像的大小为4字节,编写一个方法,将图像旋转90°。不占用额外空间能否做到?

解答:常见的问题了,有很多旋转的方式,这里就用最常用的由外向内逐层旋转的方法吧。

这里需要特别注意旋转过程中的循环停止条件和对应的坐标,不小心非常容易出错,用含义清晰的变量名有利于理清思路,避免错误。


 1 void rotate(int **matrix, int n) {
2 for (int layer = 0; layer < n / 2; ++layer) {
3 int last = n - 1 - layer;
4 for (int j = layer; j < last; ++j) {
5 int offset = j - layer;
6 //保存上
7 int temp = matrix[layer][j];
8 //左->上
9 matrix[layer][j] = matrix[last - offset][layer];
10 //下->左
11 matrix[last - offset][layer] = matrix[last][last - offset];
12 //右->下
13 matrix[last][last - offset] = matrix[j][last];
14 //上->右
15 matrix[j][last] = temp;
16 }
17 }
18 }

另外关于参数传递的问题,这里需要的是一个可变长的二维数组,C++中我们只能通过传递一个二级指针来实现,这样在函数调用的时候就需要一些处理了:


1 int main(void) {
2 int arr1[] = { 1, 2, 3, 4 };
3 int arr2[] = { 5, 6, 7, 8 };
4 int arr3[] = { 9, 10, 11, 12 };
5 int arr4[] = { 13, 14, 15, 16 };
6 int *matrix[4] = { arr1, arr2, arr3, arr4 };
7 rotate(matrix, 4);
8 return 0;
9 }

C++中不能直接给函数传递二维数组的原因是:“数组名被改写成一个指针参数”规则并不是递归定义的。数组的数组会被改写成“数组的指针”,而不是“指针的指针”。


1.7 编写一个算法,若MxN的矩阵中某个元素为0,则将其所在的行与列都清零。

解答:LeetCode上也有这道题。可以用一个长度为M的数组和一个长度为N的数组分别记录有0的行和列。此外也可以利用矩阵本身的存储空间,用其中的一行一列来记录该结果。以下代码基于后一种方法。


 1 void setZero(int **matrix, int m, int n) {
2 bool r1_zero = false, c1_zero = false; //两个变量记录第一行和第一列是否含0
3 //检查第一行
4 for (int j = 0; j < n; ++j) {
5 if (matrix[0][j] == 0) {
6 r1_zero = true;
7 break;
8 }
9 }
10 //检查第一列
11 for (int i = 0; i < m; ++i) {
12 if (matrix[i][0] == 0) {
13 c1_zero = true;
14 break;
15 }
16 }
17 //检查剩余元素
18 for (int i = 1; i < m; ++i) {
19 for (int j = 1; j < n; ++j) {
20 if (matrix[i][j] == 0) {
21 //将元素是否为0记录到第一行和第一列
22 matrix[0][j] = matrix[i][0] = 0;
23 }
24 }
25 }
26 //矩阵按规则置0
27 for (int i = 1; i < m; ++i) {
28 if (matrix[i][0] == 0) {
29 for (int j = 1; j < n; ++j) {
30 matrix[i][j] = 0;
31 }
32 }
33 }
34 for (int j = 1; j < n; ++j) {
35 if (matrix[0][j] == 0) {
36 for (int i = 0; i < m; ++i) {
37 matrix[i][j] = 0;
38 }
39 }
40 }
41 if (r1_zero) {
42 for (int j = 0; j < n; ++j) {
43 matrix[0][j] = 0;
44 }
45 }
46 if (c1_zero) {
47 for (int i = 0; i < m; ++i) {
48 matrix[i][0] = 0;
49 }
50 }
51 }


1.8
假定有一个方法isSubstring,可检查一个单词是否为其他字符串的子串。给定两个字符串s1和s2.请编写代码检查s2是否为s1旋转而成,要求只能调用一次isSubstring.(比如:waterbottle是erbottlewat旋转后的字符串)

解答:s2是s1经过一次旋转得到的,那么s1+s1拼成的字符一定把s2包含在其中。可以先判断两个字符串的长度是否相等。


1 bool isSubstring(string s1, string s2);   //s2是s1的子串是为真
2
3 bool isRotation(string s1, string s2) {
4 if (s1.size() == s2.size() && isSubstring(s1 + s1, s2) {
5 return true;
6 } else {
7 return false;
8 }
9 }


补充:第四版中的习题

1.3 设计算法并编码实现:在不使用额外缓冲区的情况下去除字符串中的重复字符。设计一些测试用例。

解答:如果允许开辟与字符串长度无关大小的数组,则可以用一个记录字符是否出现过的数组来实现。


 1 void removeDuplicate(char str[]) {
2 int length = strlen(str);
3 if (length > 1) {
4 bool flag[256];
5 memset(flag, 0, sizeof(flag));
6 for (int i = 0, j = 0; i < length; ++i) {
7 if (flag[str[i]] == false) {
8 flag[str[i]] = true;
9 str[j++] = str[i];
10 }
11 }
12 //不要忘记加上结束标志
13 str[length] = ‘\0‘;
14 }
15 }

如果不允许使用额外的数组来记录字符是否出现,那么就只有暴力搜查了。


 1 void removeDuplicate(char str[]) {
2 int length = strlen(str);
3 if (length > 1) {
4 int k = 0;
5 for (int i = 0; i < length; ++i) {
6 bool found = false;
7 for (int j = 0; j < k; ++j) {
8 if (str[i] == str[j]) {
9 found = true;
10 break;
11 }
12 }
13 if (!found) {
14 str[k++] = str[i];
15 }
16 }
17 //不要忘记结束标记
18 str[k] = ‘\0‘;
19 }
20 }

测试用例如:"", "a", "aaa", "abab", "aabb", "abbbb", "abcd"...

【Cracking the Code Interview(5th edition)】一、数组与字符串(C++),布布扣,bubuko.com

时间: 2024-10-26 17:26:26

【Cracking the Code Interview(5th edition)】一、数组与字符串(C++)的相关文章

【Cracking the Code Interview(5th edition)】二、链表(C++)

链表结点类型定义: 1 class Node { 2 public: 3 int data = 0; 4 Node *next = nullptr; 5 6 Node(int d) { 7 data = d; 8 } 9 }; 快行指针(runner)技巧: 同时使用两个指针来迭代访问链表,其中一个比另一个超前一些.快指针比慢指针先行几步或者快指针与慢指针的速度呈一定的关系. dummy元素技巧: 在链表头部添加一个哑结点,通常可以简化首部或尾部的特殊情况的处理. 2.1 编写代码,移除未排序链

Cracking the Code Interview 4.3 Array to Binary Tree

Given a sorted (increasing order) array, write an algorithm to create a binary tree with minimal height. 1.Divide the array equally into left part and right part, the mid value will be the root. 2.Recall the function to the left and right pat of the

Cracking the Coding Interview 5.7

An array A[1...n] contains all the integers from 0 to n except for one number which is missing.In this problem, we cannot access an entire integer in A with a single operation. The elements of A are represented in binary, and the only operation we ca

《Cracking the Coding Interview》——第16章:线程与锁——题目2

2014-04-27 19:14 题目:如何测量上下文切换的时间? 解法:首先,上下文切换是什么,一搜就知道.对于这么一个极短的时间,要测量的话,可以通过放大N倍的方法.比如:有A和B两件事,并且经常一起发生,每件只需要花几纳秒.如果你把A事件连续做几百万次,而B时间只做了几次,这样就能排除B事件对于测量的影响.如果总时间S = mA + nB.当m >> n 时,A≈S / m.下面的测量方法类似于打乒乓球,在主线程和副线程间互相传递一个令牌,这个令牌可以是变量.管道之类的用于通信的工具.与

《Cracking the Coding Interview》——第16章:线程与锁——题目1

2014-04-27 19:09 题目:线程和进程有什么区别? 解法:理论题,操作系统教材上应该有很详细的解释.我回忆了一下,写了如下几点. 代码: 1 // 16.1 What is the difference between process and thread? 2 Answer: 3 Process: 4 1. Basic element of resource allocation in the operating system. 5 2. Possesses independent

《Cracking the Coding Interview》——第18章:难题——题目10

2014-04-29 04:22 题目:给定一堆长度都相等的单词,和起点.终点两个单词,请从这堆单词中寻找一条变换路径,把起点词变成终点词,要求每次变换只能改一个字母. 解法:Leetcode中有Word Ladder,这题基本思路一致. 代码: 1 // 18.10 Given a list of words, all of same length. Given a source and a destionation words, you have to check if there exis

《Cracking the Coding Interview》——第18章:难题——题目11

2014-04-29 04:30 题目:给定一个由'0'或者'1'构成的二维数组,找出一个四条边全部由'1'构成的正方形(矩形中间可以有'0'),使得矩形面积最大. 解法:用动态规划思想,记录二维数组每个元素向上下左右四个方向各有多少个连续的'1',然后用O(n^3)时间计算出满足条件的最大正方形.时间复杂度O(n^3),空间复杂度O(n^2). 代码: 1 // 18.11 Given an NxN matrix of 0s and 1s, find out a subsquare whose

《Cracking the Coding Interview》——第18章:难题——题目12

2014-04-29 04:36 题目:最大子数组和的二位扩展:最大子矩阵和. 解法:一个维度上进行枚举,复杂度O(n^2):另一个维度执行最大子数组和算法,复杂度O(n).总体时间复杂度为O(n^3),还需要O(n)额外空间. 代码: 1 // 18.12 Given an n x n matrix, find the submatrix with largest sum. Return the sum as the result. 2 #include <algorithm> 3 #inc

《Cracking the Coding Interview》——第18章:难题——题目5

2014-04-29 01:51 题目:你有一个文本文件,每行一个单词.给定两个单词,请找出这两个单词在文件中出现的其中一对位置,使得这两个位置的距离最短. 解法:我的思路是建立倒排索引,计算出所有单词出现的所有位置.下面代码只给出了两个索引的处理方法.倒排索引一般以链表的形式出现,通过顺序扫描两个链表,类似归并排序算法中的merge过程,就能够找出最短距离.请看代码. 代码: 1 // 18.5 Given a text file containing words, find the shor