题目一
设计一个有set all功能的哈希表——即调用set all方法,可以将所有的key设置成相同的value
思路——打时间戳——时间复杂度O(1)
设置一个myvalue类型,里面存放value值和一个时间戳(用于记录创建的时间)。使用hash表并向表中添加元素,key就是原始的hash表的key,value是myvalue(里面保存了value值和时间戳)。
将一个哈希表和一个myvalue对象封装在一个类里面。如果调用set all 功能,则使用myvalue对象记录当前要set的值以及调用该时间的时间戳。
之后再获取哈希表中元素时,如果对应value的时间戳是调用set all之前的,则返回值为myvalue对象存储的值,而并非hash中存储的值。(即以空间换时间,并没有动手修改hash中的值,而是对每个元素加了点东西,有、东西)
实现代码:
#include<iostream>
#include<string>
#include<unordered_map>
using namespace std;
struct myvalue{
int value;
int time;
};
/*
功能: set
get
contain
remove
set_all
*/
class myhash{
public:
void set(string key, int val){
myvalue myval;
myval.value=val;
myval.time=count;
hash.insert(make_pair(key, myval));
count++;
}
bool contain(string key){
if(hash.find(key)!=hash.end())
return true;
return false;
}
void remove(string key){
if(hash.find(key)!=hash.end())
hash.erase(key);
else
cout<<"老哥,hash表里没这个键啊喂~"<<endl;
}
void set_all(int value){
myv.value=value;
myv.time=++count;
}
int get(string key){
//表中存在该键值对
if(hash.find(key)!=hash.end()){
if(hash[key].time<myv.time){
return myv.time;
}else{
return hash[key].time;
}
}else{
cout<<"老哥,hash表里没这个键啊喂~"<<endl;
return 0;
}
}
private:
myvalue myv;
int count=0;
unordered_map<string, myvalue>hash;
};
int main(){
myhash hash;
string key;
int value=0;
int time=5;
cout<<"请输入五组键值对"<<endl;
while(time--){
cin>>key>>value;
hash.set(key,value);
}
hash.set_all(99999);
time=5;
cout<<"请再输入五组键值对"<<endl;
while(time--){
cin>>key>>value;
hash.set(key,value);
}
cout<<"请输入要查询的值"<<endl;
while(cin>>key){
cout<<hash.get(key)<<endl;
}
return 0;
}
题目二
实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
要求:1.pop、push、getMin操作的时间复杂度都是O(1);
2.设计的栈类型可以使用现成的栈结构
思路:
准备两个栈,一个存数据,一个存最小值,压入数据的时候,data栈和min栈一起增长:
一开始来一个数据4,则将4压入到两个栈中:
又来了一个数,这个数与栈顶元素进行比较,如果这个数比栈顶大,则重复压入栈顶元素,如果这个数比栈顶元素小,则将该值压入栈中。
实现代码
/*
实现一个特殊的栈,在实现栈的基本功能的同时,再返回栈中最小元素的操作
一开始来一个数据,则将该数字压入到两个栈中:
又来了一个数,这个数与栈顶元素进行比较,如果这个数比栈顶大,则重复压入栈顶元素,
如果这个数比栈顶元素小,则将该值压入栈中
*/
#include "pch.h"
#include<iostream>
#include<stack>
using namespace std;
class getMin {
public:
int minNum();
void push(int num);
void pop();
private:
stack<int>myStack, minStack;
};
inline
void getMin::push(int num) {
if (myStack.empty() && minStack.empty()) {
myStack.push(num);
minStack.push(num);
return;
}
if (myStack.empty() || minStack.empty()) {
cout << "push error" << endl;
return;
}
else {
if (num >= minStack.top()) {
minStack.push(minStack.top());
}
else {
minStack.push(num);
}
myStack.push(num);
}
}
inline
void getMin::pop() {
if (myStack.empty() && minStack.empty()) {
cout << "stack is empty" << endl;
return;
}
if (myStack.empty() && minStack.empty()) {
cout << "pop error" << endl;
return;
}
myStack.pop();
minStack.pop();
}
inline
int getMin::minNum() {
return minStack.top();
}
//产生随机数
int creatRadomNum() {
return rand();
}
int main()
{
getMin test;
test.push(creatRadomNum());
test.push(creatRadomNum());
test.push(creatRadomNum());
test.push(creatRadomNum());
test.push(creatRadomNum());
test.push(creatRadomNum());
int a=test.minNum();
test.pop();
int b = test.minNum();
test.pop();
int c = test.minNum();
test.pop();
int d = test.minNum();
test.pop();
int e = test.minNum();
system("pause");
return 0;
}
题目三
如何仅用队列结构实现栈结构?
如何仅用栈结构实现队列结构?
栈实现队列
两个栈,push和pop,入队操作的时候,永远都向push栈压入元素,出队操作的时候,永远都从pop栈弹出元素。
原则:
1、push栈向pop栈倒数据,一定要把push栈倒空;
2、如果pop栈非空,则不允许push栈向pop栈倒数据;
实现代码
class MyQueue {
private:
stack<int>pushStack, popStack;
public:
void push(int num);
void pop();
int peak();
bool empty();
};
void MyQueue::push(int num) {
pushStack.push(num);
}
void MyQueue::pop() {
if (popStack.empty()) {
if (pushStack.empty()) {
cout << "queue is empty" << endl;
return;
}
while (!pushStack.empty()) {
popStack.push(pushStack.top());
pushStack.pop();
}
}
popStack.pop();
}
int MyQueue::peak() {
if (popStack.empty()) {
if (pushStack.empty()) {
cout << "queue is empty" << endl;
return;
}
while (!pushStack.empty()) {
popStack.push(pushStack.top());
pushStack.pop();
}
}
return popStack.top();
}
bool MyQueue::empty() {
if (pushStack.empty() && popStack.empty()) return true;
else return false;
}
队列实现栈
两个队列,入栈操作的时候只在一个队列中加入数据;出栈操作的时候,将数据依次弹出并加入到另一个队列中,当弹出元素为队尾元素时,将该值返回给用户,不将该值压入到另一个队列中。
实现代码
class MyStack {
private:
queue<int>data, help;
public:
/** Initialize your data structure here. */
MyStack() { }
/** Push element x onto stack. */
void push(int x) {
if (data.empty() && help.empty()) {
data.push(x);
return;
}
else if (data.empty()) {
help.push(x);
}
else {
data.push(x);
}
}
void helper(queue<int>&output, queue<int>&input) {
while (output.size() != 1) {
input.push(output.front());
output.pop();
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
if (data.empty() && help.empty()) return 0;
else if (data.empty()) {
//把另一个队列里面的元素放入data队列中,将help队列中的队尾返回
helper(help,data);
int answer = help.front();
help.pop();
return answer;
}
else {
helper(data,help);
int answer = data.front();
data.pop();
return answer;
}
}
/** Get the top element. */
int top() {
int answer = 0;
if (data.empty() && help.empty()) return answer;
else if (data.empty()) {
//把另一个队列里面的元素放入data队列中,将help队列中的队尾返回
helper(help, data);
answer = help.front();
data.push(answer);
help.pop();
}
else {
helper(data, help);
answer = data.front();
help.push(answer);
data.pop();
}
return answer;
}
/** Returns whether the stack is empty. */
bool empty() {
if (data.empty() && help.empty()) return true;
else return false;
}
};
题目四
动态规划的空间压缩技巧
给你一个二维数组matrix,其中每个数都是正数,要求从左上角走到右下角。每一步只能向右或者向下,沿途经过的数字要累加起来。最后请返回最小的路径和。
思路
对于m*n的矩阵,从左上角走到右下角,一共走的步数是m+n步。对于每一次选择,要么就是往右走,要么就是往下走,因此:
写一个小例子。从左上角走到右下角,最短的路径是多少?
可以用一张表来记录路径信息
1、初始化dp矩阵
2、初始化完成之后,每个位置的选路都是选取当前位置的值与上侧或者左侧的和的最小值作为该位置的最终结果。
例如上图中的位置,他的初始值是1,(8+1)>(7+1),因此位置的值为8(而不是9)
3、矩阵最终的结果如下:
对于动态规划数组dp,有优化的空间。可以把二维降到一维,把一维将到几个变量。对于本问题,可以降低到一维。只记录一列或者一行结果的值即可
实现代码(两种实现版本:压缩空间前的动态规划和压缩空间后的。其中,压缩空间后的动态规划使用上图方式扫描整个二维数组)
#include<iostream>
#include<vector>
using namespace std;
//在数组自身上进行修改
int calculate(vector<vector<int>>&vec){
if(vec.size()<1 || vec[0].size()<1) return 0;
//初始化第一行
for(int i=1; i<vec[0].size(); i++){
vec[0][i]+=vec[0][i-1];
}
//初始化第一列
for(int i=1; i<vec.size(); i++){
vec[i][0]+=vec[i-1][0];
}
for(int i=1; i<vec[0].size(); i++){
for(int j=1; j<vec.size(); j++){
vec[j][i]=min(vec[j][i-1]+vec[j][i],vec[j-1][i]+vec[j][i]);
}
}
return vec[vec.size()-1][vec[0].size()-1];
}
//利用一维额外数组进行实现动态规划
int dpp(vector<vector<int>>&vec){
vector<int>dp(vec.size());
dp[0]=vec[0][0];
//初始化
for(int i=1; i<vec.size(); i++){
dp[i]=vec[i][0];
dp[i]+=dp[i-1];
}
for(int i=1; i<vec[0].size(); i++){
for(int j=0; j<vec.size(); j++){
//说明是第一行的数据,此行数数据的值是当前值累加上之前累加和
if(j==0){
dp[j]=vec[j][i-1]+dp[j];
}else{
//不是第一行,此时应该考虑上方和左侧的值哪个小选哪个
//括号中用dp[j]代表左侧的值,因为dp数组是从左向右移动的,其中存的值就是左侧位置该有的值
dp[j]=vec[j][i]+min(dp[j],dp[j-1]);
}
}
}
return dp[vec.size()-1];
}
int main(){
vector<vector<int>>vec={{5,3,4,0},{2,1,3,4},{5,4,2,1}};
cout<<dpp(vec)<<endl;
return 0;
}
题目五
给定一个数组arr,已知其中所有的值都是非负的,将这个数组看作一个容器,请返回容器能装多少水
比如,arr = {3,1,2,5,2,4},根据值画出的直方图就是容器形状,该容器可以装下5格水
再比如,arr = {4,5,1,3,2},该容器可以装下2格水
思路
如果想通过代码依次找到每一个波谷,在进行累加,是行不通的。如下:
比如你在不断计算每一个波谷,结果发现之前的波谷其实在另一个更大的波谷里
关键在于建立求水量的标准
对于位置 i ,我们只关心 i 位置上侧有多少水,其他的条件一律不管。
假设位置 i 的值为10, i 的左侧为20,右侧为30,那么 i 位置一定能存10格水。把每个位置的水量都求一下。
对于每个位置 i,都遍历一下左边的水量和右边的水量,求出两侧的最大值,比你矮,就是没水,比你高,说明你的水量可以上升。此时是时间复杂度是O(n*n)
优化——预处理数组的技巧
例如数组[1,3,2,5,4,6,5,4,3,2],则可以生成数组
- 0-i 中的最大值 [1,3,3,5,5,6,6,6,6,6]——从左向右遍历生成
- i 右侧的最大值 [6,6,6,6,6,6,5,4,3,2]——从右向左遍历生成
还能再优化——不用辅助数组,只有几个变量,时间复杂度O(n)
最左位置是6,最右位置是 4 。最左和最右位置可以不管,中间位置才能产生水量。两个指针向中间移动,使用两个变量:
- leftmax——记录指针L左侧的最大值(不包括L指针指向的值)
- rightmax——记录指针R右侧的最大值(不包括R指针指向的值)
如果右侧最大值小于左侧最大值,则结算R的水量,因为R右侧最大值是4,左侧是6,6>4,R的瓶颈是右侧的最大值——4。因此R位置存储的水量=4-3=1
继续走,R的值为10,结算10的水量,是0格水更新rightmax的值。之后R继续向左移动,R指针指向1,此时rightmax=10,leftmax=6,10>6,结算L指针指向位置中存储的水。L位置是水量=6-5=1(因为瓶颈是6)
之后L指针移动,指向10,结算左侧水量(因为此时leftmax<rightmax)。水量为0,更新leftmax的值为10。L指针继续向右移动,指向1,此时leftmax=rightmax,所以结算谁都行
关键在于选择结算顺序,如果脑筋死,只考虑从左向右,而不是从两头着手,就会陷入误区。
优化的思路来源于两个
- 样本本身的特殊性
- 求解问题标准的特殊性
本题就是标准的特殊性。
实现代码:
#include<iostream>
#include<vector>
#include<deque>
using namespace std;
//使用辅助数组的解法
void cal(vector<int>&vec){
if(vec.size()<3) return;
int sum=0;
deque<int>left;
deque<int>right;
left.push_back(vec[0]);
right.push_back(vec[vec.size()-1]);
//初始化数组
for(int i=1; i<vec.size(); i++){
if(vec[i]>left[i-1]){
left.push_back(vec[i]);
}else{
left.push_back(left[i-1]);
}
}
for(int i=vec.size()-2; i>=0; i--){
if(right[0]>vec[i]){
right.push_front(right[0]);
}else{
right.push_front(vec[i]);
}
}
for(int i=1; i<vec.size()-1; i++){
//找出两侧水量的最小值
if((left[i-1]>=right[i+1])&&(vec[i]<right[i+1])){
sum+=right[i+1]-vec[i];
}
else if((left[i-1]<=right[i+1])&&(vec[i]<left[i-1])){
sum+=left[i-1]-vec[i];
}
}
cout<<sum<<endl;
}
//优化后的解法
void calculate(vector<int>&vec){
//size小于3,则无法存水
if(vec.size()<3) return;
//两根指针
int L=1;
int R=vec.size()-2;
//记录左侧和右侧的最大值
int leftmax=vec[0];
int rightmax=vec[vec.size()-1];
int sum=0;
//由于L等于R的时候的高度也要算,因此最后一次进行运算的时候是L=R的时候
while(L!=(R+1)){
//如果右侧大于左侧,则结算左侧
if(leftmax<rightmax){
if(leftmax>vec[L])
sum+=(leftmax-vec[L]);
else
leftmax=vec[L];
L++;
}else{
//结算R的水量。因为瓶颈是rightmax
if(rightmax>vec[R])
sum+=(rightmax-vec[R]);
else
rightmax=vec[R];
R--;
}
}
cout<<sum<<endl;
}
int main(){
vector<int>vec={6,5,10,1,2,4,3,1,10,3,4};
calculate(vec);
cal(vec);
return 0;
}
题目六
如果一个字符串为str,把字符串str前面任意的部分挪到后面形成的字符串叫作str的旋转词。比如str="12345",str的旋转词有"12345"、"23451"、"34512"、"45123"和"51234"。给定两个字符串a和b,请判断a和b是否互为旋转词。
比如:
- a="cdab",b="abcd",返回true。
- a="1ab2",b="ab12",返回false。
- a="2ab1",b="ab12",返回true。
思路
对于字符串a和b,经过以下两步处理即可知道是否是互为旋转词
- 先判断两个字符串长度是否相同,不相同则直接返回false
- 再判断b是不是字符串a+a的子串即可
实现代码
#include<iostream>
#include<string>
using namespace std;
bool calculate(string a, string b){
if(a.length()!=b.length())
return false;
a+=a;
auto idx=a.find(b);
//b不是a的子串
if(idx ==string::npos)
return false;
return true;
}
int main(){
string a,b;
bool answer;
while(cin>>a>>b){
answer=calculate(a,b);
if(answer){
cout<<"是旋转串"<<endl;
}else{
cout<<"不是旋转串"<<endl;
}
}
return 0;
}
题目七
给定一个数组arr长度为N,你可以把任意长度大于0且小于N的前缀作为左部分,剩下的作为右部分。但是每种划分下都有左部分的最大值和右部分的最大值,请返回最大的,左部分最大值减去右部分最大值的绝对值。
思路
整个数组中找出全局最大值max,然后看数组的开头和结尾位置的数字谁小,就让max减谁,进而得到目标结果。
细讲:
1、如果max在左部分,则我们的目标变成右边的最大值尽量小。
由于右部分一定会包含数组在N-1位置上的数,所以右部分的最大值不会比N-1位置上的数小,那么右侧的最大值最小的方法就是——使得右侧只含有N-1位置上的数
2、如果max划分在右部分,则我们的目标变成左侧的最大值尽可能小。
由于左部分一定包含数组 0 位置上的值,因此左部分的最大值不会比数组在 0 位置上的值小,因此左部分最大值尽可能小的办法就是使得左部分只包含索引位置为 0 的值。
这道题的最优解来自于标准的特殊性,看到一道题如果觉得标准太怪,那我们自己改标准
实现代码
#include<iostream>
#include<vector>
using namespace std;
int calculate(vector<int>&vec){
int max=0;
int res=0;
//找出数组最大值
for(auto i: vec){
if(i>max)
max=i;
}
//答案为最大值减去数组两端较小的那个值
res=vec[0]>vec[vec.size()-1]?max-vec[vec.size()-1]:max-vec[0];
return res;
}
int main(){
vector<int>vec={1,2,3,4,7,3,6,2,66,2,8,4,3,0,1,4};
cout<<calculate(vec)<<endl;
return 0;
}
题目八
给定一个数组,数组中的元素可能是正数、可能是负数、可能是0.。求解数组中子数组的最大累乘积
思路
看到子数组——以每个位置结尾的情况下会怎么怎么样,以每个位置开头会怎么怎么样。为什么要定成以某个位置结尾会怎么怎么样,因为我们要使用dp,从而减少重复计算。
对于位置 i ,子数组必须用 i 结尾,则可能性为
第一种——i 不决定往左扩展——只包含 i 自己
第二种——i 决定向左扩展,此时
- 如果 i 为正数,我们的目标变为——寻找以 i-1 为结尾的最大累乘积
- 如果 i 为负数,我们的目标变成——寻找以 i-1 为结尾的最小累乘积
实现代码
/*
题目:给定一个数组,数组中的元素可能是正数、可能是负数、可能是0.。求解数组中子数组的最大累乘积
*/
#include<iostream>
#include<vector>
#include<string>
using namespace std;
/*
函数功能:将string中的每个数字提取出来,存入数组中
说明:数字之间以空格分隔
input:输入的,以空格分隔的字符串
vec:传出参数,将数字提取出来存入数组中
*/
void makeinput(string input, vector<int>&vec){
if(input.length()==0) return;
//以 ‘ ’作为标记,否则会丢一个数字的
input.push_back(' ');
int num=0;
vector<char>tool;
for(int i=0; i<input.length(); i++){
if(input[i] !=' ')
tool.push_back(input[i]);
else{
if(tool.empty()) continue;
for(auto i:tool){
//把每一位转成数字存入num中
num=num*10+(i-'0');
}
//清空tool
tool.clear();
vec.push_back(num);
num=0;
}
}
}
//计算数组的最大子数组累乘积
int calculate(vector<int>&vec){
int Max=vec[0];
int Min=vec[0];
int ans=vec[0];
//对于每个位置,都有三种情况
//1、本身 2、最小累乘积 3、最大累乘积
//记录最小累乘积是为了后面可能会有负数
int self=vec[0];
int min_sum=vec[0];
int max_sum=vec[0];
for(int i=0; i<vec.size(); i++){
self=vec[i];
min_sum=Min*vec[i];
max_sum=Max*vec[i];
Max=max(max(self, min_sum),max_sum);
Min=min(min(self, min_sum),max_sum);
ans=max(ans,Max);
}
return ans;
}
int main(){
vector<int>vec;
string input;
while(getline(cin,input)){
//拆分以空格分隔的字符串,将数字依次存入数组中
makeinput(input,vec);
cout<<calculate(vec)<<endl;
vec.clear();
}
return 0;
}