您的位置:首页 > 其它

阿里巴巴笔试—— 一道动规引发的思考

2014-09-05 01:08 393 查看

题目1:最长公共连续子序列

1、给定一个query和一个text,均由小写字母组成。要求在text中找出以同样的顺序连续出现在query中的最长连续字母序列的长度。例如, query为“acbac”,text为“acaccbabb”,那么text中的“cba”为最长的连续出现在query中的字母序列,因此,返回结果应该为其长度3。请注意程序效率。

这道题其实就是最长公共连续子序列问题。可以直接用动规来实现。

i=j dp[i][j]=dp[i-1][j-1]+1

i!=j dp[i][j]=0;

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

int getLCS(string &s1, string &s2)

{

int len1 = s1.length();

int len2 = s2.length();

vector<vector<int>> matrix(2, vector<int>((len2 + 1), 0));

int MaxValue = 0;

int flag = 1;

int tmp = 0;

/*

思路是:

利用两个数组来求解滚粗的方式

*/

for(int i = 1; i <= len1; i++)

{

for(int j = 1; j <= len2; j++)

{

if(s1[i - 1] == s2[j - 1])

{

matrix[flag][j] = matrix[1 - flag][j - 1] + 1;

if(MaxValue < matrix[flag][j])

{

MaxValue = matrix[flag][j];

}

}

else

{

matrix[flag][j] = 0;

}

}

flag = 1 - flag;

}

return MaxValue;

}
下面采用两个变量和一个数组进行滚粗

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

int getLCS2(string &s1, string &s2)

{

int len1 = s1.length();

int len2 = s2.length();

vector<int> matrix(len2 + 1, 0);

int MaxValue = 0;

int flag = 1;

int tmp = 0;

int old = 0;

/*

思路是:用一维数组解决

*/

for(int i = 1; i <= len1; i++)

{

old = 0;

for(int j = 1; j <= len2; j++)

{

tmp = matrix[j];

if(s1[i - 1] == s2[j - 1])

{

matrix[j] = old + 1;

if(MaxValue < matrix[j])

{

MaxValue = matrix[j];

}

}

else

{

matrix[j] = 0;

}

old = tmp;

}

}

return MaxValue;

}

题目2:最长公共子序列

就是所谓的最长公共子序列(LCS)问题。(不要求连续出现的公共子序列)下面给出两种解法,一种利用动规数组,另外一种使用滚粗数组来完成。因为只要求长度,所以可以利用滚粗数组可以实现。

应用公式如下:

i=j dp[i][j]=dp[i-1][j-1]+1

i!=j dp[i][j]=max(dp[i-1][j],dp[i][j-1])

下面是动规数组,直接粗暴,算法复杂度O(n2),空间复杂度O(n2).

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

int LCS(string &query, string &text)

{

int len1 = query.size();

int len2 = text.size();

vector<vector<int>> vec(len1 + 1, vector<int>(len2 + 1, 0));

for(int i = 1; i <= len1; i++)

{

for(int j = 1; j <= len2; j++)

{

if(query[i - 1] == text[j - 1])

{

vec[i][j] = vec[i - 1][j - 1] + 1;

}

else

{

vec[i][j] = vec[i][j - 1] > vec[i - 1][j] ? vec[i][j - 1] : vec[i - 1][j];

}

}

}

// PrintLCS(vec,query,text,len1,len2);

return vec[len1][len2];

}
由于我们只需要计算长度,所以我们可以采用两个数组来实现动规数组,利用滚粗的方式。

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

////////进一步优化空间 假如只求长度,我们只需要最后一行的结果,因此我们只需要两行的数组。滚粗数组

int LCS2(string &query, string &text)

{

int len1 = query.size();

int len2 = text.size();

vector< vector<int> > vec(2, vector<int>(len2 + 1, 0));

int flag = 1;

for(int i = 1; i <= len1; i++)

{

for(int j = 1; j <= len2; j++)

{

if(query[i - 1] == text[j - 1])

{

vec[flag][j] = vec[1 - flag][j - 1] + 1;

}

else

{

vec[flag][j] = vec[flag][j - 1] >= vec[1 - flag][j] ? vec[flag][j - 1] : vec[1 - flag][j];

}

}

flag = 1 - flag;

}

return vec[1 - flag][len2];

}
3、滚粗数组的进一步优化,空间复杂度为O(n),利用两个整数分别记录上方数组,和斜上方的元素

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

