其他类型
本专题中记录《剑指offer》中的一些出现频率较少的类型题,包括位运算,数值运算规律等内容,以及LeetCode中的相似题目。
相关题目列表
index | description | key words | done | data |
---|---|---|---|---|
1 | 赋值运算符 | 赋值运算符 | Y | 18-3-19 |
2 | singleton | 单例模式 | Y | 18-3-20 |
9 | 斐波那契数列 | Fibonacci | Y | 18-3-21 |
10 | 二进制中1的个数 | 位运算 | Y | 18-3-21 |
11 | 数值的整数次方 | |||
12 | 打印1到最大的n位数 | |||
32 | 从1到n整数中1出现的次数 | |||
34 | 丑数 | |||
41_1 | 和为s的两个数字 | two sum | Y | 18-4-13 |
41_2 | 和为s的连续正数序列 | 类two sum | Y | 18-4-13 |
43 | n个骰子的点数 | |||
44 | 扑克牌的顺子 | |||
46 | 求1+2+3+...+n | |||
47 | 不用加减乘除做加法 | |||
48 | 不被继承的类 | |||
53 | 正则表达式匹配 | |||
66 | 矩阵中的路径 | |||
67 | 机器人的运动范围 |
面试题1: 赋值运算符函数
题目: 如下为类型CMyString的声明,请为该类型添加赋值运算符函数。
class CMyString {
public:
CMyString(char* pData = NULL);
CMyString(const CMyString& str);
~CMyString(void);
private:
char* m_pData;
};
题目分析
赋值运算符需要考虑以下几个点:
1、返回值应该为类的引用
2、函数结束return *this;
3、传入参数应该为const 的引用;
4、是否释放实例自身已有的内存;
5、自赋值问题的考虑;
参考代码
#include
#include
#include
//#include<>
using namespace std;
//No.1_赋值运算符函数
//如下为类型CMySting的声明,请为该类型添加赋值运算符函数。
class CMyString
{
public:
CMyString(char* pData = NULL); //构造函数
CMyString(const CMyString& str); //构造函数
~CMyString(void); //析构函数
CMyString& operator=(const CMyString& str);
void Print(); //为测试代码准备
private:
char* m_pData;
};
//=================================================
//构造函数
CMyString::CMyString(char *pData)
{
if (pData == NULL)
{
m_pData = new char[1];
m_pData[0] = '\0';
}
else
{
int length = strlen(pData);
m_pData = new char[length + 1];
strcpy(m_pData, pData);
}
}
//构造函数2
CMyString::CMyString(const CMyString &str)
{
int length = strlen(str.m_pData);
m_pData = new char[length + 1];
strcpy(m_pData, str.m_pData);
}
//析构函数
CMyString::~CMyString()
{
delete []m_pData;
}
//==========================题目答案============================
/*
//初级程序员
//判断自赋值-->如果不是自赋值则释放*this的内存-->开辟空间进行赋值-->返回*this
CMyString& CMyString::operator=(const CMyString &str) //赋值运算符应具有构造函数和析构函数两种功能
{
if (this == &str) //自赋值
return *this;
//先用delete释放内存,然后再用new为新的数据开辟空间,若new char内存不足,则无法正常开辟空间
//这时,原始的m_pData没有了,也无法更新新的数据,发生异常的情况下CMyString无法保持有效状态,违背了异常安全性原则
delete []m_pData;
m_pData = NULL;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
*/
//高级程序员
//先创建一个临时实参,再交换临时实参和原来的实参
CMyString& CMyString::operator=(const CMyString &str)
{
if (this != &str) //如果不是自赋值
{
CMyString strTemp(str); //创建临时实参strTemp
//将局部变量的m_pData指向的地址与需要实例m_pData指向的地址互换
//这种方法好在:
//先创建一个临时实例,若不成功,抛出异常,不影响原来的自己
//若成功,str_Temp是局部变量,在作用域if中,出了if就会自动调用析构函数
//而我们把str_Temp.m_pData和原来自己的m_pData互换了,这时候析构的就是原来自己的空间
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}
//====================测试代码=========================
void CMyString::Print()
{
cout << m_pData << endl;
}
//测试代码1
void Test1()
{
cout << "Test1 begin: " << endl;
char *text = "Hello World";
CMyString str1(text);
CMyString str2;
str2 = str1;
cout << "The expected result is : " << text << endl;
cout << "The actual result is : ";
str2.Print();
cout << endl;
}
//测试代码2--自赋值
void Test2()
{
cout << "Text2 begin: " << endl;
char *text = "Hello World";
CMyString str1(text);
CMyString str2;
str1 = str1;
cout << "The expected result is : " << text << endl;
cout << "The actual result is : ";
str1.Print();
cout << endl;
}
//测试代码3--连续赋值
void Text3()
{
cout << "Text3 begin: " << endl;
char *text = "Hello World";
CMyString str1(text);
CMyString str2, str3;
str2 = str3 = str1;
cout << "The expected result is : " << text << endl;
cout << "The actual result is : ";
str2.Print();
cout << endl;
}
int main()
{
Test1();
Test1();
Test1();
return 0;
}
面试题2: 实现Singleton模式
题目:使用C++实现单例模式
题目分析
单例模式是最经典的设计模式,还是需要掌握的。
单例模式需要满足两个条件:
1、保证一个类只能创建一个实例;
2、提过对该实例的全局访问点。
单例模式的应用:
1、日志类,一个应用往往只对应一个日志实例;
2、配置类,应用的配置集中管理,并提供全局访问;
3、管理器,比如windows系统的任务管理器就是一个例子,总是只有一个管理器实例;
4、共享资源类,加载资源需要较长时间,使用单例可以避免重复加载资源,并访问多个共享资源。
参考代码
/* lazy Singleton */
class Singleton
{
public:
/* 提供static的Instance()方法,作为全局访问点;
* 如果有现成的实例,则直接返回;
*如果没有,则将新生成的实例保存到私有的static属性中
*/
/* Instance()返回的是实例的引用而不是指针,如果是指针有被外部调用者delete的风险。
* 直到Instance()方法被访问,才会生成实例,这种特性被称为延迟初始化(Lazy Singleton)
*/
/* 但是这种方式不是线程安全的,比如有线程A和B
* 都通过了instance==nullptr判断,则两个线程会分配创建新实例,这样单例模式就被打破了。
*/
static Singleton& Instance() {
if (instance == nullptr)
instance = new Singleton;
return *instance;
}
private:
//构造函数与拷贝构造函数,拷贝赋值运算符都声明为私有方法,这样杜绝从外部生成新的实例
Singleton();
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton* instance;
};
//实现文件中
Singleton* Singleton::instance = 0;
/* Eager Singleton */
/* 这种模式在程序开始就完成了实例的创建。与Lazy相反;
* 因为在main函数之前初始化,所以没有线程安全的问题;
* 但是潜在的问题在于no-local static对象(函数外的static对象)
* 在不同编译单元中的初始化顺序是未定义的。
* 如果在初始化完成之前调用了Instance()方法就会返回一个未定义的实例。
* 也就是说不能保证先完成初始化。
*/
class Singleton
{
public:
static Singleton& Instance() {
return instance;
}
private:
Singleton();
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static Singleton instance;
};
/* Meyers Singleton*/
/* 下面是effective C++中提出的一种模式,使用local static对象
* 实现了当第一次访问Instance()方法时才创建实例。
*/
class Singleton
{
public:
static Singleton& Instance() {
static Singleton instance;
return instance;
}
private:
Singleton();
~Singleton();
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
};
面试题9:斐波那契数列
题目1: 写一个函数,输入n,求斐波那契数列的第n项。
题目分析
本题为直接考查斐波那契数列,可以利用多种方式解答。
参考代码
//Fibonacci数列
#include
using namespace std;
//递归方式,包含大量重复计算
int Fibonacci_Recursive(int n)
{
if (n < 0)
return -1;
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return Fibonacci_Recursive(n - 1) + Fibonacci_Recursive(n - 2);
}
//循环方式
int Fibonacci_Iterative(int n)
{
int result[2] = {0, 1};
if (n < 2)
return result[n];
int i = 0;
int j = 1;
int FibN = 0;
for (int index = 2; index <= n; ++index) //相当于两个指针不断向后移动
{
FibN = i + j;
i = j;
j = FibN;
}
return FibN;
}
int main()
{
cout << Fibonacci_Recursive(6) << endl;
cout << Fibonacci_Iterative(6) << endl;
return 0;
}
题目2:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级台阶总共有多少种跳法。
题目分析
本题为Fibonacci的典型应用。参考代码如上。
相似题目
本题与LeetCode中的70. Climbing Stairs完全一致,参考代码见:
LeetCode 70 code
还可以在牛客网 剑指offer上完成对本题的练习。
面试题10: 二进制中1的个数
题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。
题目分析
本题为典型的位运算应用题。可以记住如下规律:
将一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变为0。 通过这个规律可以得到鲁棒性最强的代码。
参考代码
#include
using namespace std;
//通过将n不断右移与1相与判断,但是这样对于有符号数中的负数会陷入死循环
int NumberOf1_Solution1(int n)
{
int count = 0;
while (n)
{
if (n & 1)
count ++;
n = n >> 1;
}
return count;
}
//通过将1不断左移1->10->100... 与n相与计算结果,但是干循环的次数由于while循环条件,等于二进制的位数,即32.
int NumberOf1_Solution2(int n)
{
int count = 0;
unsigned int i = 1;
while (i)
{
if (n & i)
count ++;
i = i << 1;
}
return count;
}
//整数中有几个1就只需要循环几次
int NumberOf1_Solution3(int n)
{
int count = 0;
while (n)
{
count++;
n = n & (n - 1);
}
return count;
}
int main()
{
cout << NumberOf1_Solution1(9) << endl;
//cout << NumberOf1_Solution1(-9) << endl; //死循环
cout << NumberOf1_Solution2(9) << endl;
cout << NumberOf1_Solution2(-9) << endl;
cout << NumberOf1_Solution3(9) << endl;
cout << NumberOf1_Solution3(-9) << endl;
return 0;
}
相似题目
本题与LeetCode中338. Counting Bits类似,只是将本题扩展了。参考代码见:
LeetCode 338 code
还可以在牛客网 剑指offer上完成对本题的练习。
面试题41_1: 和为s的两个数字
题目: 输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s, 输出任意一对即可。
题目分析
由于是已排序的递增数组,本题可以采用夹逼的方式,用双指针实现。
参考代码
#include
#include
using namespace std;
class Solution {
public:
vector result;
vector FindNumbersWithSum(vector array,int sum) {
if (array.size() <= 0)
return result;
int small = 0;
int big = array.size() - 1;
while (small < big){
if (array[small] + array[big] == sum){
result.push_back(array[small]);
result.push_back(array[big]);
break;
}
else if (array[small] + array[big] > sum){
big--;
}
else
small++;
}
return result;
}
};
int main()
{
vector data = {1,2,3,4,5,6,7,8};
Solution solu;
vector result = solu.FindNumbersWithSum(data, 11);
for (int i = 0; i < result.size(); ++i)
{
cout << result[i] << " ";
}
return 0;
}
相似题目
本题与LeetCode中的1. Two Sum完全一致。参考代码见:
LeetCode 1 code
还可以在牛客网 剑指offer上完成对本题的练习。
面试题41_2: 和为s的连续正数序列
题目: 输入一个正数s,打印出所有和为s的连续正整数序列(至少含有两个数)。例如输入15,由于1+2+3+4+5=4+5+6=7+8+15,所以打印结果就为这三个序列。
题目分析
根据41_1的解题经验,我们可以采用两个数small和big分别表示序列中的最小值和最大值,当序列的和大于target时,可以增大small,当序列和小于target时,可以增大big。
由于要求最少有两个数,所以big一直增大到(1+target)/2。
参考代码
#include
#include
using namespace std;
class Solution {
public:
vector> result;
vector > FindContinuousSequence(int sum) {
int small = 1;
int big = 2;
int Cursum = 3;
int middle = (1+sum)/2;
while (small < middle){
Cursum = (small + big)*(big - small +1)/2;
if (Cursum < sum)
++big;
if (Cursum == sum){
vector temp;
for (int i = small; i <= big; ++i){
temp.push_back(i);
}
result.push_back(temp);
++small; //此句很重要,不可缺少,要不然无法完成while循环
}
if (Cursum > sum)
++small;
}
return result;
}
};
int main()
{
Solution solu;
vector> result;
result = solu.FindContinuousSequence(15);
for (int i = 0; i < result.size(); ++i)
{
for (int j = 0; j < result[i].size(); ++j)
{
cout << result[i][j] << " ";
}
cout << endl;
}
return 0;
}
相似题目
可以在牛客网 剑指offer上完成对本题的练习。
【参考】
[1] 《剑指offer》
欢迎转载,转载请注明出处wenmingxing 《剑指offer》其他类型题