您的位置:首页 > 编程语言 > C语言/C++

面试考题之9.2:链表(C/C++版)

2015-06-07 16:35 393 查看
2.1 编写代码,移除未排序链表中的重复结点。进阶:如果不得使用临时缓冲区,该怎么解决?

解决方案:

方案1: 使用散列表

暂略

方案2:不借助额外缓冲区

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

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include <cstdlib> // free()

#include "LinkedList.h"

using namespace std;

void printData(LinkedList aList)

{

cout << aList->data << " ";

return;

}

/************************************************************************/

// 函数名称:deleteDups

// 函数目的:移除链表中重复的节点

// 函数参数:myList: 待操作的链表

// 函数返回:无

// 使用条件:

/************************************************************************/

void deleteDups(LinkedList theList)

{

if (theList == NULL) return;

LinkedList current = theList;

while(current != NULL){

// 移除后续值相同的所有结点

LinkedList runner = current;

while (runner->next != NULL){

if (runner->next->data == current->data){

LinkedList nextNode = runner->next;

runner->next = runner->next->next;

freeNode(nextNode);

}

else

runner = runner->next;

}

current = current->next;

}

return;

}

int main()

{

int arr[] = { 12, 13, 2, 4, 12, 6, 6, 8, 10, 4, 4, 2 };

for (size_t i = 0; i < sizeof(arr) / sizeof(int); i++){

//insert( makeNode(arr[i]) );

insertBack( makeNode(arr[i]) );

}

traverse(printData);

cout << endl;

LinkedList theList = getHead();

deleteDups(theList);

cout << "调用deleteDups()函数后:" << endl;

traverse(printData);

cout << endl;

destroy(); // 释放链表

getchar();

return 0;

}

运行结果:



思考体会:

1、散列表怎样解决该问题?

2.2 实现一个算法,找出单向链表中倒数第K个结点。

解决方案:

方案1:递归

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

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include "LinkedList.h"

using namespace std;

void printData(LinkedList aList)

{

cout << aList->data << " ";

return;

}

/************************************************************************/

// 函数名称:nthToLast

// 函数目的:求倒数第K个结点

// 函数参数:aList: 待操作的链表, k:第K个结点、i:计数器

// 函数返回:无

// 使用条件:

/************************************************************************/

LinkedList nthToLast(LinkedList aList, size_t k, size_t& i)

{

if (NULL == aList) {

return NULL;

}

LinkedList theList = nthToLast(aList->next, k, i);

i += 1;

if (i == k){

return aList;

}

return theList;

}

int main()

{

int arr[] = { 12, 13, 2, 4, 12, 6, 6, 8, 10, 4, 4, 2 };

for (size_t i = 0; i < sizeof(arr) / sizeof(int); i++){

insertBack( makeNode(arr[i]) );

}

traverse(printData);

cout << endl;

LinkedList theList = getHead();

size_t k1 = 1, k2 = 4, i1 = 0, i2 = 0;

LinkedList alist1 = nthToLast(theList, k1, i1);

LinkedList alist2 = nthToLast(theList, k2, i2);

cout << "K1 = " << alist1->data << endl;

cout << "K2 = " << alist2->data << endl;
destroy(); // 释放链表
getchar();

return 0;

}

其他方案:

运行结果:



思考体会:

1、链表与递归的关系总是那么若影若离,本题主要考查递归在链表中的使用。
2、其他高效方案?

2.3 实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点。

示例

输入:单向链表a->b->c->d->e中的结点c.

输出:不返回任何数据,但该链表变为:a->b->d->e.

解决方案:

解法:将后继结点的数据复制到当前结点,然后删除这个后继结点。

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

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include "LinkedList.h"

using namespace std;

void printData(LinkedList aList)

{

cout << aList->data << " ";

return;

}

/************************************************************************/

// 函数名称:deletNode2

// 函数目的:删除单链表中的某个结点

// 函数参数:pNode:链表结点

// 函数返回:true:删除成功

// 使用条件:pNode为非尾结点

/************************************************************************/