//再进一步优化,采用一个整数来记录上方的元素

int getLcsLength(string &query, string &text)

{

int len1 = query.size();

int len2 = text.size();

vector<int > dp(len2 + 1, 0);

int old = 0;

int tmp = 0;

for(int i = 1; i <= len1; i++)

{

old = 0;

for(int j = 1; j <= len2; j++)

{

tmp = dp[j];

if(query[i - 1] == text[j - 1])

{

dp[j] = old + 1;

}

else

{

if(dp[j] < dp[j - 1]) dp[j] = dp[j - 1];

}

old = tmp;

}

}

return dp[len2];

}
4、我们有时候需要把最长公共子序列打印出来,我们采用递归的方式进行打印。具体实现如下:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//根据动规矩阵 和递归打印最长公共字符串

void PrintLCS(vector<vector<int>> &vec, string &query, string &text, int i, int j)

{

if(i == 0 || j == 0)

return ;

if(query[i - 1] == text[j - 1])

{

PrintLCS(vec, query, text, i - 1, j - 1); //从后面开始递归回状态

cout << query[i - 1];

}

else if(vec[i - 1][j] >= vec[i][j - 1]) //

{

PrintLCS(vec, query, text, i - 1, j);

}

else

{

PrintLCS(vec, query, text, i, j - 1);

}

}
4、上面的算法只能把一个可能性打印出来,但是有时候需要把所有可能的公共子序列都打印出来,此时,我们需要使用下面的算法来实现。

这个时候我们需要新建一个辅助数组,记录公共子序列的走向。我们采用递归回溯的方法来实现搜索所有可能的路径。

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

//下面我们来实现把所有的最长公共子序列都打印出来试试

vector<char> result;

void Display_Lcs(int i, int j, string &x, vector<vector<int> > &mvec, int current_len, int lcs_max_len)

{

if(i == 0 || j == 0)

{

if(result.size() != lcs_max_len)

{

result.clear();

return;

}

for(int s = 0; s < lcs_max_len; s++)

{

cout << result[lcs_max_len - s - 1];

}

cout << endl;

result.clear();

return;

}

if(mvec[i][j] == 1)

{

current_len--;

result.push_back(x[i - 1]);

Display_Lcs(i - 1, j - 1, x, mvec, current_len - 1, lcs_max_len);

}

else

{

if(mvec[i][j] == 2)

{

Display_Lcs(i - 1, j, x, mvec, current_len - 1, lcs_max_len);

}

else

{

if(mvec[i][j] == 3)

{

Display_Lcs(i, j - 1, x, mvec, current_len, lcs_max_len);

}

else

{

Display_Lcs(i, j - 1, x, mvec, current_len, lcs_max_len);

Display_Lcs(i - 1, j, x, mvec, current_len - 1, lcs_max_len);

}

}

}

}

int LCS4(string &query, string &text)

{

int len1 = query.size();

int len2 = text.size();

vector<vector<int>> vec(len1 + 1, vector<int>(len2 + 1, 0));

vector<vector<int> >mvec(len1 + 1, vector<int>(len2 + 1, 0));

for(int i = 1; i <= len1; i++)

{

for(int j = 1; j <= len2; j++)

{

if(query[i - 1] == text[j - 1])

{

vec[i][j] = vec[i - 1][j - 1] + 1;

mvec[i][j] = 1;

}

else if(vec[i - 1][j] > vec[i][j - 1])

{

vec[i][j] = vec[i - 1][j];

mvec[i][j] = 2;

}

else if(vec[i - 1][j] < vec[i][j - 1])

{

vec[i][j] = vec[i][j - 1];

mvec[i][j] = 3;

}

else

{

vec[i][j] = vec[i][j - 1];

mvec[i][j] = 4;

}

}

int m;

}

// PrintLCS(vec,query,text,len1,len2);

Display_Lcs( len1, len2, query, mvec, vec[len1][len2], vec[len1][len2] );

return vec[len1][len2];

}

