剑指offer|解析和答案(C++/Python) (五)下

剑指offer|解析和答案(C++/Python) (五)下

参考剑指offer(第二版),这里做一个学习汇总,包括解析及代码。代码均在牛客网进行验证(摘自自己的牛客网笔记)。

整个剑指offer解析链接:
剑指offer|解析和答案(C++/Python) (一).
剑指offer|解析和答案(C++/Python) (二).
剑指offer|解析和答案(C++/Python) (三).
剑指offer|解析和答案(C++/Python) (四).
剑指offer|解析和答案(C++/Python) (五)上.
剑指offer|解析和答案(C++/Python) (五)下.
剑指offer|解析和答案(C++/Python) (六).

习题

面试中各项能力
15.扑克牌中的顺子
16.圆圈中最后剩下的数字
17.股票的最大利润
18.求1 + 2 + 3 +…+n
19.不用加减乘除做加法
20.构建乘积数组

15.扑克牌中的顺子

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。

思路:
1.把数组进行排序。
2.统计数组中0的个数。
3.统计数组中排序之后的数组中相邻数字之间的空缺总数。
4.进行比较。如果空缺总数大于0的个数,则不能凑成顺子。反之,空缺总数小于等于0的个数,则能凑成顺子。
代码:
C++

class Solution {
public:
    bool IsContinuous( vector<int> numbers ) {
        int length = numbers.size();
		if(length == 0)
			return false;
		//对vector 进行排序
		sort(vector.begin(), vector.end());
		int numbersOf0 = 0;//0的个数
		int numbersOfGrap = 0;//数字空缺数
		//统计0的个数
		for(int i = 0; i < length && numbers[i] == 0; ++i){
			++numbersOf0;
		}
		//统计数字中间隔数
		int small = numbersOf0;
		int big = small + 1;
		while(big < length){
			//如果存在两个数字相等,则比如存在对子,不可能是顺子
			if(numbers[small] == numbers[big])
				return false;
			numbersOfGrap += numbers[big] - numbers[small] - 1;
			small = big;
			++big;
		}
		if(numbersOfGrap > numbersOf0)
			return false;
		else
			return true;
    }
};

Python

# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        # write code here
        length = len(numbers)
        if length == 0:
            return False
        #进行排序
        numbers.sort()
        numbersOf0 = 0
        numbersOfGrap = 0
        #统计数组中0的个数
        for i in range(length):
            if numbers[i] == 0:
                numbersOf0 += 1
        #统计数组中的间隔数目
        small = numbersOf0
        big = small + 1
        while big < length:
            #两个数字相等 则不可能是顺子
            if numbers[big] == numbers[small]:
                return False
            numbersOfGrap = numbers[big] - numbers[small] - 1
            small = big
            big += 1
        if numbersOfGrap > numbersOf0:
            return False
        else:
            return True

16.圆圈中最后剩下的数字

0,1,…,n-1,这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里最后剩下的数字。

思路:
解法一:经典的解法,用环形链表模拟。
既然题目中有一个数字圆圈,就使用一个数据结果来模拟这个圆圈。可以创建一共有n个节点的环形链表,然后每次在这个链表中删除第m个节点。
在这里插入图片描述
代码:
C++

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n < 1 || m < 1)
            return -1;
        unsigned int i = 0;
        list<int> numbers;//新建一个队列
        for(i = 0; i < n; ++i){
            numbers.push_back(i);//0 ~ n - 1
        }
        list<int>::iterator current = numbers.begin();
        while(numbers.size() > 1){
            for(int i = 1; i < m; ++i){//从0开始计算m个数字
                ++current;
                //可以调用list容器的end()函数来得到list末端下一位置
                if(current == numbers.end())
                    current = numbers.begin();
            }
            list<int>::iterator next = ++current;
            if(next == numbers.end())
                next = numbers.begin();
             
            --current;
            numbers.erase(current);//删除
            current = next;
        }
        return *(current);
    }
};

Python

class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        if n < 1 or m < 1:
            return -1
        numbers = []
        for i in range(n):
            numbers.append(i)
        current = 0
        while len(numbers) > 1:
            #移步
            for i in range(1, m):
                if current == len(numbers) - 1:
                    current = 0
                else:
                    current += 1
            if current == len(numbers) - 1:
                next = 0
            else:
                next = current + 1
            nextNum = numbers[next]
            numbers.pop(current)
            current = numbers.index(nextNum)
        return numbers[0]

解法二:创新的解法,数学推导。
1.定义一个关于n和m的方程 f ( n , m ) f(n, m) f(n,m),表示每次在n个数字0,1,…,n - 1,中删除第m个数字最后剩下的数字
2.在这n个数字中,第一个每删除的数字是 ( m − 1 ) % n (m - 1)\%n (m1)%n,为方便起见把 ( m − 1 ) % n (m - 1)\%n (m1)%n定义为k
3.删除k之后剩下的n - 1个数字为0,1,…,k-1,k+1,…,n-1。 在剩下的序列中,k+1排在最前面,从而形成k+1,…,n-1,0,1,…,k-1。 该序列最后剩下的数字也应该是关于n和m的函数,记为 f ′ ( n − 1 , m ) f'(n-1, m) f(n1,m)
4.最初序列最后剩下的数字 f ( n , m ) f(n, m) f(n,m)一定是删除一个数字之后的序列最后剩下的数字,即 f ( n , m ) = f ′ ( n − 1 , m ) f(n, m) = f'(n-1, m) f(n,m)=f(n1,m)
5.把剩下的n - 1个数字的序列k+1,…,n-1,0,1,…,k-1进行映射,映射结果是形成一个0 - n-2的序列。
在这里插入图片描述
6.把映射定义成 p p p,则 p ( x ) = ( x − k − 1 ) % n p(x)=(x-k-1) \% n p(x)=(xk1)%n。如果映射前的数字是 x x x,那么映射后的数字是 ( x − k − 1 ) % n (x-k-1) \% n (xk1)%n。该映射的逆映射是 p − 1 ( x ) = ( x + k + 1 ) % n p^{-1}(x)=(x+k+1) \% n p1(x)=(x+k+1)%n
7.由于映射之后的序列和最初的序列具有相同的形式,都是从0开始的连续序列,仍然可以使用 f f f来表示,记为 f ( n − 1 , m ) f(n-1,m) f(n1,m)。根据映射规则,映射之前的序列中最后剩下的数字 f ′ ( n − 1 , m ) = p − 1 [ f ( n − 1 , m ) ] = [ f ( n − 1 , m ) + k + 1 ] % n f^{\prime}(n-1, m)=p^{-1}[f(n-1, m)]=[f(n-1, m)+k+1] \% n f(n1,m)=p1[f(n1,m)]=[f(n1,m)+k+1]%n,把 k = ( m − 1 ) % n k=(m - 1)\%n k=(m1)%n代入得到 f ( n , m ) = f ′ ( n − 1 , m ) = [ f ( n − 1 , m ) + m ] % n f(n, m)=f^{\prime}(n-1, m)=[f(n-1, m)+m] \% n f(n,m)=f(n1,m)=[f(n1,m)+m]%n
根据上面的推导,可以得到递归公式:
f ( n , m ) = { 0 , n = 1 [ f ( n − 1 , m ) + m ] % n , n > m f(n, m)=\left\{\begin{array}{l}{0, n=1} \\ {[f(n-1, m)+m] \% n, n>m}\end{array}\right. f(n,m)={0,n=1[f(n1,m)+m]%n,n>m
代码:
C++

class Solution {
public:
    int LastRemaining_Solution(int n, 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;
    }
};

Python

class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        if n < 1 or m < 1:
            return -1
        last = 0
        if n > 1:
            for i in range(2, n + 1):
                last = (last + m)%i
        
        return last

17.股票的最大利润

假设把某股票的价格按时间先后顺序存储在数组中,请问买卖股票一次可能获得的最大利润是多少?例如,一只股票在某些时间节点的价格为{9,11,8,5,7,12,16,14}。如果我们能在价格为5的时候买入并在价格为16的时候卖出,则能收获最大的利润是11

思路:
股票交易的利润来自股票买入和卖出价格的差价。当然必须在买入股票之后才能卖出。如果买入和卖出价两个数字组成一个数对,那么利润就是这个数对的差值。最大利润就是数组中所有利润的最大差值。
传统方法:遍历所有数对来进行求解。由于长度为n的数组中存在O(n2)个数对,因此这种算法时间复杂度为O(n2)。
差值法:定义函数 d i f f ( i ) diff(i) diff(i)为当卖出价为数组中第i个数字时可能获得的最大利润。在卖出价固定时,买入价越低则获得的利润越大。如果在扫描数组中的第i个数字时,只需要得到之前i - 1个数字中的最小值,就能算出当前价位卖出时可能得到的最大利润。
代码:
C++

class Solution {
public:
	int MaxDiff(const int* numbers, unsigned length){
		if(numbers == nullptr && length < 2)
			return 0;
		int min = 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;
			if(currentDiff > maxDiff)
				maxDiff = currentDiff;
		}
		return maxDiff;
	}
};

18.求1 + 2 + 3 +…+n

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

思路:
解法一:利用构造函数求解
解法二:利用虚函数求解
解法三:利用函数指针求解
解法四:利用模板类型求解
解法五:利用逻辑与短路特性求解
逻辑运算的短路特性:
(表达式1)&&(表达式2) 如果表达式1为假,则表达式2不会进行运算,即表达式2“被短路”
(表达式1)||(表达式2) 如果表达式1为真,则表达式2不会进行运算,即表达式2“被短路”
使用这种方法就不需要使用if来对n的大小进行判断。
代码:
C++

class Solution {
public:
    int Sum_Solution(int n) {
		int sum = 0;
		//n = 0 时 返回 sum = 0
		//n > 0 时 返回 sum=n+Sum_Solution(n-1) 进行递归
        (n>0) && (sum=n+Sum_Solution(n-1));
        return sum;
    }
};

Python
注意:a = (True) and num , a = num

# -*- coding:utf-8 -*-
class Solution:
    def Sum_Solution(self, n):
        # write code here
        num = n
        temp = n > 0 and self.Sum_Solution(n - 1)
        
        return num + temp

19.不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

思路:
由于不能使用四则运算,则使用二进制中的位操作。
举例:5 + 17 =22。5的二进制是101,17的二进制是10001。第一步:各位相加但不记进位,得到的结果是10001。第二步:记下进位,这个例子中结果是10。第三步:把前两部结果相加,得到结果是10110。
得到规律:
1.不考虑进位对每一位相加。这和异或的结果是一样的。
2.考虑进位。只有1 + 1才会产生进位,所以是操作。
3.把前两个步骤的结果相加。同时重复前面两步,直到不产生进位为止(因为进位可能会产生新的进位)。
代码:
C++

class Solution {
public:
    int Add(int num1, int num2)
    {
		int sum;//和
		int carry;//进位
		do{
			sum = num1 ^ num2;//异或 +
			carry = (num1 & num2)<<1;//与计算进位
			num1 = sum;
			num2 = carry;
		}while(num2 != 0)
		return sum;
    }
};

Python
Python的位操作不同。在Python内部对整数的处理分为普通整数和长整数,普通整数长度为机器位长,通常都是32位,超过这个范围的整数就自动当长整数处理,而长整数的范围几乎完全没限制所以long类型运算内部使用大数字算法实现,可以做到无长度限制。

  1. 移位不会溢出。 所以使用0xFFFFFFFF把数字限定在32位。但这样会造成符号位丢失。详情可见:
    https://blog.csdn.net/s1314p/article/details/95769392
  2. 找回符号位。即 ~(num1^0xFFFFFFFF)
class Solution:
    def Add(self, num1, num2):
        # write code here
        sum = 0
        carry = 0
        while 1:
            sum = (num1 ^ num2) & 0xFFFFFFFF#限定成一个32位的数字
            carry = ((num1 & num2) << 1) & 0xFFFFFFFF
            num1 = sum
            num2 = carry
            if num2 == 0:
                break
        #python 会把int 转long 就不会溢出
        #num1第一位是符号位 0是正数 1是负数 但是 &0xFFFFFFFF 会丢失符号位
        #所以使用~(num1^0xFFFFFFFF) 找回符号位
        return num1 if num1<=0x7FFFFFFF else ~(num1^0xFFFFFFFF)

20.构建乘积数组

给定一个数组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]。不能使用除法。

思路:
1.传统方法。由于不能使用除法,一种直观的解法是使用连乘n-1个数字得到B[i]。时间复杂度位O(n2)。
2.高效的方法。可以把 B [ i ] = A [ 0 ] × A [ 1 ] × … × A [ i − 1 ] × A [ i + 1 ] × … × A [ n − 1 ] B[i]=A[0] \times A[1] \times \ldots \times A[i-1] \times A[i+1] \times \ldots \times A[n-1] B[i]=A[0]×A[1]××A[i1]×A[i+1]××A[n1]看成 C [ i ] = A [ 0 ] × A [ 1 ] × … × A [ i − 1 ] C[i]=A[0] \times A[1] \times \ldots \times A[i-1] C[i]=A[0]×A[1]××A[i1] D [ i ] = A [ i + 1 ] × … × A [ n − 2 ] × A [ n − 1 ] D[i]=A[i+1] \times \ldots \times A[n-2] \times A[n-1] D[i]=A[i+1]××A[n2]×A[n1]两者的乘积。并且 C [ i ] C[i] C[i]可以用自上而下的顺序计算出来: C [ i ] = C [ i − 1 ] × A [ i − 1 ] C[i]=C[i-1] \times A[i-1] C[i]=C[i1]×A[i1]。同理, D [ i ] D[i] D[i]可以通过自下而上的顺序计算出来: D [ i ] = D [ i + 1 ] × A [ i + 1 ] D[i]=D[i+1] \times A[i+1] D[i]=D[i+1]×A[i+1]。参考图示:
在这里插入图片描述
代码:
C++

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
		int length = A.size();
		vector<int> B(length);
		if(length > 1){
			B[0] = 1;
			for(int i = 1; i < length; ++i){
				B[i] = B[i - 1] * A[i - 1];//C[i] = C[i-1] * A[i-1]
			}
			double temp = 1;
			for(int i = length - 2; i >= 0; --i){
				temp *= A[i + 1];//D[i] = D[i+1] * A[i+1]
				B[i] *= temp; //B[i] = C[i] * D[i]
			}
		}
		return B;
    }
};

Python

class Solution:
    def multiply(self, A):
        # write code here
        length = len(A)
        B = []
        if length > 1:
            for i in range(length):
                B.append(1)#初始化
            
            for i in range(1,length):
                B[i] = B[i-1] * A[i-1]
            temp = 1
            for i in rang(length - 2, -1, -1):
                temp *= A[i+1]
                B[i] *= temp
        
        return B
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值