《剑指offer》习题解答(C/C++)
1.二维数组中的查找
/* 题目:在一个二维数组中,没一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 */ #include<stdio.h> #include<string.h> // 从右上角开始比较 bool Find(int *matrix, int rows, int columns, int number) { bool found = false; if (matrix != NULL && rows > 0 && columns > 0) { int row = 0; int column = columns - 1; while (row < rows&&column >= 0) { if (matrix[row*columns + column] == number) { found = true; break; } else if (matrix[row*columns + column ]> number) column--; else row++; } } return found; } // 从左下角开始比较 bool Find_2(int *arr, int rows, int columns, int number) { bool find = false; if (arr != NULL&&rows > 0 && columns > 0) { int row = rows - 1; int column = 0; while (row >=0 && column <= columns - 1) { if (arr[row*columns + column] == number) { find = true; break; } else if (arr[row*columns+column] < number) column++; else row--; } } return find; } int main() { int arr [4][4]= { {1, 2, 8, 9}, {2,4 ,9 ,12}, {4, 7, 10, 13}, {6, 8, 11, 15} }; Find_2(*arr, 4, 4, 7); return 0; }
2.字符串
C/C++中的每个字符串都以’\0’结尾。为了节省空间,C/C++经常把常量字符串放到一个单独的内存区域。当几个指针赋值给相同的常量字符串时,它们实际会指向相同的地址空间。例如:
#include<stdio.h> int main() { char str1[] = "Hello World"; char str2[] = "Hello world"; char *str3 = "Hello world"; char *str4 = "Hello world"; if (str1 == str2) printf("str1 and str2 are same. \n"); else printf("str1 and str2 are not same.\n"); if (str3 == str4) printf("str3 and str4 are same.\n"); else printf("str3 and str4 are not same.\n"); return 0; }
输出如下:
str1 and str2 are not same.
str3 and str4 are same.
题目:请实现一个函数,把字符串中的每个空格替换成”%20”。例如输入“We are happy.”,则输出为”We%20are%20%20happy.”。实现代码如下:
#include<stdio.h> // lenght 为字符数组string的总容量 void ReplaceSpace(char string[], int length) { if (string == NULL&&length <= 0) return; // originalLenght为字符串string的实际长度 int originalLenght = 0; int numOfBlank = 0; int i = 0; while (string[i] != '\0') { originalLenght++; if (string[i] == ' ') numOfBlank++; i++; } int newLenght = originalLenght + numOfBlank * 2; if (newLenght <= originalLenght) return; int indexOfOriginal = originalLenght; int indexOfNew = newLenght; while (indexOfOriginal >= 0 && indexOfNew > indexOfOriginal) { if (string[indexOfOriginal] == ' ') { string[indexOfNew--] = '0'; string[indexOfNew--] = '2'; string[indexOfNew--] = '%'; } else string[indexOfNew--] = string[indexOfOriginal]; indexOfOriginal--; } } int main() { char string[] = "We are happy."; int lenght = sizeof(string) / sizeof(char); ReplaceSpace(string, lenght); int j = 0; while (string[j] != '\0') { printf("%c", string[j]); j++; } printf("\n"); return 0; }
输出结果:
We%20are%20happy.
相关题目:有两个排序的数组A1和A2,内存在A1末尾有足够的空余空间容纳A2。请实现一个函数,把A2中的所有数字插入到A1中并且所有的数字是排序的。实现代码如下:
#include<stdio.h> // len1为数组arr1的有效数字的长度 void unionArray(int arr1[], int len1, int arr2[], int len2) { // arr1_all是啊让人整个数组的空间长度 int arr1_all = len1 + len2; if (len2 ==0) return; int index_1 = len1-1; int index_2 = arr1_all-1; int index_3 = len2-1; while (index_2 > index_1) { if (arr1[index_1] > arr2[index_3]) { arr1[index_2--] = arr1[index_1]; index_1--; } else { arr1[index_2--] = arr2[index_3]; index_3--; } } } int main() { int arr1[9] = { 1,2,3,4 }; int arr2[5] = { 5,6,7,8,9 }; unionArray(arr1, 4, arr2, 5); int len = sizeof(arr1) / sizeof(int); int i = 0; while (i < len) { printf("%d", arr1[i]); ++i; } printf("\n"); return 0; }
输出结果:
123456789
3.链表
// 单向链表的节点定义 struct ListNode { int m_nValue; ListNode *m_pNext; }; // 往链表的末尾添加一个节点 void AddToTail(ListNode** pHead, int value) { ListNode* pNew = new ListNode(); // 定义一个新节点指针并为它分配内存 pNew->m_nValue = value; pNew->m_pNext = NULL; // 如果链表为空,则新添加的节点为头节点,使头指针pHead指向该节点 if (pHead == NULL) { pHead = &pNew; } else { ListNode* pNode = *pHead; while (pNode->m_pNext != NULL) pNode = pNode->m_pNext; pNode->m_pNext = pNew; } } // 删除节点函数 void RemoveNode(ListNode** pHead, int value) { if (pHead == NULL || *pHead == NULL) { return; } ListNode *pToBeDeleted = NULL; if ((*pHead)->m_nValue == value) { pToBeDeleted = *pHead; *pHead = (*pHead)->m_pNext; } else { ListNode* pNode = *pHead; while (pNode->m_pNext != NULL&&pNode->m_pNext->m_nValue!=value) pNode = pNode->m_pNext; if (pNode->m_pNext != NULL&&pNode->m_pNext->m_nValue == value) { pToBeDeleted = pNode->m_pNext; pNode->m_pNext = pNode->m_pNext->m_pNext; } if (pToBeDeleted != NULL) { delete pToBeDeleted; pToBeDeleted = NULL; } } }
题目:输入一个链表的头节点,从尾到头打印出每个节点的值。
// 题目:输入一个链表的头节点,从尾到头打印出每个节点的值。 // 方法一:从头到尾遍历链表,把节点依次放进一个栈中,当遍历完整个链表后,再从栈顶开始输出节点的值 void PrintListReversingly_Interatively(ListNode *pHead) { std::stack<ListNode*> nodes; ListNode *pNode = pHead; while (pNode != NULL) { nodes.push(pNode); pNode = pNode->m_pNext; } while (!nodes.empty()) { pNode = nodes.top(); printf("%d\n", pNode->m_nValue); nodes.pop(); } } // 方法二:利用递归。每次访问一个节点时,先输出它后面的节点 void PrintListReversingly_Recursively(ListNode* pHead) { if (pHead != NULL) { if (pHead->m_pNext != NULL) { PrintListReversingly_Recursively(pHead->m_pNext); } printf("%d\n", pHead->m_nValue); } } void haha(ListNode** pHead) { while ((*pHead)->m_pNext != NULL) { printf("%d\n", (*pHead)->m_nValue); (*pHead) = (*pHead)->m_pNext; } }
4.树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果都不含重复的数字。
#include<stdio.h> #include<iostream> // 二叉树结点定义 struct BinaryTreeNode { int m_nvalue; BinaryTreeNode* m_pLeft; BinaryTreeNode* m_pRight; }; // 函数声明 BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder); // 用递归的方法构建左右子树和根节点 BinaryTreeNode* Construct(int *preorder, int *inorder, int length) { if (preorder == NULL || inorder == NULL || length <= 0) { return NULL; } return ConstructCore(preorder,preorder+length-1,inorder,inorder+length-1); } BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder, int* startInorder, int* endInorder) { // 前序遍历第一个数字即为根节点的值 int rootValue = startPreorder[0]; BinaryTreeNode* root = new BinaryTreeNode(); root->m_nvalue = rootValue; root->m_pLeft = NULL; root->m_pRight = NULL; if (startPreorder == endPreorder) { if (startInorder == endInorder&&*startPreorder == *startPreorder) return root; else throw std::exception("Invalid input"); } // 在中序遍历中找到根节点的值 int* rootInorder = startPreorder; while (*rootInorder != rootValue && rootInorder <= endInorder) { ++rootInorder; } if (*rootInorder != rootValue&&rootInorder == endInorder) throw std::exception("Invalid input"); int leftLength = rootInorder - startInorder; int* leftPreorderEnd = startInorder + leftLength; if (leftLength > 0) { // 构建左子树 root->m_pLeft = ConstructCore(startPreorder + 1, endPreorder, startInorder, leftPreorderEnd - 1); } if (leftLength < endInorder - startInorder) { // 构建右子树 root->m_pRight = ConstructCore(leftPreorderEnd+1,endPreorder, rootInorder+1,endInorder); } }
5.栈和队列
题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入节点和在队列头部删除节点的功能。
// 队列的声明 template <typename T> class CQueue { public: CQueue(void); ~CQueue(void); // 在队列末尾添加一个结点 void appendTail(const T& node); // 删除队列的头结点 T deleteHead(); private: stack<T> stack1; stack<T> stack2; }; template <typename T> CQueue<T>::CQueue(void) { } template <typename T> CQueue<T>::~CQueue(void) { }
接下来定义两个函数:
// 往第一个栈中插入元素 template<typename T> void CQueue<T>::appendTail(const T& element) { stack1.push(element); } // 删除最先插入的元素 template<typename T> T CQueue<T>::deleteHead() { // 如果第二个栈为空,把栈一的元素依次弹出插入栈二 if (stack2.size() <= 0) { while (stack1.size()>0) { T& data = stack1.top(); stack1.pop(); stack2.push(data); } } if (stack2.size() == 0) throw new exception("queue is empty"); // 依次弹出并返回栈二栈顶元素 T head = stack2.top(); stack2.pop(); return head; }
6.查找和排序
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
// 利用二分查找法 #include<stdio.h> #include<exception> int MinInOrder(int* numbers, int index1, int index2); int Min(int* numbers, int length) { if (numbers == NULL || length <= 0) throw new std::exception("Invalid parameters"); int index1 = 0; int index2 = length - 1; int indexMid = index1; while (numbers[index1] >= numbers[index2]) { // 如果index1和index2指向相邻的两个数, // 则index1指向第一个递增子数组的最后一个数字, // index2指向第二个子数组的第一个数字,也就是数组中的最小数字 if (index2 - index1 == 1) { indexMid = index2; break; } // 如果下标为index1、index2和indexMid指向的三个数字相等, // 则只能顺序查找 indexMid = (index1 + index2) / 2; if (numbers[index1] == numbers[index2] && numbers[indexMid] == numbers[index1]) return MinInOrder(numbers, index1, index2); // 缩小查找范围 if (numbers[indexMid] >= numbers[index1]) index1 = indexMid; else if (numbers[indexMid] <= numbers[index2]) index2 = indexMid; } return numbers[indexMid]; } int MinInOrder(int* numbers, int index1, int index2) { int result = numbers[index1]; for (int i = index1 + 1; i <= index2; ++i) { if (result > numbers[i]) result = numbers[i]; } return result; } // ====================测试代码==================== void Test(int* numbers, int length, int expected) { int result = 0; try { result = Min(numbers, length); for (int i = 0; i < length; ++i) printf("%d ", numbers[i]); if (result == expected) printf("\tpassed\n"); else printf("\tfailed\n"); } catch (...) { if (numbers == NULL) printf("Test passed.\n"); else printf("Test failed.\n"); } } int main() { // 典型输入,单调升序的数组的一个旋转 int array1[] = { 3, 4, 5, 1, 2 }; Test(array1, sizeof(array1) / sizeof(int), 1); // 有重复数字,并且重复的数字刚好的最小的数字 int array2[] = { 3, 4, 5, 1, 1, 2 }; Test(array2, sizeof(array2) / sizeof(int), 1); // 有重复数字,但重复的数字不是第一个数字和最后一个数字 int array3[] = { 3, 4, 5, 1, 2, 2 }; Test(array3, sizeof(array3) / sizeof(int), 1); // 有重复的数字,并且重复的数字刚好是第一个数字和最后一个数字 int array4[] = { 1, 0, 1, 1, 1 }; Test(array4, sizeof(array4) / sizeof(int), 0); // 单调升序数组,旋转0个元素,也就是单调升序数组本身 int array5[] = { 1, 2, 3, 4, 5 }; Test(array5, sizeof(array5) / sizeof(int), 1); // 数组中只有一个数字 int array6[] = { 2 }; Test(array6, sizeof(array6) / sizeof(int), 2); // 输入NULL Test(NULL, 0, 0); return 0; }
7.递归和循环
递归虽然有简洁的优点,但是缺点也是显著的。由于递归式函数调用自身,而函数的调用是由空间和时间消耗的,所以递归的效率不如循环。
题目一:写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。斐波那契数列定义如下:
F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2)
解答代码:
#include<stdio.h> #include <cassert> // ====================方法1:递归==================== // 时间复杂度以n的指数方式递增 long long Fibonacci_Solution1(unsigned int n) { if (n <= 0) return 0; if (n == 1) return 1; return Fibonacci_Solution1(n - 1) + Fibonacci_Solution1(n - 2); } // ====================方法2:循环==================== // 时间复杂度为O(n) long long Fibonacci_Solution2(unsigned n) { int result[2] = { 0, 1 }; if (n < 2) return result[n]; long long fibNMinusOne = 1; long long fibNMinusTwo = 0; long long fibN = 0; for (unsigned int i = 2; i <= n; ++i) { fibN = fibNMinusOne + fibNMinusTwo; fibNMinusTwo = fibNMinusOne; fibNMinusOne = fibN; } return fibN; } // ====================方法3:基于矩阵乘法==================== // 时间复杂度为O(logn) struct Matrix2By2 { Matrix2By2 ( long long m00 = 0, long long m01 = 0, long long m10 = 0, long long m11 = 0 ) :m_00(m00), m_01(m01), m_10(m10), m_11(m11) { } long long m_00; long long m_01; long long m_10; long long m_11; }; Matrix2By2 MatrixMultiply ( const Matrix2By2& matrix1, const Matrix2By2& matrix2 ) { return Matrix2By2( matrix1.m_00 * matrix2.m_00 + matrix1.m_01 * matrix2.m_10, matrix1.m_00 * matrix2.m_01 + matrix1.m_01 * matrix2.m_11, matrix1.m_10 * matrix2.m_00 + matrix1.m_11 * matrix2.m_10, matrix1.m_10 * matrix2.m_01 + matrix1.m_11 * matrix2.m_11); } Matrix2By2 MatrixPower(unsigned int n) { assert(n > 0); Matrix2By2 matrix; if (n == 1) { matrix = Matrix2By2(1, 1, 1, 0); } else if (n % 2 == 0) { matrix = MatrixPower(n / 2); matrix = MatrixMultiply(matrix, matrix); } else if (n % 2 == 1) { matrix = MatrixPower((n - 1) / 2); matrix = MatrixMultiply(matrix, matrix); matrix = MatrixMultiply(matrix, Matrix2By2(1, 1, 1, 0)); } return matrix; } long long Fibonacci_Solution3(unsigned int n) { int result[2] = { 0, 1 }; if (n < 2) return result[n]; Matrix2By2 PowerNMinus2 = MatrixPower(n - 1); return PowerNMinus2.m_00; } // ====================测试代码==================== void Test(int n, int expected) { if (Fibonacci_Solution1(n) == expected) printf("Test for %d in solution1 passed.\n", n); else printf("Test for %d in solution1 failed.\n", n); if (Fibonacci_Solution2(n) == expected) printf("Test for %d in solution2 passed.\n", n); else printf("Test for %d in solution2 failed.\n", n); if (Fibonacci_Solution3(n) == expected) printf("Test for %d in solution3 passed.\n", n); else printf("Test for %d in solution3 failed.\n", n); } int main() { Test(0, 0); Test(1, 1); Test(2, 1); Test(3, 2); Test(4, 3); Test(5, 5); Test(6, 8); Test(7, 13); Test(8, 21); Test(9, 34); Test(10, 55); Test(40, 102334155); return 0; }
题目二:一只青蛙一次可以跳上1级台阶,也可以跳上2级.求该青蛙跳上一个n级台阶有多少种跳法。
其实这道题的解答就是求上面的斐波那契数列。
8.位计算
位运算是把数字用二进制表示之后,对每一位上0或者1的运算。
题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1.因此如果输入为9,输出为2.
/*考虑到有负数的情况,如果只是对输入进行简单的右移,那么负数的高位会自动的添加1则可能会进入死循环*/ /*常规解法: 不进行右移,先把输入与标志位进行与判断最低位是不是1,再把标志位进行左移判断次低位是不是1 依次类推直到最高位,即可计算出1的个数 */ int NumberOf1(int n){ int count=0; unsigned int flag=1; while(flag){ if(n&flag) count++; flag<<1; } return count; } /*进阶解法: 可进行论证把一个数减去1,再和原来数做与运算,会把该整数最右边一个1变成0。例如1100,减去1后为1011,两数与后为1000。 */ int NumberOf2(int n){ int count=0; while(n){ ++count; n=(n-1)&n; } return count; }