题目3:最长递增子序列

最长递增子序列LIS(Longest Increasing Subsequence):

给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4。

分析:其实此LIS问题可以转换成最长公子序列问题,为什么呢?

原数组为A {5, 6, 7, 1, 2, 8}

排序后:A‘{1, 2, 5, 6, 7, 8}

因为,原数组A的子序列顺序保持不变,而且排序后A‘本身就是递增的,这样,就保证了两序列的最长公共子序列的递增特性。如此,若想求数组A的最长递增子序列,其实就是求数组A与它的排序数组A‘的最长公共子序列。

此外,本题也可以使用动态规划来求解,读者可以继续思考。

下面我们直接用动态规划的思想来解决:

设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),

则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),

找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),

找出最大值即为最大递增子序列。时间复杂度为O(N^2)。

例如给定的数组为{5,6,7,1,2,8},则L(0)=1, L(1)=2, L(2)=3, L(3)=1, L(4)=2, L(5)=4。

所以该数组最长递增子序列长度为4,序列为{5,6,7,8}。

这个解决的思想就是用到了替换的思想,每次只保留最后末尾的值来显示最长递增。

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

int getLIS(vector<int> &s)

{

int len = s.size();

vector<int> longest(len, 1);

int Max = 1;

for(int i = 1; i < len; i++)

{

for(int j = 0; j < i; j++)

{

if(s[j] < s[i] && longest[i] < longest[j] + 1)

{

longest[i] = longest[j] + 1;

if(Max < longest[i])

{

Max = longest[i];

}

}

}

}

return Max;

}
解法二:

这种解法用得比较巧妙。用到了二叉搜索树的概念。

基本思路:

新建一个O(N) 的辅助数组

需要遍历所有的dp[0]~dp[i-1]。而这也是优化的关键点,即如何减少每次对前面i项的遍历。

试想一下,如果某个val[j]<val[i],使得dp[i]更新为dp[j]+1,那么所有小于dp[j]的项其实无需再考虑。

对于长度相同的项,只记录下末尾数最小的项,因为如果这一项都无法满足小于val[i],其他项就更不可能满足这个条件。

根据第二种方法,我们只需要建立一个len数组,其中len[k]表示当前长度为k的递增子序列最小的末尾数是len[k]。

考虑第i项时,只需二分len数组已存在的下标范围,找到满足len[k]<val[i]且k最大的位置,并用val[i]更新len[k+1],

如果len[k+1]在更新前没赋值过,就将len数组的最大下标增一,最后的结果就是len数组的最大下标。

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

int BiSearch(vector<int > &Bitree, int len, int value)

{

int left = 0, right = len - 1;

int mid;

while(left <= right)

{

mid = left + (right - left) / 2;

if(Bitree[mid] > value)

right = mid - 1;

else if(Bitree[mid] < value)

left = mid + 1;

else

return mid;

}

return left;

}

int searchLIS(vector<int> &arr)

{

int len = 1;

int size = arr.size();

vector<int> Bitree(size, 0);

Bitree[0] = arr[0];

int pos = 0;

for(int i = 1; i < size; ++i)

{

if(arr[i] > Bitree[len - 1]) //如果大于Bitree中最大的元素,则直接插入到B数组尾

{

Bitree[len] = arr[i];

++len;

}

else

{

pos = BiSearch(Bitree, len, arr[i]); //二分查找需要插入的位置

Bitree[pos] = arr[i];

}

}

return len;

}

题目三:最长回文子串

这道题就是求一个字符串里面的最长的一个回文子串。这里并没有用到动规的方法。

而是直接采用搜索的方法。分别对长度为奇数和长度为偶数的回文子串进行搜索。

搜索到最长的返回。

代码如下所示:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

int MaxPromelineSubSuqueue(string &str)

