接上一篇博客,突然觉得把所有的题都放在一起会显得特别臃肿,所以感觉还是分下来比较好。于是决定一篇只写是个题。那么,继续上一篇博客的刷题之旅!
11.输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
常规解法:最直观的思路就是不断的左移,判断n的从右到左的位是不是有1。共循环整数的位数的次数,例如32位整数则循环32次,代码如下:
//运行时间:3ms 占用内存:376k
class Solution {
public:
int NumberOf1(int n) {
int count = 0;
unsigned int flag = 1;
while (flag) {
if (n & flag)
count++;
flag = flag << 1;
}
return count;
}
};
最巧妙的解法:如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。有几个1则只需循环几次。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。代码如下:
//运行时间:3ms 占用内存:460k
class Solution {
public:
int NumberOf1(int n) {
int count = 0;
while(n) {
++count;
n = (n-1) & n;
}
return count;
}
};
12.给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0。
主要的就是特殊情况的处理。
运行时间:3ms 占用内存:504k
class Solution {
public:
double Power(double base, int exponent) {
if (base == 0.0) {
return 0.0;
}
double result = 1;// 前置结果设为1.0,即当exponent=0 的时候,就是这个结果
int e = exponent > 0 ? exponent : -exponent;// 获取指数的绝对值
for(int i = 1 ; i <= e; i ++) {// 根据指数大小,循环累乘
result *= base;
}
return exponent > 0 ? result : 1 / result;// 根据指数正负,返回结果
}
};
13.输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
首先是一种偷懒的做法,即利用一个新的数组去做:
//运行时间:5ms 占用内存:480k
class Solution {
public:
void reOrderArray(vector<int> &array) {
vector<int> result;//创建一个新的数组
int length=array.size();//所输入数组的大小
for(int i=0;i<length;i++) {
if(array[i]%2==1)
result.push_back(array[i]);//先将奇数存入数组
}
for(int i=0;i<length;i++) {
if(array[i]%2==0)
result.push_back(array[i]);//再将偶数放入数组
}
array=result;
}
};
另一种做法,类似于冒泡排序的思想:
//运行时间:4ms 占用内存:460k
for (int i = 0; i < array.size();i++) {
for (int j = array.size() - 1; j>i;j--) {
if (array[j] % 2 == 1 && array[j - 1]%2 == 0) {//前偶后奇交换
int temp=array[j];
array[j]=array[j-1];
array[j-1]=temp;
}
}
}
多提一嘴,offer书上的要求是不用顺序要求,因此书上的做法是这样:
int length = array.size()-1;
if (array.empty())
return;
int* pBegin = &array[0];
int* pEnd = &array[length];
while (pBegin < pEnd)
{
// 向后移动pBegin,直到它指向偶数
while (pBegin < pEnd && (*pBegin & 0x1) != 0)
pBegin++;
// 向前移动pEnd,直到它指向奇数
while (pBegin < pEnd && (*pEnd & 0x1) == 0)
pEnd--;
if (pBegin < pEnd)
{
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
}
}
/*答案错误:您提交的程序没有通过所有的测试用例
case通过率为0.00%
用例:
[1,2,3,4,5,6,7]
对应输出应该为:
[1,3,5,7,2,4,6]
你的输出为:
[1,7,3,5,4,6,2]
*/
14.输入一个链表,输出该链表中倒数第k个结点。
运行时间:6ms 占用内存:472k
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
if(pListHead == nullptr || k == 0)
return nullptr;
ListNode *pAhead = pListHead;
ListNode *pBehind = nullptr;
for(unsigned int i = 0; i < k - 1; ++ i)
{
if(pAhead->next != nullptr)
pAhead = pAhead->next;
else
{
return nullptr;
}
}
pBehind = pListHead;
while(pAhead->next != nullptr)
{
pAhead = pAhead->next;
pBehind = pBehind->next;
}
return pBehind;
}
};
15.输入一个链表,反转链表后,输出新链表的表头。
//运行时间:3ms 占用内存:460k
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* pReversedHead = nullptr;
ListNode* pNode = pHead;
ListNode* pPrev = nullptr;
while(pNode != nullptr) {
ListNode* pNext = pNode->next;
if(pNext == nullptr)
pReversedHead = pNode;
pNode->next = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReversedHead;
}
};
16.输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
//运行时间:3ms 占用内存:488k
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1 == nullptr)
return pHead2;
else if(pHead2 == nullptr)
return pHead1;
ListNode* pMergedHead = nullptr;
if(pHead1->val < pHead2->val) {
pMergedHead = pHead1;
pMergedHead->next = Merge(pHead1->next, pHead2);
}
else {
pMergedHead = pHead2;
pMergedHead->next = Merge(pHead1, pHead2->next);
}
return pMergedHead;
}
};
17.输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
这题也还是得用递归来做:
//运行时间:3ms 占用内存:476k
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool result = false;
if(pRoot1 != nullptr && pRoot2 != nullptr) {//当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
if(pRoot1->val == pRoot2->val)//以这个根节点为为起点判断是否包含Tree2
result = DoesTree1HaveTree2(pRoot1, pRoot2);
if(!result)//如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
result = HasSubtree(pRoot1->left, pRoot2);
if(!result)//如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
result = HasSubtree(pRoot1->right, pRoot2);
}
return result;//返回结果
}
bool DoesTree1HaveTree2(TreeNode* pRoot1, TreeNode* pRoot2) {
if(pRoot2 == nullptr)//如果Tree2已经遍历完了都能对应的上,返回true
return true;
if(pRoot1 == nullptr)//如果Tree2还没有遍历完,Tree1却遍历完了。返回false
return false;
if(pRoot1->val != pRoot2->val) //如果其中有一个点没有对应上,返回false
return false;
//如果根节点对应的上,那么就分别去子节点里面匹配(递归思想)
return DoesTree1HaveTree2(pRoot1->left, pRoot2->left) &&
DoesTree1HaveTree2(pRoot1->right, pRoot2->right);
}
};
值得一提的是,offer书上的val是double类型。因此在判断是否相等时不能简单的直接用==,而是要通过做差来判断:
bool Equal(double num1, double num2){
if((num1 - num2 > -0.0000001) && (num1 - num2 < 0.0000001))
return true;
else
return false;
}
18.操作给定的二叉树,将其变换为源二叉树的镜像。
先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子节点,当交换完所有的非叶子结点的左右子结点之后,就得到了树的镜像。
递归方法代码如下:
//运行时间:5ms 占用内存:400k
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot==nullptr)
return;
//交换左右节点
TreeNode *pTemp = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = pTemp;
//递归实现
if(pRoot->left)
Mirror(pRoot->left);
if(pRoot->right)
Mirror(pRoot->right);
}
};
基于循环的方法如下:
//运行时间:3ms 占用内存:476k
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot == nullptr)
return;
std::stack<TreeNode*> stackTreeNode;
stackTreeNode.push(pRoot);
while(stackTreeNode.size() > 0) {
TreeNode *pRoot = stackTreeNode.top();
stackTreeNode.pop();
TreeNode *pTemp = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = pTemp;
if(pRoot->left)
stackTreeNode.push(pRoot->left);
if(pRoot->right)
stackTreeNode.push(pRoot->right);
}
}
};
19.输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 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.
这题还是有点难的,主要就是循环套循环,还有边界条件的各种坑点。主要思路是:利用左上和右下的坐标定位出一次要旋转打印的数据,一次旋转打印结束后,往对角分别前进和后退一个单位。通过对top与bottom,left与right的比较来限定for循环范围。
//运行时间:4ms 占用内存:376k
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
int rows = matrix.size();
int columns = matrix[0].size();
vector<int> result;
//若为空则直接返回
if (rows == 0 || columns == 0)
return result;
//记录上下左右界限
int left = 0, top = 0, right = columns - 1, bottom = rows - 1;
while (left <= right && top <= bottom)
{
for (int i = left; i <= right; ++i) //从左到右打印一行
result.push_back(matrix[top][i]);
for (int i = top + 1; i <= bottom; ++i)//从上到下打印一列
result.push_back(matrix[i][right]);
if (top != bottom) //从右到左打印一行
for (int i = right - 1; i >= left; --i)
result.push_back(matrix[bottom][i]);
if (left != right) //从下到上打印一行
for (int i = bottom - 1; i > top; --i)
result.push_back(matrix[i][left]);
left++,top++,right--,bottom--;
}
return result;
}
};
20.定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
//运行时间:5ms 占用内存:480k
class Solution {
public:
void push(int value) {
m_data.push(value);//把新元素添加到辅助栈
if(m_min.empty())//当新元素比之前的最小元素小时,把新元素插入辅助栈里;
m_min.push(value);
if(m_min.top()>value)//否则把之前的最小元素重复插入辅助栈里
m_min.push(value);
}
void pop() {
if(m_min.top()==m_data.top())
m_min.pop();
m_data.pop();
}
int top() {
return m_data.top();
}
int min() {
return m_min.top();
}
private:
stack<int> m_data;//数据栈,存放栈的所有元素
stack<int> m_min;// 辅助栈,存放栈的最小元素
};