Happy剑指offer:第2章题目笔记
概念题
定义一个空的类,里面没有任何成员变量和成员函数。对该类型求sizeof,会得到的结果是1。因为虽然没有成员变量和成员函数,没有有用信息,但是声明实例的时候,它必须在内存之中占用一定的空间,空间的大小由编译器决定,Visual Studio其分配空间为1。哪怕类内定义了构造函数和析构函数,这个结果仍然不会改变,仍然会是1。因为在实例需要初始化或者析构的时候,只需要调用这两个函数,即取得这两个函数的地址即可。这两个函数不会为实例增添任何内容。
在上面的例子之中,如果把析构函数构造成虚函数,那么结果会发生变化。这是因为编译器一旦发现类之中有虚函数,那么它一定会生成一个指向虚函数的虚函数表。在虚函数表之中,生成一个指向虚函数表的指针,指针的大小随系统的位数决定。32位系统指针占用4个字节,求sizeof之后,结果为4,64为系统占用8个字节,最终结果为8 。
判断题1
涉及到的一个问题是类内赋值构造函数的传值问题。如果允许复制构造函数传值,就会在赋值构造函数内部递归一般的进行对复制构造函数的调用,最终必然导致栈溢出。
如果仔细观察就会发现,如果要是调用 复制构造函数,赋值构造函数的输入参数必然是引用形式。
举例;
A(const A &other)
面试题1
自己在定义一个类的时候,对构造函数,仍然会有一点点的不确定。
尝试了一把运算符重载。感觉还好,注意的是
1. 输入为引用,输出为引用
2. 对于指针,注意分配内存和释放内存。
内存泄露指的是在定义指针之后为其动态分配了内存,但是在使用完毕之后并未释放该内存。导致该内存一直被占用。其实说白了就是该内存空间使用完毕之后未回收。
//offer1 class CMyString { public: CMyString(char* pData = NULL);//这个应该只能算是声明吧 CMyString(const CMyString& str); ~CMyString(void); CMyString& operator =(const CMyString &str); private: char* m_pData; }; CMyString& CMyString::operator = (const CMyString &str) { if (this == &str) return *this; delete[]m_pData; m_pData == NULL; m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData, str.m_pData); return *this; }
面试题2
singleton模式:即单例模式。数学与逻辑学中,singleton定义为“有且仅有一个元素的集合”
如果一个类和singleton模式相关联,那么毫无疑问,这是一个特殊类,因为这种类只有一个对象,所以称为单例,单个例子嘛。一般这种单例非常容易被外界访问。
优点:节约系统资源。
生成实例的方法是静态方法。
//offer2 实现singleton模式 class Singleton { public: static Singleton& GetInstance() { static Singleton Instance; return Instance; } private: Singleton(); Singleton(const Singleton& other); ~Singleton(); Singleton& operator=(const Singleton& other); };
面试题3
注意int* matrix只能是代表一维数组。思路知道之后这道题突然间变得好简单。
//offer3 bool Find(int* matrix, int rows, int columns, int number) { bool IsFound = false; if (matrix != NULL && rows > 0 && columns > 0) { int row = rows-1; int column = 0; while (row >= 0 && column < columns) { if (matrix[row*columns + column] == number) { IsFound = true; break; } else { if (matrix[row*columns + column] < number) { ++column; } else { --row; } } } } return IsFound; }
判断题2:
两个字符串均定义为char str[]类型的时候,判断两个字符串是否相等,需要判定两个是不是指向相同的内存空间。这个问题十分重要。并非内容一样就可以的。另一种:char* str就不存在这个问题。它们定义的时候会自动指向对应的内存地址的。
面试题4:
//offer4 void ReplaceBlank(char str[], int length) { if (str == NULL || length < 0) return; int LengthOriginal = strlen(str); int LengthNew = LengthOriginal; int i = 0; while (str[i] != '\0') { if (str[i] == ' ') { LengthNew += 2; } ++i; } int IndexOri = LengthOriginal; int IndexNew = LengthNew; while (IndexOri >= 0 && IndexNew > IndexOri)//IndexNew>IndexOri如果前面一大片都没有空格的话,那么这个判断可以节约很多时间 { if (str[IndexOri] == ' ') { str[IndexNew--] = '0'; str[IndexNew--] = '2'; str[IndexNew--] = '%'; } else str[IndexNew--] = str[IndexOri]; IndexOri--; } }
面试题4附加题
void InerstNumber(int* arr1, int length1,int* arr2, int length2,int length) { if (arr1 == NULL || length < 0) return; if (arr2 == NULL) { arr2 = arr1; return; } if (length1 + length2>length) return; int index1 = length1-1; int index2 = length2-1; int index3 = length1 + length2-1; while (index1>=0&&index2>=0&&index3 >=0 ) { if (arr1[index1] > arr2[index2]) { arr2[index3--] = arr1[index1--]; } else if (arr1[index1] < arr2[index2]) { arr2[index3--] = arr2[index2--]; } else { arr2[index3--] = arr2[index2--]; arr2[index3--] = arr1[index1--]; } } if (index2 < 0) { while (index1>=0) arr2[index3--] = arr1[index1--]; } }
在链表的尾部添加结点
void AddToTail(ListNode **pHead, int value) { ListNode* pNew = new ListNode(); pNew->m_nValue = value; pNew->m_pNext = NULL; 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; 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; } }
面试题5从尾到头打印链表。这道题可以在栈的帮助下用迭代的方式实现。或者使用递归方式实现。
迭代方式:
void PrintListNode(ListNode* pHead) { if (pHead == NULL) return; ListNode* pNode = pHead; stack<ListNode*> nodes; while (pNode != NULL) { nodes.push(pNode); pNode = pNode->m_pNext; } while (!nodes.empty()) { pNode = nodes.top(); nodes.pop(); cout << pNode->m_nValue << "\t"; } }
递归方式
//从尾到头打印链表,递归方式 void PrintListNode_Recursively(ListNode* pHead) { ListNode* pNode = pHead; if (pNode != NULL) { if (pNode->m_pNext != NULL) { PrintListNode_Recursively(pNode->m_pNext); } } cout << pNode->m_nValue << "\t"; }
//面试题6 重建二叉树 BinaryTreeNode* Construct(int* PreOrder, int* Inorder, int length) { if (PreOrder == NULL || Inorder == NULL || length <= 0) return NULL; else return ConstructCore(PreOrder, PreOrder + length - 1, Inorder, Inorder + length - 1); } BinaryTreeNode* ConstructCore(int* PreOrderStart, int* PreOrderEnd, int* InOrderStart, int*InOrderEnd) { int RootValue = PreOrderStart[0]; BinaryTreeNode* Root = new BinaryTreeNode(); Root->m_nValue = RootValue; Root->m_pLeft = Root->m_pRight = NULL; if (PreOrderEnd == PreOrderStart) { if (InOrderStart == InOrderEnd&&InOrderStart[0] == RootValue) return Root; if (InOrderStart == InOrderEnd&&InOrderStart[0] != RootValue) throw exception("Invalid Input"); } //构建左右子树 int* PreLeftRoot = PreOrderStart + 1; int LeftTreeLength = 0; int* InRoot = InOrderStart; while (InRoot!=InOrderEnd&&*InRoot != RootValue) { InRoot++; LeftTreeLength++; } if (InRoot == InOrderEnd&&*InRoot != RootValue) throw exception("Invalid Input"); if (LeftTreeLength > 0) { Root->m_pLeft = ConstructCore(PreOrderStart + 1, PreOrderStart + LeftTreeLength, InOrderStart, InOrderStart + LeftTreeLength - 1); } if (LeftTreeLength < InOrderEnd - InOrderStart) { Root->m_pRight = ConstructCore(PreOrderStart + LeftTreeLength + 1, PreOrderEnd, InRoot + 1, InOrderEnd); } }
//用两个堆栈做成一个队列 template<typename T>class CQueue { public: CQueue(); ~CQueue(void); void AppendTail(const T& node); T& DeleteHead(); private: stack<T> stack1; stack<T> stack2; }; template<typename T> void CQueue::AppendTail(const T& node) { stack1.push(node); } template<typename T> T& CQueue::DeleteHead() { if (stack2.empty()) { if (!stack1.empty()) { while (!stack1.empty()) { T& node = stack1.top(); stack1.pop(); stack2.push(node); } } else throw exception("Invalid Input"); } T node2 = stack2.top(); stack2.pop(); return node2; }
用两个队列实现一个堆栈的方法和上面类似。只要充分利用队列先进先出和堆栈后进先出的特点即可。
template<typename T>T CStack::DeleteHead(const T& node) { if (q1.empty()) { while (!q2.empty()) { T& node = q2.top(); q2.pop(); if (!q2.empty()) q1.push(node); } } else { while (!q1.empty()) { T& node = q1.top(); q1.pop(); if (!q1.empty()) q2.push(node); } } }
//快速排序 void swap(int &a, int &b) { int temp; temp = a; a = b; b = temp; } int Partition(int data[], int length, int start, int end) { if (data == NULL || length <= 0 || start < 0 || end >= length) throw exception("Invalid Input"); int index = (start + end) / 2; int small = start - 1; swap(data[index], data[end]); for (int index = start; index < end; ++index) { if (data[index] < data[end]) { ++small; if (small != index) swap(data[small], data[index]); } } ++small; swap(data[small], data[end]); return small; } void QuickSort(int data[], int length, int start, int end) { if (start == end) return; int index = Partition(data, length, start, end); if (index>start) QuickSort(data, length, start, index - 1); if (index < end) QuickSort(data, length, index + 1, end); }