剑指offer解题思路锦集11-20题
Posted ExitQuit
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指offer解题思路锦集11-20题相关的知识,希望对你有一定的参考价值。
又来更新剑指offer上的题目思路啦。
11、【二进制中1的个数】
题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
eg:NumberOf1(1)=1 NumberOf1(2)=0 NumberOf1(3)=2 NumberOf1(4)=1 NumberOf1(5)=2 NumberOf1(6)=2 NumberOf1(7)=3
思路:每次都将数字n的最后一位1反转成0,不断反转到这个数字变成0,然后我们统计反转了多少次,这样不就可以成功得到这个数字有多少位了吗?
难点:如何反转一个数字的最后一位1,解决方案如下:
n = n & (n - 1);
代码:
int NumberOf1(int n) { int number = 0; while (n != 0) { n = n & (n - 1); ++number; } return number; }
思路2:可不可以尝试不去改变这个数字呢?答案也可以的。假设是int类型,如果我循环32次,判断每个位是否为1,这样不也可以得到这个数字有多少位为1吗?诚然,这样也是存在着缺点的。
缺点:比思路1的时间复杂度略高,不过也是常数级别的,因为数字的位数是有限制的,两者可能系数上存在着差别吧!~~~还有一个缺点,就是这样的代码移植性较差~~
存在坑:
(temp & n) != 0
这里的括号不能去掉,因为&的优先级比 != 低。。。。
代码:
int NumberOf1(int n) { int number = 0; int temp = 1; for (int i = 0; i < 32; ++i) { if ((temp & n) != 0) { ++number; } temp = temp<<1; } return number; }
12、【数值的整数次方】
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
例子:2^10
思路1:2*2*2*2*2*2*2*2*2*2,循环N次即可。时间复杂度O(n)没啥好说的,跳过。
思路2: 分治想法,假设是求a^n
如果n为偶数,那么转化成求x=a^(n/2),然后返回x*x即可。
如果n为奇数,那么转化成求x=a^(n-1/2),然后返回x*x*a即可。
时间复杂度是O(lgn)。。。怎么证明?
应用算法导论中的主方法即可。
这里,有T(n) = T(n/2) + 1,即 b = 2, a = 1,f(n)=1。因此满足条件2,因此时间复杂度为O(nlogb(a))=O(nlog2(1)*lgn)=O(1*lgn)=O(lgn)。
哈哈,是不是瞬间感觉写难了。。。。
13、【调整数组顺序使奇数位于偶数前面】
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路:两个数组,一个按顺序保存奇数,另一个按顺序保存偶数。。。时间复杂度O(N),空间复杂度O(N)。
思路2:创建一个数组,循环2次,第一次只扫描奇数,丢进数组中。第二次只扫描偶数,丢进数组。。十分简单。。。
思路3:类似冒泡算法,前偶后奇数就交换。
void reOrderArray(vector<int> &arr) {//双指针 int n = arr.size(); for(int i = 0; i < n; ++i) { for(int j = 0; j < n-i-1; ++j) { if(arr[j]%2 == 0 && arr[j+1]%2 == 1) swap(arr[j],arr[j+1]); } } }
思路4:两个指针咯,一个指针1从头到尾,另外一个指针2从尾到头,如果指针1发现偶数,那么停下来,如果指针2发现奇数,那么也停下来。再交换两个指针指向的数就Ok了。
问题:这样是真的可以吗?答案是不可以的哦~哈哈,因为这样违反了题目要求——要求奇数和偶数顺序不能改变。。。嗯,是的,如果是让顺序可以随意的话,那么这就是很nice的解。
14、【链表中倒数第k个结点】
题目:输入一个链表,输出该链表中倒数第k个结点。
思路1:遍历得到链表长度n,然后再遍历一次,给返回第n-k+1个元素。这里遍历了两次,时间复杂度为0(n),空间复杂度为O(1)。
思路2:思路1是否存在优化的地方呢?是存在的,我们可以考虑考虑能不能不遍历两次,这时候我们想能不能用空间换时间。
好比,将链表转化成数组保存下来,那么我们第二次遍历链表操作可以换成获取数组中的n-k+1个元素操作了,只需要遍历一次链表就可以了。但是这种方法的空间复杂度为O(N)。
思路3:两个指针,也就是传说中的快慢指针。一个指针先走k步,然后慢指针才开始跑。快指针到头了,那么落后了k步的慢指针不就是倒数第k个元素了吗?
代码:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { if(NULL == pListHead) return NULL; ListNode* pfront = pListHead; ListNode* pend = pListHead; for (int i = 0; i < k; i++) { if(pfront != NULL) { pfront = pfront->next; } else { return NULL;//超出了 } } for(;pfront != NULL;pfront = pfront->next) { pend = pend->next; } return pend; }
15、【反转链表】
输入一个链表,反转链表后,输出链表的所有元素。
A->B->C->D->E A->B->C->D<-E (D->NULL) A->B->C<-D<-E (C->NULL) A->B<-C<-D<-E (B->NULL) A<-B<-C<-D<-E (A->NULL)
代码
ListNode* ReverseList(ListNode* pHead) { if(pHead == NULL||pHead->next == NULL) return pHead;//得到链表尾部 ListNode* tail = ReverseList(pHead->next); pHead->next->next = pHead; pHead->next = NULL; return tail; }
16、【合并两个排序的链表】
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
额,这题啥也不用说了吧,,,不就是归并排序的合并过程么。。。。直接贴代码吧。。。
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) { ListNode* res = NULL; ListNode* cur = NULL; ListNode* add = NULL; if(pHead1 == NULL) return pHead2; if(pHead2 == NULL) return pHead1; while(pHead1 != NULL &&pHead2 != NULL)//两个迭代器 { add = pHead1->val >= pHead2->val ?pHead2:pHead1; if(res == NULL) res = cur = add;////////////第一次才执行 在于选择一条链出来 else { cur->next = add; cur = cur->next; } if(add == pHead1) pHead1=pHead1->next; else pHead2 = pHead2->next; } if(pHead1 != NULL) cur->next = pHead1; if(pHead2 != NULL) cur->next = pHead2; return res; }
17、【树的子结构】
题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
其实这里主要是靠对问题的拆分能力。
子函数CompTree:判断两棵树是否相等。
判断是否为子树:假设当前节点为根,那么是否为子结构?
代码:
class Solution { private: bool CompTree(TreeNode* pRoot1, TreeNode* pRoot2)//判断2是否为1的子树。。。。 { if(pRoot2 == NULL) return true; if(pRoot1 == NULL) return false; if(pRoot1->val == pRoot2->val) return CompTree(pRoot1->left,pRoot2->left)&&CompTree(pRoot1->right,pRoot2->right); return false; } public: bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) { if(pRoot1 == NULL ||pRoot2 == NULL ) return false; return CompTree(pRoot1,pRoot2) || HasSubtree(pRoot1->left,pRoot2) || HasSubtree(pRoot1->right, pRoot2); } };
18、二叉树的镜像
如果这时候,我们用自然语言去描述,可以这样认为怎么将一颗二叉树转化成源二叉树的镜像:交换两棵树的左子树指针、右子树指针。
假设树的结构如下:
struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } };
我们轻易得到第一个版本可以AC的代码。使用递归即可。
零、先序遍历递归版本
class Solution { public: void Mirror(TreeNode *pRoot) { if(pRoot != NULL) { TreeNode *tmp = pRoot->left; pRoot->left = pRoot->right; pRoot->right = tmp; if (pRoot->left != NULL) { Mirror(pRoot->left); } if (pRoot->right != NULL) { Mirror(pRoot->right); } } } };
但是这个代码是不是最优的呢?很显然,不是,因为其用到递归,可以用非递归实现吗?答案是可以的。
这时候,我们仔细观看一下上面的代码,发现,如果我们将交换左右子树指针的代码转换成print树节点node值。那么上面的代码不就是先序遍历树的代码吗?
因此,我们可以深思,本质上,这条题的解答,就是遍历树,然后将每一个节点的左右孩子交换。
那么我们又想到了树的遍历方式,不难得到以下几种解答:
一、基于队列的广度优先遍历
class Solution { public: void Mirror(TreeNode *pRoot) { // if(pRoot != NULL) { //基于队列广度优先遍历的的版本1 queue<TreeNode*> que; que.push(pRoot); while(!que.empty()) { TreeNode* pt = que.front(); que.pop(); if(pt->left != NULL || pt->right != NULL ) { TreeNode* temp = pt->left; pt->left = pt->right; pt->right = temp; } if(pt->left != NULL){que.push(pt->left);} if(pt->right != NULL){que.push(pt->right);} } } } };
二、后续遍历的递归版本
class Solution { public: void Mirror(TreeNode *pRoot) { // if(pRoot != NULL) { //后序遍历整个二叉树,交换里面的两个指针, Mirror(pRoot->left); Mirror(pRoot->right); TreeNode* temp = pRoot->left; pRoot->left = pRoot->right; pRoot->right = temp; */ } } };
三、先续遍历版本改出来的非递归版本
class Solution { public: void Mirror(TreeNode *pRoot) { // if(pRoot != NULL) { stack<TreeNode*> stk; stk.push(pRoot); while(!stk.empty()) { TreeNode* pt = stk.top(); stk.pop(); if(pt->left != NULL){stk.push(pt->left);} if(pt->right != NULL){stk.push(pt->right);} if(pt->left != NULL || pt->right != NULL ) { TreeNode* temp = pt->left; pt->left = pt->right; pt->right = temp; } } } } };
四、基于后续遍历版本改出来的非递归版本
class Solution { public: void Mirror(TreeNode *pRoot) { // if(pRoot != NULL) { stack<TreeNode*> stk; stk.push(pRoot); while(!stk.empty()) { TreeNode* pt = stk.top(); stk.pop(); if(pt->left != NULL || pt->right != NULL ) { TreeNode* temp = pt->left; pt->left = pt->right; pt->right = temp; } if(pt->left != NULL){stk.push(pt->left);} if(pt->right != NULL){stk.push(pt->right);} } } } } };
神奇的代码,,,已经写完了,其实这里考的是将一个未知问题转化成一个已知问题去解决。
19、逆时针打印矩阵
题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,
例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
还是画图吧,假设要打印的矩阵为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
我们首先打印
1 2 3 4 5 8 9 12 13 14 15 16
然后打印
6 7 10 11
是不是很清晰?我们只要实现一个子函数,完成打印正方形的工作,然后写个循环,不断从外到里去调用那个子函数就ok了。
代码如下:
class Solution { public: void GetArray(vector<vector<int> > &matrix,vector<int> &res, int ibegin,int iend,int jbegin,int jend) { //注意 ibegin == iend时候 循环 1 3执行两次 所以要判断 //同理 jbegin == jend也有这个情况 int i = 0; //cout<<ibegin<<" "<<iend<<" "<<jbegin<<" "<<jend<<" "<<endl; for(i = jbegin; i <= jend;++i) res.push_back(matrix[ibegin][i]);//左到右 for(i = ibegin+1; i <= iend;++i) res.push_back(matrix[i][jend]);//上到下 if(ibegin != iend)for(i = jend-1; i >= jbegin;--i) res.push_back(matrix[iend][i]);//右到左 if(jbegin != jend)for(i = iend-1; i > ibegin;--i) res.push_back(matrix[i][jbegin]);//下到上 } vector<int> printMatrix(vector<vector<int> > matrix) { int row = matrix.size();//行 int col = matrix[0].size();//列 vector<int> res; int ibegin = 0; int iend = row-1;// int jbegin = 0; int jend = col-1; for(;ibegin <= iend && jbegin <= jend;++ibegin,--iend,++jbegin,--jend) { GetArray(matrix,res,ibegin,iend,jbegin,jend); } return res; } };
这里考的是对问题的分解能力。
20、包含max函数的栈
题目:定义栈的数据结构,请在该类型中实现一个能够得到栈最大元素的max函数。
假设存在栈 1, 2, 3, 3, 2, 4, 5, 2, 1
那么可以搞一个辅助栈 1, 2, 3, 3, 3, 4, 5, 5, 5
其中辅助栈保存的是栈中的最大元素即可,当弹出时候,两边同时弹出即可。
代码:
class Solution { public: void push(int value) { mdata.push(value); if(mmax.empty()) mmax.push(value); else { int val = mmax.top(); val = val < value ? val:value; mmax.push(val); } } void pop() { mdata.pop(); mmax.pop(); } int top() { mmax.pop(); int val = mdata.top(); mdata.pop(); return val; } int min() { return mmax.top(); } private: stack<int> mdata; stack<int> mmax; };
以上是关于剑指offer解题思路锦集11-20题的主要内容,如果未能解决你的问题,请参考以下文章