bool deletNode2(LinkedList pNode)

{

if (pNode == NULL || pNode->next == NULL)

return false;

LinkedList nextNode = pNode->next;

pNode->data = nextNode->data;

pNode->next = nextNode->next;

freeNode(nextNode);

return true;

}

int main()

{

int arr[] = { 12, 13, 2, 4, 12, 6, 6, 8, 10, 4, 4, 2 };

for (size_t i = 0; i < sizeof(arr) / sizeof(int); i++){

insertBack( makeNode(arr[i]) );

}

traverse(printData);

cout << endl;

LinkedList theList = getHead();

LinkedList pNode = search(8);

deletNode2(pNode);

traverse(printData);

destroy(); // 释放链表

getchar();

return 0;

}
运行结果:



思考体会:

1、只要考查特殊情况。

2、在C++中删除后继结点时要手动删除。

2.4 编写代码,以给定值x为基准将链表分割为两部分,所有小于x的结点排在大于或等于x的结点之前。

解决方案:

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

91

92

93

94

95

96

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include "LinkedList.h"

using namespace std;

void printData(LinkedList aNode)

{

cout << aNode->data << " ";

return;

}

/************************************************************************/

// 函数名称:partition

// 函数目的:以定值X分割pList

// 函数参数:pList:待操作链表首结点

// 函数返回:

// 使用条件:

/************************************************************************/

LinkedList partition(LinkedList pList, int x)

{

LinkedList beforeStart = NULL;

LinkedList beforeEnd = NULL;

LinkedList afterStart = NULL;

LinkedList afterEnd = NULL;

if (pList == NULL) return NULL;

// 分割链表

LinkedList current = pList; // 指向第一个元素

while (current != NULL){

// current结点要插入before或after表

LinkedList nextNode = current->next;

current->next = NULL;

if (current->data < x){

// 将结点插入before链表

if (beforeStart == NULL){

beforeStart = current;

beforeEnd = beforeStart;

}else {

beforeEnd->next = current;

beforeEnd = current;

}

}else {

// 将结点插入before链表

if (afterStart == NULL){

afterStart = current;

afterEnd = afterStart;

}else {

afterEnd->next = current;

afterEnd = current;

}

}

current = nextNode;

} // end while()

if (beforeStart == NULL) {

return afterStart;

}

// 合并before和after链表

beforeEnd->next = afterStart;

return beforeStart;

}

int main()

{

int arr[] = { 12, 13, 2, 4, 12, 6, 6, 8, 10, 4, 4, 2 };

for (size_t i = 0; i < sizeof(arr) / sizeof(int); i++){

insertBack( makeNode(arr[i]) );

}

traverse(printData);

cout << endl;

LinkedList theList = getHead();

theList = partition(theList, 8);

sethead(theList);

traverse(printData);

cout << endl;

destroy(); // 释放链表

getchar();

return 0;

}

运行结果:



思考体会:

1、没有重新分配内存存放新的链表,靠移动指针来完成题设要求。

2、注意处理时一些细节。

2.5 给定两个链表表示的整数,每个结点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。

示例:

输入:(7->1->6) + (5->9->2), 即:617 + 295.

输出:2->1->9,即:912.

进阶:假设这些数位是正向存放的,请在做一遍。

示例:(6->1->7) + (2->9->5), 即:617 + 295.

输出:9->1->2, 即:912。

解决方案:

方案1:数位反向存放

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

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include "LinkedList.h"

using namespace std;

void printData(LinkedList aNode)

{

cout << aNode->data << " ";

return;

}

/************************************************************************/

// 函数名称:addLists

// 函数目的:两个链表相加

// 函数参数:list1、ist2

// 函数返回:相加之后的链表

// 使用条件: 数位反向存放

/************************************************************************/

LinkedList addLists(LinkedList list1, LinkedList list2)

