面试题63:股票的最大利润
暴力搜索复杂度O(N^2),换个思路,遍历到某点,只需要记录改点之前最小的点即可,二者之差即为当前最大利润,时间复杂度O(N)
1 int MaxDiff(vector<int> numbers) 2 { 3 int len = numbers.size(); 4 if (len < 2) 5 return 0; 6 int min = numbers[0]; 7 int maxDiff = numbers[1] - min; 8 9 for (int i = 2; i < len; i++) 10 { 11 if (numbers[i - 1] < min) 12 min = numbers[i - 1]; 13 int currentDiff = numbers[i] - min; 14 if (currentDiff > maxDiff) 15 maxDiff = currentDiff; 16 } 17 return maxDiff; 18 }
面试题62:圆圈中最后剩下的数字
典型的约瑟夫环的问题,但是需要注意到达链表末尾时需要把指针移到头部
1 class Solution { 2 public: 3 int LastRemaining_Solution(int n, int m) 4 { 5 if(n < 1 || m < 1) 6 return -1; 7 8 unsigned int i = 0; 9 10 list<int> numbers; 11 for(i = 0; i < n; ++ i) 12 numbers.push_back(i); 13 14 auto current = numbers.begin(); 15 while(numbers.size() > 1) 16 { 17 for(int i = 1; i < m; ++ i) 18 { 19 current ++; 20 if(current == numbers.end()) 21 current = numbers.begin(); 22 } 23 24 auto next = ++ current; 25 if(next == numbers.end()) 26 next = numbers.begin(); 27 28 -- current; 29 numbers.erase(current); 30 current = next; 31 } 32 33 return *current; 34 } 35 };
面试题61:扑克牌中的顺子
三步走:排序;统计大小王数量;统计gap数量 如果后者小于前者,则可以凑成顺子,反之不行
1 class Solution { 2 public: 3 bool IsContinuous(vector<int> numbers) { 4 int len = numbers.size(); 5 if (len < 1) 6 return false; 7 sort(numbers.begin(), numbers.end()); 8 int numberOfZero = 0; 9 int numberOfGap = 0; 10 //统计数组中0 的个数 11 for (int i = 0; i < len && numbers[i] == 0; i++) 12 numberOfZero++; 13 int small = numberOfZero; 14 int big = small + 1; 15 while (big < len) 16 { 17 //如果有对子,肯定不是顺子 18 if (numbers[small] == numbers[big]) 19 return false; 20 numberOfGap += numbers[big] - numbers[small] - 1; 21 small = big; 22 big++; 23 } 24 return numberOfZero >= numberOfGap ? true : false; 25 } 26 };
面试题57-2:和为s的连续正数序列
方法与1类似,遍历然后根据数字求和之后的大小和sum大小之间的关系来确定指针的移动方向,注意while循环的边界条件,while中必须为small<middle,而不能是<=
1 class Solution { 2 public: 3 vector<vector<int> > FindContinuousSequence(int sum) { 4 vector<vector<int> > res; 5 if (sum < 3) 6 return res; 7 int small = 1; 8 int big = 2; 9 int middle = (sum + 1) / 2; 10 int curSum = small + big; 11 while (small < middle) // 这里千万不要写成 <=,否则在最后一次循环执行过程中,会陷入死循环 12 { 13 if (curSum == sum) 14 { 15 vector<int> temp; 16 for (int i = small; i <= big; i++) 17 temp.push_back(i); 18 res.push_back(temp); 19 } 20 //解释一下这里为何要将small往前而不是big往后,因为big往后的验证过了 21 22 while (curSum > sum &&small < middle) //这里需要注意,两处while条件中 small middle中的符号必须一致,都是< 23 { 24 curSum -= small; 25 small++; 26 if (curSum == sum) 27 { 28 vector<int> temp; 29 for (int i = small; i <= big; i++) 30 temp.push_back(i); 31 res.push_back(temp); 32 } 33 } 34 big++; 35 curSum += big; 36 } 37 return res; 38 39 } 40 }; 41 42 int main() { 43 Solution A; 44 vector<vector<int> > v; 45 v = A.FindContinuousSequence(3); 46 for (int i = 0; i < v.size(); i++) 47 { 48 for (int j = 0; j < v[i].size(); j++) 49 cout << v[i][j] << " "; 50 cout << endl; 51 52 } 53 //cout << "hhh" << endl; 54 system("pause"); 55 return 0; 56 }
面试题57:和为s的两个数字
经典的双指针为题,首尾开始往中间遍历
1 class Solution { 2 public: 3 vector<int> FindNumbersWithSum(vector<int> array, int sum) { 4 vector<int> res; 5 int ahead = 0; 6 int behind = array.size() - 1; 7 while (ahead < behind) 8 { 9 if (array[ahead] + array[behind] == sum) 10 { 11 res.push_back(array[ahead]); 12 res.push_back(array[behind]); 13 break; 14 } 15 else if (array[ahead] + array[behind] < sum) 16 ahead++; 17 else 18 behind--; 19 } 20 return res; 21 } 22 };
面试题56-1:数组中数字出现的次数
可以直接排序然后遍历搜索,时间复杂度O(nlogn)
1 class Solution { 2 public: 3 void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) { 4 sort(data.begin(),data.end()); 5 int count = 0; 6 for (int i = 0; i < data.size();) 7 { 8 if (data[i] == data[i+1]) 9 i = i+2; 10 else 11 { 12 if (count == 0) 13 { 14 *num1 = data[i]; 15 count++; 16 i++; 17 } 18 else 19 { 20 *num2 = data[i]; 21 break; 22 } 23 } 24 } 25 } 26 };
更好的方式是利用数位的异或运算,详细内容请参考书本
面试题55:二叉树的深度
递归的典型应用
1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 int TreeDepth(TreeNode* pRoot) 13 { 14 if (!pRoot) 15 return 0; 16 return 1 + max(TreeDepth(pRoot->left),TreeDepth(pRoot->right)); 17 } 18 };
面试题55-2:平衡二叉树
和1挺像,如果调用1中的求深度的函数来构造递归,可以运行,但是会多次重复遍历节点,会存在问题,会有节点多次重复调用
1 class Solution { 2 public: 3 bool IsBalanced_Solution(TreeNode* pRoot) { 4 if (pRoot == nullptr) 5 return true; 6 int left = TreeDepth(pRoot->left); 7 int right = TreeDepth(pRoot->right); 8 int diff = left - right; 9 if (diff > 1 || diff < -1) 10 return false; 11 return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right); 12 } 13 int TreeDepth(TreeNode* pRoot) 14 { 15 if (!pRoot) 16 return 0; 17 return 1 + max(TreeDepth(pRoot->left),TreeDepth(pRoot->right)); 18 } 19 20 };
其实可以有改进,做到每个节点只访问一次,也就是后序遍历二叉树时,遍历到某个节点之后记录下来它的深度,就可以一边遍历一遍判断每个节点是否平衡。注意:递归时一定要要传递指针(或者引用)而不是数值
1 class Solution { 2 public: 3 bool IsBalanced_Solution(TreeNode* pRoot) { 4 int depth = 0; 5 return isBalanced(pRoot, depth); 6 } 7 bool isBalanced(TreeNode* pRoot, int& depth ) 8 { 9 if (pRoot == nullptr) 10 { 11 depth = 0; 12 return true; 13 } 14 int left, right; 15 if (isBalanced(pRoot->left, left) && isBalanced(pRoot->right, right)) 16 { 17 int diff = left - right; 18 if (diff >= -1 && diff <= 1) 19 { 20 depth = 1 + max(left, right); 21 return true; 22 } 23 } 24 return false; 25 } 26 };
面试题54:二叉搜索树的第K大节点
就是个中序遍历,但是书上代码不是很好理解,其实可以中序遍历所有节点然后读取第k个就可以
1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 }; 10 */ 11 class Solution { 12 public: 13 TreeNode* KthNode(TreeNode* pRoot, int k) 14 { 15 if (pRoot == nullptr || k == 0) 16 return nullptr; 17 return KthNodeCore(pRoot,k); 18 } 19 TreeNode* KthNodeCore(TreeNode* pRoot, int &k) 20 { 21 TreeNode* temp = nullptr; 22 if (pRoot->left != nullptr) 23 temp = KthNodeCore(pRoot->left, k); 24 if (temp == nullptr) 25 { 26 if (k==1) 27 temp = pRoot; 28 k--; 29 } 30 if (temp == nullptr && pRoot->right != nullptr) 31 temp = KthNodeCore(pRoot->right, k); 32 return temp; 33 } 34 35 36 };
面试题53-3:数组中数值和下标相等的元素
思路与53-2类似,二分查找
1 int GetNumberSameAsIndex(vector<int> data) 2 { 3 if (data.empty()) 4 return -1; 5 int lo = 0, hi = data.size() - 1; 6 while (lo <= hi) 7 { 8 int mid = (lo + hi) >> 1; 9 if (data[mid] > mid) 10 hi = mid - 1; 11 else if (data[mid] < mid) 12 lo = mid + 1; 13 else 14 return mid; 15 } 16 if (lo == data.size()) 17 return data.size(); 18 return -1; 19 //return data.size(); 20 }
面试题53-2:0~n-1中缺失的数字
可以顺序遍历,但不是最优解法,没有利用数组递增特性,思路与53-1相似,寻找第一个数字与对应下标不一样的元素即可
我自己写的
1 //这种情我自己写的,也算是做出来了,但是感觉讨论的情况太多,而且很容易出现遗漏或者说数组访问越界 2 //还是应该学习《剑指offer》的思路 3 int GetMissingNumber(vector<int> data) 4 { 5 int lo = 0, hi = data.size() - 1; 6 while (lo <= hi) 7 { 8 if (data[0] != 0) 9 return 0; 10 if (data[data.size() - 1] == data.size() - 1) 11 return data.size(); 12 int mid = (lo + hi) / 2; 13 if (data[mid] != mid && data[mid - 1] == mid - 1) 14 return mid; 15 else if (data[mid] == mid && data[mid + 1] != mid + 1) 16 return mid + 1; 17 else if (data[mid] == mid && data[mid + 1] == mid + 1) 18 lo = mid + 1; 19 else if (data[mid] != mid && data[mid - 1] != mid - 1) 20 hi = mid - 1; 21 } 22 //return data.size(); 23 }
书上的
1 int GetMissingNumber(vector<int> data) 2 { 3 if (data.empty()) 4 return -1; 5 int lo = 0, hi = data.size() - 1; 6 while (lo <= hi) 7 { 8 int mid = (lo + hi) >> 1; 9 if (data[mid] != mid) 10 { 11 if (mid == 0 || data[mid - 1] == mid - 1) 12 return mid; 13 hi = mid - 1; 14 } 15 else 16 lo = mid + 1; 17 } 18 if (lo == data.size()) 19 return data.size(); 20 return -1; 21 //return data.size(); 22 }
面试题53-1:在排序数组中查找数字
可以从头到尾数,时间复杂度O(n)
1 class Solution { 2 public: 3 int GetNumberOfK(vector<int> data ,int k) { 4 int res = 0; 5 for (int i = 0; i < data.size(); i++) 6 if (data[i] == k) 7 res++; 8 return res; 9 } 10 };
但是没有利用排序特性,一般看见“排序”二字,就应该想到,二分查找,最后可以得到log(n)的复杂度,这道题的关键在于如何寻找第一个该数字和最后一个该数字的位置(牛客网上折腾至少一个半小时,自己写的死活过不去,还是粘贴官方代码然后修改,mmp!!!!)
1 class Solution { 2 public: 3 int GetNumberOfK(vector<int> data ,int k) { 4 int number = 0; 5 6 if(data.size() > 0) 7 { 8 int first = GetFirstK(data, k, 0, data.size() - 1); 9 int last = GetLastK(data, k, 0, data.size() - 1); 10 11 if(first > -1 && last > -1) 12 number = last - first + 1; 13 } 14 return number; 15 } 16 17 int GetFirstK(vector<int> data, int k, int start, int end) 18 { 19 if(start > end) 20 return -1; 21 22 int middleIndex = (start + end) / 2; 23 int middleData = data[middleIndex]; 24 25 if(middleData == k) 26 { 27 if((middleIndex > 0 && data[middleIndex - 1] != k) 28 || middleIndex == 0) 29 return middleIndex; 30 else 31 end = middleIndex - 1; 32 } 33 else if(middleData > k) 34 end = middleIndex - 1; 35 else 36 start = middleIndex + 1; 37 38 return GetFirstK(data, k, start, end); 39 } 40 41 // 找到数组中最后一个k的下标。如果数组中不存在k,返回-1 42 int GetLastK(vector<int> data, int k, int start, int end) 43 { 44 if(start > end) 45 return -1; 46 47 int middleIndex = (start + end) / 2; 48 int middleData = data[middleIndex]; 49 50 if(middleData == k) 51 { 52 if((middleIndex < data.size() - 1 && data[middleIndex + 1] != k) 53 || middleIndex == data.size() - 1) 54 return middleIndex; 55 else 56 start = middleIndex + 1; 57 } 58 else if(middleData < k) 59 start = middleIndex + 1; 60 else 61 end = middleIndex - 1; 62 63 return GetLastK(data, k, start, end); 64 } 65 };
次日早上重新学习之后自己编写完成上面的程序,一个循环,一个递归,调试一下居然通过了,开心,编程序就是这样,很多技巧书本无法交给我们,只能依靠自己一次次碰壁来获得,加油,希望这是一个好的开始,然后有一个坚持,就酱紫!——2018.06.16
1 class Solution { 2 public: 3 int GetNumberOfK(vector<int> data ,int k) { 4 int res = 0; 5 if (data.empty()) 6 return res; 7 int first = getFirstK(data,k,0,data.size()-1); 8 int last = getLastK(data,k,0,data.size()-1); 9 if (first != -1 && last != -1) 10 return last - first + 1; 11 return res; 12 13 } 14 //递归写法 15 int getFirstK(vector<int> data, int k, int lo, int hi) 16 { 17 if (lo > hi) //这里很重要,是递归的终止条件 18 return -1; 19 int mid = (lo + hi )/ 2; 20 if (data[mid] > k) 21 return getFirstK(data,k,lo,mid - 1); 22 // hi = mid - 1; 23 else if (data[mid] < k) 24 return getFirstK(data,k,mid+1,hi); 25 // lo = mid + 1; 26 else //if (data[mid] == k) 27 { 28 if (mid == 0 || data[mid - 1] != k) 29 return mid; 30 else 31 return getFirstK(data,k,lo,mid - 1); 32 } 33 return -1; 34 } 35 36 //循环写法 37 int getLastK(vector<int> data, int k, int lo, int hi) 38 { 39 int mid = (lo + hi) / 2; 40 while (lo <= hi) 41 { 42 if (data[mid] > k) 43 hi = mid - 1; 44 else if (data[mid] < k) 45 lo = mid + 1; 46 else //if (data[mid] == k) 47 { 48 if (mid == data.size()-1 || data[mid + 1] != k) 49 return mid; 50 else 51 lo = mid + 1; 52 } 53 mid = (lo + hi)/2; 54 } 55 return -1; 56 } 57 };
牛客评论区看见的更巧的解法,寻找k-0.5和k+0.5这俩数字的位置,然后相减即可
1 class Solution { 2 public: 3 int GetNumberOfK(vector<int> data ,int k) { 4 return biSearch(data, k+0.5) - biSearch(data, k-0.5) ; 5 } 6 private: 7 int biSearch(const vector<int> & data, double num){ 8 int s = 0, e = data.size()-1; 9 while(s <= e){ 10 int mid = (e - s)/2 + s; 11 if(data[mid] < num) 12 s = mid + 1; 13 else if(data[mid] > num) 14 e = mid - 1; 15 } 16 return s; 17 } 18 };
牛客网,循环与递归的写法
1 public class Solution { 2 public int GetNumberOfK(int [] array , int k) { 3 int length = array.length; 4 if(length == 0){ 5 return 0; 6 } 7 int firstK = getFirstK(array, k, 0, length-1); 8 int lastK = getLastK(array, k, 0, length-1); 9 if(firstK != -1 && lastK != -1){ 10 return lastK - firstK + 1; 11 } 12 return 0; 13 } 14 //递归写法 15 private int getFirstK(int [] array , int k, int start, int end){ 16 if(start > end){ 17 return -1; 18 } 19 int mid = (start + end) >> 1; 20 if(array[mid] > k){ 21 return getFirstK(array, k, start, mid-1); 22 }else if (array[mid] < k){ 23 return getFirstK(array, k, mid+1, end); 24 }else if(mid-1 >=0 && array[mid-1] == k){ 25 return getFirstK(array, k, start, mid-1); 26 }else{ 27 return mid; 28 } 29 } 30 //循环写法 31 private int getLastK(int [] array , int k, int start, int end){ 32 int length = array.length; 33 int mid = (start + end) >> 1; 34 while(start <= end){ 35 if(array[mid] > k){ 36 end = mid-1; 37 }else if(array[mid] < k){ 38 start = mid+1; 39 }else if(mid+1 < length && array[mid+1] == k){ 40 start = mid+1; 41 }else{ 42 return mid; 43 } 44 mid = (start + end) >> 1; 45 } 46 return -1; 47 } 48 }
面试题42:连续子数组最大和
动态规划的思想,线性复杂度,数组中某个数字之前的所有数字的和如果小于0,那就不用再考虑了,加上只有比本身还小,无意义
1 class Solution { 2 public: 3 int FindGreatestSumOfSubArray(vector<int> array) { 4 if (array.size() < 1) 5 return 0; 6 int nCurSum = 0; 7 int nGreatestSum = INT_MIN; 8 for (int i = 0; i < array.size(); i++) 9 { 10 if (nCurSum <= 0) 11 nCurSum = array[i]; 12 else 13 nCurSum += array[i]; 14 if (nCurSum > nGreatestSum) 15 nGreatestSum = nCurSum; 16 } 17 return nGreatestSum; 18 19 } 20 };
二刷:AC
面试题36:二叉搜索树与双向链表
这道题主要涉及中序遍历,以及指针方向的调整,难点在于递归的过程,个人认为递归并不需要知道细节,只需要卡准更简单的递归形式和递归的终止条件即可,根本不需要单步调试,详细过程参看书本
代码如下:
1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 11 class Solution { 12 public: 13 TreeNode* Convert(TreeNode* pRootOfTree) 14 { 15 TreeNode *pLastNodeInList = nullptr; 16 ConvertCore(pRootOfTree,&pLastNodeInList); 17 TreeNode *head = pLastNodeInList; 18 while (head != nullptr && head->left != nullptr) 19 head = head->left; 20 return head; 21 } 22 23 void ConvertCore(TreeNode *pNode, TreeNode **pLastNodeInList) 24 { 25 if (pNode == nullptr) 26 return; 27 TreeNode *cur = pNode; 28 if (pNode->left != nullptr) 29 ConvertCore(pNode->left,pLastNodeInList); 30 cur->left = *pLastNodeInList; 31 if (*pLastNodeInList != nullptr) 32 (*pLastNodeInList)->right = cur; 33 *pLastNodeInList = cur; 34 if (cur->right != nullptr) 35 ConvertCore(cur->right,pLastNodeInList); 36 } 37 };
面试题35:复杂链表的赋值
思路很直接,三步走:1、赋值节点并连接在原节点后方;2、还原新节点的random指针;3、拆分链表
代码如下:
1 /* 2 struct RandomListNode { 3 int label; 4 struct RandomListNode *next, *random; 5 RandomListNode(int x) : 6 label(x), next(NULL), random(NULL) { 7 } 8 }; 9 */ 10 class Solution { 11 public: 12 RandomListNode* Clone(RandomListNode* pHead) 13 { 14 if (!pHead) 15 return nullptr; 16 RandomListNode *pNode = pHead; 17 while (pNode) 18 { 19 RandomListNode *pCloned = new RandomListNode(pNode->label); 20 pCloned->next = pNode->next; 21 pNode->next = pCloned; 22 pNode = pCloned->next; 23 } 24 pNode = pHead; 25 while (pNode) 26 { 27 if (pNode->random) 28 pNode->next->random = pNode->random->next; 29 pNode = pNode->next->next; 30 } 31 RandomListNode *newHead = pHead->next; 32 pNode = pHead; 33 while (pNode) 34 { 35 RandomListNode *l1 = pNode->next; 36 pNode->next = l1->next; 37 pNode = pNode->next; 38 if (l1->next) 39 l1->next = l1->next->next; 40 } 41 /*for (l1 = pHead; l1 != nullptr; l1 = l1->next) 42 { 43 l2 = l1->next; 44 l1->next = l2->next; 45 if (l2->next != nullptr) 46 l2->next = l2->next->next; 47 }*/ 48 return newHead; 49 } 50 };
2018-03-22二刷:前两步没问题,最后一步折腾我很久,最后才知道是处理最后一个节点的null时出问题了,我不小心修改了原链表末尾的null。链表类题目一定要小心头和尾部;另外,用while循环自己一定要处理好循环变量的增加与范围。
面试题33:二叉搜索树的后序遍历序列
书上的思路不难理解,利用递归进行,但是把书上的内容生搬硬套使用向量这让我很不爽,郁闷中……
代码如下:
1 class Solution { 2 public: 3 bool VerifySquenceOfBST(vector<int> sequence) { 4 int len = sequence.size(); 5 if (len == 0) 6 return false; 7 int* array = new int[len]; 8 for (int i = 0; i < len; i++) 9 array[i] = sequence[i]; 10 return myVerifySquenceOfBST(array,len); 11 } 12 bool myVerifySquenceOfBST(int sequence[], int length) 13 { 14 if (sequence == nullptr || length <= 0) 15 return false; 16 int root = sequence[length - 1]; 17 //二叉搜索树中左子树节点值小于根节点的值 18 int i = 0; 19 for (; i < length - 1; i++) 20 if (sequence[i]>root) 21 break; 22 int j = i; 23 for (; j < length - 1; j++) 24 if (sequence[j] < root) 25 return false; 26 //判断左子树是否为二叉树; 27 bool left = true; 28 if (i > 0) 29 left = myVerifySquenceOfBST(sequence, i); 30 //判断右子树是否为二叉树; 31 bool right = true; 32 if (i < length - 1) 33 right = myVerifySquenceOfBST(sequence + i, length - 1 - i); 34 return left&&right; 35 36 37 } 38 39 };
面试题32-3:之字形打印二叉树
与32-1类似,可以看做是层序遍历的变种,牛客网上的解答仍然是利用层序遍历,只是在偶数层的vector需要调用reverse函数进行翻转;《剑指offer》书上提供的解法则是是用来两个栈
代码如下:
1 struct TreeNode { 2 int val; 3 struct TreeNode *left; 4 struct TreeNode *right; 5 TreeNode(int x) : 6 val(x), left(NULL), right(NULL) { 7 } 8 }; 9 10 //这道题本质上也是一个层序遍历,只是偶数层需要反序;书上给的方法是两个栈 11 //我个人觉得有点绕,牛客网上的解答有的是用层序遍历,调用reverse函数反转偶数层 12 //但是有人说自己面试时用reverse被面试官批评,海量数据用这个会很慢 13 //我最开始的思路是用双端队列来做,因为双端队列本身就兼具队列和栈的特性 14 //实践之后证明双端队列并不好用,尽量少用,不伦不类,容易混乱 15 //这里队列存的是指针而非数值,涉及到访问左右子节点的问题,必须仔细,这也是用双端队列不方便的地方 16 class Solution 17 { 18 public: 19 vector<vector<int> > Print(TreeNode* pRoot) 20 { 21 vector<vector<int>> res; 22 if (pRoot == nullptr) 23 return res; 24 queue<TreeNode*> nodes; 25 bool even = false; 26 nodes.push(pRoot); 27 while (!nodes.empty()) 28 { 29 vector<int> vec; 30 int size = nodes.size(); 31 for (int i = 0; i < size; i++) 32 { 33 TreeNode* pNode = nodes.front(); 34 nodes.pop(); 35 vec.push_back(pNode->val); 36 if (pNode->left != nullptr) 37 nodes.push(pNode->left); 38 if (pNode->right != nullptr) 39 nodes.push(pNode->right); 40 } 41 if (even) 42 reverse(vec.begin(), vec.end()); 43 even = !even; 44 res.push_back(vec); 45 } 46 return res; 47 } 48 49 };
面试题32-2:分行从上到下方打印二叉树(层序遍历/广度优先遍历)
与32-1类似,但是细节方面略有差异,实现上是用的向量的向量
代码如下:
1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 }; 10 */ 11 class Solution { 12 public: 13 vector<vector<int> > Print(TreeNode* pRoot) { 14 vector<vector<int> > result; //类似于二维数组 15 if (pRoot == nullptr) 16 return result; 17 queue<TreeNode*> nodes; //建立一个队列,存放层序遍历的节点 18 nodes.push(pRoot); 19 while (!nodes.empty()) 20 { 21 vector<int> nodesInLevel; //存放每一层的节点,其实是局部变量,每次循环结束之后会自动释放的 22 int lo = 0, hi = nodes.size();//队列中元素始末位置,要求队列中元素是二叉树中一层的元素 23 while (lo < hi) 24 { 25 TreeNode* temp = nodes.front(); //这里其实应该考虑要不要释放的 26 nodesInLevel.push_back(temp->val); 27 nodes.pop(); 28 if (temp->left) nodes.push(temp->left); 29 if (temp->right) nodes.push(temp->right); 30 lo++; 31 } 32 result.push_back(nodesInLevel); 33 } 34 return result; 35 } 36 37 };
面试题32-1:从上到下方打印二叉树(层序遍历/广度优先遍历)
先访问的节点其子节点也会被先访问,符合队列的特征
代码如下:
1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 11 class Solution { 12 public: 13 vector<int> PrintFromTopToBottom(TreeNode* root) { 14 vector<int> result; 15 if (root == nullptr) //这里必须加上对空指针的处理,否则牛客网会报“段错误” 16 return result; 17 deque<TreeNode*> dequeTreeNode;//利用队列来实现; 18 dequeTreeNode.push_back(root); 19 while (dequeTreeNode.size()) 20 { 21 TreeNode* pNode = dequeTreeNode.front(); 22 result.push_back(pNode->val); 23 dequeTreeNode.pop_front(); 24 if (pNode->left) 25 dequeTreeNode.push_back(pNode->left); 26 if (pNode->right) 27 dequeTreeNode.push_back(pNode->right); 28 } 29 return result; 30 } 31 };
面试题31:栈的压入、弹出序列
利用辅助栈,重现栈的压入弹出操作。
代码如下:
1 class Solution { 2 public: 3 bool IsPopOrder(vector<int> pushV, vector<int> popV) 4 { 5 if (pushV.empty() || popV.empty() || pushV.size() != popV.size()) 6 return false; 7 std::stack<int> sta; //辅助栈 8 int pPop = 0;//用来记录弹出序列的读取位置 9 //读取栈的压入序列,压入sta,当读到的数字与弹出序列pPop对应 10 //的数字相同,弹出该数字,pPop+1 11 //重复以上过程,若sta最后为空,则该序列是压栈序列的弹出序列,否则不是 12 for (int i = 0; i < pushV.size(); i++) 13 { 14 sta.push(pushV[i]); 15 while (!sta.empty() && sta.top() == popV[pPop]) 16 { 17 sta.pop(); 18 pPop++; 19 } 20 } 21 if (sta.empty()) 22 return true; 23 return false; 24 } 25 };
面试题30:包含min函数的栈
这道题应当学会把抽象问题具体化。可以画图分析。关键点在于利用辅助栈处理,辅助栈中包含的是当前情况下的最小的元素
代码如下:
1 class Solution { 2 public: 3 void push(int value) { 4 m_data.push(value); 5 if (m_min.empty() || value < m_min.top()) 6 m_min.push(value); 7 else 8 m_min.push(m_min.top()); 9 10 } 11 void pop() { 12 m_data.pop(); 13 m_min.pop(); 14 } 15 int top() { 16 return m_data.top(); 17 } 18 int min() { 19 return m_min.top(); 20 } 21 private: 22 std::stack<int> m_data; 23 std::stack<int> m_min; 24 };
面试题29:顺时针打印矩阵
主要是两部分,一是确定打印的圈数,二是确定打印一圈的操作,注意打印一圈时,需要判断什么情况下需要打印第二、第三、第四步
一刷:没控制好打印一圈的条件,测试用例显示出现重复打印
代码如下:
1 //牛客网 2 class Solution { 3 public: 4 vector<int> printMatrix(vector<vector<int> > matrix) { 5 int rows = matrix.size(); //矩阵行数 6 int cols = matrix[0].size(); //矩阵列数 7 int start = 0; //左上角的起始标号(start,start) 8 vector<int> result; 9 //打印一圈的操作 10 while (rows > start * 2 && cols > start * 2) 11 { 12 int endX = cols - 1 - start; //终止行号 13 int endY = rows - 1 - start; //终止列号 14 15 //第一次写这部分代码连续写了4个for循环,最后只通过了部分测试用例 16 //原因在于第一步是所有的矩阵都需要的,但是后面三步却未必,需要判断是否需要打印,否则会有重复 17 //从左到右打印一行 18 for (int i = start; i <= endX; i++) 19 result.push_back(matrix[start][i]); //像访问数组一样访问向量的向量 20 //从上到下打印一列 21 if (start < endY) 22 { 23 for (int i = start + 1; i <= endY; i++) 24 result.push_back(matrix[i][endX]); 25 } 26 //从右到左打印一行 27 if (start < endX&&start < endY) 28 { 29 for (int i = endX - 1; i >= start; i--) 30 result.push_back(matrix[endY][i]); 31 } 32 //从下到上打印一列 33 if (start < endX&&start < endY - 1) 34 { 35 for (int i = endY - 1; i >= start + 1; i--) 36 result.push_back(matrix[i][start]); 37 } 38 ++start; 39 } 40 return result; 41 } 42 };
面试题26:树的子结构
递归思想,主要是确定递归终止情况,并且确定具有更简单参数的递归调用
一刷:DoesTree1HaveTree2函数中return后面是 递归函数,参数写错了,应该是左右子树分别匹配;
代码如下:
1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) 13 { 14 //由于val是int类型的,所以可以用“==”直接判断是否相等 15 //否则就需要去写个equal函数来判断double或者float是否相等 16 17 bool result = false; 18 //初次运行HasSubtree函数,两个指针必须都是非空,否则直接返回false 19 if (pRoot1 != nullptr&&pRoot2 != nullptr) 20 { 21 //若根节点相同,继续判断树1中以R为根节点的子树是否包含树2 22 if (pRoot1->val == pRoot2->val) 23 result = DoesTree1HaveTree2(pRoot1, pRoot2); 24 //当以R为根节点的树1不包含树2时,继续从R的左子树寻找 25 if (!result) 26 result = HasSubtree(pRoot1->left, pRoot2); 27 //如果仍然找不到,从R的右子树寻找 28 if (!result) 29 result = HasSubtree(pRoot1->right, pRoot2); 30 } 31 //返回结果 32 return result; 33 } 34 //该函数的作用是判断以pRoot1为根节点的树是否包含以pRoot2为根节点的树 35 bool DoesTree1HaveTree2(TreeNode* pRoot1, TreeNode *pRoot2) 36 { 37 //如果tree2遍历完,所有节点在tree1中都找得到,返回true 38 if (pRoot2 == nullptr) 39 return true; 40 //如果tree2没遍历完但是tree1遍历完了,返回false 41 if (pRoot1 == nullptr) 42 return false; 43 //如果根节点数值不相等,返回false 44 if (pRoot1->val != pRoot2->val) 45 return false; 46 //若根节点相等了,那么左右子树也必须都相等才可以,所以用&& 47 return DoesTree1HaveTree2(pRoot1->left, pRoot2->left) && 48 DoesTree1HaveTree2(pRoot1->right, pRoot2->right); 49 } 50 };
面试题25:合并两个排序链表
递归思路,每次两个链表头部数值较小的就是新链表的头
一刷:if判决语句中pMergedHead的next指向错了,一定注意处理好链表结构体中next指针的指向
代码如下:
1 /* 2 struct ListNode { 3 int val; 4 struct ListNode *next; 5 ListNode(int x) : 6 val(x), next(NULL) { 7 } 8 };*/ 9 class Solution { 10 public: 11 ListNode* Merge(ListNode* pHead1, ListNode* pHead2) 12 { 13 if (pHead1 == nullptr) 14 return pHead2; 15 else if (pHead2 == nullptr) 16 return pHead1; 17 18 ListNode *pMergedHead = nullptr; 19 if (pHead1->val < pHead2->val) 20 { 21 pMergedHead = pHead1; 22 pMergedHead->next = Merge(pHead1->next, pHead2); //MD错了,应该是pMerged->next 23 } 24 else 25 { 26 pMergedHead = pHead2; 27 pMergedHead->next = Merge(pHead1, pHead2->next); 28 } 29 return pMergedHead; 30 } 31 };
面试题21:调整数组顺序使奇数位于偶数前面
牛客网不仅要求分开奇数偶数,还要求相对位置不变,我新开辟了一个向量,两次扫描原来向量。时间空间复杂度均是O(n)
代码如下:
1 //原来书上 的解题思路不保证顺序,只是让奇数位于前半部分,偶数位于后半部分 2 //这里既然要求顺序,可以考虑再开辟一个相同大小的vector 3 class Solution { 4 public: 5 void reOrderArray(vector<int> &array) { 6 int length = array.size(); 7 //vector<int> result(length, 0);//初始化可以不需要的 8 vector<int> result; 9 for (int i = 0; i < length; i++) 10 if ((array[i] & 0x1) != 0) 11 result.push_back(array[i]); 12 for (int i = 0; i < length; i++) 13 if ((array[i] & 0x1) == 0) 14 result.push_back(array[i]); 15 array = result; 16 } 17 };
面试题19:正则表达式匹配
分类讨论的关键点在于——正则表达式中第二个字符是否是“*”。若第二个字符不是*,字符与模式都后移一位;若第二个字符是*,则有一下几种可能:1、字符串后移一位,模式不变(对应于*以及它前面的字符多次出现);2、字符串后移一个,模式后移两个(对应于*和它前面的字符认为只出现一次);3、字符串不动,模式后移2个(对应于*前面的字符一次都不出现)
代码如下:
1 class Solution { 2 public: 3 bool match(char* str, char* pattern) 4 { 5 if (str == nullptr || pattern == nullptr) 6 return false; 7 return matchCore(str, pattern); 8 } 9 10 bool matchCore(char* str, char* pattern) 11 { 12 if (*str == '