{

int len = str.length();

int maxlength = 0;

int start = 0;

//查找长度为奇数的最长回文子串

for(int i = 1; i < len; i++)

{

int j = i - 1, k = i + 1;

while(j >= 0 && k < len && str.at(j) == str.at(k))

{

if(k - j + 1 > maxlength)

{

maxlength = k - j + 1;

start = j;

}

j--;

k++;

}

}

//查找长度为偶数的最长回文子串

for(int i = 0; i < len; i++)

{

int j = i;

int k = i + 1;

while(j >= 0 && k < len && str.at(j) == str.at(k))

{

if(k - j + 1 > maxlength)

{

maxlength = k - j + 1;

start = j;

}

j--;

k++;

}

}

if(maxlength > 0)

{

string s = str.substr(start, maxlength);

cout << s << endl;

}

return maxlength;

}

题目四:最长不重复子串

根据题目的意思是:从一个字符串中找到一个连续子串,该子串任何两个字符不能相同,求子串的最大长度并输出最长不重复子串。

例如输入abcbef,输出cbef

这道题有点hash表的味道。新建一个位数256的数组,对字符进行映射。然后逐个去找最长的不重复的子串。

基本思路如下:

O(N)的算法,具体思路如下:

以abcbef这个串为例,用一个数组pos记录每个元素曾出现的下标,初始化为-1。从s[0]开始,依次考察每个字符,例如pos['a'] == -1,说明a还未出现过,令pos['a'] = 0,视为将‘a’加入当前串,同时长度+1,同理pos['b'] = 1,pos['c'] = 2,考察s[3],pos['b'] != -1,说明'b'在前面已经出现过了,此时可得到一个不重复串"abc",刷新当前的最大长度,然后更新pos['b']及起始串位置。

过程如下:

1、建一个256个单元的数组,每一个单元代表一个字符,数组中保存上次该字符出现的位置;

2、依次读入字符串,同时维护数组的值;

3、如果遇到冲突了,考察当前起始串位置到冲突字符的长度,如果大于当前最大长度,

则更新当前最大长度并维护冲突字符的位置,更新起始串位置,继续第二步。

具体代码如下所示:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

int GetMaxSubStr(string &str )

{

int hash[256]; //hash记录每个字符的出现位置

for (int i = 0; i < 256; i++)//初始化

{

hash[i] = -1;

}

int CurrentStart = 0, CurrentLength = 0, MaxStart = 0, MaxEnd = 0;

int strLen = str.length();

for(int i = 0; i < strLen; i++)

{

if(CurrentStart > hash[str[i]]) //如果没有重复

{

hash[str[i]] = i;

}

else

{

CurrentLength = i - CurrentStart; //当前长度

if(CurrentLength > MaxEnd - MaxStart) //如果当前长度最长

{

MaxEnd = i;

MaxStart = CurrentStart;

}

CurrentStart = hash[str[i]] + 1; //更新当前最长的起点

hash[str[i]] = i; //更新字符出现的位置

}

}

if (MaxEnd == 0)//没有重复字符,返回源串

{

cout << str << endl;

return strLen;

}

CurrentLength = strLen - CurrentStart; //当前长度

if(CurrentLength > MaxEnd - MaxStart)//如果当前长度最长

{

MaxEnd = strLen;

MaxStart = CurrentStart;

}

int MaxLength = MaxEnd - MaxStart;

string s = str.substr(MaxStart, MaxLength);

cout << s << endl;

return MaxLength;

}

题目五:最大子序列和

这道题即不说,已经见过很多次了。基本思想是:抛弃没有贡献的前段,重新开始。

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

int MaxSumSubSequeue(vector<int> &arr)

{

int len = arr.size();

int sum = 0;

int Max = 0;

for(int i = 0; i < len; i++)

{

if(sum + arr[i] > 0)

{

sum += arr[i];

}

else

{

sum = 0;

}

if(sum > Max)

{

Max = sum;

}

}

return Max;

}

题目六:字符串编辑距离

这道题是动规的一个非常经典的题目,简直是屡见不鲜了。掌握动规的基本思想即可解决这个问题。

基本解法如下:

这是一个经典的动规问题

以str1c和str2d,从最后一个字符串来看

如果c=d则有 dp[i][j]=dp[i-1][j-1];

如果是

插入 dp[i][j]=dp[i][j-1]+1

删除 dp[i][j]=dp[i-1][j]+1

替换 dp[i][j]=dp[i-1][j-1]+1

代码如下所示:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

int EditDistance(string &s1, string &s2)