{

LinkedList lt1 = list1, lt2 = list2, newList = NULL;

int addData = 0; // 相加和

int carryI = 0; // 进位值

while ( lt1 != NULL || lt2 != NULL ){

if (lt1 != NULL && lt2 != NULL){

addData = lt1->data + lt2->data;

lt1 = lt1->next;

lt2 = lt2->next;

}else if (lt1 == NULL && lt2 != NULL){

addData = lt2->data;

lt2 = lt2->next;

}else {

addData = lt1->data;

lt1 = lt1->next;

}

int totalData = addData + carryI;

newList = insertBack( newList, makeNode(totalData % 10) );

//newList = insert( newList, makeNode(totalData % 10) );

carryI = totalData / 10;

}

if (carryI > 0){

newList = insertBack( newList, makeNode(carryI) );

//newList = insert( newList, makeNode(carryI) );

}

return newList;

}

int main()

{

int arr1[] = {6, 1, 7};

int arr2[] = {2, 9, 5};

LinkedList list1 = NULL, list2 = NULL, newList = NULL;

for (size_t i = 0; i < sizeof(arr1) / sizeof(int); i++){

//list1 = insertBack( list1, makeNode(arr1[i]) );

list1 = insert( list1, makeNode(arr1[i]) );

}

for (size_t i = 0; i < sizeof(arr2) / sizeof(int); i++){

//list2 = insertBack( list2, makeNode(arr2[i]) );

list2 = insert( list2, makeNode(arr2[i]) );

}

newList = addLists(list1, list2);

cout << "list1 = "; traverse(list1, printData); cout << endl;

cout << "list2 = "; traverse(list2, printData); cout << endl;

cout << "newList = "; traverse(newList, printData); cout << endl;

getchar();

return 0;

}

运行结果:



方案2:数位正向存放

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

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include "LinkedList.h"

using namespace std;

void printData(LinkedList aNode)

{

cout << aNode->data << " ";

return;

}

/************************************************************************/

// 函数名称:addLists

// 函数目的:两个链表相加

// 函数参数:list1、ist2

// 函数返回:相加之后的链表

// 使用条件: 数位反向存放

/************************************************************************/

LinkedList addLists(LinkedList list1, LinkedList list2)

{

LinkedList lt1 = list1, lt2 = list2, newList = NULL;

int addData = 0; // 相加和

int carryI = 0; // 进位值

while ( lt1 != NULL || lt2 != NULL ){

if (lt1 != NULL && lt2 != NULL){

addData = lt1->data + lt2->data;

lt1 = lt1->next;

lt2 = lt2->next;

}else if (lt1 == NULL && lt2 != NULL){

addData = lt2->data;

lt2 = lt2->next;

}else {

addData = lt1->data;

lt1 = lt1->next;

}

int totalData = addData + carryI;

newList = insertBack( newList, makeNode(totalData % 10) );

carryI = totalData / 10;

}

if (carryI > 0){

newList = insertBack( newList, makeNode(carryI) );

}

return newList;

}

int main()

{

int arr1[] = {6, 1, 7};

int arr2[] = {3, 9, 5};

LinkedList list1 = NULL, list2 = NULL, newList = NULL;

for (size_t i = 0; i < sizeof(arr1) / sizeof(int); i++){

list1 = insertBack( list1, makeNode(arr1[i]) );

}

for (size_t i = 0; i < sizeof(arr2) / sizeof(int); i++){

list2 = insertBack( list2, makeNode(arr2[i]) );

}

list1 = reverse(list1);

list2 = reverse(list2);

newList = addLists(list1, list2);

// 转换回原来的顺序

list1 = reverse(list1);

list2 = reverse(list2);

newList = reverse(newList);

cout << "list1 = "; traverse(list1, printData); cout << endl;

cout << "list2 = "; traverse(list2, printData); cout << endl;

cout << "newList = "; traverse(newList, printData); cout << endl;

getchar();

return 0;

}

运行结果:



思考体会:

1、正向存放题设解决这里用一个函数reverse来反转链表,在通过原来的addList计算,再把得到结果反转回去。

2、其他更高效简洁方法?

3、递归求解?

2.6 给定一个有环链表,实现一个算法返回环路的开头结点。有环链表定义:

