上一篇是 51~60 题,本篇是 61~68 题。⛽️⛽️
面试题 61:扑克牌的顺子
题目:从扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这 5 张牌是不是连续的。2〜10 为数字本身,A 为 1,J 为 11,Q 为 12,K 为 13,而大、小王可以看成任意数字。
namespace ContinousCards {
int compare(const void* arg1, const void* arg2);
bool isContinuous(int* numbers, int length);
}
// 给快速排序比较大小规则用的函数
int ContinousCards::compare(const void* arg1, const void* arg2) {
return *(int*)arg1 - *(int*)arg2;
}
bool ContinousCards::isContinuous(int* numbers, int length) {
if (numbers == nullptr || length < 1) {
return false;
}
// 数组快速排序
qsort(numbers, length, sizeof(int), compare);
int numberOfZero = 0;
int numberOfGap = 0;
// 统计数组中 0 的个数
for (int i = 0; i < length && numbers[i] == 0; ++i) {
++numberOfZero;
}
// 统计数组中的间隔数目
// 因为数组已经是排过序的了,所以数组中如果有 0 的话一定是位于数组前面的。
// 所以这里 numbers[small] 是数组中第一个非零的数字,
// numbers[big] 则是第二个数字,
// 即 numbers[small] 和 numbers[big] 是一前一后两个数字
int small = numberOfZero;
int big = small + 1;
while (big < length) {
// 两个数相等,有对子,不可能是顺子
if (numbers[small] == numbers[big]) {
return false;
}
// 统计已排序数组相邻两个数字之间的距离
numberOfGap += numbers[big] - numbers[small] - 1;
// small 和 big 都前进一步
small = big;
big++;
}
// 如果 0 的个数小于间隔的数目,则无法构成顺子,否则可以构成顺子
return (numberOfGap > numberOfZero) ? false: true;
}
面试题 62:圆圈中最后剩下的数字
题目:0, 1, ⋯, n-1 这 n 个数字排成一个圆圈,从数字 0 开始每次从这个圆圈里删除第 m 个数字。求出这个圆圈里剩下的最后一个数字。
namespace LastNumberInCircle {
int lastRemaining_Solution1(unsigned int n, unsigned int m);
int lastRemaining_Solution2(unsigned int n, unsigned int m);
}
// 使用链表表示圆圈
int LastNumberInCircle::lastRemaining_Solution1(unsigned int n, unsigned int m) {
// 入参判断
if (n < 1 || m < 1) {
return -1;
}
// 把 [0, n - 1] 这 n 个数加入链表中
unsigned int i = 0;
list<int> numbers;
for (i = 0; i < n; ++i) {
numbers.push_back(i);
}
//迭代器
list<int>::iterator current = numbers.begin();
// while 循环,直到 numbers 剩最后一个节点
while (numbers.size() > 1) {
for (int i = 1; i < m; ++i) {
++current;
// 当遍历到 numbers 结尾后,调转迭代器到链表头部继续遍历
if (current == numbers.end()) {
current = numbers.begin();
}
}
// next 记录下一个节点
list<int>::iterator next = ++current;
// 判断 next 是否是链表结尾,如果是则需要转到链表头部
if (next == numbers.end()) {
next = numbers.begin();
}
// current 自减一步,(对应 next 的 ++)
--current;
// current 擦除
numbers.erase(current);
// 更新 current
current = next;
}
// 返回 current
return *(current);
}
// 数学的魅力
int LastNumberInCircle::lastRemaining_Solution2(unsigned int n, unsigned int m) {
if (n < 1 || m < 1) {
return -1;
}
int last = 0;
for (int i = 2; i <= n; ++i) {
last = (last + m) % i;
}
return last;
}
面试题 63:股票的最大利润
题目:假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖交易该股票可能获得的利润是多少?例如一只股票在某些时间节点的价格为 {9, 11, 8, 5, 7, 12, 16, 14}。如果我们能在价格为 5 的时候买入并在价格为 16 时卖出,则能收获最大的利润 11。
namespace MaximalProfit {
int maxDiff(const int* numbers, unsigned length);
}
int MaximalProfit::maxDiff(const int* numbers, unsigned length) {
// 入参判断,至少有一个买入价格和一个卖出价格
if (numbers == nullptr || length < 2) {
return 0;
}
// 最小值从 0 下标元素开始
int min = numbers[0];
// 首先记录 numbers[1] 和 numbers[0] 的差价
int maxDiff = numbers[1] - min;
for (int i = 2; i < length; ++i) {
// 用于记录买入最小值
if (numbers[i - 1] < min) {
min = numbers[i - 1];
}
// 记录当前价格和最小值的差价
int currentDiff = numbers[i] - min;
// 判断 maxDiff 记录最大差价
if (currentDiff > maxDiff) {
maxDiff = currentDiff;
}
}
// 返回最大差价
return maxDiff;
}
面试题 64:求 1+2+⋯+n
题目:求 1+2+⋯+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。
namespace Accumulate {
// 方法一: 利用构造函数求解
class Temp {
public:
Temp() { ++N; Sum += N; }
static void Reset() { N = 0; Sum = 0; }
static unsigned int GetSum() { return Sum; }
private:
static unsigned int N;
static unsigned int Sum;
};
unsigned int Temp::N = 0;
unsigned int Temp::Sum = 0;
unsigned int Sum_Solution1(unsigned int n) {
Temp::Reset();
Temp *a = new Temp[n];
delete [] a;
a = NULL;
return Temp::GetSum();
}
// 方法二: 利用虚函数求解
class A;
A* Array[2];
class A {
public:
virtual unsigned int Sum (unsigned int n) {
return 0;
}
};
class B: public A {
public:
virtual unsigned int Sum (unsigned int n) {
return Array[!!n]->Sum(n-1) + n;
}
};
int Sum_Solution2(int n) {
A a;
B b;
Array[0] = &a;
Array[1] = &b;
int value = Array[1]->Sum(n);
return value;
}
// 方法三: 利用函数指针求解
typedef unsigned int (*fun)(unsigned int);
unsigned int Solution3_Teminator(unsigned int n) {
return 0;
}
unsigned int Sum_Solution3(unsigned int n) {
static fun f[2] = {Solution3_Teminator, Sum_Solution3};
return n + f[!!n](n - 1);
}
// 方法四: 利用模版类型求解
template <unsigned int n> struct Sum_Solution4 {
enum Value { N = Sum_Solution4<n - 1>::N + n};
};
template <> struct Sum_Solution4<1> {
enum Value { N = 1};
};
template <> struct Sum_Solution4<0> {
enum Value { N = 0};
};
}
面试题 65:不用加减乘除做加法
题目:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、×、÷ 四则运算符号。
namespace AddTwoNumbers {
int add(int num1, int num2);
}
// 用位运算进行加法操作
int AddTwoNumbers::add(int num1, int num2) {
int sum, carry;
do {
sum = num1 ^ num2; // 异或结果可表示二进制表示逐位相加,但是不做进位
carry = (num1 & num2) << 1; // carry 则表示要进行进位的位
// 第三步,如果 carray 是 0 的话,不需要进行进位,即 sum 即是 num1 + num2 的结果,
// 但是如果 carray 不是 0,则要进行 sum 和 carry 相加求和才能最后得 num1 + num2 的结果
// while 条件正是判断是否发生了进位,如果发生了进位则持续进行以位操作所代替的加法操作
num1 = sum;
num2 = carry;
} while (num2 != 0);
return num1;
}
面试题 66:构建乘积数组
题目:给定一个数组 A[0, 1, ⋯, n-1],请构建一个数组 B[0, 1, ⋯, n-1],其中 B 中的元素 B[i] =A[0]×A[1]×⋯ ×A[i-1]×A[i+1]×⋯×A[n-1]。不能使用除法。
void ConstuctArray::buildProductionArray(const vector<double>& input, vector<double>& output) {
unsigned long length1 = input.size();
unsigned long length2 = output.size();
if (length1 == length2 && length2 > 1) {
output[0] = 1;
for (int i = 1; i < length1; ++i) {
output[i] = output[i - 1] * input[i - 1];
}
double temp = 1;
for (unsigned long i = length1 - 2; i >= 0; --i) {
temp *= input[i + 1];
output[i] *= temp;
}
}
}
面试题 67:把字符串转换成整数
题目:请你写一个函数 StrToInt,实现把字符串转换成整数这个功能。当然,不能使用 atoi 或者其他类似的库函数。
namespace StringToInt {
enum States { kValid = 0, kInvalid};
int g_nStatus = kValid;
long long strToIntCore(const char* str, bool minus);
int strToInt(const char* str);
}
long long StringToInt::strToIntCore(const char* digit, bool minus) {
long long num = 0;
while (*digit != '\0') {
if (*digit >= '0' && *digit <= '9') {
// 正数或者负数
int flag = minus ? -1: 1;
num = num * 10 + flag * (*digit - '0');
// 溢出时的情况
if ((!minus && num > 0x7FFFFFFF) || (minus && num < (signed int)0x80000000)) {
num = 0;
break;
}
// 指针移动
digit++;
} else {
// 如果有 0 ~ 9 之外的字符则直接 break
num = 0;
break;
}
}
// digit 没有移动到空字符表示字符串中有非数字字符
if (*digit == '\0') {
g_nStatus = kValid;
}
return num;
}
int StringToInt::strToInt(const char* str) {
// g_nStatus 标记 str 是否能转化为整数
g_nStatus = kInvalid;
long long num = 0;
if (str != nullptr && *str != '\0') {
// minus 表示是负数还是正数
bool minus = false;
// 前进一步,撇开前面的正号和负号
if (*str == '+') {
++str;
} else if (*str == '-') {
++str;
// 表示当前数字是负数
minus = true;
}
// *str 不是空字符,进行字符串到数字的转换
if (*str != '\0') {
num = strToIntCore(str, minus);
}
}
return (int)num;
}
68:树中两个结点的最低公共祖先
题目:输入两个树结点,求它们的最低公共祖先。
bool GetNodePath(const TreeNode* pRoot, const TreeNode* pNode, list<const TreeNode*>& path)
{
if(pRoot == pNode)
return true;
path.push_back(pRoot);
bool found = false;
vector<TreeNode*>::const_iterator i = pRoot->m_vChildren.begin();
while(!found && i < pRoot->m_vChildren.end())
{
found = GetNodePath(*i, pNode, path);
++i;
}
if(!found)
path.pop_back();
return found;
}
const TreeNode* GetLastCommonNode
(
const list<const TreeNode*>& path1,
const list<const TreeNode*>& path2
)
{
list<const TreeNode*>::const_iterator iterator1 = path1.begin();
list<const TreeNode*>::const_iterator iterator2 = path2.begin();
const TreeNode* pLast = nullptr;
while(iterator1 != path1.end() && iterator2 != path2.end())
{
if(*iterator1 == *iterator2)
pLast = *iterator1;
iterator1++;
iterator2++;
}
return pLast;
}
const TreeNode* GetLastCommonParent(const TreeNode* pRoot, const TreeNode* pNode1, const TreeNode* pNode2)
{
if(pRoot == nullptr || pNode1 == nullptr || pNode2 == nullptr)
return nullptr;
list<const TreeNode*> path1;
GetNodePath(pRoot, pNode1, path1);
list<const TreeNode*> path2;
GetNodePath(pRoot, pNode2, path2);
return GetLastCommonNode(path1, path2);
}