刷题(五)C++

题目一

字符串只由'0'和'1'两种字符构成,
当字符串长度为1时,所有可能的字符串为"0"、"1";
当字符串长度为2时,所有可能的字符串为"00"、"01"、"10"、"11";
当字符串长度为3时,所有可能的字符串为"000"、"001"、"010"、"011"、"100"、"101"、"110"、"111"
...
如果某一个字符串中,只要是出现'0'的位置,左边就靠着'1',这样的字符串叫作达标字符串。
给定一个正数N,返回所有长度为N的字符串中,达标字符串的数量。
比如,N=3,返回3,因为只有"101"、"110"、"111"达标。

思考——时间复杂度可以达到O(logn)

对于位置 x ,要求x-1 的位置为1时,P(i)代表字符串[ x —N-1]区间一共有多少种合法的字符串,其中该区间的长度为 i 。对于位置 x 有两种可能:

  • x位置的值为0,因为x-1的位置为1,因此x位置可以取0.此时x+1的位置只能是1,余下的位置有P(i-2)中解法
  • x的位置为1,则余下位置有P(i-1)种解法。

因此得到递推式

P(N)=P(N-2)+P(N-1)

这还是费波纳茨数列。可以使用矩阵快速幂

实现代码以及更加详细的解题思路——来啃硬骨头——费波纳茨(Fibonacci)矩阵快速幂 c++

 

 

 

题目二

在迷迷糊糊的大草原上,小红捡到了n根木棍,第i根木棍的长度为i,小红现在很开心。想选出其中的三根木棍组成美丽的三角形。但是小明想捉弄小红,想去掉一些木棍,使得小红任意选三根木棍都不能组成三角形。

  • 请问小明最少去掉多少根木棍呢?
  • 给定N,返回至少去掉多少根?

思路

只保留1到N中的费波纳茨数,其余删掉。删掉的就是改善掉的木柜。

因为三角形a+b>c,最经济的做法就是使得a+b=c,而不是a+b<c。这算是一个贪心策略

实现代码

#include<iostream>
using namespace std;

void calculate(int n){
	if(n<3) return;

	//因为第i根木棍的长度为i,因此要保留的数字为:
	//第一个数是1,第二个数是2,第三个数是3,第四个数是5
	int num=2;
	int f_1=1, f_2=2;
	while(f_2+f_1<=n){
		num++;
		f_2=f_1+f_2;
		f_1=f_2-f_1;
	}
	cout<<n-num<<endl;
}

int main(){
	int n=0;
	cin>>n;
	calculate(n);
	return 0;
}

 

 

 

 

题目三

给定一个数组arr,如果通过调整可以做到arr中任意两个相邻的数字相乘是4的倍数,返回true;如果不能返回false

思路

有三种变量

  • 偶数,但不是4的倍数
  • 奇数
  • 4的倍数

策略

  • 奇数两侧一定是4的倍数。
  • 不是4的倍数的偶数要集中的放在数组的一边,这一堆东西的两边要么就都是4的倍数的偶数,要么就是数组的开头或者结尾。

实现代码:

/*
给定一个数组arr,如果通过调整可以做到arr中任意两个相邻的数字相乘是4的倍数,
返回true;如果不能返回false
*/
#include<iostream>
#include<vector>
using namespace std;

bool calculate(vector<int>&nums){
	if(nums.size()<1) return false;
	
	bool res=false;
	//二的倍数、四的倍数、奇数
	int even_2=0, even_4=0, odd=0;
	//统计三种数的个数
	for(auto i :nums){
		if(i%2==0){
			if(i%4==0) even_4++;
			else even_2++;
		}else
			odd++;
	}

	//根据情况来进行计算
	if(even_2==0){
		if(even_4>=(odd-1)) 
			res=true;
	}else{
		if(even_4>=odd)
			res=true;
	}
	return res;
}

int main(){
	vector<int>nums;
	string input;
	int temp=0;
	//输入以空格分隔的字符串,拆分出各个数字
	while(getline(cin,input)){
		for(auto i:input){
			if(i>='0'&&i<='9'){
				temp=temp*10+(i-'0');
			}else{
				if(temp){
					nums.push_back(temp);
					temp=0;
				}
			}
		}
		if(temp){
			nums.push_back(temp);
			temp=0;
		}
		cout<<calculate(nums)<<endl;
		nums.clear();
	}
		
	return 0;
}

 

 

 

题目四

给定一个字符串,如果该字符串符合人们日常书写一个整数的形式,返回int类型的这个数;如果不符合或者越界返回-1或者报错。

思路

抠细节的题,详细思路以及实现代码见——来啃硬骨头——c++各种字符串的题

 

 

 

题目五

设计并实现TopKRecord结构,可以不断地向其中加入字符串,并且可以根据字符串出现的情况随时打印加入次数最多的前k个字符串。具体为:
1)k在TopKRecord实例生成时指定,并且不再变化(k是构造TopKRecord的参数)。
2)含有 add(String str)方法,即向TopKRecord中加入字符串。
3)含有 printTopK()方法,即打印加入次数最多的前k个字符串,打印有哪些字符串和对应的次数即可,不要求严格按排名顺序打印。
4)如果在出现次数最多的前k个字符串中,最后一名的字符串有多个,比如出现次数最多的前3个字符串具体排名为:
A 100次 B 90次 C 80次 D 80次 E 80次,其他任何字符串出现次数都不超过80次
那么只需要打印3个,打印ABC、ABD、ABE都可以。也就是说可以随意抛弃最后一名,只要求打印k个
要求:

  • 1)在任何时候,add 方法的时间复杂度不超过 O(logk)
  • 2)在任何时候,printTopK方法的时间复杂度不超过O(k)。

思路

优先队列虽然很方便,但是有局限——如果堆中某个值发生了变化,让堆因此进行相应的调整,这种定制是优先队列没有的。因为堆这块我们还是需要会自己手写。

准备工作:

  • 构建一个结构体,用于存储加入的字符串以及字符串出现的次数
  • 准备一个大小为k的大顶堆(底层是数组结构)

进行计算:

对于进来的字符串abc,查看abc是否在堆上,

  • 如果在堆上,则进行相应的调整
  • 如果不在堆上,则将新的字符串插入到堆中

实现代码

#include<iostream>
#include<vector>
#include<string>
using namespace std;

struct heapNode {
	int time;
	string str;
	heapNode(int i = 1) :time(i), str("a") {}
};

class TopKRecord {
public:
	void add(string str);
	void printTopK(int time);
	void heapinsert();
private:
	vector<heapNode>myheap;
	bool flag = false;
	int index = 0;
};

inline
void TopKRecord::add(string input) {
	flag=false;
	for (int i = 0; i < myheap.size(); i++) {
		//大顶堆里面存储了该字符串
		if (myheap[i].str == input) {
			myheap[i].time++;
			index = i;
			flag = true;
			break;
		}
	}

	//大顶堆中没有存储该字符串
	if (!flag) {
		heapNode node;
		node.str = input;
		myheap.push_back(node);
		index = myheap.size() - 1;
	}
	//调节最新加入的字符串所在节点在大顶堆汇总的位置
	heapinsert();
}

inline
void TopKRecord::heapinsert() {
	//每次与父节点进行比较。如果比父节点大,则与父节点交换位置
	//父节点索引位置 (i-1)/2
	int father = (index - 1) / 2;
	heapNode node;
	/*
	退出循环的条件:
	1、父节点比该节点大
	2、该节点已经移动到数组的根节点
	*/
	while (true) {
		if (index > 0 && myheap[index].time > myheap[father].time) {
			node = myheap[index];
			myheap[index] = myheap[father];
			myheap[father] = node;
			index = father;
			father = (index - 1) / 2;
		}
		else {
			break;
		}
	}
}

inline
void TopKRecord::printTopK(int time) {
	for (int i = 0; i < time; i++) {
		cout << myheap[i].str << " " << myheap[i].time << endl;
	}
}

int main() {
	vector<string>str = { "a","a","a","b","c","a","b","c","a" };

	TopKRecord myheap;
	for (auto i : str)
		myheap.add(i);

	myheap.printTopK(2);
	return 0;
}

 

 

 

题目六——背包问题

牛牛准备参加学校组织的春游, 出发前牛牛准备往背包里装入一些零食, 牛牛的背包容量为w。
牛牛家里一共有n袋零食, 第i袋零食体积为v[i]。
牛牛想知道在总体积不超过背包容量的情况下,他一共有多少种零食放法(总体积为0也算一种放法)。

思路

假定背包的大小为7,有五种零食,零食的体积分别为 2,3,2,4,1 。让我们来填一下动态规划表。横向为背包容量纵向为各个体积的零食编号,0号放置体积为2的零食,1号放置体积为1的零食,以此类推。上图吧,

  • dp[i][j]——对于可以使用 0到 i 号零食的情况下,背包有多少种放法。
  • 第一行代表背包尺寸为0-7时,放第一袋零食的情况,后面的是2是因为有放和不放两种情况。
  • 第一列代表背包容量为1时,对于各个零食的放置情况。
  • 每个格子都有两种情况,放当前行的零食或者不放、

 