在链表中某个结点的next元素指向在它前面出现过的结点,则表明该链表存在环路。

示例:

输入:A->B->C->D->E->C(C结点出现两次)。

输出:C

解决方案:

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

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include "LinkedList.h"

using namespace std;

/************************************************************************/

// 函数名称:findBegining

// 函数目的:找到有环链表的开头结点

// 函数参数:head:有环链表

// 函数返回:环开头结点

// 使用条件:

/************************************************************************/

Node* findBegining(LinkedList head)

{

LinkedList slow = head;

LinkedList fast = head;

/* 找出碰撞处,将处于链表中LOOP_SIZE-k步的位置 */

while (fast != NULL && fast->next != NULL){

slow = slow->next;

fast = fast->next->next;

if (slow == fast) { // 碰撞

break;

}

}

/* 错误检查,没有碰撞处,也即没有环路*/

if (fast == NULL || fast->next == NULL){

return NULL;

}

/* 将slow指向首部,fast指向碰撞处,两者

* 距离环路起始处k步,若两者以相同的速度移动,

* 则必定会在环路处碰撞在一起 */

slow = head;

while (slow != fast){

slow = slow->next;

fast = fast->next;

}

/* 至此两者均指向环路起始处 */

return fast;

}

int main()

{

int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9};

for (size_t i = 0; i < sizeof(arr) / sizeof(int); i++){

insertBack( makeNode(arr[i]) );

}

// 插入后形成环路

Node* pNode = search(5);

insertBack( pNode );

LinkedList head = getHead();

Node* nodeBegin = findBegining( head );

cout << "回环开头结点是:" << ((nodeBegin != NULL) ? nodeBegin : NULL ) << endl;

cout << "回环开头结点值是:" << ((nodeBegin != NULL) ? nodeBegin->data : 0 ) << endl;

getchar();

return 0;

}

运行结果:



思考体会:

1、解答该题找到条件成立的点,得到规律。

2、经典面试题:检测链表是否有环路,的变体。

2.7 编写一个函数,检查链表是否为回文。

解决方案:

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

#include <iostream>

#include <cstring>

#include <cstdio> // getchar()

#include "LinkedList.h"

using namespace std;

/************************************************************************/

// 函数名称:isPalindromes (递归实现)

// 函数目的:判断一个链表是否是回文

// 函数参数:head: 链表

// 函数返回:true: 是回文

// 使用条件:

/************************************************************************/

bool isPalindrome(LinkedList head, size_t length, Node** nextNode)

{

bool success = false;

if ( NULL == head || length == 0){

return false;

}

else if (length == 2){ // 偶数

*nextNode = head->next;

}

else if (length == 3 ) { // 链表有奇数个元素,跳过中间元素

*nextNode = head->next->next;

}

else {

success = isPalindrome(head->next, length - 2, nextNode);

if (!success) return false;

}

if ( nextNode == NULL || (*nextNode) == NULL) {

return false;

}

// test

cout << head->data << "\t" << (*nextNode)->data << endl;

if (head->data != (*nextNode)->data) {

return false;

}

*nextNode = (*nextNode)->next; // 后移一位

return true;

}

int main()

{

int arr1[] = {6, 1, 7, 1, 6};

int arr2[] = {3, 9, 5, 5, 9, 3};

LinkedList list1 = NULL, list2 = NULL;

for (size_t i = 0; i < sizeof(arr1) / sizeof(int); i++){

list1 = insertBack( list1, makeNode(arr1[i]) );

}

for (size_t i = 0; i < sizeof(arr2) / sizeof(int); i++){

list2 = insertBack( list2, makeNode(arr2[i]) );

}

Node** theNode = &list1;

cout << "list1 = " << (isPalindrome(list1, size(list1), theNode) ?

"true" : "false") << endl;

cout << "list2 = " << (isPalindrome(list2, size(list2), theNode) ?

"true" : "false") << endl;

getchar();

return 0;

}

运行结果:



思考体会:

1、理解递归的的的巧妙运用.

2、仔细体会指针的指针的应用.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: