剑指offer错题记录
错误重点:
1. 传递vector参数时,如果调用函数改变了vector的内容,一定一定要&,传引用保持一致
旋转数组的最小数字:有重复数字情况,二分查找照样搞。情况考虑要周全,当a[mid]==a[l]==a[r]时,target在左右区间都可能出现,所以要枚举去搞,当a[mid]>a[r]在右边,当a[mid]<a[l]在左边,剩余为递增顺序,取左边。
矩形覆盖:找dp最优子结构啊,从小开始找规律,变成特殊的斐波那契了。
二进制中1的个数:上bitset啊,count啊。敲黑板,自己写居然出问题了,原因是负数补码移位操作会补1导致出现问题,解决方案两个,一个是外循环32次,另一个将1一步步左移,注意与操作不为0即可。
树的子结构: 在树的遍历过程中,找与模板root结点相同的结点,进一步子树check。考察树的递归思想
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 bool ans=false; 15 if(pRoot1!=NULL&&pRoot2!=NULL) 16 { 17 if(pRoot1->val==pRoot2->val) 18 ans=check(pRoot1,pRoot2); 19 if(!ans) 20 ans=HasSubtree(pRoot1->left,pRoot2); 21 if(!ans) 22 ans=HasSubtree(pRoot1->right,pRoot2); 23 } 24 return ans; 25 } 26 bool check(TreeNode* pRoot1, TreeNode* pRoot2) 27 { 28 if(pRoot2==NULL)return true; 29 if(pRoot1==NULL)return false; 30 if(pRoot1->val!=pRoot2->val)return false; 31 return check(pRoot1->left,pRoot2->left)&& check(pRoot1->right,pRoot2->right); 32 } 33 };
顺时针打印矩阵:简单模拟,用左上和右下的坐标定位出一次要旋转打印的数据,一次旋转打印结束后,往对角分别前进和后退一个单位。
1 class Solution { 2 public: 3 vector<int> v; 4 vector<int> printMatrix(vector<vector<int> > matrix) 5 { 6 int col = matrix[0].size(); 7 int row = matrix.size(); 8 if(col==0||row==0)return v; 9 // 定义四个关键变量,表示左上和右下的打印范围 10 int left=0,right=col-1,top=0,bottom=row-1; 11 while(left<=right&&top<=bottom) 12 { 13 // left to right 14 for(int i=left;i<=right;i++)v.push_back(matrix[top][i]); 15 // top to bottom 16 for(int i=top+1;i<=bottom;i++)v.push_back(matrix[i][right]); 17 // right to left 18 if(top!=bottom) // 当形成单行时,不能重复 19 for(int i=right-1;i>=left;i--)v.push_back(matrix[bottom][i]); 20 // bottom to top 21 if(left!=right) // 形成单列 22 for(int i=bottom-1;i>top;i--)v.push_back(matrix[i][left]); 23 left++,right--,top++,bottom--; 24 } 25 return v; 26 } 27 };
栈的压入,弹出序列:模拟
1 class Solution { 2 public: 3 vector<int> s; 4 bool IsPopOrder(vector<int> pushV,vector<int> popV) { 5 if(pushV.size()==0)return false; 6 for(int i=0,j=0;i<pushV.size();) 7 { 8 s.push_back(pushV[i++]); 9 while(j<popV.size() && s.back()==popV[j])s.pop_back(),j++; 10 } 11 return s.empty(); 12 } 13 };
二叉搜索树的后序遍历序列:注意从右顶点出发,可以满足所有特殊情况
1 class Solution { 2 public: 3 bool check(vector<int> v, int l, int r) 4 { 5 if(l>=r)return true; 6 int i=r; 7 while(i>l&&v[i-1]>v[r])i--; 8 for(;i>l;i--)if(v[i-1]>v[r])return false; 9 return check(v,l,i-1)&&check(v,i,r-1); 10 } 11 bool VerifySquenceOfBST(vector<int> sequence) { 12 if(sequence.size()==0)return false; 13 return check(sequence,0,sequence.size()-1); 14 } 15 };
二叉树中和为某一值得路径:
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 vector<vector<int> > vv; 13 vector<int> v; 14 vector<vector<int> > FindPath(TreeNode* root,int expectNumber) { 15 if(root==NULL)return vv; 16 if(root->val<=expectNumber) 17 { 18 v.push_back(root->val); 19 if(expectNumber-root->val==0&&root->left==NULL&&root->right==NULL) 20 { 21 vv.push_back(v); 22 v.pop_back();// 回溯 23 return vv; 24 } 25 FindPath(root->left,expectNumber-root->val); 26 FindPath(root->right,expectNumber-root->val); 27 v.pop_back(); // 回溯 28 } 29 return vv; 30 } 31 };
数组中只出现一次的数字: 题目中要找两个出现一次的数字,出现一次的题会做吧,异或就OK,利用两个相同的数字异或后为0。同样从头到尾异或一遍,最后求得这两个数的异或,现在考虑如何分开他,还是利用异或的性质,找两数异或后这个数的末尾第一个1的位置,从而将拆分为两个数组,分别异或即可。
不用加减乘除做加法
二进制做加法:具体步骤1.不进位求和:a^b,2.计算进的位:(a&b)<<1, 然后迭代计算步骤1+步骤2的结果直到进位为0
表示数值的字符串:判断非法情况逻辑要清晰
1 class Solution { 2 public: 3 bool isNumeric(char* str) { 4 // 标记符号、小数点、e是否出现过 5 bool sign = false, decimal = false, hasE = false; 6 for (int i = 0; i < strlen(str); i++) { 7 if (str[i] == 'e' || str[i] == 'E') { 8 if (i == strlen(str)-1) return false; // e后面一定要接数字 9 if (hasE) return false; // 不能同时存在两个e 10 hasE = true; 11 } else if (str[i] == '+' || str[i] == '-') { 12 // 第二次出现+-符号,则必须紧接在e之后 13 if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false; 14 // 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后 15 if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false; 16 sign = true; 17 } else if (str[i] == '.') { 18 // e后面不能接小数点,小数点不能出现两次 19 if (hasE || decimal) return false; 20 decimal = true; 21 } else if (str[i] < '0' || str[i] > '9') // 不合法字符 22 return false; 23 } 24 return true; 25 } 26 };
对称的二叉树: 递归
1 class Solution { 2 public: 3 bool isSymmetrical(TreeNode* pRoot) 4 { 5 if(pRoot==NULL)return true; 6 return check(pRoot->left,pRoot->right); 7 } 8 9 bool check(TreeNode* left, TreeNode* right) 10 { 11 if(left==NULL)return right==NULL; 12 if(right==NULL)return false; 13 if(left->val!=right->val)return false; 14 return check(left->left,right->right)&&check(left->right,right->left); 15 } 16 };
序列化二叉树: NULL的位置补位,一种遍历方式也可以序列话,传参时注意引用的使用,递归遍历和建树
1 class Solution { 2 public: 3 char* Serialize(TreeNode *root) { 4 if(root==NULL)return "#"; 5 string r = to_string(root->val); 6 r.push_back(','); 7 char* left = Serialize(root->left); 8 char* right = Serialize(root->right); 9 char* res = new char[strlen(left) + strlen(right) + r.size()]; 10 strcpy(res,r.c_str()); 11 strcat(res,left); 12 strcat(res,right); 13 return res; 14 } 15 TreeNode* Deserialize(char *str) { 16 return decode(str); 17 } 18 TreeNode* decode(char* & str) 19 { 20 if(*str=='#'){ 21 str++; 22 return NULL; 23 } 24 int num=0; 25 while(*str!=',') 26 { 27 num=num*10+(*(str++)-'0'); 28 } 29 str++; 30 TreeNode* node = new TreeNode(num); 31 node->left = decode(str); 32 node->right = decode(str); 33 return node; 34 } 35 };
数据流中的中位数:维护两个平衡堆,大根堆个数==小根堆个数 或 大根堆个数+1==小根堆个数
1 class Solution { 2 public: 3 priority_queue<int, vector<int>, less<int> > p;//大根堆 4 priority_queue<int, vector<int>, greater<int> > q;//小根堆 5 void Insert(int num) 6 { 7 if(p.empty()||num<=p.top())p.push(num); 8 else q.push(num); 9 if(p.size()==q.size()+2)q.push(p.top()),p.pop(); 10 if(p.size()+1==q.size())p.push(q.top()),q.pop(); 11 } 12 13 double GetMedian() 14 { 15 return p.size()==q.size()?(p.top()+q.top())/2.0:p.top();//返回double 16 } 17 18 };
滑动窗口的最大值: 有重复,deque版的尺取法,队首记录窗口内最大值的下标
1 class Solution { 2 public: 3 vector<int> maxInWindows(const vector<int>& num, unsigned int size) 4 { 5 vector<int> res; 6 deque<int> s;// 双端队列 7 for(int i=0;i<num.size();i++) 8 { 9 while(s.size()&&num[s.back()]<num[i])s.pop_back();//队尾<当前值,出队 10 while(s.size()&&i-s.front()+1>size)s.pop_front();//队首出窗,出队 11 s.push_back(i);//每次都入队 12 if(size&&i+1>=size)//大于窗口时开始写入 13 res.push_back(num[s.front()]); 14 } 15 return res; 16 } 17 };
把数组排成最小的数: to_string比较x+y, y+x 排序
二叉搜索树与双向链表: 递归和非递归解法,相对简单,注意树的递归思想,只考虑一个单元的处理,以及出口的设置。
1 TreeNode* Convert(TreeNode* pRootOfTree) 2 { 3 if(pRootOfTree==NULL)return NULL; 4 TreeNode* leftRoot = Convert(pRootOfTree->left); 5 TreeNode* root = leftRoot; 6 while(leftRoot!=NULL&&leftRoot->right!=NULL)leftRoot=leftRoot->right; 7 if(leftRoot==NULL){ 8 root = pRootOfTree; 9 pRootOfTree->left = NULL; 10 } 11 else { 12 leftRoot->right=pRootOfTree; 13 pRootOfTree->left=leftRoot; 14 } 15 TreeNode* rightRoot = Convert(pRootOfTree->right); 16 pRootOfTree->right = rightRoot; 17 if(rightRoot!=NULL)rightRoot->left = pRootOfTree; 18 return root; 19 } 20 21 // 非递归中序遍历记录pre结点 22 TreeNode* Convert(TreeNode* pRootOfTree) 23 { 24 if(pRootOfTree==NULL)return NULL; 25 stack<TreeNode*> s; 26 TreeNode* p = pRootOfTree; 27 TreeNode* pre = NULL; 28 TreeNode* root = NULL; 29 while(p||s.size()) 30 { 31 while(p!=NULL) 32 { 33 s.push(p); 34 p = p->left; 35 } 36 p = s.top(); 37 s.pop(); 38 39 if(pre==NULL)root = p; 40 else 41 { 42 pre->right = p; 43 p->left = pre; 44 } 45 pre = p; 46 p=p->right; 47 } 48 return root; 49 }
整数中1出现的次数(从1到n整数中1出现的次数): 找规律,从个位到最高位一个一个来,统计出现次数,编程之美统计整数中x出现的次数
1 class Solution { 2 public: 3 int NumberOf1Between1AndN_Solution(int n) 4 { 5 if(n<0)return 0; 6 int high,low,cur,tmp,i=1; 7 high=n; 8 int tot=0; 9 while(high!=0) 10 { 11 high = n/(int)pow(10,i); 12 tmp = n%(int)pow(10,i); 13 cur = tmp/(int)pow(10,i-1); 14 low = tmp%(int)pow(10,i-1); 15 if(cur==1) 16 tot+=high*(int)pow(10,i-1)+low+1; 17 else if(cur<1) 18 tot+=high*(int)pow(10,i-1); 19 else 20 tot+=(high+1)*(int)pow(10,i-1); 21 i++; 22 } 23 return tot; 24 } 25 };
平衡二叉树: 普通递归求解,需要维护一个求深度的递归函数,根据该结点的左右子树高度差判断是否平衡,然后递归地对左右子树进行判断
1 class Solution { 2 public: 3 bool IsBalanced_Solution(TreeNode * root) { 4 if(root==NULL)return true; 5 return IsBalanced_Solution(root->left)&&IsBalanced_Solution(root->right)&&abs(depth(root->left)-depth(root->right))<=1; 6 } 7 int depth(TreeNode * root) 8 { 9 if(root==NULL)return 0; 10 return max(depth(root->left),depth(root->right))+1; 11 } 12 };
这种做法有很明显的问题,在判断上层结点的时候,会多次重复遍历下层结点,增加了不必要的开销。如果改为从下往上遍历,如果子树是平衡二叉树,则返回子树的高度;如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次
1 bool IsBalanced_Solution(TreeNode * root) { 2 if(root==NULL)return true; 3 return depth(root)!=-1; 4 } 5 6 int depth(TreeNode * root) 7 { 8 if(root==NULL)return 0; 9 int left = depth(root->left); 10 if(left==-1)return -1; 11 int right = depth(root->right); 12 if(right==-1)return -1; 13 return abs(left-right)>1?-1:max(left,right)+1; 14 }
丑数:构造第index个丑数
文字转自:lizo,code转自:anybody
如果p是丑数,那么p=2^x * 3^y * 5^z
那么只要赋予x,y,z不同的值就能得到不同的丑数。
如果要顺序找出丑数,要知道下面几个特点。
对于任何丑数p:
(一)那么2*p,3*p,5*p都是丑数,并且2*p<3*p<5*p
(二)如果p<q, 那么2*p<2*q,3*p<3*q,5*p<5*q
算法思想:
由于1是最小的丑数,那么从1开始,把2*1,3*1,5*1,进行比较,得出最小的就是1
的下一个丑数,也就是2*1,
这个时候,多了一个丑数‘2’,也就又多了3个可以比较的丑数,2*2,3*2,5*2,
这个时候就把之前‘1’生成的丑数和‘2’生成的丑数加进来也就是
(3*1,5*1,2*2,3*2,5*2)进行比较,找出最小的。。。。如此循环下去就会发现,
每次选进来一个丑数,该丑数又会生成3个新的丑数进行比较。这样
暴力方法也能解决,但是如果在面试官用这种方法,估计面试官只会摇头吧。下面说一个O(n)的算法。
在上面的特点中,既然有p<q, 那么2*p<2*q,那么
“我”在前面比你小的数都没被选上,你后面生成新的丑数一定比“我”大吧,那么你乘2
生成的丑数一定比我乘2的大吧,那么在我选上之后你才有机会选上。
其实每次我们只用比较3个数:用于乘2的最小的数、用于乘3的最小的数,用于乘5的最小的
数。也就是比较(2*x , 3*y, 5*z) ,x>=y>=z的
1 class Solution { 2 public: 3 int GetUglyNumber_Solution(int index) { 4 if(index<7)return index; 5 vector<int> res(index); 6 res[0]=1; 7 int t2=0,t3=0,t5=0; 8 for(int i=1;i<index;i++) 9 { 10 res[i]=min(res[t2]*2,min(res[t3]*3,res[t5]*5)); 11 if(res[i]==res[t2]*2)t2++; 12 if(res[i]==res[t3]*3)t3++; 13 if(res[i]==res[t5]*5)t5++; 14 } 15 return res[index-1]; 16 } 17 };
扑克牌顺子:考虑顺子的特征,不要考虑细节,抓住主要特征,1.除了大小王没有重复的,2.max-min<5
正则表达式匹配:分为后一个为*或不为*,两种情况,不为*则一个一个比,为*考虑两种情况,匹配0,和匹配1(匹配多个在匹配1后递归中包含)
1 class Solution { 2 public: 3 bool match(char* str, char* pattern) 4 { 5 6 if (*str == '\0' && *pattern == '\0') 7 return true; 8 if (*str != '\0' && *pattern == '\0') 9 return false; 10 //if the next character in pattern is not '*' 11 if (*(pattern+1) != '*') 12 { 13 if (*str == *pattern || (*str != '\0' && *pattern == '.')) 14 return match(str+1, pattern+1); 15 else 16 return false; 17 } 18 //if the next character is '*' 19 else 20 { 21 if (*str == *pattern || (*str != '\0' && *pattern == '.')) 22 return match(str, pattern+2) || match(str+1, pattern); 23 else 24 return match(str, pattern+2); 25 } 26 } 27 };
孩子们的游戏(圆圈中最后剩下的数):
1 int index=-1; 2 while(c.size()>1) 3 { 4 index = (m+index)%c.size(); 5 c.erase(c.begin()+index); 6 index--; 7 }
二叉树的下一个结点: 中序遍历的下一个结点
1 class Solution { 2 public: 3 TreeLinkNode* GetNext(TreeLinkNode* pNode) 4 { 5 if(pNode==NULL)return NULL; 6 if(pNode->right!=NULL) // 从右子树找后继结点 7 { 8 pNode=pNode->right; 9 while(pNode->left!=NULL) 10 pNode=pNode->left; 11 return pNode; 12 } 13 while(pNode->next!=NULL) // 向上回溯,该节点是其父节点的左孩子才行 14 { 15 if(pNode->next->left==pNode)return pNode->next; 16 pNode=pNode->next; 17 } 18 return NULL; //返回到root还没有找到 19 } 20 };