面试题16:数值的整数次方
//题目:数值的整数次方
/*
给定一个double类型的浮点数x和int类型的整数n。求x的n次方。
保证x和n不同时为0
*/
//牛客:https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&&tqId=11165&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
//leetcode:https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/
//思路1:剑指offer原书中的解法 比较繁琐但是好理解
/*
* 首先需要排除底数为0同时幂为负数的情况,此时无解
* 因为幂可以为正数可以为负数,故首先要针对负数进行处理,对于负数幂n,求x的n次方等于求x的-n次方的倒数
* 随后求double类型的正数幂次方
* 为降低运算量 先将幂改成二进制形式 并通过递归的方式进行运算
* 比如 计算2的5次方 5为101,可以写成2的2次方的平方+2的1次方,这里的1就是因为5的二进制最低为不为0
* 如果是计算2的四次方,则可以写成2的2次方的平方
* 以此类推,采用对幂指数按位分解的方式可以降低运算量 最终得到结果
* 如果原幂为正数,则得到的值就是最终解,如果原幂为负数,则最终结果为值的倒数
*/
bool equal(double a,double b)
{
if (a - b<0.00001&&b - a<0.00001)
return true;
return false;
}
double PowerWithabsExp(double x, unsigned int absN)
{
if (absN == 1)
return x;
if (absN == 0)
return 1.0;
double result = PowerWithabsExp(x, absN >> 1);
result *= result;
if ((absN & 1) == 1)
result *= x;
return result;
}
double Power(double x, int n) {
if (equal(x, 0.0) && n<0)
return -1.0;
unsigned int absExp = (unsigned int)n;
if (n<0)
absExp = (unsigned int)(-n);
double result = PowerWithabsExp(x, absExp);
if (n<0)
result = 1 / result;
return result;
}
//思路2:来自leetcode的大佬,只用了几行代码就实现了功能
/*
* 思路还是一样的, 思路1中,递归还是会不可避免的带来多余的运算量
* 比如要算PowerWithabsExp(2,5),要先算PowerWithabsExp(2,2),其中又需要计算PowerWithabsExp(2,1)
* 层层计算再层层返回,执行起来比较复杂
* 新的思路则是避免了递归,通过不断更新变量x来保存中间量的值
* 在每一次循环里实现对n的按位分解和对x的更新
* 举个例子,还是求2的5次方,采用新的思路计算过程会更加简单:
* 首先i=n=5,因为5%2!=0,res从1变为2,而i变为2,然后x更新为4,即2的2次方
* 随后因为2%2==0,则res不变,i变为1,x更新为16,即4的2次方 2的4次方
* 最后一次循环,i为1,1%2!=0,res更新为16*2=32,i变为0,循环结束
* 最终根据n的正负决定是返回原值还是返回倒数
* 需要指出的是这里没有考虑底数为0同时幂为负数的情况,如果考虑这一情况,只需要像思路1一样加上一个判断语句即可
* 思路1和思路2其本质实质是一样的,都要判断幂的正负,都是通过对幂的分解来降低运算量
* 这个解法巧妙之处在于通过对现有变量x的更新,避免了陷入多次递归的情况发生
* 更简单的说,思路1是先从顶层到底层,再带着结果一步步回来
* 而思路2则是直接从底层出发,一次得到结果,所以更加简洁和高效
*/
double myPow(double x, int n) {
double res = 1.0;
for (int i = n; i != 0; i /= 2) {
if (i % 2 != 0)
res *= x;
x *= x;
}
return n>0 ? res : 1 / res;
}
int main() {
double res = Power(2.0, 4);
cout << res << endl;
return 0;
}
面试题17:打印从1到最大的n位数
#include<iostream>
#include<vector>
#include<string>
using namespace std;
//题目:打印从1到最大的n位数
/*
* 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。
* 比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
*/
//思路:
/*
* 这道题无需过多的思考,也不需要什么技巧,主要的问题就在于对数字的构造和变化,因为n可能很大
* 所以整个打印过程要以字符串的方式来进行
* 首先构造由n个'0'构成的字符串
* 然后每次给最低位+1,并模拟数字相加的效果,手动地实现加法、进位等操作,将变化后的str字符串转成数字并打印
* 设置标志overFlow表示溢出,控制打印的终止;设置takeOver变量为进位量,当低位+1后变为10时,需要手动实现进位
* 即低位变为0,而设置takeOver为1,最后takeOver会叠加到高一位的计算中
* 如果低位+1后小于10,说明高位都将不会变化,此时可以终止操作
* 至此对字符串数字的一次Increment操作 即++操作完成
* 当最高位值变为10时,到达终止条件,举个例子,对于n=3,最大数字为999,再加1时最高位就会变成10,此时需要终止打印过程
* 对于每一个完成+1操作的字符串数字,不能采用stoi来进行转换,前面说过如果n过大,stoi的转换会失败
* 因此打印时仍需要输出字符串,每次打印前只需要去除前导0即可
* leetcode题中可以直接通过stoi转换并装入res数组,因为res数组元素已经预设为int类型,这里需要注意一下
*/
//leetcode:https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/
//牛客:暂未找到
bool Increment(string& str) {
bool overFlow = false;
int takeOver = 0;
for (int i = str.length() - 1; i >= 0; i--) {
int num = str[i] - '0' + takeOver;
if (i == str.length() - 1)
num++;
if (num >= 10) {
if (i == 0)
overFlow = true;
else {
num -= 10;
takeOver = 1;
str[i] = num + '0';
}
}
else {
str[i] = num + '0';
break;
}
}
return overFlow;
}
void printNumbers(int n) {
if (n <= 0)
return;
string str(n, '0');
while (!Increment(str)) {
int i = 0;
while (str[i] == '0')
i++;
cout << str.substr(i);
}
}
int main() {
printNumbers(2);
return 0;
}
面试题18-1:o(1)复杂度删除给定链表节点
#include<iostream>
using namespace std;
//题目一:在O(1)时间内删除链表节点
//给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。
//思路:
/*
* 要在o(1)时间删除链表的节点,普通的方法是不行的,只有通过修改节点值才能完成
* 比如我要删除节点pToBeDeleted,我只需要把pToBeDeleted节点值改为其下一个节点的值
* 并删除其下一个节点即可
* 除此之外,有一些特殊情况需要考虑:
* 首先,如果整个链表只有待删除节点一个节点,即该节点既是头又是尾,需要将其删除并修改链表头指针
* 其次,如果待删除节点是链表最后一个节点,需要遍历一次链表找到pToBeDeleted的前一节点,并进行处理
* 所以,总共需要分三种情况来处理
*/
struct ListNode
{
int val;
ListNode* next;
ListNode(int x) :val(x), next(nullptr) {}
};
void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted)
{
if (!pListHead || !pToBeDeleted)
return;
//要删除的节点不是尾节点
if (pToBeDeleted->next != nullptr)
{
ListNode* pNext = pToBeDeleted->next;//定义指针指向删除节点下一个节点
pToBeDeleted->val = pNext->val;//把下一个节点复制到删除节点该在的位置
pToBeDeleted->next = pNext->next;//删除掉下一个节点,即让本该删除的节点指向下下一个节点
delete pNext;
pNext = nullptr;
}
//要删的是尾节点
else if (*pListHead == pToBeDeleted)//只有一个节点时
{
delete pToBeDeleted;
pToBeDeleted = nullptr;
*pListHead = nullptr;
}
else//有多个节点时
{
ListNode* pNode = *pListHead;
while (pNode->next != pToBeDeleted)//找到尾节点位置
pNode = pNode->next;
pNode->next = nullptr;
delete pToBeDeleted;
pToBeDeleted = nullptr;
}
}
面试题18-2:删除链表中的重复节点
#include<iostream>
using namespace std;
//题目:删除排序链表的重复节点
//若原链表为1->2->3->3->4->4->5
//删除后为1->2->5
//思路:
/*
* 先来看看剑指offer中的做法:
* 首先处理链表为空的情况,然后设置前驱节点preNode和当前节点pNode
* 进入循环,设置pNext为pNode的后继节点,若pNext非空且值等于pNode,设置标志needDelete为true
* 否则设为false,更新pNode和preNode后,进入下一次循环
* 当标志为true时,表明需要进行删除操作,保存当前pNode的val为value,从pNode开始往后,只要节点值为value
* 即将节点删除,最后通过preNode前驱节点的next指针将链表重新连起来
* 相比之下,leetcode的做法相对更加简洁,但思路完全一致
*/
//leetcode:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
//牛客:https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&&tqId=11209&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
struct ListNode
{
int val;
ListNode *next;
ListNode(int x) :val(x) {}
};
//剑指offer中的做法
void DeleteDuplication(ListNode* pHead)
{
if (pHead == nullptr)
return;
ListNode* pPreNode = nullptr;
ListNode* pNode =pHead;
while (pNode != nullptr)
{
ListNode* pNext = pNode->next;
bool needDelete = false;
if (pNext != nullptr&&pNext->val == pNode->val)
needDelete = true;
if (!needDelete)//未发现重复时,正常遍历
{
pPreNode = pNode;
pNode = pNode->next;
}
else//发现重复时,先取重复值,删除重复节点,并保证链表连续性
{
int value = pNode->val;
ListNode* pToBeDel = pNode;
while (pToBeDel != nullptr&&pToBeDel->val == value)
{
pNext = pToBeDel->next;
delete pToBeDel;
pToBeDel = nullptr;
pToBeDel = pNext;
}
if (pPreNode == nullptr)
pHead = pNext;
else
pPreNode->next = pNext;
pNode = pNext;
}
}
}
//leetcode的做法:
ListNode* deleteDuplicates(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
ListNode *newHead = new ListNode(-1);
newHead->next = head;
ListNode *prev = newHead;
ListNode *pNode = head;
while (pNode != nullptr&&pNode->next != nullptr) {
if (pNode->next->val == pNode->val) {
int tmp = pNode->val;
while (pNode != nullptr&&pNode->val == tmp) {
ListNode*pDel = pNode;
pNode = pNode->next;
delete pDel;
}
prev->next = pNode;
}
else {
prev = pNode;
pNode = pNode->next;
}
}
return newHead->next;
}
int main() {
ListNode *l1 = new ListNode(1);
ListNode *l2 = new ListNode(2);
ListNode *l3 = new ListNode(2);
ListNode *l4 = new ListNode(2);
l1->next = l2;
l2->next = l3;
l3->next = l4;
l4->next = nullptr;
deleteDuplicates(l1);
return 0;
}
面试题19:正则表达式匹配
#include<iostream>
#include<vector>
using namespace std;
//题目:正则表达式匹配
/*
给定一个字符串(s) 和一个字符模式(p)。实现支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符。
'*' 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串(s) ,而不是部分字符串。
*/
//思路
/*这个题算是比较难的题目,第一次拿到的时候还是没什么思路,与剑指offer中的第20题类似
* 但是那里使用递归来做,这里直接用动态规划会比较快
* 具体的细节已经做了较为详细的注解,不再重复
*/
//leetcode:https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/
//牛客:https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&&tqId=11205&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
bool isMatch(string s, string p) {
int sl = s.size(), pl = p.size();
vector<vector<bool> > dp(sl + 1, vector<bool>(pl + 1, false));//向量的行数和列数分别比原串 和匹配串长度大1
dp[0][0] = true;
for (int i = 0; i <= sl; i++) {
for (int j = 1; j <= pl; j++) {
if (j>1 && p[j - 1] == '*')
//此时p[j-1]为*,有如下几种情况:
//1.dp[i][j-2]保存着匹配至原串下标i-1和匹配串下标j-3时的结果,如果该结果为真,匹配到原串下标i-1和匹配串下标j-1时也为真
//举例: 原串: a | 匹配串: a | *
// 第i-1位 | 第j-2位 | 第j-1位
//说明:此时*匹配了它前面的字符0次,即直接越过p[j-2]和p[j-1]所在位置
//2.dp[i-1][j]保存着匹配至原串下标i-2和匹配串下标j-1时的结果,若该结果为真,同时
//原串下标i-1的值等于匹配串j-2处的值,或者匹配串j-2处为'.',匹配到原串下标i-1和匹配串下标j-1时也为真
//举例1: 原串: | a 匹配串: a * |
// | 第i-1位 第j-2位 第j-1位 |
//举例2: 原串: | a 匹配串: . * |
// | 第i-1位 第j-2位 第j-1位 |
//说明:举例1和举例2中,把*前面的元素匹配了一遍
dp[i][j] = dp[i][j - 2] || (i>0 && (p[j - 2] == '.' || s[i - 1] == p[j - 2]) && dp[i - 1][j]);
else dp[i][j] = i>0 && (s[i - 1] == p[j - 1] || p[j - 1] == '.') && dp[i - 1][j - 1];
//举例1: 原串: X | a 匹配串: X | a
// 第i-2位 | 第i-1位 第j-2位 | 第j-1位
//举例2: 原串: X | 任意 匹配串: X | .
// 第i-2位 | 第i-1位 第j-2位 | 第j-1位
}
}
return dp[sl][pl];//dp[s1][p1]表示匹配至原串下标s1-1和匹配串下标p1-1时的结果,
}
面试题20:表示数值的字符串
#include<iostream>
using namespace std;
//题目:表示数值的字符串
/*
实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、"5e2"、"-123"等都表示数值
数字的格式可以用A[.[B]][e|EC]或者.B[e|EC]表示,其中A和C都是整数(可以有正负号,也可以没有),而B是一个无符号整数
函数checkUnsigned用来扫描字符串中0-9的数位,可以用来匹配前面数值模式中的B部分
*/
//思路:
/*
*
* 首先调用check函数判断字符串的第一个字符是'+'或者'-'或者没有,随后调用checkUnsigned函数来判断一段连续的0~9的数字
* 即整数部分,整数部分判断完毕后 应该是小数点或者e或者E
* 如果不是那说明无法表示数值,如果是小数点,则小数点后应该为无符号数字,采用checkUnsigned来进行判断,判断结果与前面的判断结果只要有一个为true即可,所以用|即可
* 因为整数部分和小数部分只要不同时为空即可
* 如果是指数形式,为e或E,则后面可以跟有符号的数字,但必须是e(E)前后都有数字才行,所以才用的&
* 走完上述流程后,当numeric为true同时字符串已经走完时,即说明字符串可以表示数值
*/
//leetcode:https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/
//牛客:https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&&tqId=11206&rp=1&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
bool checkUnsigned(char **string)
{
char *before = *string;
while (**string != '\0'&&**string <= '9'&&**string >= '0')
(*string)++;
return before<*string;
}
bool check(char **string)
{
if (**string == '+' || **string == '-')
(*string)++;
return checkUnsigned(string);
}
bool isNumeric(char* string)
{
if (string == nullptr)
return false;
bool numeric = check(&string);
if (*string == '.')
{
string++;
numeric = checkUnsigned(&string) || numeric;
}
if (*string == 'e' || *string == 'E')
{
string++;
numeric = numeric && check(&string);
}
return numeric && *string == '\0';
}