{

int len1 = s1.length();

int len2 = s2.length();

vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));

for(int i = 1; i <= len1; ++i)

{

dp[i][0] = i;

}

for(int i = 1; i <= len2; ++i)

{

dp[0][i] = i;

}

for(int i = 1; i <= len1; i++)

{

for(int j = 1; j <= len2; j++)

{

if(s1[i - 1] == s2[j - 1])

{

dp[i][j] = dp[i - 1][j - 1];

}

else

{

int mn = min(dp[i][j - 1], dp[i - 1][j]);

dp[i][j] = min(dp[i - 1][j - 1], mn) + 1;

}

}

}

return dp[len1][len2];

}

/*

利用滚粗数组实现

*/

int EditDistance2(string &s1, string &s2)

{

if(s1.length() > s2.length())

return EditDistance2(s2, s1);

int len1 = s1.length();

int len2 = s2.length();

vector<int> dp(len2 + 1, 0);

int upperleft = 0;

int old = 0;

for(int i = 1; i <= len2; i++)

{

dp[i] = i;

}

for(int i = 1; i <= len1; i++)

{

old = i;

for(int j = 1; j <= len2; j++)

{

upperleft = dp[j];

if(s1[i - 1] == s2[j - 1])

{

dp[j] = old;

}

else

{

dp[j] = min(dp[j], min(dp[j - 1], old)) + 1;

}

old = upperleft;

}

}

return dp[len2];

}

题目七:Word Break &&Word Break II

这个题目的意思是说,给你一个字符串,和一个字典。看看能不能用字典的元素组成这个字符串。题目II需要把所有可能都找出来。

我们先来解决题目I:

对于这道题我们首先想到的是用深度搜索来解决这个问题,意思就是说,用for循环和递归,一个一个的查找有没有合适的分割,有合适的分割就进行递归查找其他的部分是否由合适的分割。但是这种深度搜索的复杂度太高了。

然后我们考虑的是用动态规划的方法来实现。采用动规的方法,需要总结出这个动规的初始状态和动规的转移方程。

设状态为f(i) ,表示s[0,i]是否可以分词(二维状态,我们采用bool型来表示)。显而易见的一个规律是,0到i直接,存在任何一个s[0,j]为true,并且s[i,j]属于字典,则为正,假如s[0,j]和s[i,j]属于字典这两个任何一个为false ,则为false。因此我们有:

f(i)=any_of(f(j)&&s[j+1,i]属于dict),0<=j<i。

下面是两种解法的代码:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

class Solution1

{

public:

bool wordBreak(string s, unordered_set<string> &dict)

{

//这是一个蛮力解决的方法。 深度搜索

int len = s.length();

if(len < 1)

return true;

bool flag = false;

for(int i = 1; i <= len; i++)

{

string tmpstr = s.substr(0, i);

if(dict.find(tmpstr) != dict.end())

{

if(tmpstr.length() == len)return true;

flag = wordBreak(s.substr(i), dict);

}

if(flag == true)

{

return true;

}

}

}

};

class Solution

{

public:

bool wordBreak(string s, unordered_set<string> &dict)

{

int len = s.length();

vector<bool> bvec(len + 1, false);

bvec[0] = true;

for(int i = 1; i <= len; i++)

{

for(int j = 0; j < i; j++)

{

if(bvec[j] && (dict.find(s.substr(j, i - j)) != dict.end()))

{

bvec[i] = true;

break;

}

}

}

return bvec[len];

}

};
解决了问题I,下面我们来解决问题II。要把所有的可能性求出来,我们需要再问题I的基础上完成。基本思想是:定义一个二维的数组来,代替一维的数组,毕竟只有二维的数组才能指明方向。 然后用深度搜索的方法,以及回溯剪枝的形式把这些可能性给遍历回来,虽然复杂度有点高,不过要求所有可能性那也是没有办法的了。

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

class Solution

