剑指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) (六).

习题

面试中各项能力
1.数字在排序数组中出现的次数
2.0 ~ n-1中缺失的数字
3.数组中数值和下标相等的元素
4.二叉搜索树的第k个节点
5.二叉树的深度
6.平衡二叉树
7.数组中只出现一次的两个数字
8.数组中唯一只出现一次的数字
9.和为s的数字
10.和为s的连续正数序列
11.翻转单词顺序
12.左旋转字符串
13.滑动窗口的最大值
14.n个骰子的点数

1.数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。例如:输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4。

思路:
使用二分法查找
在二分法查找的基础上进行优化。
1.分别找出第一个数字k和最后一个数字k。使用二分法,判断中间的数字与k的比较,由于是排序数组,则很好判断第一个数字k和最后一个数字k的索引,通过索引计算重复次数。
2.先判断中间数字是否等于k,若等于k,则直接从中间开始搜索第一个k和最后一个k对应的索引,通过索引计算重复次数。
两个方法基本一致,时间复杂度均为**O(logn),**分别用C++和Python实现。
代码:
C++

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.size() == 0)//无符号整型不可能<0
			return -1;
		int number = 1;//重复的个数
		int first = GetFirstK(data, k, 0, data.size() - 1);
		int last = GetLastK(data, k, 0, data.size() - 1);
		if(first > -1 && last > -1)//正确性判断
			number = last - first + 1;
			
		return number;
    }
	int GetFirstK(vector<int> data, int k, int start, int end){
		//正确性判断
		if(start > end)
			return -1;
		int middleIndex = (start + end)/2;
		int middleData = data[middleIndex];
		if(middleData == k){//中间那个数等于k
			if((middleIndex > 0 && data[middleIndex - 1]!= k )|| middleIndex == 0)
				//中间那个数就是第一个k 其实也是边界条件
				return middleIndex;
			else//第一个k还在前面 
				end = middleIndex - 1;
		}else if(middleData > k)//第一个k在 前半段
			end = middleIndex - 1;
		else //第一个k在后半段
			start = middleIndex + 1;
		
		return GetFirstK(data, k, start, end);//进行递归
	}
	int GetLastK(vector<int> data, int k, int start, int end){
		if(start > end)
			return -1;
		int middleIndex = (start + end)/2;
		int middleData = data[middleIndex];
		if(middleData == k){//中间那个数等于k
			if((middleIndex < data.size() - 1 && data[middleIndex + 1] != k)
				|| middleIndex == data.size() - 1){//中间那个数就是最后一个k
				return middleIndex;//其实也是边界条件
			}else
				start = middleIndex + 1;//最后一个k 在后半段
		}else if(middleData > k)
			end = middleIndex - 1;//最后一个k 在前半段
		else
			start = middleIndex + 1;//最后一个k 在后半段
		
		return GetLastK(data, k, start, end);//进行递归
	}
};

Python

# -*- coding:utf-8 -*-
class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        if len(data) == 0:
            return 0
        middle = len(data)//2 # //向下取整
        #如果中间的数等于k
        if data[middle] == k:
            #开始计算k的开始和结束
            start ,end = middle, middle
            for i in range(middle -1, -1, -1):# 从中间往前面找
                if data[i] == k:
                    start -= 1

            for i in range(middle +1 , len(data), 1):#从中间往后面找
                if data[i] == k:
                    end += 1

            return end - start +1
        elif data[middle] < k:#k全部在后面那个部分
            #递归
            return self.GetNumberOfK(data[middle + 1:], k)
        else:#k全部在前面那个部分
            #递归
            return self.GetNumberOfK(data[0: middle], k)
        

2.0 ~ n-1中缺失的数字

一个长度为n - 1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0 - n-1之内。在范围0 - n-1之内的n个数字中有且只有一个数字不在该数组中,找出这个数字。

思路:
由于m不在数组中,那么m+1处在下标为m的位置,m+2处在下标为m+1的位置,以此类推。发现m正好是数组中第一个数值和下标不相等的下标,因此这个问题转换成:在排序数组中找出第一个值和下标不相等的元素
借鉴上道题的二分法查找,每次通过比较数组中间的值进行递归。
当中间的数值不等于索引,但中间前面的数值等于索引,那么中间的索引值便是缺失的数值。
代码:
C++

class Solution {
public:
	int GetMissingNumber(const int* numbers, int length){
		if(numbers == nullptr || length <=0)
			return -1;
		int left = 0;
		int right = length -1;
		while(left <= right){
			int middle = (left + right)>>1;//除以2
			if(numbers[middle] != middle){//中间索引与对应的数字不相等时
				//前面那个数字相等或者只有一个数字时
				if(middle ==0 || numbers[middle -1] == middle -1)
					return middle;//此时为边界条件 即缺失的数字
				//否则缺失的数字在前半部分
				right = middle -1;			
			}else
				//缺失的数字在右半部分
				left = middle + 1;
		}
		if(left == length)
			return length
		//无效的输入,比如数组不是按要求排序的
		//或者有数字不在0~n-1范围之内的
		return -1;
	}
};

3.数组中数值和下标相等的元素

假设一个单调递增的数组里的每个元素都是整数并且是唯一的。请编程实现一个函数,找出数组中任意一个数值等于其下标的元素。例如:在数组{-3, -1, 1, 3, 5}中,数字3和它的下标相等。

思路:
参考前面两道题,使用二分法查找
当下标和数值相等时,为递归的边界条件。
代码:
C++

class Solution {
public:
	int GetNumberSameAsIndex(const int* numbers, int length){
		if(numbers == nullptr || length <= 0){
			return -1;
		}
		int left = 0;
		int right = length - 1;
		while(left <= right){
			int middle = left + ((right - left)>>1);
			if(numbers[middle] == middle)
				return middle;//找到这个数字
			if(numbers[middle] > middle)
				right = middle -1;//这个数字在左边
			else
				left = middle + 1;//这个数字在右边
		}
		return -1;//每找到
	}
		
};

4.二叉搜索树的第k个节点

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

思路:
参考图示:
在这里插入图片描述
该图的二叉搜索树的中序遍历为:{2, 3, 4, 5, 6, 7, 8}。因此只需要中序遍历一颗二叉搜索树,我们就很容易找到它第k个节点。
代码:
C++

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
		if(pRoot == NULL || k == 0)
			return NULL;
		return KthNodeCore(pRoot, k);
    }
	//中序遍历
	TreeNode* KthNodeCore(TreeNode* pRoot,  int& k){
		TreeNode* target = NULL;
		if(pRoot->left != NULL)//遍历左子树
			target = KthNodeCore(pRoot->left, k);
		if(target == NULL){
			if(k == 1)//表示已经到第k小的节点了
				target = pRoot;
			k--;
		}
		//target = NULL 表示没有找到
		//开始遍历右子树
		if(target == NULL && pRoot->right != NULL){
			target = KthNodeCore(pRoot->right, k);
		}
		return target;
	}
};

Python

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回对应节点TreeNode
    def KthNode(self, pRoot, k):
        # write code here
        if pRoot == None or k <= 0:
            return None;
        #开始中序遍历
        tree = []
        self.KthNodeCore(pRoot, tree)
        if len(tree) < k:
            return None
        return tree[k - 1]
        
    def KthNodeCore(self, pRoot, tree):
        if pRoot.left:
            self.KthNodeCore(pRoot.left, tree)
        tree.append(pRoot)#添加节点
        if pRoot.right:
            self.KthNodeCore(pRoot.right, tree)

5.二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

思路:
参考图示:
在这里插入图片描述
该二叉树的深度为4,分别经过节点1,节点2,节点5,节点7。
1.如果一个树只有一个节点,那么深度为1.
2.如果一个根节点有子树,那么深度加1.
3.如果一个根节点即有左子树,也有右子树,那么该树的深度就是左、右子树深度的较大值加1.
代码:
C++

class Solution {
public:
    int TreeDepth(TreeNode* pRoot)
    {	
		//没有子节点
		if(pRoot == NULL)
			return 0;
		//左子树的深度
		int left = TreeDepth(pRoot->left);
		//右子树的深度
		int right = TreeDepth(pRoot->right);
		//左子树比右子树深
		if(left > right)
		//当前节点深度是较深子树深度+1	
			return (left + 1);
		else
			return (right + 1);
		
    }
};

Python

class Solution:
    def TreeDepth(self, pRoot):
        # write code here
        if pRoot == None:
            #没有节点 深度为0
            return 0
        left = self.TreeDepth(pRoot.left)
        right = self.TreeDepth(pRoot.right)
        
        if left > right:
            return left + 1
        else:
            return right + 1

6.平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。如果某二叉树中任意节点的左、右子树的深度相差不超过1,那么它就是一颗平衡二叉树。

思路:
1.重复遍历每个节点,计算深度并判断。可以参考上一题,计算每个节点的深度,但是这样会存在重复计算节点的深度。也就是,判断每个节点的时候,会计算左右子树的深度,然后在遍历左右子树进行判断时,部分节点会被重复计算。
参考代码:
C++:

class Solution {
public:
	bool IsBalanced_Solution(TreeNode* pRoot) {
		if(pRoot == NULL)
			return true;		
		int left = TreeDepth(pRoot->left);
		int right = TreeDepth(pRoot->right);
		int diff = left - right;
		if(diff>1 || diff<-1)
			return false;
		
		return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right);
    }
    int TreeDepth(TreeNode* pRoot)
    {	
		//没有子节点
		if(pRoot == NULL)
			return 0;
		//左子树的深度
		int left = TreeDepth(pRoot->left);
		//右子树的深度
		int right = TreeDepth(pRoot->right);
		//左子树比右子树深
		if(left > right)
		//当前节点深度是较深子树深度+1	
			return (left + 1);
		else
			return (right + 1);
		
    }
};

2.每个节点只遍历一次的解法。 使用后续遍历二叉树的每个节点,那么在遍历到每一个节点之前就已经遍历了它的左、右子树。只要在遍历的时候记录它的深度,就可以一边遍历一边判断每个节点是否是平衡的。
代码:
C++:

class Solution {
public:
	bool IsBalanced_Solution(TreeNode* pRoot) {
		int depth = 0;
		return IsBalanced(pRoot, &depth);
    }
	bool IsBalanced(TreeNode* pRoot, int* depth){
		if(pRoot == NULL){
			*depth = 0;
			return true;
		}
		int left,right;
		//后续遍历 先遍历左子树 再遍历右子树
		if(IsBalanced(pRoot->left, &left)
			&& IsBalanced(pRoot->right, &right)){
			int diff = left - right;//做差
			if(diff<=1 && diff>=-1){//满足平衡树条件
				if(left > right)//当前节点深度是较深子树深度
					*depth = 1 + left;
				else
					*depth = 1 + right;
				return true;
			}
		}
		//当 |diff| > 1时不满足平衡树条件
		return false;
	}
};

Python
参考:https://blog.csdn.net/qq_41359265/article/details/84224485

class Solution:
    def IsBalanced_Solution(self, pRoot):
        # write code here
        
        return self.IsBalanced(pRoot) != -1
    #通过后续遍历计算深度  如果为 -1 表示不是平衡树
    def IsBalanced(self, pRoot):
        if pRoot == None :
            return 0
        left = self.IsBalanced(pRoot.left)
        #不是平衡树 直接返回 不再进一步判断计算深度
        if left == -1:
            return -1
        right = self.IsBalanced(pRoot.right)
        if right == -1:
            return -1
        #判断是否符合平衡树
        if abs(left - right) > 1:
            return -1
        #返回当前层的深度
        return max(left, right) + 1

7.数组中只出现一次的两个数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n), 空间复杂度是O(1)。

例如输入数组:{2,4,3,6,3,2,5,5},只有4和6只出现了一次,其余均出现了2次,所以输出4,6。
思路:
1.把问题拆开看,首先考虑,一组数组除了一个数字只出现一次,其余均出现了2次,请找出这个数字?这里利用异或运算的一个性质:任何一个数异或它自己都等于0。那么从头开始异或这组数字,那么由于只有一个数字只出现了1次,其他的数字均出现了2次,那么最后得到的数字便是只出现一次的数字。参考下列图示:
在这里插入图片描述
2.本题是有2个数字只出现了一次,那么考虑分组,把2个数字分别分到两个数组中,再利用上述的思路就可以分别求得对应的只出现1次的数字。 把数组{2,4,3,6,3,2,5,5}从头开始异或,得到0010 。0010表示数组中只出现一次的数字(4和6)在第21位上的数字是不同的,可以凭借这个条件进行分组成{2,3,6,3,2}和{4,5,5} 两组。如下图所示:
在这里插入图片描述
代码:
C++

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
		if(data.size() < 2)//vector的长度不可能小于2
			return ;
		//设置每次异或的结果
		int resultExclusiveOr = 0;
		//遍历数组 依次异或
		for(int i =0; i < data.size(); ++i){
			resultExclusiveOr ^= data[i];
		}
		
		unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOr);
		
		*num1 = *num2 = 0;
		//再次遍历数组 分别异或求得只重复一次的数
		for(int i = 0; i < data.size(); ++i){
			if(IsBit1(data[i], indexOf1)){
				*num1 ^= data[i];//数组1
			}else{
				*num2 ^= data[i];//数组2
			}		
		}
    }
	//求得整数num 在二进制中最右边是1的位
	unsigned int FindFirstBitIs1(int num){
		unsigned int indexBit = 0;
		while(((num & 1) == 0) && (indexBit < 8 * sizeof(int))){//防止indexBit溢出
			num = num >> 1;
			++indexBit;
		}
		return indexBit;
	}
	//判断num的二进制表达的第index位是否为1
	bool IsBit1(int num, unsigned int indexBit){
		num = num >> indexBit;
		return (num & 1);
	}
};

Python

# -*- coding:utf-8 -*-
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        # write code here
        length = len(array)
        if length < 2:
            return []
            
        resultExclusiveOr = 0
        for i in array:
            resultExclusiveOr ^= i
        
        indexBit = 0
        while resultExclusiveOr & 1 == 0:
            resultExclusiveOr >>= 1
            indexBit += 1#位加1
        #再次遍历
        num1 = 0
        num2 = 0
        for i in array:
            if self.IsBitIs1(i, indexBit):
                num1 ^= i
            else:
                num2 ^= i
        
        return [num1, num2]
    def IsBitIs1(self, num, index):
        num >>= index#移动到index位上
        return (num & 1)#判断该位上是否为1     

8.数组中唯一只出现一次的数字

在一个数组中只出现一次之外,其他数字都出现了三次,请找出那个只出现一次的数字。

思路:
可以参考上一道题的思路,但是不同的是这题不再采用异或这种方式,因为重复的数字是三次,使用异或无法抵消。不过仍然可以沿用位运算的思路:如果一个数字出现了三次,那么它的二进制表示的每一位(0或者1)都出现了3次。如果把所有出现三次的数字的二进制表示的每一位都分别加起来,那么每一位都能被3整除。
把数组的中的每个数字的二进制表示的每一位都加起来。如果某一位的和能被3整除,那么那个只出现一次的数字二进制表示中的那一位就是0;否则就是1.
这种解法的时间复杂度是O(n),空间复杂度是O(1)
代码:
C++

class Solution {
public:
	int FindNumberAppearingOnce(int numbers[], int length){
		if(numbers == NULL || length <= 0)
			throw new std::exception("Invaild input");
		int bitSum[32] = {0};//辅助数组 用来保存二进制的每一位
		for(int i = 0; i <length; ++i){
			int bitMask = 1;
			for(int j = 31; j >= 0; --j){
				//j=31 表示从右边(最低位)开始进行与运算
				int bit = numbers[i] & bitMask;
				if(bit != 0)
					bitSum[j] += 1;
				bitMask = bitMask<<1;//向高位移动移位
			}
		}
		int result = 0;
		for(int i = 0; i < 32; ++i){
			result = result << 1;//首先计算的是高位  然后向高位移动(*2) 
			result += bitSum[i]%3;
		}
		return result;
	}
};

9.和为s的数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:
对应每个测试案例,输出两个数,小的先输出。

思路:
例如输入数组{1,2,4,7,11,15}和数字15。由于4+11=15,因此输出4和11。
更快的方法:定义两个指针,分别指向最后一个数字和第一个数字,并求和。如果求和数字大于s,则后面的指针向前移动。如果求和数字小于s,在前面的指针向后移动。如果相等,返回这两个数字。
代码:
C++

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> array,int sum) {
		int start = 0;
		int end = array.size() - 1;
		int sumArr;
		vector<int> found;
		if(end <= 0)
			return found;
		while(start <= end){
			sumArr = array[start] + array[end];
			if(sumArr == sum){
				found.push_back(array[start]);
				found.push_back(array[end]);
				break;//一定要加 不然超时 并且只需要得到一对即可
			}else if(sumArr > sum){
				--end;
			}else{
				++start;
			}
			
		}
		return found;
    }
};

Python

# -*- coding:utf-8 -*-
class Solution:
    def FindNumbersWithSum(self, array, tsum):
        # write code here
        length = len(array)
        found = []
        if length <= 1:
            return found
        start = 0
        end = length - 1
        while start <= end:
            sum = array[start] + array[end]
            if sum == tsum:
                found.append(array[start])
                found.append(array[end])
                break
            elif sum < tsum:
                start += 1
            else:
                end -= 1
        return found
        

10.和为s的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
例如:
输入15,由于1+2+3+4+5 = 4+5+6 = 7+8,所以打印出3个连续序列1 - 5,4 - 6,7 - 8.

思路:
参考上道题。也分别使用small和big分别表示序列的最小值和最大值。
1.如果从small到big的序列的和大于s,则可以从序列中去掉较小的值,也就是增大small。
2.如果从small到big的序列的和小于s,则可以让序列包含更多的值,也就是增大big。
3.如果从small到big的序列的和等于s,则保存。
注意:
由于是连续正数序列,且至少包含2个数,那么一直增加small到(1+s)/2为止。
参考图示:
在这里插入图片描述
代码:
C++

class Solution {
public:
    vector<vector<int> > FindContinuousSequence(int sum) {
		vector<vector<int> >  allSumIsS;//所有和为s的序列
		vector<int> sumIsS;//单个和为s的序列
        if(sum < 3)//至少有两个数 1,2,3,4......
			return allSumIsS;
		int small = 1;
		int big = 2;
		int middle = (1+sum)/2;//因为必须有两个数
		int curSum = small + big;
		while(small < middle){
			if(curSum == sum){
				//放入数据
				for(int i = small; i <= big; ++i)
					sumIsS.push_back(i);
				allSumIsS.push_back(sumIsS);
				sumIsS.clear();//清空
			}
			while(curSum > sum && small < middle){
				curSum -= small;
				++small;
				if(curSum == sum){
					//放入数据
					for(int i = small; i <= big; ++i)
						sumIsS.push_back(i);
					allSumIsS.push_back(sumIsS);
					sumIsS.clear();
				}				
			}
			++big;
			curSum += big;			
		}
		return allSumIsS;		
    }
};

Python

# -*- coding:utf-8 -*-
class Solution:
    def FindContinuousSequence(self, tsum):
        # write code here
        sumIsS = []
        allSumIsS = []
        if tsum < 3:
            return allSumIsS
        small = 1
        bif = 2
        middle = (tsum + 1)//2
        curSum = small + big
        while small < middle:
            if curSum = tsum:
                for i in range(small, big + 1):
                    sumIsS.append(i)
                allSumIsS.append(sumIsS)
                sumIsS = []
            while curSum > sum and small < middle:
                curSum -= small
                small -= 1
                if curSum = tsum:
                    for i in range(small, big + 1):
                        sumIsS.append(i)
                    allSumIsS.append(sumIsS)
                    sumIsS = []
            big += 1
            curSum += big
        
        return allSumIsS
            

11.翻转单词顺序

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

思路:
1.翻转句子中所有的字符。
比如,翻转“I am a student.”中所有字符为:“.tneduts a ma I”。
2.再翻转句子中每个单词中的字符。 这样就可以得到“student. a am I”.
代码:
C++

class Solution {
public:
    string ReverseSentence(string str) {
        if(str == "")
			return "";
		int length = str.length();
		int endID = length - 1;
		int startID = 0;
		//翻转整个句子
		Reverse(str, startID, endID);
		//翻转句子中的每个单词
		startID = 0;
		endID = 0;
		while(startID < length){
			if(str[startID] == ' '){//遇到空格
				++startID;
				++endID;
			}else if(str[endID] == ' '|| str[endID] == '\0'){//endID找到一个单词
			    //endID必须是前面那个有数字的数 不能是' '
				Reverse(str, startID, endID - 1);
				++endID;//开始下一个单词
				startID = endID;
			}else{
				++endID;
			}
            cout<<"startID="<<startID<<endl;
            cout<<"endID="<<endID<<endl;
            cout<<str<<endl;
		}
		return str;
    }
	void Reverse(string &str, int startID, int endID){
		if(str == "" || startID >= endID)
			return ;
		while(startID < endID){
			char temp = str[startID];
			str[startID] = str[endID];
			str[endID] = temp;
			++startID;
			--endID;
		}
	}
};

Python

# -*- coding:utf-8 -*-
class Solution:
    def ReverseSentence(self, s):
        # write code here
        #string 转list
        listS = list(s)
        length = len(listS)
        if length == 0:
            return ""
        end = length - 1
        start = 0
        #翻转整个句子
        self.Reverse(listS, start, end)
        #开始翻转每个单词
        start = 0
        end = 0
        while start < length:
            #寻找单词的开头
            if listS[start] == ' ':
                start += 1
                end += 1
            elif listS[end] == ' ' :#寻找单词的末尾
                self.Reverse(listS, start, end - 1)
                end += 1
                start = end
            else:
                end += 1
        #列表转字符串
        s = "".join(listS)
        return s
              
    def Reverse(self, s, start, end):
        if s == "" or start >= end:
            return 
        while start < end:
            #交换顺序
            s[start],s[end] = s[end], s[start]
            start += 1
            end -= 1

12.左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
这道题其实就是字符串的左旋转操作。左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转功能。比如:输入字符串“abcdefg”和数字2,该函数返回左旋转2位的结果是“cdefgab”。

思路:
以“abcdefg”为例,我们可以把它分为2个部分。把前面要选择的字符分为第一个部分,剩下的字符为第二个部分。
1.先分别选择这两部分。于是就得到“bagfedc”。
2.接下来翻转整个字符串。可以得到结果“cdefgab”。
代码:
C++

class Solution {
public:
    string LeftRotateString(string str, int n) {
        if(str == "" || n < 0)
			return "";
		#翻转前面n个字符串
		int startID = 0;
		int endID = n - 1;
		Reverse(str, startID, endID);
		#翻转剩下的字符串
		startID = n;
		endID = str.length() - 1;
		Reverse(str, startID, endID);
		#翻转整个字符串
		startID = 0;
		endID = str.length() - 1;	
		Reverse(str, startID, endID);				
    }
	void Reverse(string &str, int startID, int endID){
		if(str == "" || startID >= endID)
			return ;
		while(startID < endID){
			char temp = str[startID];
			str[startID] = str[endID];
			str[endID] = temp;
			++startID;
			--endID;
		}
	}	
};

Python

# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-
class Solution:
    def LeftRotateString(self, s, n):
        # write code here
        listS = list(s)#转列表
        length = len(s)
        if length == 0 or n < 0:
            return ""
        #翻转前面n个数字
        startID = 0
        endID = n -1
        self.Reverse(listS, startID, endID)
        #翻转后面的数字
        startID = n
        endID = length - 1
        self.Reverse(listS, startID, endID)
        #翻转整个字符串
        startID = 0
        endID = length - 1
        self.Reverse(listS, startID, endID)
        
        s = "".join(listS)
        
        return s
        
    def Reverse(self, s, start, end):
        if s == "" or start >= end:
            return 
        while start < end:
            #交换顺序
            s[start],s[end] = s[end], s[start]
            start += 1
            end -= 1

13.滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

思路:
1.传统方法。滑动窗口大学为k,需要在O(k)的时间里找到最大值。对于长度为n的数组,总复杂度是O(nk)。
2.使用辅助空间。剑指offer|解析和答案(C++/Python) (三)(https://blog.csdn.net/qq_24739717/article/details/100558101)中第4题(包含min函数的栈)中实现在O(1)时间内得到最小值的栈。同样,也可以在O(1)时间内得到栈的最大值。在剑指offer|解析和答案(C++/Python) (一)(https://blog.csdn.net/qq_24739717/article/details/100109632)中第7题(用两个栈实现队列)如何用2个栈实现一个队列。综合这两道题,如果把队列用2个栈实现,可以用O(1)时间得到栈中的最大值,那么也可以有O(1)时间内得到队列的最大值,因此总的时间复杂度就降到O(n)。
3.保存队列中最大值的下标。不把滑动窗口的每个值都存如队列,而是把有可能成为滑动窗口最大值的数值存入一个两端开口的队列。接着以输入数组{2,3,4,2,6,2,5,1}为例一步步分析。
参考图示:
在这里插入图片描述
a.队列中的下标依次存入。在存入数字之前,首先要判断队列里已有的数字是否小于待存入的数字。如果已有的数字小于待存入的数字,那么这些数字不可能是滑动窗口的最大值。因此它们将会被依次从队列尾部删除。
b.如果队列头部的数字已经从窗口里滑出,那么滑出的数字也需要从队列头部删除
代码:
C++

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
        vector<int> maxValues;
		if(num.size() >= size && size >= 1){
			//新建队列
			deque<int> index;
			//先输入一个滑动窗口的值
			for(unsigned int i = 0; i < size; ++i){
				//首先要判断队列里已有的数字是否小于待存入的数字
				//如果小于待存入数字,则不可能是滑动窗口的最大值
				//因此它们会被依次从队列中删除
				while(!index.empty() && num[i] >= num[index.back()])
					index.pop_back();
				index.push_back(i);
			}
			for(unsigned int i = size; i < num.size(); ++i){
				maxValues.push_back(num[index.front()]);
				while(!index.empty() && num[i] >= num[index.back()])
					index.pop_back();
				if(!index.empty() && index.front() <= (int)(i - size))
					index.pop_front();
				
				index.push_back(i);
			}
			maxValues.push_back(num[index.front()]);			
		}
		return maxValues;
    }
};

Python

# -*- coding:utf-8 -*-
class Solution:
    def maxInWindows(self, num, size):
        # write code here
        maxValues = []
        if len(num) >= size and size >= 1:
            #index用保存队列中的下标
            index = []
            #第一个滑动窗口
            for i in range(size):
                #当新放入是的数据比队列中所有的都大
                #那么原队列中不可能存在最大值,需要清空
                while len(index) > 0 and num[i] > num[index[-1]]:
                    index.pop()#删除队列的最后的元素
                index.append(i)
            #滑动窗口开始滑动
            for i in range(size, len(num))
                #先放入最大值
                maxValues.append(num[index[0]])
                #当新放入是的数据比队列中所有的都大
                #那么原队列中不可能存在最大值,需要清空
                while len(index) > 0 and num[i] > num[index[-1]]:
                    index.pop()#删除队列的最后的元素
                #如果队列头部的数字已经从窗口滑出 也需要删除
                while len(index) > 0 and index[0] <= (i - size):
                    index.pop(0)
                        

14.n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

思路:
n个骰子的点数和的最小值为n,最大值为6n,所有点数的排列数为6n。要解决整个问题,需要统计出每个点数出现的次数,然后把每个点数出现的次数除以6n,就能求出每个点数出现的次数。
解法一:基于递归求骰子点数,时间效率不够高。
可以把n个骰子分为2堆:第一堆只有1个,另外一堆有n - 1个。单独一堆可能出现1 - 6的点数。需要计算1 - 6点数和剩下的n - 1个骰子来计算点数和。把剩下的n - 1仍然分为2堆:第一堆只有1个,另外一堆有n - 2个。依次递归,递归结束条件是最后只剩下一个骰子。
可以定义一个长度为6n - n + 1长度的数组,将和为s的点数出现的次数保存到第s - n个元素里。
在这里插入图片描述
代码:
C++

class Solution {
public:
	int g_maxValue = 6;
	void PrintProbability(int number){
		if(number < 1)
			return ;
		int maxSum = number * g_maxValue;//6n
		int *pProbabilities = new int[maxSum - number + 1];//6n - n + 1
		for(int i = number; i <= maxSum; ++i)
			pProbabilities[i - number] = 0;//初始化
		//计算和为s出现的次数并保存在pProbabilities中
		Probability(number, pProbabilities);
		int total = pow((double)g_maxValue, number);
		for(int i = number; i <= maxSum; ++i){
			//计算概率
			double ratio = (double)pProbabilities[i - number]/total;
			printf("%d:%e\n",i, ratio);
		}
		delete[] pProbabilities;
	}
	void Probability(int number, int* pProbabilities){
		for(int i = 1; i < g_maxValue; ++i)
			//第一堆每个数字(1 - 6)都会出现一次 并在此基础下进行递归
			ProbabilityCore(number, number, i, pProbabilities);
	}
	void ProbabilityCore(int original, int current, int sum, int* pProbabilities){
		if(current == 1){
			//递归结束条件就是只剩一个
			pProbabilities[sum - original]++;// s - n
		}else{
			//把n个骰子分为2堆  一堆只有一个  另外一堆有n-1个
			for(int i = 1; i <= g_maxValue; ++i){
				//第一堆每个数字(1 - 6)都会出现一次 并在此基础下进行递归
				ProbabilityCore(original, current - 1; i + sum; pProbabilities);
			}
		}		
	}
};

解法二:基于循环求骰子点数,时间性能好。
可以考虑用两个数组来存储骰子点数的每个总数出现的次数。
1.在一轮循环中,第一个数组中的第n个数字表示骰子和为n出现的次数。
2.在下一轮循环中,我们加上一个新骰子,此时和为n的骰子出现的次数应该等于上一轮循环骰子点数和为n - 1,n - 2, n - 3, n - 4, n - 5, n - 6个数字之和(骰子可能出现的点数为1 - 6)。

class Solution {
public:
	int g_maxValue = 6;
	void PrintProbability(int number){
		if(number < 1)
			return ;

		int* pProbabilities[2];
		//循环都是从1开始 比较好理解 所以数组长度为 6n + 1
		pProbabilities[0] = new int[g_maxValue * number + 1];//6n + 1
		pProbabilities[1] = new int[g_maxValue * number + 1];
		//初始化
		for(int i = 0; i < g_maxValue * number + 1; ++i){
			pProbabilities[0][i] = 0;
			pProbabilities[1][i] = 0;
		}
		int flag = 0;
		for(int i = 1; i <= g_maxValue; ++i){
            pProbabilities[flag][i] = 1;
		}
		for(int k = 2; k <= number; ++k){
            //把另一个数组中的不可能出现的和置零
			for(int i = 0; i < k; ++i)
				pProbabilities[1 - flag][i] = 0;
			for(int i = k; i <= g_maxValue * k; ++i){
				pProbabilities[1 - flag][i] = 0;
				//下一个循环中的和为n的次数 为上一次和为
				// n-1,n-2,n-3,n-4,n-5,n-6的次次数之和 (骰子可能为1 - 6)
				for(int j = 1; j <= i && j <= g_maxValue; ++j)
					pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];
			}
			flag = 1 - flag;
		}
		double total = pow((double)g_maxValue, number);
		for(int i = number; i <= g_maxValue*number; ++i){
			double ratio = (double)pProbabilities[flag][i]/total;
			printf("%d:%e\n",i,ratio);
		}
		delete[] pProbabilities[0];
		delete[] pProbabilities[1];
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值