实现代码如下:

#include<iostream>
#include<vector>
using namespace std;

int dp(vector<int>&food, int w){
	if((food.size()<1)||(w<=0)) return 0;
	
	//初始化第一列
	vector<vector<int>>dpcon(food.size(),vector<int>(w+1));
	for(int i=0; i<food.size(); i++){
		dpcon[i][0]=1;
	}

	for(int i=1; i<=w; i++){
		if(i<food[0])
			dpcon[0][i]=1;
		else
			dpcon[0][i]=2;
	}

	for(int i=1;i<food.size(); i++){
		for(int j=1; j<=w; j++){
			dpcon[i][j]=dpcon[i-1][j];
			if(j-food[i]>=0)
				dpcon[i][j]+=dpcon[i-1][j-food[i]];
		}
	}
	
	return dpcon[food.size()-1][w];
}

int main(){
	vector<int>food={2,3,2,4,1};
	int w=7;
	cout<<dp(food, w)<<endl;
	return 0;

}

 

 

 

加题——leetcode 354 俄罗斯套娃信封问题

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

说明:
不允许旋转信封。

示例:

  • 输入: envelopes =[[5,4],[6,4],[6,7],[2,3]]
  • 输出: 3
  • 解释: 最多信封的个数为3, 组合为:[2,3] => [5,4] => [6,7]。

思路

最长递增子序列的思想。两个维度,一个从小到大,一个从大到小,这是常用的套路。

长度由小到大,同样长度的信封要求宽度由大到小排列(下图中圆圈内是信封的长度,纵向的数字代表信封的宽度由大到小排列)

 

之后把宽度组成一个数组,在宽度组成的数组中[6,5,4,3,4,3,2,1,7,6,5,3],求最长递增子序列,子序列的长度就是信封可以套多少层

因为假设宽度数组中有个宽度 x,如果他前面遇到一个比自己小的宽度 y,y的长度必比x的长度小,因为数组是按照长度由小到大排进来的。

实现代码

struct Node{
    int length;
    int width;
    Node(int i=0, int j=0):length(i),width(j){}
};

bool cmp_first(const Node node1, const Node node2){
    return node1.length<node2.length;
}

bool cmp_second(const Node node1, const Node node2){
    return node1.width>node2.width;
}

int lengthOfLIS(vector<int>& nums,vector<Node>&vec) {
    if(nums.empty()) return 0;
        
    vector<int>dp(nums.size());
    dp[0]=1;
    int LIS=1;
    for(int i=1; i<dp.size(); i++){
        dp[i]=1;
        for(int j=0; j<i; j++){
            //由于要求长度和宽度都要更大,因此一种长度的信封只能刘一个
            //因此需要增加判断条件vec[i].length!=vec[j].length
            if(nums[i]>nums[j] && dp[i]<dp[j]+1  && vec[i].length!=vec[j].length){
                dp[i]=dp[j]+1;
            }
        }
        if(LIS<dp[i]) LIS=dp[i];
    }
    return LIS;
}

class Solution {
public:
    int maxEnvelopes(vector<pair<int, int>>& envelopes) {
        if(envelopes.size()<2) return envelopes.size();
        
        Node node;
        vector<Node>vec;
        for(auto i:envelopes){
            node.length=i.first;
            node.width=i.second;
            vec.push_back(node);
        }
        
        //将数组按照长度由小到大的顺序排列
        sort(vec.begin(), vec.end(),cmp_first);
        
        //构造结尾节点的标志位。由于首先按照长度由小到大排序,设置标志位的长度为0,不会造成混淆
        node.length=0;
        node.width=0;
        vec.push_back(node);
        
        //相同长度的信封,按照宽度从大到小
        int len=vec.size()-1;
        vector<int>index;
        int temp=0;
        //找出所有的信封长度分界点
        while(temp<len){
            if(vec[temp].length!=vec[temp+1].length){
                index.push_back(temp);
            }
            temp++;
        }
        
        //相同长度的信封,按照宽度由小到大排序
        temp=0;
        int start=0;
        int end=index[temp];
        index.push_back(0);
        len=index.size()-1;
        while(temp<len){
            sort(&vec[start],&vec[end],cmp_second);
            temp++;
            start=end+1;
            end=index[temp];
        }
        
        vector<int>ans;
        for(auto i : vec)
            ans.push_back(i.width);
        
        //求最长上升子序列
        return lengthOfLIS(ans,vec);
    }
};

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值