{

public:

vector<string> wordBreak(string s, unordered_set<string> &dict)

{

int len = s.size();

vector<vector<bool>> prev(len + 1, vector<bool>(len, false));

vector<bool> dp(len + 1, false);

dp[0] = true;

for(int i = 1; i <= len; i++)

{

for(int j = 0; j < i; j++)

{

if(dp[j] && dict.find(s.substr(j, i - j)) != dict.end())

{

dp[i] = true;

prev[i][j] = true;

}

}

}

vector<string> path;

vector<string> ret;

gen_path(s, s.length(), prev, path, ret);

return ret;

}

void gen_path(const string &s, int cur, const vector<vector<bool>> &prev, vector<string> &path, vector<string> &ret)

{

if(cur == 0)

{

string tmp;

int i = path.size() - 1;

for(; i > 0; i--)

{

tmp += path[i] + " ";

}

tmp += path[0];

ret.push_back(tmp);

}

for(int i = 0; i < s.length(); i++)

{

if(prev[cur][i])

{

path.push_back(s.substr(i, cur - i));

gen_path(s, i, prev, path, ret);

path.pop_back();

}

}

}

};

问题八:Scarmble String

这道题的题目有点难以理解。相当费劲啊。

Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty

substrings recursively.

Below is one possible representation of s1 = "great":

great

/ \

gr eat

/ \ / \

g r e at

/ \

a t

To scramble the string, we may choose any non-leaf node and swap its two children.

For example, if we choose the node "gr" and swap its two children, it produces a scrambled string "rgeat".

rgeat

/ \

rg eat

/ \ / \

r g e at

/ \

a t

We say that "rgeat" is a scrambled string of "great".

Similarly, if we continue to swap the children of nodes "eat" and "at",

it produces a scrambled string "rgtae".

rgtae

/ \

rg tae

/ \ / \

r g ta e

/ \

t a

We say that "rgtae" is a scrambled string of "great".

Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1.

其实这个题目简单的说就是:

s1和s2是scramble的话,那么必然存在一个在s1上的长度l1,将s1分成s11和s12两段,同样有s21和s22。那么要么s11和s21是scramble的并且s12和s22是scramble的;要么s11和s22是scramble的并且s12和s21是scramble的。

基本思路:其实上面就说了基本思路了。这道题原本是可以用动规来做的。不过由于不是很理解动规的做法,无奈之下只能用深度搜索的方法来做了。

深度搜索的复杂度比较高,因此我们需要采用很多的剪枝判断条件。也用到了一些优化的小技巧来实现。

具体代码如下:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

class Solution

{

public:

bool isScramble(string s1, string s2)

{

int len1 = s1.length();

int len2 = s2.length();

if(s1 == s2)

return true;

if(len1 != len2)

return false;

int A[26] = {0};

for(int i = 0; i < len1; i++)

{

A[s1[i] - 'a']++;

}

for(int i = 0; i < len2; i++)

{

A[s2[i] - 'a']--;

}

for(int i = 0; i < 26; i++)

{

if(A[i] != 0)

return false;

}

bool result = false;

for(int i = 1 ; i < s1.size() ; ++i)

{

result = isScramble(s1.substr(0, i) , s2.substr(0, i)) && isScramble(s1.substr(i) , s2.substr(i));

if(result) return true;

result = isScramble(s1.substr(0, i) , s2.substr(s1.size() - i, i)) && isScramble(s1.substr(i) , s2.substr(0 , s1.size() - i));

if(result) return true;

}

return false;

}

};

题目九:Triage

题目

[

[2]

[3,4]

[6,5,7]

[4,1,8,3]

]

由上面往下面走,求路径最小的。

这个一道经典的动规题目

首先求动规的转移方程

dp[i][j]=min(dp[i][j+1],dp[i][j+1])+1;

里面有个小技巧就是从倒数第二行开始计算。重复利用原来的数组。

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

class Solution

{

public:

int mininumTatal(vector<vector<int> > &triangle)

{

int len = triangle.size();

for(int i = len - 2; i >= 0; i--)

{

for(int j = 0; j < triangle[i].size(); j++)

{

triangle[i][j] += min(triangle[i + 1][j], triangle[i + 1][j + 1]);

}

}

return triangle[0][0];

}

};

题目十:Palindrome Partitioning II

这道题的意思是:给你一个字符串,用最小的刀切割字符串,使得字符串要么是回文字符串,要么是单个字符(没有回文的情况下)。

For example, given s = "aab",

Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.

这道题我们第一眼看上是可以用深度搜索来解决问题的。

我们当然是从整个字符串是否是回文开始判断,其实for循环加上递归和剪枝就可以完成了。

但是用深度搜索复杂度比较高,下面我们用动态规划来实现:

动规的基本套路就是:初始化状态+状态转移方程。

首先我们来看看思路,我们先假设所有的字符都需要切割,因此有最大的切割术,数组n值为-1,数组n-1值为0,这样一直下去。

然后来看看状态转移方程:

我们还是需要一个辅助的数组dp[i][j],这个数组主要是用来判断回文用的,因为回文字符串有相应规律,其判断条件是[s[i]==s[j]&&(j-i>2||dp[i+1][j-1])],然后,用二级for循环进行计算。

具体实现的代码如下:

深度搜索的代码如下所示:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

/*

下面是一个剪枝回溯的方法:*/

bool isPalindrome(string &s, int start, int end)

{

while(start < end)

{

if(s[start++] != s[end--])

return false;

}

return true;

}

void DFS(string &s, int start, int depth, int &min)

{

if(start == s.size())

{

if(min > depth - 1)

min = depth - 1;

return ;

}

for(int i = s.size() - 1; i >= start; i--)

{

if(isPalindrome(s, start, i))

{

DFS(s, i + 1, depth + 1, min);

}

}

}

int minCut1(string s)

{

int min = INT_MAX;

DFS(s, 0, 0, min);

return min;

}
动态规划:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

int minCut(string s)

{

int len = s.size();

vector<vector<bool> >dp(len, vector<bool>(len, false));

vector<int> f(len + 1, 0);

for(int i = 0; i <= len; i++)

{

f[i] = len - i - 1;

}

for(int i = len - 1; i >= 0; i--)

{

for(int j = i; j < len; j++)

{

if(s[i] == s[j] && (j - i > 2 || dp[i + 1][j - 1]))

{

dp[i][j] = true;

f[i] = min(f[i], f[j] + 1);

}

}

}

return f[0];

}

题目十一:Interleaving String

这道题的意思是说,给你两个字符,然后有一个第三个字符串,问这个第三个字符是否有前面两个字符的唯一两个子序列呢。

这是一道典型动规题目。

首先我们判断两个字符串的长度之和等于第三个字符串。

然后我们利用动规的方法来实现实现解决:

我们先来看看初始化状态:

f[i][0]=f[i-1][0]&&(s1[i-1]==s3[i-1]);

f[0][i]=f[0][i-1]&&(s2[i-1]==s3[i-1]);

我们从最后一个字符串来考虑问题

如果 s1[i-1]==s3[i+j-1]相同,前面的状态f[i-1][j]是true。

如果 s2[j-1]==s3[i+j-1]相同,前面的状态f[i][j-1]是true。

两个其中一个正确即可,实现状态转移。

代码实现如下:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

class Solution

{

public:

bool isInterleave(string s1, string s2, string s3)

{

int len1 = s1.size();

int len2 = s2.size();

int len3 = s3.size();

if(len3 != len1 + len2)

return false;

vector<vector<bool>> f(len1 + 1, vector<bool>(len2 + 1, true));

//先求初始化状态

for(int i = 1; i <= len1; i++)

{

f[i][0] = f[i - 1][0] && (s1[i - 1] == s3[i - 1]);

}

for(int i = 1; i <= len2; ++i)

{

f[0][i] = f[0][i - 1] && (s2[i - 1] == s3[i - 1]);

}

//根据动态规划的状态转移方程求解结果

for(int i = 1; i <= len1; i++)

{

for(int j = 1; j <= len2; j++)

{

f[i][j] = (s1[i - 1] == s3[i + j - 1] && f[i - 1][j]) ||

(s2[j - 1] == s3[i + j - 1] && f[i][j - 1]);

}

}

return f[len1][len2];

}

};

题目十二:
[title3]
Distinct Subsequences[/title3]

Given a string S and a string T, count the number of distinct subsequences of T in S.

A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie,
"ACE"
is
a subsequence of
"ABCDE"
while
"AEC"
is
not).

Here is an example:

S =
"rabbbit"
, T =
"rabbit"


Return
3
.
这道题就是说,T在S中有多少组合代替方式
这也是一道经典的动态规划的问题。

还是根据动规的基本规律来搞,

首先是初始化状态 dp[i][0]=1;

然后我们用两个for循环来实现,如果S[i-1]==T[j-1]我们可以知道i,j的状态是有i-1,j-1或者i-1,j转换过来,所以对他们求和。

如果不等的话,直接去掉S的一位dp[i][j]=dp[i-1][j](即是忽略这一位。)

我们直接上代码吧

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

class Solution

{

public:

int numDistinct(string S, string T)

{

int len1 = S.length();

int len2 = T.length();

vector<vector<int >> dp(len1 + 1, vector<int>(len2 + 1, 0));

//初始状态

for(int i = 0; i <= len1; i++)

{

dp[i][0] = 1;

}

for(int i = 1; i <= len1; i++)

{

for(int j = 1; j <= len2; j++)

{

if(S[i - 1] == T[j - 1])

{

dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];

}

else

{

dp[i][j] = dp[i - 1][j];

}

}

}

return dp[len1][len2];

}

};


题目十三:
[title3]
Best Time to Buy and Sell Stock III[/title3]

这个题目的意思是:

两次买卖,使得利润最大

我们可以这样考虑:

新建两个数组,一个数组表示从0到i天的最大利润,第二个数组表示第i天到n-1天的最大利润,

从左往右考虑,f1max=f[i]-Min ,但是这个要考虑之前最大值。

从右往左考虑,f2max=Max-f[i] ,但是要考虑之前的最大值。

根据上面两个数组,我们再扫描一下数组,求出f1+f2的数组的最大值。

代码实现如下所示:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

class Solution

{

public:

int maxProfit(vector<int> &prices)

{

int len = prices.size();

if(len < 2)

return 0;

vector<int> f1(len, 0);

vector<int> f2(len, 0);

int MinPrices = prices[0];

int MaxPrices = prices[len - 1];

for(int i = 1; i < len; i++)

{

if(prices[i] < MinPrices)

{

MinPrices = prices[i];

}

f1[i] = max(prices[i] - MinPrices, f1[i - 1]);

}

for(int i = len - 2; i >= 0; i--)

{

if(prices[i] > MaxPrices)

{

MaxPrices = prices[i];

}

f2[i] = max(MaxPrices - prices[i], f2[i + 1]);

}

int Max = f1[0] + f2[0];

for(int i = 1; i < len; i++)

{

if(Max < f1[i] + f2[i])

{

Max = f1[i] + f2[i];

}

}

return Max;

}

};

题目十四:
[title3]
Decode Ways[/title3]

A message containing letters from
A-Z
is being encoded
to numbers using the following mapping:
'A' -> 1
'B' -> 2
...
'Z' -> 26


Given an encoded message containing digits, determine the total number of ways to decode it.

For example,

Given encoded message
"12"
, it could be decoded as
"AB"
(1
2) or
"L"
(12).

The number of ways decoding
"12"
is 2.

这道题其实就是变相的斐波拉契数列。只不过这道题是有约束条件的。很明显每两个数可以分别拆成两种方式,一种是分别一个一个单,以及两位数。特别要注意的是'0',以及大于'7'的数字。这种动规的题目,需要从后面
代码如下:

C++ Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

class Solution

{

public:

int numDecodings(string s)

{

if(s.length() == 0)

return 0;

int len = s.length();

if(len < 1)

return 0;

vector<int> arr(len, 0);

int i = len - 1;

arr[i] = (s.at(i) == '0') ? 0 : 1; //从后面开始算起

i--; //从倒数第二位开始算起

while(i >= 0)

{

if(s.at(i) == '0') //下一位是10

{

arr[i] = 0;

}

else if(s.at(i) == '1' || s.at(i) == '2' && s.at(i + 1) <= '6') //可以分解成两种可能

{

if(i == len - 2)

{

arr[i] = arr[i + 1] + 1;

}

else

{

arr[i] = arr[i + 1] + arr[i + 2];

}

}

else //只有一种可能分解

{

arr[i] = arr[i + 1];

}

i--;

}

return arr[0];

}

};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: