剑指offer相关代码整理7-8
49 把字符串转换成整数
有一个小细节,如果直接在代码中用整型的最小值0x80000000与res来进行比较,实际上默认为无符号整数。
/*49 把字符串转换成整数 */ int g_validInput = true; int MAX = 0x7fffffff; int MIN = 0x80000000; int StrToInt(string str) { if (str.size() == 0) { g_validInput = false; return 0; } int ind = 0; int flag = false; if (str[ind]<'0' || str[ind]>'9') { if (str[ind] == '+') ind++; else if (str[ind] == '-') { flag = true; ind++; } else { g_validInput = false; return 0; } } long long res = 0; for (;ind < str.size();ind++) { if (str[ind]<'0' || str[ind]>'9') { g_validInput = false; return 0; } res = res * 10 + (str[ind] - '0')*(flag?-1:1); if (res > MAX || res < MIN) { g_validInput = false; return 0; } } return (int)res; } int main() { int res = StrToInt(string("123")); return 0; }
51 数组中的重复数字
利用容器map会非常简单,也就是一种消耗空间换时间的算法。
另外有一种巧妙的解法是利用了数字的特征——长度为n的数组,数组中所有的数字范围是0-n-1。利用这个特征,可以实现更加方便快捷且不消耗空间的算法。
代码细节还是需要格外注意:
1) 首先要判断参数是否是合理的;
2) 存在数字重复的条件是——numbers[i]==numbers[numbers[i]]。
bool duplicate(int numbers[], int length, int* duplication) { if (length <= 0 || numbers == NULL) return false; for (int i = 0; i < length; i++) { if (numbers[i] < 0 || numbers[i] >= length) return false; } for (int i = 0; i < length; i++) { while (i != numbers[i]) { if (numbers[i] == numbers[numbers[i]]) { *duplication = numbers[i]; return true; }else swap(numbers[i], numbers[numbers[i]]); } } return false; }
52 构建乘积数组
53 正则表达式匹配
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。
【这道题,只有看一遍解法之后才能完整正确地写出来。主要是厘清思路不容易。】
bool match(char* str, char* pattern) { if (str == NULL || pattern == NULL) return false; if (*str=='\0'&&*pattern == '\0') return true; if (*str != '\0'&&*pattern == '\0') return false; if (*(pattern + 1) == '*') { //现在一定已经能保证pattern+1一定不出界 /*需要格外注意这里的条件判断,尤其是在*pattern==.的时候。 在这里,只有在str!='\0'时,pattern中的'.'字符才是有意义的!*/ if (*pattern == *str || (*pattern == '.'&&*str != '\0')) return match(str, pattern + 2) || match(str + 1, pattern + 2) || match(str + 1, pattern); else return match(str, pattern + 2); } if (*pattern == *str || (*pattern == '.'&&*str != '\0')) return match(str + 1, pattern + 1); return false; }
54 表示数值的字符串
非常麻烦的一道题目,并且到底对于哪些数字是合理合法的需要辨识清楚。
55 字符流中第一个不重复的字符
56 链表中环的入口
只要知道方法,那么接下来的一切都将是细心与链表的问题。
57 删除链表中重复的结点
58 二叉树的下一个结点——中序遍历的下一个结点
不复杂,代码小心。
TreeLinkNode* GetNext(TreeLinkNode* pNode) { if (pNode == NULL) return NULL; if (pNode->right == NULL&&pNode->next == NULL) return NULL; if (pNode->right) { TreeLinkNode* tlnode = pNode->right; while (tlnode->left) { tlnode = tlnode->left; } return tlnode; } if (pNode->next->left == pNode) return pNode->next; if (pNode->next->right == pNode) { TreeLinkNode* tlnode = pNode->next; while (tlnode->next&&tlnode->next->right == tlnode) tlnode = tlnode->next; if (tlnode == NULL) return NULL; else return tlnode->next; } return NULL; }
59 对称的二叉树
很漂亮的解法,只要知道方法就会非常简单。
bool funcIST(TreeNode* pNode1, TreeNode* pNode2) { if (pNode1 == NULL&&pNode2 == NULL) return true; if (pNode1 == NULL || pNode2 == NULL) return false; if (pNode1->val != pNode2->val) return false; return funcIST(pNode1->left, pNode2->right) && funcIST(pNode1->right, pNode2->left); } bool isSymmetrical(TreeNode* pRoot) { return funcIST(pRoot, pRoot); }
60 把二叉树打印成多行
61 按照之字形顺序打印二叉树
我之前用的一个很好使的方法,非常有通用性——对于每行的结点用vector1来进行存储,根据该vector1,按照题意来打印每行结点。并根据该vector1中的结点用另一个新的vector2来进行存储。
效率不太高,但是思路清晰明了。
62 序列化二叉树
这题的关键是在于理解题目到底需要你干嘛。也就是说,需要将二叉树转化为一个序列,要求能够将该序列重新构建成一棵树。
几个注意点:
1) 格外注意typedef char* pchar; 记得每次一旦函数内涉及修改指针,就用这种形式。在作为参数时一定要写成引用的形式。否则每写必错。
2) 实际上很容易写成默认结点的值是一位的整数。并不是如此,因此需要有符号将所有的数字隔开,于是问题会变得稍稍复杂。
3) 另外,这题的代码在构建二叉树用来测试写的代码的时候,可以用一用。格式是调用Deserialize()函数,该函数的作用是将序列转换为二叉树。注意输入的参数是char*类型的序列。该序列举例为char* str = “1,222,#,#,32,42,#,#,#,#”
/*62 序列化二叉树 */ char* Serialize(TreeNode *root) { if (!root) { return "#,"; } string str = to_string(root->val); str.push_back(','); char* left = Serialize(root->left); char* right = Serialize(root->right); char* res = new char[strlen(left) + strlen(right) + str.size() + 1]; strcpy(res, str.c_str()); strcat(res, left); strcat(res, right); res[strlen(res)] = '\0'; return res; } typedef char* pchar; TreeNode* Deserialize_tmp(pchar& str) { while (*str != '\0' && (*str == ',')) { str++; } if (*str == '\0' || *str == '#') { str++; return NULL; } else { int val = 0; while (*str >= '0'&&*str <= '9') { val = val * 10 + *str - '0'; str++; } TreeNode* root = new TreeNode(val); root->left = Deserialize_tmp(str); root->right = Deserialize_tmp(str); return root; } } TreeNode* Deserialize(char *str) { TreeNode* root = Deserialize_tmp(str); return root; } int main() { char* str = "1,2,#,#,3,#,#"; TreeNode* root = Deserialize(str); char *res = Serialize(root); return 0; }
63 二叉搜索树的第k个结点
实际上就是中序遍历而已。但是还是折腾好久。
/*63 二叉搜索树的第k个结点 */ TreeNode* funcKthNode(TreeNode* pRoot, int& k) { TreeNode* resNode = NULL; if (pRoot == NULL||k == 0) return NULL; resNode = funcKthNode(pRoot->left, k); if (resNode == NULL) { k--; if (k == 0) resNode = pRoot; } if (resNode == NULL) resNode = funcKthNode(pRoot->right, k); return resNode; } TreeNode* KthNode(TreeNode* pRoot, int k) { return funcKthNode(pRoot, k); }
64 数据流中的中位数
思路分析:
1) 数组是最简单的数据容器,如果数组没有排序,可以用partition函数找出数组中的中位数。在没有排序的数组中插入一个数字时间复杂度为O(1),找出中位数的时间复杂度为O(n)。
2) 或者维持数组为排序数组,然后直接从排序数组中找出中位数。
3) 排序的链表(注意定义两个指针指向链表中间的结点),与2)一样。
4) 二叉搜索树可以把插入新数据的平均时间降为O(logn)。为了得到中位数,可以在数据结构中增加一个表示子树结点数目的字段。有了这个字段,可以在平均O(logn)时间得到中位数。
5)4)的改进——平衡的二叉搜索树AVL树。并且可以稍作修改,把AVL的平衡因子从左右子树的高度差改为左右子树结点数目之差。有了这个改动之后,可以用O(logn)时间往AVL树添加新的结点。同时用O(1)时间得到所有结点的中位数。
6)维持最大堆最小堆的方法:为了程序方便,可以使用stl中现成的工具——堆的相关函数:push_heap,pop_heap。以及容器vector。
这是一个非常漂亮的算法。并且由于直接利用了stl中堆的相关工具,因此也十分简洁。最关键的是再次熟悉一下stl中有关于堆的工具。
/*64 数据流中的中位数 */ #include<functional> #include<algorithm> vector<int> minHeap, maxHeap; void Insert(int num) { if ((minHeap.size() + maxHeap.size()) & 0x1 == 1) { /*应当存放入最小堆,但是要判断万一num中的数比最大堆里的数小怎么办。 一旦发生这种情况,就先安置到最大堆,然后取最大堆里的最大元素放到最小堆。 */ if (maxHeap.size() > 0 && maxHeap[0] > num) { maxHeap.push_back(num); push_heap(maxHeap.begin(), maxHeap.end(), less<int>()); num = maxHeap[0]; pop_heap(maxHeap.begin(), maxHeap.end(), less<int>()); maxHeap.pop_back(); } minHeap.push_back(num); push_heap(minHeap.begin(), minHeap.end(), greater<int>()); } else { if (minHeap.size() > 0 && minHeap[0] < num) { minHeap.push_back(num); push_heap(minHeap.begin(), minHeap.end(), greater<int>()); num = minHeap[0]; pop_heap(minHeap.begin(), minHeap.end(), greater<int>()); minHeap.pop_back(); } maxHeap.push_back(num); push_heap(maxHeap.begin(), maxHeap.end(), less<int>()); } } double GetMedian() { int sz = minHeap.size() + maxHeap.size(); if (sz == 0) throw exception("No numbers are available!"); if ((minHeap.size() + maxHeap.size()) & 0x1 == 1) return maxHeap[0]; else return double(minHeap[0] + maxHeap[0]) / 2; }
65 滑动窗口的最大值
题目描述:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}。
实际也不难。只要掌握了deque容器的用法。以及一个小小的技巧,deque中直接存储数组元素下标。
写代码最害怕给我一个内存溢出的错误。很可能怎么也找不到问题老是要怀疑网上的这个编译器是不是坏的。然而实际上,百分之百是代码有问题,并且十有八九是因为条件判断没有做好。——一定要先把不符合条件的输入都过滤掉。
/*65 滑动窗口的最大值 */ vector<int> maxInWindows(const vector<int>& num, unsigned int size) { vector<int> res; if (num.size() == 0||num.size() < size||size == 0) return res; deque<int> dq; int cnt = 0; while (cnt < size) { int tmp = num[cnt]; while (!dq.empty()&&num[dq.back()] <= tmp) { dq.pop_back(); } dq.push_back(cnt); cnt++; } res.push_back(num[dq.front()]); while (cnt < num.size()) { int tmp = num[cnt]; while (!dq.empty() && num[dq.back()] <= tmp) { dq.pop_back(); } dq.push_back(cnt); if (dq.front() <= (int)(cnt - size)) dq.pop_front(); res.push_back(num[dq.front()]); cnt++; } return res; }