剑指offer 16~20

面试题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';
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值