题目一
字符串只由'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);
}
};